Merge remote-tracking branch 'origin/stable'

# Conflicts:
#	package-lock.json
#	package.json
This commit is contained in:
zadam 2021-04-05 11:44:42 +02:00
commit 926e9e12c0
6 changed files with 93 additions and 46 deletions

Binary file not shown.

View File

@ -87,14 +87,16 @@ describe("Lexer expression", () => {
.toEqual(["#label", "*=*", "text"]); .toEqual(["#label", "*=*", "text"]);
}); });
it("simple label operator with in quotes and without", () => { it("simple label operator with in quotes", () => {
expect(lex("#label*=*'text'").expressionTokens) expect(lex("#label*=*'text'").expressionTokens)
.toEqual([ .toEqual([
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5}, {token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
{token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8}, {token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8},
{token: "text", inQuotes: true, startIndex: 10, endIndex: 13} {token: "text", inQuotes: true, startIndex: 10, endIndex: 13}
]); ]);
});
it("simple label operator with param without quotes", () => {
expect(lex("#label*=*text").expressionTokens) expect(lex("#label*=*text").expressionTokens)
.toEqual([ .toEqual([
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5}, {token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
@ -103,6 +105,16 @@ describe("Lexer expression", () => {
]); ]);
}); });
it("simple label operator with empty string param", () => {
expect(lex("#label = ''").expressionTokens)
.toEqual([
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
{token: "=", inQuotes: false, startIndex: 7, endIndex: 7},
// weird case for empty strings which ends up with endIndex < startIndex :-(
{token: "", inQuotes: true, startIndex: 10, endIndex: 9}
]);
});
it("note. prefix also separates fulltext from expression", () => { it("note. prefix also separates fulltext from expression", () => {
expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token)) expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token))
.toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]); .toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]);

View File

@ -19,6 +19,13 @@ function tokens(toks, cur = 0) {
}); });
} }
function assertIsArchived(exp) {
expect(exp.constructor.name).toEqual("PropertyComparisonExp");
expect(exp.propertyName).toEqual("isArchived");
expect(exp.operator).toEqual("=");
expect(exp.comparedValue).toEqual("false");
}
describe("Parser", () => { describe("Parser", () => {
it("fulltext parser without content", () => { it("fulltext parser without content", () => {
const rootExp = parse({ const rootExp = parse({
@ -29,8 +36,9 @@ describe("Parser", () => {
expect(rootExp.constructor.name).toEqual("AndExp"); expect(rootExp.constructor.name).toEqual("AndExp");
expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp"); expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
expect(rootExp.subExpressions[1].constructor.name).toEqual("NoteCacheFlatTextExp"); expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
expect(rootExp.subExpressions[1].tokens).toEqual(["hello", "hi"]); expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp");
expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
}); });
it("fulltext parser with content", () => { it("fulltext parser with content", () => {
@ -40,9 +48,12 @@ describe("Parser", () => {
searchContext: new SearchContext({includeNoteContent: true}) searchContext: new SearchContext({includeNoteContent: true})
}); });
expect(rootExp.constructor.name).toEqual("OrExp"); expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
const subs = rootExp.subExpressions; expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
const subs = rootExp.subExpressions[1].subExpressions;
expect(subs[0].constructor.name).toEqual("NoteCacheFlatTextExp"); expect(subs[0].constructor.name).toEqual("NoteCacheFlatTextExp");
expect(subs[0].tokens).toEqual(["hello", "hi"]); expect(subs[0].tokens).toEqual(["hello", "hi"]);
@ -61,10 +72,12 @@ describe("Parser", () => {
searchContext: new SearchContext() searchContext: new SearchContext()
}); });
expect(rootExp.constructor.name).toEqual("LabelComparisonExp"); expect(rootExp.constructor.name).toEqual("AndExp");
expect(rootExp.attributeType).toEqual("label"); assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.attributeName).toEqual("mylabel"); expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
expect(rootExp.comparator).toBeTruthy(); expect(rootExp.subExpressions[1].attributeType).toEqual("label");
expect(rootExp.subExpressions[1].attributeName).toEqual("mylabel");
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
}); });
it("simple attribute negation", () => { it("simple attribute negation", () => {
@ -74,10 +87,12 @@ describe("Parser", () => {
searchContext: new SearchContext() searchContext: new SearchContext()
}); });
expect(rootExp.constructor.name).toEqual("NotExp"); expect(rootExp.constructor.name).toEqual("AndExp");
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp"); assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpression.attributeType).toEqual("label"); expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
expect(rootExp.subExpression.attributeName).toEqual("mylabel"); expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("label");
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("mylabel");
rootExp = parse({ rootExp = parse({
fulltextTokens: [], fulltextTokens: [],
@ -85,10 +100,12 @@ describe("Parser", () => {
searchContext: new SearchContext() searchContext: new SearchContext()
}); });
expect(rootExp.constructor.name).toEqual("NotExp"); expect(rootExp.constructor.name).toEqual("AndExp");
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp"); assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpression.attributeType).toEqual("relation"); expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
expect(rootExp.subExpression.attributeName).toEqual("myrelation"); expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("relation");
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("myrelation");
}); });
it("simple label AND", () => { it("simple label AND", () => {
@ -99,7 +116,10 @@ describe("Parser", () => {
}); });
expect(rootExp.constructor.name).toEqual("AndExp"); expect(rootExp.constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions; assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
expect(firstSub.attributeName).toEqual("first"); expect(firstSub.attributeName).toEqual("first");
@ -116,7 +136,10 @@ describe("Parser", () => {
}); });
expect(rootExp.constructor.name).toEqual("AndExp"); expect(rootExp.constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions; assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
expect(firstSub.attributeName).toEqual("first"); expect(firstSub.attributeName).toEqual("first");
@ -132,8 +155,11 @@ describe("Parser", () => {
searchContext: new SearchContext() searchContext: new SearchContext()
}); });
expect(rootExp.constructor.name).toEqual("OrExp"); expect(rootExp.constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions; assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
expect(firstSub.attributeName).toEqual("first"); expect(firstSub.attributeName).toEqual("first");
@ -155,8 +181,9 @@ describe("Parser", () => {
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp"); expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
expect(firstSub.propertyName).toEqual('isArchived'); expect(firstSub.propertyName).toEqual('isArchived');
expect(secondSub.constructor.name).toEqual("NoteCacheFlatTextExp"); expect(secondSub.constructor.name).toEqual("OrExp");
expect(secondSub.tokens).toEqual(["hello"]); expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp");
expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
expect(thirdSub.constructor.name).toEqual("LabelComparisonExp"); expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
expect(thirdSub.attributeName).toEqual("mylabel"); expect(thirdSub.attributeName).toEqual("mylabel");
@ -169,8 +196,11 @@ describe("Parser", () => {
searchContext: new SearchContext() searchContext: new SearchContext()
}); });
expect(rootExp.constructor.name).toEqual("OrExp"); expect(rootExp.constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions; assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
expect(firstSub.attributeName).toEqual("first"); expect(firstSub.attributeName).toEqual("first");
@ -232,10 +262,12 @@ describe("Invalid expressions", () => {
searchContext: new SearchContext() searchContext: new SearchContext()
}); });
expect(rootExp.constructor.name).toEqual("LabelComparisonExp"); expect(rootExp.constructor.name).toEqual("AndExp");
expect(rootExp.attributeType).toEqual("label"); assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.attributeName).toEqual("first"); expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
expect(rootExp.comparator).toBeTruthy(); expect(rootExp.subExpressions[1].attributeType).toEqual("label");
expect(rootExp.subExpressions[1].attributeName).toEqual("first");
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
}); });
it("searching by relation without note property", () => { it("searching by relation without note property", () => {

View File

@ -562,8 +562,8 @@ describe("Search", () => {
expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria"); expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria");
expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy"); expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy");
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 0', searchContext); searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1', searchContext);
expect(searchResults.length).toEqual(0); expect(searchResults.length).toEqual(1);
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext); searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext);
expect(searchResults.length).toEqual(4); expect(searchResults.length).toEqual(4);

View File

@ -1,6 +1,9 @@
const {note} = require('./note_cache_mocking.js'); const {note} = require('./note_cache_mocking.js');
const ValueExtractor = require('../../src/services/search/value_extractor.js'); const ValueExtractor = require('../../src/services/search/value_extractor.js');
const noteCache = require('../../src/services/note_cache/note_cache.js'); const noteCache = require('../../src/services/note_cache/note_cache.js');
const SearchContext = require("../../src/services/search/search_context.js");
const dsc = new SearchContext();
describe("Value extractor", () => { describe("Value extractor", () => {
beforeEach(() => { beforeEach(() => {
@ -10,7 +13,7 @@ describe("Value extractor", () => {
it("simple title extraction", async () => { it("simple title extraction", async () => {
const europe = note("Europe").note; const europe = note("Europe").note;
const valueExtractor = new ValueExtractor(["note", "title"]); const valueExtractor = new ValueExtractor(dsc, ["note", "title"]);
expect(valueExtractor.validate()).toBeFalsy(); expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(europe)).toEqual("Europe"); expect(valueExtractor.extract(europe)).toEqual("Europe");
@ -21,12 +24,12 @@ describe("Value extractor", () => {
.label("Capital", "Vienna") .label("Capital", "Vienna")
.note; .note;
let valueExtractor = new ValueExtractor(["note", "labels", "capital"]); let valueExtractor = new ValueExtractor(dsc, ["note", "labels", "capital"]);
expect(valueExtractor.validate()).toBeFalsy(); expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(austria)).toEqual("Vienna"); expect(valueExtractor.extract(austria)).toEqual("Vienna");
valueExtractor = new ValueExtractor(["#capital"]); valueExtractor = new ValueExtractor(dsc, ["#capital"]);
expect(valueExtractor.validate()).toBeFalsy(); expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(austria)).toEqual("Vienna"); expect(valueExtractor.extract(austria)).toEqual("Vienna");
@ -38,12 +41,12 @@ describe("Value extractor", () => {
.child(note("Austria") .child(note("Austria")
.child(vienna)); .child(vienna));
let valueExtractor = new ValueExtractor(["note", "children", "children", "title"]); let valueExtractor = new ValueExtractor(dsc, ["note", "children", "children", "title"]);
expect(valueExtractor.validate()).toBeFalsy(); expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(europe.note)).toEqual("Vienna"); expect(valueExtractor.extract(europe.note)).toEqual("Vienna");
valueExtractor = new ValueExtractor(["note", "parents", "parents", "title"]); valueExtractor = new ValueExtractor(dsc, ["note", "parents", "parents", "title"]);
expect(valueExtractor.validate()).toBeFalsy(); expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(vienna.note)).toEqual("Europe"); expect(valueExtractor.extract(vienna.note)).toEqual("Europe");
@ -56,12 +59,12 @@ describe("Value extractor", () => {
.relation('neighbor', czechRepublic.note) .relation('neighbor', czechRepublic.note)
.relation('neighbor', slovakia.note); .relation('neighbor', slovakia.note);
let valueExtractor = new ValueExtractor(["note", "relations", "neighbor", "labels", "capital"]); let valueExtractor = new ValueExtractor(dsc, ["note", "relations", "neighbor", "labels", "capital"]);
expect(valueExtractor.validate()).toBeFalsy(); expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(austria.note)).toEqual("Prague"); expect(valueExtractor.extract(austria.note)).toEqual("Prague");
valueExtractor = new ValueExtractor(["~neighbor", "labels", "capital"]); valueExtractor = new ValueExtractor(dsc, ["~neighbor", "labels", "capital"]);
expect(valueExtractor.validate()).toBeFalsy(); expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(austria.note)).toEqual("Prague"); expect(valueExtractor.extract(austria.note)).toEqual("Prague");
@ -70,17 +73,17 @@ describe("Value extractor", () => {
describe("Invalid value extractor property path", () => { describe("Invalid value extractor property path", () => {
it('each path must start with "note" (or label/relation)', it('each path must start with "note" (or label/relation)',
() => expect(new ValueExtractor(["neighbor"]).validate()).toBeTruthy()); () => expect(new ValueExtractor(dsc, ["neighbor"]).validate()).toBeTruthy());
it("extra path element after terminal label", it("extra path element after terminal label",
() => expect(new ValueExtractor(["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy()); () => expect(new ValueExtractor(dsc, ["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy());
it("extra path element after terminal title", it("extra path element after terminal title",
() => expect(new ValueExtractor(["note", "title", "isProtected"]).validate()).toBeTruthy()); () => expect(new ValueExtractor(dsc, ["note", "title", "isProtected"]).validate()).toBeTruthy());
it("relation name and note property is missing", it("relation name and note property is missing",
() => expect(new ValueExtractor(["note", "relations"]).validate()).toBeTruthy()); () => expect(new ValueExtractor(dsc, ["note", "relations"]).validate()).toBeTruthy());
it("relation is specified but target note property is not specified", it("relation is specified but target note property is not specified",
() => expect(new ValueExtractor(["note", "relations", "myrel"]).validate()).toBeTruthy()); () => expect(new ValueExtractor(dsc, ["note", "relations", "myrel"]).validate()).toBeTruthy());
}); });

View File

@ -21,8 +21,8 @@ function lex(str) {
} }
} }
function finishWord(endIndex) { function finishWord(endIndex, createAlsoForEmptyWords = false) {
if (currentWord === '') { if (currentWord === '' && !createAlsoForEmptyWords) {
return; return;
} }
@ -71,7 +71,7 @@ function lex(str) {
} }
} }
else if (quotes === chr) { else if (quotes === chr) {
finishWord(i - 1); finishWord(i - 1, true);
quotes = false; quotes = false;
} }