diff --git a/spec/search.spec.js b/spec/search.spec.js index 8a4589499..f0b1fcbc4 100644 --- a/spec/search.spec.js +++ b/spec/search.spec.js @@ -56,6 +56,38 @@ describe("Search", () => { expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy(); }); + it("label comparison with short syntax", async () => { + rootNote + .child(note("Europe") + .child(note("Austria") + .label('capital', 'Vienna')) + .child(note("Czech Republic") + .label('capital', 'Prague')) + ); + + const parsingContext = new ParsingContext(); + + let searchResults = await searchService.findNotesWithQuery('#capital=Vienna', parsingContext); + expect(searchResults.length).toEqual(1); + expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy(); + }); + + it("label comparison with full syntax", async () => { + rootNote + .child(note("Europe") + .child(note("Austria") + .label('capital', 'Vienna')) + .child(note("Czech Republic") + .label('capital', 'Prague')) + ); + + const parsingContext = new ParsingContext(); + + let searchResults = await searchService.findNotesWithQuery('# note.labels.capital=Prague', parsingContext); + expect(searchResults.length).toEqual(1); + expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); + }); + it("numeric label comparison", async () => { rootNote .child(note("Europe") @@ -162,12 +194,12 @@ describe("Search", () => { const parsingContext = new ParsingContext(); - let searchResults = await searchService.findNotesWithQuery('# note.parent.title = Europe', parsingContext); + let searchResults = await searchService.findNotesWithQuery('# note.parents.title = Europe', parsingContext); expect(searchResults.length).toEqual(2); expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy(); expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); - searchResults = await searchService.findNotesWithQuery('# note.parent.title = Asia', parsingContext); + searchResults = await searchService.findNotesWithQuery('# note.parents.title = Asia', parsingContext); expect(searchResults.length).toEqual(1); expect(findNoteByTitle(searchResults, "Taiwan")).toBeTruthy(); }); @@ -182,17 +214,17 @@ describe("Search", () => { const parsingContext = new ParsingContext(); - let searchResults = await searchService.findNotesWithQuery('# note.child.title =* Aust', parsingContext); + let searchResults = await searchService.findNotesWithQuery('# note.children.title =* Aust', parsingContext); expect(searchResults.length).toEqual(2); expect(findNoteByTitle(searchResults, "Europe")).toBeTruthy(); expect(findNoteByTitle(searchResults, "Oceania")).toBeTruthy(); - searchResults = await searchService.findNotesWithQuery('# note.child.title =* Aust AND note.child.title *= republic', parsingContext); + searchResults = await searchService.findNotesWithQuery('# note.children.title =* Aust AND note.children.title *= republic', parsingContext); expect(searchResults.length).toEqual(1); expect(findNoteByTitle(searchResults, "Europe")).toBeTruthy(); }); - it("filter by relation's note properties", async () => { + it("filter by relation's note properties using short syntax", async () => { const austria = note("Austria"); const portugal = note("Portugal"); @@ -216,6 +248,27 @@ describe("Search", () => { expect(searchResults.length).toEqual(1); expect(findNoteByTitle(searchResults, "Spain")).toBeTruthy(); }); + + it("filter by relation's note properties using long syntax", async () => { + const austria = note("Austria"); + const portugal = note("Portugal"); + + rootNote + .child(note("Europe") + .child(austria) + .child(note("Czech Republic") + .relation('neighbor', austria.note)) + .child(portugal) + .child(note("Spain") + .relation('neighbor', portugal.note)) + ); + + const parsingContext = new ParsingContext(); + + const searchResults = await searchService.findNotesWithQuery('# note.relations.neighbor.title = Austria', parsingContext); + expect(searchResults.length).toEqual(1); + expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); + }); }); /** @return {Note} */ diff --git a/src/services/search/parser.js b/src/services/search/parser.js index bb03b2d73..db6c3b21c 100644 --- a/src/services/search/parser.js +++ b/src/services/search/parser.js @@ -52,18 +52,40 @@ function getExpression(tokens, parsingContext) { i++; - if (tokens[i] === 'parent') { + if (tokens[i] === 'parents') { i += 1; return new ChildOfExp(parseNoteProperty()); } - if (tokens[i] === 'child') { + if (tokens[i] === 'children') { i += 1; return new ParentOfExp(parseNoteProperty()); } + if (tokens[i] === 'labels') { + if (tokens[i + 1] !== '.') { + parsingContext.addError(`Expected "." to separate field path, god "${tokens[i + 1]}"`); + return; + } + + i += 2; + + return parseLabel(tokens[i]); + } + + if (tokens[i] === 'relations') { + if (tokens[i + 1] !== '.') { + parsingContext.addError(`Expected "." to separate field path, god "${tokens[i + 1]}"`); + return; + } + + i += 2; + + return parseRelation(tokens[i]); + } + if (tokens[i] === 'title') { const propertyName = tokens[i]; const operator = tokens[i + 1]; @@ -81,6 +103,45 @@ function getExpression(tokens, parsingContext) { } } + function parseLabel(labelName) { + parsingContext.highlightedTokens.push(labelName); + + if (i < tokens.length - 2 && isOperator(tokens[i + 1])) { + let operator = tokens[i + 1]; + const comparedValue = tokens[i + 2]; + + parsingContext.highlightedTokens.push(comparedValue); + + if (parsingContext.fuzzyAttributeSearch && operator === '=') { + operator = '*=*'; + } + + const comparator = comparatorBuilder(operator, comparedValue); + + if (!comparator) { + parsingContext.addError(`Can't find operator '${operator}'`); + } else { + i += 2; + + return new LabelComparisonExp('label', labelName, comparator); + } + } else { + return new AttributeExistsExp('label', labelName, parsingContext.fuzzyAttributeSearch); + } + } + + function parseRelation(relationName) { + parsingContext.highlightedTokens.push(relationName); + + if (i < tokens.length - 2 && tokens[i + 1] === '.') { + i += 1; + + return new RelationWhereExp(relationName, parseNoteProperty()); + } else { + return new AttributeExistsExp('relation', relationName, parsingContext.fuzzyAttributeSearch); + } + } + for (i = 0; i < tokens.length; i++) { const token = tokens[i]; @@ -93,45 +154,13 @@ function getExpression(tokens, parsingContext) { } else if (token.startsWith('#')) { const labelName = token.substr(1); - parsingContext.highlightedTokens.push(labelName); - if (i < tokens.length - 2 && isOperator(tokens[i + 1])) { - let operator = tokens[i + 1]; - const comparedValue = tokens[i + 2]; - - parsingContext.highlightedTokens.push(comparedValue); - - if (parsingContext.fuzzyAttributeSearch && operator === '=') { - operator = '*=*'; - } - - const comparator = comparatorBuilder(operator, comparedValue); - - if (!comparator) { - parsingContext.addError(`Can't find operator '${operator}'`); - continue; - } - - expressions.push(new LabelComparisonExp('label', labelName, comparator)); - - i += 2; - } - else { - expressions.push(new AttributeExistsExp('label', labelName, parsingContext.fuzzyAttributeSearch)); - } + expressions.push(parseLabel(labelName)); } else if (token.startsWith('~')) { const relationName = token.substr(1); - parsingContext.highlightedTokens.push(relationName); - if (i < tokens.length - 2 && tokens[i + 1] === '.') { - i += 1; - - expressions.push(new RelationWhereExp(relationName, parseNoteProperty())); - } - else { - expressions.push(new AttributeExistsExp('relation', relationName, parsingContext.fuzzyAttributeSearch)); - } + expressions.push(parseRelation(relationName)); } else if (token === 'note') { i++;