mirror of
https://github.com/zadam/trilium.git
synced 2025-01-16 03:58:53 +08:00
start of note cache expression implementation
This commit is contained in:
parent
5f1f65a3c2
commit
f07025f741
2 changed files with 221 additions and 33 deletions
|
@ -18,7 +18,7 @@ async function getAutocomplete(req) {
|
|||
results = await getRecentNotes(activeNoteId);
|
||||
}
|
||||
else {
|
||||
results = await noteCacheService.findNotes(query);
|
||||
results = await noteCacheService.findNotesWithFulltext(query);
|
||||
}
|
||||
|
||||
const msTaken = Date.now() - timestampStarted;
|
||||
|
@ -67,4 +67,4 @@ async function getRecentNotes(activeNoteId) {
|
|||
|
||||
module.exports = {
|
||||
getAutocomplete
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,6 +12,13 @@ let notes;
|
|||
let branches
|
||||
/** @type {Object.<String, Attribute>} */
|
||||
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 = {};
|
||||
|
||||
|
@ -37,10 +44,11 @@ class Note {
|
|||
/** @param {Attribute[]|null} */
|
||||
this.attributeCache = null;
|
||||
/** @param {Attribute[]|null} */
|
||||
this.templateAttributeCache = null;
|
||||
/** @param {Attribute[]|null} */
|
||||
this.inheritableAttributeCache = null;
|
||||
|
||||
/** @param {Attribute[]} */
|
||||
this.targetRelations = [];
|
||||
|
||||
/** @param {string|null} */
|
||||
this.flatTextCache = null;
|
||||
|
||||
|
@ -74,16 +82,11 @@ class Note {
|
|||
|
||||
this.attributeCache = parentAttributes.concat(templateAttributes);
|
||||
this.inheritableAttributeCache = [];
|
||||
this.templateAttributeCache = [];
|
||||
|
||||
for (const attr of this.attributeCache) {
|
||||
if (attr.isInheritable) {
|
||||
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 {Attribute[]} */
|
||||
get templateAttributes() {
|
||||
if (!this.templateAttributeCache) {
|
||||
this.attributes; // will refresh also this.templateAttributeCache
|
||||
}
|
||||
|
||||
return this.templateAttributeCache;
|
||||
}
|
||||
|
||||
hasAttribute(type, name) {
|
||||
return this.attributes.find(attr => attr.type === type && attr.name === name);
|
||||
}
|
||||
|
@ -167,7 +161,6 @@ class Note {
|
|||
this.flatTextCache = null;
|
||||
|
||||
this.attributeCache = null;
|
||||
this.templateAttributeCache = null;
|
||||
this.inheritableAttributeCache = null;
|
||||
}
|
||||
|
||||
|
@ -178,11 +171,13 @@ class Note {
|
|||
childNote.invalidateSubtreeCaches();
|
||||
}
|
||||
|
||||
for (const templateAttr of this.templateAttributes) {
|
||||
const targetNote = templateAttr.targetNote;
|
||||
for (const targetRelation of this.targetRelations) {
|
||||
if (targetRelation.name === 'template') {
|
||||
const note = targetRelation.note;
|
||||
|
||||
if (targetNote) {
|
||||
targetNote.invalidateSubtreeCaches();
|
||||
if (note) {
|
||||
note.invalidateSubtreeCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,14 +189,59 @@ class Note {
|
|||
childNote.invalidateSubtreeFlatText();
|
||||
}
|
||||
|
||||
for (const templateAttr of this.templateAttributes) {
|
||||
const targetNote = templateAttr.targetNote;
|
||||
for (const targetRelation of this.targetRelations) {
|
||||
if (targetRelation.name === 'template') {
|
||||
const note = targetRelation.note;
|
||||
|
||||
if (targetNote) {
|
||||
targetNote.invalidateSubtreeFlatText();
|
||||
if (note) {
|
||||
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 {
|
||||
|
@ -263,6 +303,16 @@ class Attribute {
|
|||
this.isInheritable = !!row.isInheritable;
|
||||
|
||||
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() {
|
||||
|
@ -270,6 +320,10 @@ class Attribute {
|
|||
|| (this.type === 'relation' && this.name === 'template');
|
||||
}
|
||||
|
||||
get note() {
|
||||
return notes[this.noteId];
|
||||
}
|
||||
|
||||
get targetNote() {
|
||||
if (this.type === 'relation') {
|
||||
return notes[this.value];
|
||||
|
@ -309,7 +363,133 @@ async function load() {
|
|||
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) {
|
||||
return [];
|
||||
}
|
||||
|
@ -888,14 +1068,22 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||
|
||||
if (entity.isDeleted) {
|
||||
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);
|
||||
|
||||
if (attr.isAffectingSubtree) {
|
||||
note.invalidateSubtreeCaches();
|
||||
const targetNote = attr.targetNote;
|
||||
|
||||
if (targetNote) {
|
||||
targetNote.targetRelations = targetNote.targetRelations.filter(rel => rel.attributeId !== attributeId);
|
||||
}
|
||||
}
|
||||
|
||||
delete attributes[attributeId];
|
||||
delete attributeIndex[`${attr.type}-${attr.name}`];
|
||||
}
|
||||
else if (attributeId in attributes) {
|
||||
const attr = attributes[attributeId];
|
||||
|
@ -903,7 +1091,7 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||
// attr name and isInheritable are immutable
|
||||
attr.value = entity.value;
|
||||
|
||||
if (attr.isAffectingSubtree) {
|
||||
if (attr.isAffectingSubtree || note.isTemplate) {
|
||||
note.invalidateSubtreeFlatText();
|
||||
}
|
||||
else {
|
||||
|
@ -915,7 +1103,7 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||
attributes[attributeId] = attr;
|
||||
|
||||
if (note) {
|
||||
if (attr.isAffectingSubtree) {
|
||||
if (attr.isAffectingSubtree || note.isTemplate) {
|
||||
note.invalidateSubtreeCaches();
|
||||
}
|
||||
else {
|
||||
|
@ -944,7 +1132,7 @@ sqlInit.dbReady.then(() => utils.stopWatch("Note cache load", load));
|
|||
|
||||
module.exports = {
|
||||
loadedPromise,
|
||||
findNotes,
|
||||
findNotesWithFulltext,
|
||||
getNotePath,
|
||||
getNoteTitleForPath,
|
||||
getNoteTitleFromPath,
|
||||
|
|
Loading…
Reference in a new issue