mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
start of note cache expression implementation
This commit is contained in:
parent
5f1f65a3c2
commit
f07025f741
@ -18,7 +18,7 @@ async function getAutocomplete(req) {
|
|||||||
results = await getRecentNotes(activeNoteId);
|
results = await getRecentNotes(activeNoteId);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
results = await noteCacheService.findNotes(query);
|
results = await noteCacheService.findNotesWithFulltext(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
const msTaken = Date.now() - timestampStarted;
|
const msTaken = Date.now() - timestampStarted;
|
||||||
@ -67,4 +67,4 @@ async function getRecentNotes(activeNoteId) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getAutocomplete
|
getAutocomplete
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,13 @@ let notes;
|
|||||||
let branches
|
let branches
|
||||||
/** @type {Object.<String, Attribute>} */
|
/** @type {Object.<String, Attribute>} */
|
||||||
let attributes;
|
let attributes;
|
||||||
|
/** @type {Object.<String, Attribute[]>} Points from attribute type-name to list of attributes them */
|
||||||
|
let attributeIndex;
|
||||||
|
|
||||||
|
/** @return {Attribute[]} */
|
||||||
|
function findAttributes(type, name) {
|
||||||
|
return attributeIndex[`${type}-${name}`] || [];
|
||||||
|
}
|
||||||
|
|
||||||
let childParentToBranch = {};
|
let childParentToBranch = {};
|
||||||
|
|
||||||
@ -37,10 +44,11 @@ class Note {
|
|||||||
/** @param {Attribute[]|null} */
|
/** @param {Attribute[]|null} */
|
||||||
this.attributeCache = null;
|
this.attributeCache = null;
|
||||||
/** @param {Attribute[]|null} */
|
/** @param {Attribute[]|null} */
|
||||||
this.templateAttributeCache = null;
|
|
||||||
/** @param {Attribute[]|null} */
|
|
||||||
this.inheritableAttributeCache = null;
|
this.inheritableAttributeCache = null;
|
||||||
|
|
||||||
|
/** @param {Attribute[]} */
|
||||||
|
this.targetRelations = [];
|
||||||
|
|
||||||
/** @param {string|null} */
|
/** @param {string|null} */
|
||||||
this.flatTextCache = null;
|
this.flatTextCache = null;
|
||||||
|
|
||||||
@ -74,16 +82,11 @@ class Note {
|
|||||||
|
|
||||||
this.attributeCache = parentAttributes.concat(templateAttributes);
|
this.attributeCache = parentAttributes.concat(templateAttributes);
|
||||||
this.inheritableAttributeCache = [];
|
this.inheritableAttributeCache = [];
|
||||||
this.templateAttributeCache = [];
|
|
||||||
|
|
||||||
for (const attr of this.attributeCache) {
|
for (const attr of this.attributeCache) {
|
||||||
if (attr.isInheritable) {
|
if (attr.isInheritable) {
|
||||||
this.inheritableAttributeCache.push(attr);
|
this.inheritableAttributeCache.push(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr.type === 'relation' && attr.name === 'template') {
|
|
||||||
this.templateAttributeCache.push(attr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,15 +102,6 @@ class Note {
|
|||||||
return this.inheritableAttributeCache;
|
return this.inheritableAttributeCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return {Attribute[]} */
|
|
||||||
get templateAttributes() {
|
|
||||||
if (!this.templateAttributeCache) {
|
|
||||||
this.attributes; // will refresh also this.templateAttributeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.templateAttributeCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasAttribute(type, name) {
|
hasAttribute(type, name) {
|
||||||
return this.attributes.find(attr => attr.type === type && attr.name === name);
|
return this.attributes.find(attr => attr.type === type && attr.name === name);
|
||||||
}
|
}
|
||||||
@ -167,7 +161,6 @@ class Note {
|
|||||||
this.flatTextCache = null;
|
this.flatTextCache = null;
|
||||||
|
|
||||||
this.attributeCache = null;
|
this.attributeCache = null;
|
||||||
this.templateAttributeCache = null;
|
|
||||||
this.inheritableAttributeCache = null;
|
this.inheritableAttributeCache = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,11 +171,13 @@ class Note {
|
|||||||
childNote.invalidateSubtreeCaches();
|
childNote.invalidateSubtreeCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const templateAttr of this.templateAttributes) {
|
for (const targetRelation of this.targetRelations) {
|
||||||
const targetNote = templateAttr.targetNote;
|
if (targetRelation.name === 'template') {
|
||||||
|
const note = targetRelation.note;
|
||||||
|
|
||||||
if (targetNote) {
|
if (note) {
|
||||||
targetNote.invalidateSubtreeCaches();
|
note.invalidateSubtreeCaches();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,14 +189,59 @@ class Note {
|
|||||||
childNote.invalidateSubtreeFlatText();
|
childNote.invalidateSubtreeFlatText();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const templateAttr of this.templateAttributes) {
|
for (const targetRelation of this.targetRelations) {
|
||||||
const targetNote = templateAttr.targetNote;
|
if (targetRelation.name === 'template') {
|
||||||
|
const note = targetRelation.note;
|
||||||
|
|
||||||
if (targetNote) {
|
if (note) {
|
||||||
targetNote.invalidateSubtreeFlatText();
|
note.invalidateSubtreeFlatText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isTemplate() {
|
||||||
|
return !!this.targetRelations.find(rel => rel.name === 'template');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return {Note[]} */
|
||||||
|
get subtreeNotes() {
|
||||||
|
const arr = [[this]];
|
||||||
|
|
||||||
|
for (const childNote of this.children) {
|
||||||
|
arr.push(childNote.subtreeNotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const targetRelation of this.targetRelations) {
|
||||||
|
if (targetRelation.name === 'template') {
|
||||||
|
const note = targetRelation.note;
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
arr.push(note.subtreeNotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return {Note[]} - returns only notes which are templated, does not include their subtrees
|
||||||
|
* in effect returns notes which are influenced by note's non-inheritable attributes */
|
||||||
|
get templatedNotes() {
|
||||||
|
const arr = [this];
|
||||||
|
|
||||||
|
for (const targetRelation of this.targetRelations) {
|
||||||
|
if (targetRelation.name === 'template') {
|
||||||
|
const note = targetRelation.note;
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
arr.push(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Branch {
|
class Branch {
|
||||||
@ -263,6 +303,16 @@ class Attribute {
|
|||||||
this.isInheritable = !!row.isInheritable;
|
this.isInheritable = !!row.isInheritable;
|
||||||
|
|
||||||
notes[this.noteId].ownedAttributes.push(this);
|
notes[this.noteId].ownedAttributes.push(this);
|
||||||
|
|
||||||
|
const key = `${this.type-this.name}`;
|
||||||
|
attributeIndex[key] = attributeIndex[key] || [];
|
||||||
|
attributeIndex[key].push(this);
|
||||||
|
|
||||||
|
const targetNote = this.targetNote;
|
||||||
|
|
||||||
|
if (targetNote) {
|
||||||
|
targetNote.targetRelations.push(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAffectingSubtree() {
|
get isAffectingSubtree() {
|
||||||
@ -270,6 +320,10 @@ class Attribute {
|
|||||||
|| (this.type === 'relation' && this.name === 'template');
|
|| (this.type === 'relation' && this.name === 'template');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get note() {
|
||||||
|
return notes[this.noteId];
|
||||||
|
}
|
||||||
|
|
||||||
get targetNote() {
|
get targetNote() {
|
||||||
if (this.type === 'relation') {
|
if (this.type === 'relation') {
|
||||||
return notes[this.value];
|
return notes[this.value];
|
||||||
@ -309,7 +363,133 @@ async function load() {
|
|||||||
loadedPromiseResolve();
|
loadedPromiseResolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findNotes(query, searchInContent) {
|
const expression = {
|
||||||
|
operator: 'and',
|
||||||
|
operands: [
|
||||||
|
{
|
||||||
|
operator: 'exists',
|
||||||
|
fieldName: 'hokus'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
class AndOp {
|
||||||
|
constructor(subExpressions) {
|
||||||
|
this.subExpressions = subExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(noteSet) {
|
||||||
|
for (const subExpression of this.subExpressions) {
|
||||||
|
noteSet = subExpression.execute(noteSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return noteSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrOp {
|
||||||
|
constructor(subExpressions) {
|
||||||
|
this.subExpressions = subExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(noteSet) {
|
||||||
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
|
for (const subExpression of this.subExpressions) {
|
||||||
|
resultNoteSet.mergeIn(subExpression.execute(noteSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultNoteSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteSet {
|
||||||
|
constructor(arr = []) {
|
||||||
|
this.arr = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(note) {
|
||||||
|
this.arr.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAll(notes) {
|
||||||
|
this.arr.push(...notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNoteId(noteId) {
|
||||||
|
// TODO: optimize
|
||||||
|
return !!this.arr.find(note => note.noteId === noteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeIn(anotherNoteSet) {
|
||||||
|
this.arr = this.arr.concat(anotherNoteSet.arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExistsOp {
|
||||||
|
constructor(attributeType, attributeName) {
|
||||||
|
this.attributeType = attributeType;
|
||||||
|
this.attributeName = attributeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(noteSet) {
|
||||||
|
const attrs = findAttributes(this.attributeType, this.attributeName);
|
||||||
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
|
for (const attr of attrs) {
|
||||||
|
const note = attr.note;
|
||||||
|
|
||||||
|
if (noteSet.hasNoteId(note.noteId)) {
|
||||||
|
if (attr.isInheritable) {
|
||||||
|
resultNoteSet.addAll(note.subtreeNotes);
|
||||||
|
}
|
||||||
|
else if (note.isTemplate) {
|
||||||
|
resultNoteSet.addAll(note.templatedNotes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resultNoteSet.add(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EqualsOp {
|
||||||
|
constructor(attributeType, attributeName, attributeValue) {
|
||||||
|
this.attributeType = attributeType;
|
||||||
|
this.attributeName = attributeName;
|
||||||
|
this.attributeValue = attributeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(noteSet) {
|
||||||
|
const attrs = findAttributes(this.attributeType, this.attributeName);
|
||||||
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
|
for (const attr of attrs) {
|
||||||
|
const note = attr.note;
|
||||||
|
|
||||||
|
if (noteSet.hasNoteId(note.noteId) && attr.value === this.attributeValue) {
|
||||||
|
if (attr.isInheritable) {
|
||||||
|
resultNoteSet.addAll(note.subtreeNotes);
|
||||||
|
}
|
||||||
|
else if (note.isTemplate) {
|
||||||
|
resultNoteSet.addAll(note.templatedNotes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resultNoteSet.add(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findNotesWithExpression(expression) {
|
||||||
|
const allNoteSet = new NoteSet(Object.values(notes));
|
||||||
|
|
||||||
|
expression.execute(allNoteSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findNotesWithFulltext(query, searchInContent) {
|
||||||
if (!query.trim().length) {
|
if (!query.trim().length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -888,14 +1068,22 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||||||
|
|
||||||
if (entity.isDeleted) {
|
if (entity.isDeleted) {
|
||||||
if (note && attr) {
|
if (note && attr) {
|
||||||
|
// first invalidate and only then remove the attribute (otherwise invalidation wouldn't be complete)
|
||||||
|
if (attr.isAffectingSubtree || note.isTemplate) {
|
||||||
|
note.invalidateSubtreeCaches();
|
||||||
|
}
|
||||||
|
|
||||||
note.ownedAttributes = note.ownedAttributes.filter(attr => attr.attributeId !== attributeId);
|
note.ownedAttributes = note.ownedAttributes.filter(attr => attr.attributeId !== attributeId);
|
||||||
|
|
||||||
if (attr.isAffectingSubtree) {
|
const targetNote = attr.targetNote;
|
||||||
note.invalidateSubtreeCaches();
|
|
||||||
|
if (targetNote) {
|
||||||
|
targetNote.targetRelations = targetNote.targetRelations.filter(rel => rel.attributeId !== attributeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete attributes[attributeId];
|
delete attributes[attributeId];
|
||||||
|
delete attributeIndex[`${attr.type}-${attr.name}`];
|
||||||
}
|
}
|
||||||
else if (attributeId in attributes) {
|
else if (attributeId in attributes) {
|
||||||
const attr = attributes[attributeId];
|
const attr = attributes[attributeId];
|
||||||
@ -903,7 +1091,7 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||||||
// attr name and isInheritable are immutable
|
// attr name and isInheritable are immutable
|
||||||
attr.value = entity.value;
|
attr.value = entity.value;
|
||||||
|
|
||||||
if (attr.isAffectingSubtree) {
|
if (attr.isAffectingSubtree || note.isTemplate) {
|
||||||
note.invalidateSubtreeFlatText();
|
note.invalidateSubtreeFlatText();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -915,7 +1103,7 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||||||
attributes[attributeId] = attr;
|
attributes[attributeId] = attr;
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
if (attr.isAffectingSubtree) {
|
if (attr.isAffectingSubtree || note.isTemplate) {
|
||||||
note.invalidateSubtreeCaches();
|
note.invalidateSubtreeCaches();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -944,7 +1132,7 @@ sqlInit.dbReady.then(() => utils.stopWatch("Note cache load", load));
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loadedPromise,
|
loadedPromise,
|
||||||
findNotes,
|
findNotesWithFulltext,
|
||||||
getNotePath,
|
getNotePath,
|
||||||
getNoteTitleForPath,
|
getNoteTitleForPath,
|
||||||
getNoteTitleFromPath,
|
getNoteTitleFromPath,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user