added ancestor

This commit is contained in:
zadam 2020-05-23 23:44:55 +02:00
parent 8ce2afff8a
commit 9ede77aead
5 changed files with 132 additions and 1 deletions

View File

@ -3,6 +3,7 @@ const Note = require('../src/services/note_cache/entities/note');
const Branch = require('../src/services/note_cache/entities/branch');
const Attribute = require('../src/services/note_cache/entities/attribute');
const ParsingContext = require('../src/services/search/parsing_context');
const dateUtils = require('../src/services/date_utils');
const noteCache = require('../src/services/note_cache/note_cache');
const randtoken = require('rand-token').generator({source: 'crypto'});
@ -131,6 +132,48 @@ describe("Search", () => {
expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy();
});
it("smart date comparisons", async () => {
// dates should not be coerced into numbers which would then give wrong numbers
rootNote
.child(note("My note")
.label('year', new Date().getFullYear().toString())
.label('month', dateUtils.localNowDate().substr(0, 7))
.label('date', dateUtils.localNowDate())
.label('dateTime', dateUtils.localNowDateTime())
);
const parsingContext = new ParsingContext();
async function test(query, expectedResultCount) {
const searchResults = await searchService.findNotesWithQuery(query, parsingContext);
expect(searchResults.length).toEqual(expectedResultCount);
if (expectedResultCount === 1) {
expect(findNoteByTitle(searchResults, "My note")).toBeTruthy();
}
}
await test("#year = YEAR", 1);
await test("#year >= YEAR", 1);
await test("#year <= YEAR", 1);
await test("#year < YEAR+1", 1);
await test("#year > YEAR+1", 0);
await test("#month = MONTH", 1);
await test("#date = TODAY", 1);
await test("#date > TODAY", 0);
await test("#date > TODAY-1", 1);
await test("#date < TODAY+1", 1);
await test("#date < 'TODAY + 1'", 1);
await test("#dateTime <= NOW+10", 1);
await test("#dateTime < NOW-10", 0);
await test("#dateTime >= NOW-10", 1);
await test("#dateTime < NOW-10", 0);
});
it("logical or", async () => {
rootNote
.child(note("Europe")
@ -217,6 +260,29 @@ describe("Search", () => {
expect(findNoteByTitle(searchResults, "Prague")).toBeTruthy();
});
it("filter by note's ancestor", async () => {
rootNote
.child(note("Europe")
.child(note("Austria"))
.child(note("Czech Republic")
.child(note("Prague").label('city')))
)
.child(note("Asia")
.child(note('Taiwan')
.child(note('Taipei').label('city')))
);
const parsingContext = new ParsingContext();
let searchResults = await searchService.findNotesWithQuery('#city AND note.ancestors.title = Europe', parsingContext);
expect(searchResults.length).toEqual(1);
expect(findNoteByTitle(searchResults, "Prague")).toBeTruthy();
searchResults = await searchService.findNotesWithQuery('#city AND note.ancestors.title = Asia', parsingContext);
expect(searchResults.length).toEqual(1);
expect(findNoteByTitle(searchResults, "Taipei")).toBeTruthy();
});
it("filter by note's child", async () => {
rootNote
.child(note("Europe")
@ -411,7 +477,7 @@ class NoteBuilder {
this.note = note;
}
label(name, value, isInheritable = false) {
label(name, value = '', isInheritable = false) {
new Attribute(noteCache, {
attributeId: id(),
noteId: this.note.noteId,

View File

@ -53,6 +53,9 @@ class Note {
if (protectedSessionService.isProtectedSessionAvailable()) {
this.decrypt();
}
/** @param {Note[]|null} */
this.ancestorCache = null;
}
/** @return {Attribute[]} */
@ -164,6 +167,7 @@ class Note {
this.attributeCache = null;
this.inheritableAttributeCache = null;
this.ancestorCache = null;
}
invalidateSubtreeCaches() {
@ -258,6 +262,29 @@ class Note {
return this.attributes.length;
}
get ancestors() {
if (!this.ancestorCache) {
const noteIds = new Set();
this.ancestorCache = [];
for (const parent of this.parents) {
if (!noteIds.has(parent.noteId)) {
this.ancestorCache.push(parent);
noteIds.add(parent.noteId);
}
for (const ancestorNote of parent.ancestors) {
if (!noteIds.has(ancestorNote.noteId)) {
this.ancestorCache.push(ancestorNote);
noteIds.add(ancestorNote.noteId);
}
}
}
}
return this.ancestorCache;
}
/** @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() {

View File

@ -0,0 +1,30 @@
"use strict";
const Expression = require('./expression');
const NoteSet = require('../note_set');
class DescendantOfExp extends Expression {
constructor(subExpression) {
super();
this.subExpression = subExpression;
}
execute(inputNoteSet, searchContext) {
const resNoteSet = new NoteSet();
for (const note of inputNoteSet.notes) {
const subInputNoteSet = new NoteSet(note.ancestors);
const subResNoteSet = this.subExpression.execute(subInputNoteSet, searchContext);
if (subResNoteSet.notes.length > 0) {
resNoteSet.add(note);
}
}
return resNoteSet;
}
}
module.exports = DescendantOfExp;

View File

@ -2,6 +2,7 @@
class NoteSet {
constructor(notes = []) {
/** @type {Note[]} */
this.notes = notes;
}

View File

@ -4,6 +4,7 @@ const AndExp = require('./expressions/and');
const OrExp = require('./expressions/or');
const NotExp = require('./expressions/not');
const ChildOfExp = require('./expressions/child_of');
const DescendantOfExp = require('./expressions/descendant_of');
const ParentOfExp = require('./expressions/parent_of');
const RelationWhereExp = require('./expressions/relation_where');
const PropertyComparisonExp = require('./expressions/property_comparison');
@ -64,6 +65,12 @@ function getExpression(tokens, parsingContext) {
return new ParentOfExp(parseNoteProperty());
}
if (tokens[i] === 'ancestors') {
i += 1;
return new DescendantOfExp(parseNoteProperty());
}
if (tokens[i] === 'labels') {
if (tokens[i + 1] !== '.') {
parsingContext.addError(`Expected "." to separate field path, god "${tokens[i + 1]}"`);