mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	implemented property based access + parent
This commit is contained in:
		@@ -18,8 +18,8 @@ describe("Lexer fulltext", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("you can use different quotes and other special characters inside quotes", () => {
 | 
					    it("you can use different quotes and other special characters inside quotes", () => {
 | 
				
			||||||
        expect(lexer("'i can use \" or ` or #@=*' without problem").fulltextTokens)
 | 
					        expect(lexer("'i can use \" or ` or #~=*' without problem").fulltextTokens)
 | 
				
			||||||
            .toEqual(["i can use \" or ` or #@=*", "without", "problem"]);
 | 
					            .toEqual(["i can use \" or ` or #~=*", "without", "problem"]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("if quote is not ended then it's just one long token", () => {
 | 
					    it("if quote is not ended then it's just one long token", () => {
 | 
				
			||||||
@@ -33,15 +33,15 @@ describe("Lexer fulltext", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("escaping special characters", () => {
 | 
					    it("escaping special characters", () => {
 | 
				
			||||||
        expect(lexer("hello \\#\\@\\'").fulltextTokens)
 | 
					        expect(lexer("hello \\#\\~\\'").fulltextTokens)
 | 
				
			||||||
            .toEqual(["hello", "#@'"]);
 | 
					            .toEqual(["hello", "#~'"]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Lexer expression", () => {
 | 
					describe("Lexer expression", () => {
 | 
				
			||||||
    it("simple attribute existence", () => {
 | 
					    it("simple attribute existence", () => {
 | 
				
			||||||
        expect(lexer("#label @relation").expressionTokens)
 | 
					        expect(lexer("#label ~relation").expressionTokens)
 | 
				
			||||||
            .toEqual(["#label", "@relation"]);
 | 
					            .toEqual(["#label", "~relation"]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("simple label operators", () => {
 | 
					    it("simple label operators", () => {
 | 
				
			||||||
@@ -50,12 +50,17 @@ describe("Lexer expression", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("spaces in attribute names and values", () => {
 | 
					    it("spaces in attribute names and values", () => {
 | 
				
			||||||
        expect(lexer(`#'long label'="hello o' world" @'long relation'`).expressionTokens)
 | 
					        expect(lexer(`#'long label'="hello o' world" ~'long relation'`).expressionTokens)
 | 
				
			||||||
            .toEqual(["#long label", "=", "hello o' world", "@long relation"]);
 | 
					            .toEqual(["#long label", "=", "hello o' world", "~long relation"]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("complex expressions with and, or and parenthesis", () => {
 | 
					    it("complex expressions with and, or and parenthesis", () => {
 | 
				
			||||||
        expect(lexer(`# (#label=text OR #second=text) AND @relation`).expressionTokens)
 | 
					        expect(lexer(`# (#label=text OR #second=text) AND ~relation`).expressionTokens)
 | 
				
			||||||
            .toEqual(["#", "(", "#label", "=", "text", "or", "#second", "=", "text", ")", "and", "@relation"]);
 | 
					            .toEqual(["#", "(", "#label", "=", "text", "or", "#second", "=", "text", ")", "and", "~relation"]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("dot separated properties", () => {
 | 
				
			||||||
 | 
					        expect(lexer(`# ~author.title = 'Hugh Howey' AND note.title = 'Silo'`).expressionTokens)
 | 
				
			||||||
 | 
					            .toEqual(["#", "~author", ".", "title", "=", "hugh howey", "and", "note", ".", "title", "=", "silo"]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ describe("Parser", () => {
 | 
				
			|||||||
            parsingContext: new ParsingContext()
 | 
					            parsingContext: new ParsingContext()
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(rootExp.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(rootExp.attributeType).toEqual("label");
 | 
					        expect(rootExp.attributeType).toEqual("label");
 | 
				
			||||||
        expect(rootExp.attributeName).toEqual("mylabel");
 | 
					        expect(rootExp.attributeName).toEqual("mylabel");
 | 
				
			||||||
        expect(rootExp.comparator).toBeTruthy();
 | 
					        expect(rootExp.comparator).toBeTruthy();
 | 
				
			||||||
@@ -53,10 +53,10 @@ describe("Parser", () => {
 | 
				
			|||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
					        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(secondSub.attributeName).toEqual("second");
 | 
					        expect(secondSub.attributeName).toEqual("second");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -70,27 +70,27 @@ describe("Parser", () => {
 | 
				
			|||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
					        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(secondSub.attributeName).toEqual("second");
 | 
					        expect(secondSub.attributeName).toEqual("second");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("simple label OR", () => {
 | 
					    it("simple label OR", () => {
 | 
				
			||||||
        const rootExp = parser({
 | 
					        const rootExp = parser({
 | 
				
			||||||
            fulltextTokens: [],
 | 
					            fulltextTokens: [],
 | 
				
			||||||
            expressionTokens: ["#first", "=", "text", "OR", "#second", "=", "text"],
 | 
					            expressionTokens: ["#first", "=", "text", "or", "#second", "=", "text"],
 | 
				
			||||||
            parsingContext: new ParsingContext()
 | 
					            parsingContext: new ParsingContext()
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("OrExp");
 | 
					        expect(rootExp.constructor.name).toEqual("OrExp");
 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
					        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(secondSub.attributeName).toEqual("second");
 | 
					        expect(secondSub.attributeName).toEqual("second");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,30 +107,30 @@ describe("Parser", () => {
 | 
				
			|||||||
        expect(firstSub.constructor.name).toEqual("NoteCacheFulltextExp");
 | 
					        expect(firstSub.constructor.name).toEqual("NoteCacheFulltextExp");
 | 
				
			||||||
        expect(firstSub.tokens).toEqual(["hello"]);
 | 
					        expect(firstSub.tokens).toEqual(["hello"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(secondSub.attributeName).toEqual("mylabel");
 | 
					        expect(secondSub.attributeName).toEqual("mylabel");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("label sub-expression", () => {
 | 
					    it("label sub-expression", () => {
 | 
				
			||||||
        const rootExp = parser({
 | 
					        const rootExp = parser({
 | 
				
			||||||
            fulltextTokens: [],
 | 
					            fulltextTokens: [],
 | 
				
			||||||
            expressionTokens: ["#first", "=", "text", "OR", ["#second", "=", "text", "AND", "#third", "=", "text"]],
 | 
					            expressionTokens: ["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]],
 | 
				
			||||||
            parsingContext: new ParsingContext()
 | 
					            parsingContext: new ParsingContext()
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("OrExp");
 | 
					        expect(rootExp.constructor.name).toEqual("OrExp");
 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
					        const [firstSub, secondSub] = rootExp.subExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("AndExp");
 | 
					        expect(secondSub.constructor.name).toEqual("AndExp");
 | 
				
			||||||
        const [firstSubSub, secondSubSub] = secondSub.subExpressions;
 | 
					        const [firstSubSub, secondSubSub] = secondSub.subExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(firstSubSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(firstSubSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(firstSubSub.attributeName).toEqual("second");
 | 
					        expect(firstSubSub.attributeName).toEqual("second");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(secondSubSub.constructor.name).toEqual("FieldComparisonExp");
 | 
					        expect(secondSubSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
        expect(secondSubSub.attributeName).toEqual("third");
 | 
					        expect(secondSubSub.attributeName).toEqual("third");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,31 @@ describe("Search", () => {
 | 
				
			|||||||
        expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy();
 | 
					        expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("numeric label comparison fallback to string comparison", async () => {
 | 
				
			||||||
 | 
					        rootNote.child(
 | 
				
			||||||
 | 
					            note("Europe")
 | 
				
			||||||
 | 
					                .label('country', '', true)
 | 
				
			||||||
 | 
					                .child(
 | 
				
			||||||
 | 
					                    note("Austria")
 | 
				
			||||||
 | 
					                        .label('established', '1955-07-27')
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .child(
 | 
				
			||||||
 | 
					                    note("Czech Republic")
 | 
				
			||||||
 | 
					                        .label('established', '1993-01-01')
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .child(
 | 
				
			||||||
 | 
					                    note("Hungary")
 | 
				
			||||||
 | 
					                        .label('established', '..wrong..')
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const parsingContext = new ParsingContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const searchResults = await searchService.findNotesWithQuery('#established < 1990', parsingContext);
 | 
				
			||||||
 | 
					        expect(searchResults.length).toEqual(1);
 | 
				
			||||||
 | 
					        expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("logical or", async () => {
 | 
					    it("logical or", async () => {
 | 
				
			||||||
        rootNote.child(
 | 
					        rootNote.child(
 | 
				
			||||||
            note("Europe")
 | 
					            note("Europe")
 | 
				
			||||||
@@ -140,6 +165,51 @@ describe("Search", () => {
 | 
				
			|||||||
        expect(searchResults.length).toEqual(1);
 | 
					        expect(searchResults.length).toEqual(1);
 | 
				
			||||||
        expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy();
 | 
					        expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("filter by note property", async () => {
 | 
				
			||||||
 | 
					        rootNote.child(
 | 
				
			||||||
 | 
					            note("Europe")
 | 
				
			||||||
 | 
					                .child(
 | 
				
			||||||
 | 
					                    note("Austria")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .child(
 | 
				
			||||||
 | 
					                    note("Czech Republic")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const parsingContext = new ParsingContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const searchResults = await searchService.findNotesWithQuery('# note.title =* czech', parsingContext);
 | 
				
			||||||
 | 
					        expect(searchResults.length).toEqual(1);
 | 
				
			||||||
 | 
					        expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("filter by note's parent", async () => {
 | 
				
			||||||
 | 
					        rootNote.child(
 | 
				
			||||||
 | 
					            note("Europe")
 | 
				
			||||||
 | 
					                .child(
 | 
				
			||||||
 | 
					                    note("Austria")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .child(
 | 
				
			||||||
 | 
					                    note("Czech Republic")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					            .child(
 | 
				
			||||||
 | 
					                note("Asia")
 | 
				
			||||||
 | 
					                    .child(note('Taiwan'))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const parsingContext = new ParsingContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let searchResults = await searchService.findNotesWithQuery('# note.parent.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);
 | 
				
			||||||
 | 
					        expect(searchResults.length).toEqual(1);
 | 
				
			||||||
 | 
					        expect(findNoteByTitle(searchResults, "Taiwan")).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @return {Note} */
 | 
					/** @return {Note} */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const Expression = require('./expression');
 | 
					const Expression = require('./expression');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AndExp extends Expression{
 | 
					class AndExp extends Expression {
 | 
				
			||||||
    static of(subExpressions) {
 | 
					    static of(subExpressions) {
 | 
				
			||||||
        subExpressions = subExpressions.filter(exp => !!exp);
 | 
					        subExpressions = subExpressions.filter(exp => !!exp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								src/services/search/expressions/child_of.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/services/search/expressions/child_of.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Expression = require('./expression');
 | 
				
			||||||
 | 
					const NoteSet = require('../note_set');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChildOfExp extends Expression {
 | 
				
			||||||
 | 
					    constructor(subExpression) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.subExpression = subExpression;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    execute(inputNoteSet, searchContext) {
 | 
				
			||||||
 | 
					        const subInputNoteSet = new NoteSet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const note of inputNoteSet.notes) {
 | 
				
			||||||
 | 
					            subInputNoteSet.addAll(note.parents);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const subResNoteSet = this.subExpression.execute(subInputNoteSet, searchContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const resNoteSet = new NoteSet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const parentNote of subResNoteSet.notes) {
 | 
				
			||||||
 | 
					            for (const childNote of parentNote.children) {
 | 
				
			||||||
 | 
					                if (inputNoteSet.hasNote(childNote)) {
 | 
				
			||||||
 | 
					                    resNoteSet.add(childNote);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return resNoteSet;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = ChildOfExp;
 | 
				
			||||||
@@ -4,6 +4,7 @@ class Expression {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param {NoteSet} noteSet
 | 
					     * @param {NoteSet} noteSet
 | 
				
			||||||
     * @param {object} searchContext
 | 
					     * @param {object} searchContext
 | 
				
			||||||
 | 
					     * @return {NoteSet}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    execute(noteSet, searchContext) {}
 | 
					    execute(noteSet, searchContext) {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ const Expression = require('./expression');
 | 
				
			|||||||
const NoteSet = require('../note_set');
 | 
					const NoteSet = require('../note_set');
 | 
				
			||||||
const noteCache = require('../../note_cache/note_cache');
 | 
					const noteCache = require('../../note_cache/note_cache');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FieldComparisonExp extends Expression {
 | 
					class LabelComparisonExp extends Expression {
 | 
				
			||||||
    constructor(attributeType, attributeName, comparator) {
 | 
					    constructor(attributeType, attributeName, comparator) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,4 +37,4 @@ class FieldComparisonExp extends Expression {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = FieldComparisonExp;
 | 
					module.exports = LabelComparisonExp;
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/services/search/expressions/property_comparison.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/services/search/expressions/property_comparison.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Expression = require('./expression');
 | 
				
			||||||
 | 
					const NoteSet = require('../note_set');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PropertyComparisonExp extends Expression {
 | 
				
			||||||
 | 
					    constructor(propertyName, comparator) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.propertyName = propertyName;
 | 
				
			||||||
 | 
					        this.comparator = comparator;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    execute(noteSet, searchContext) {
 | 
				
			||||||
 | 
					        const resNoteSet = new NoteSet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const note of noteSet.notes) {
 | 
				
			||||||
 | 
					            const value = note[this.propertyName].toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.comparator(value)) {
 | 
				
			||||||
 | 
					                resNoteSet.add(note);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return resNoteSet;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = PropertyComparisonExp;
 | 
				
			||||||
@@ -77,7 +77,7 @@ function lexer(str) {
 | 
				
			|||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (!quotes) {
 | 
					        else if (!quotes) {
 | 
				
			||||||
            if (currentWord.length === 0 && (chr === '#' || chr === '@')) {
 | 
					            if (currentWord.length === 0 && (chr === '#' || chr === '~')) {
 | 
				
			||||||
                fulltextEnded = true;
 | 
					                fulltextEnded = true;
 | 
				
			||||||
                currentWord = chr;
 | 
					                currentWord = chr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,7 +87,7 @@ function lexer(str) {
 | 
				
			|||||||
                finishWord();
 | 
					                finishWord();
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (fulltextEnded && ['(', ')'].includes(chr)) {
 | 
					            else if (fulltextEnded && ['(', ')', '.'].includes(chr)) {
 | 
				
			||||||
                finishWord();
 | 
					                finishWord();
 | 
				
			||||||
                currentWord += chr;
 | 
					                currentWord += chr;
 | 
				
			||||||
                finishWord();
 | 
					                finishWord();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,11 +6,19 @@ class NoteSet {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    add(note) {
 | 
					    add(note) {
 | 
				
			||||||
        this.notes.push(note);
 | 
					        if (!this.hasNote(note)) {
 | 
				
			||||||
 | 
					            this.notes.push(note);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addAll(notes) {
 | 
					    addAll(notes) {
 | 
				
			||||||
        this.notes.push(...notes);
 | 
					        for (const note of notes) {
 | 
				
			||||||
 | 
					            this.add(note);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hasNote(note) {
 | 
				
			||||||
 | 
					        return this.hasNoteId(note.noteId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hasNoteId(noteId) {
 | 
					    hasNoteId(noteId) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,10 @@
 | 
				
			|||||||
const AndExp = require('./expressions/and');
 | 
					const AndExp = require('./expressions/and');
 | 
				
			||||||
const OrExp = require('./expressions/or');
 | 
					const OrExp = require('./expressions/or');
 | 
				
			||||||
const NotExp = require('./expressions/not');
 | 
					const NotExp = require('./expressions/not');
 | 
				
			||||||
 | 
					const ChildOfExp = require('./expressions/child_of');
 | 
				
			||||||
 | 
					const PropertyComparisonExp = require('./expressions/property_comparison');
 | 
				
			||||||
const AttributeExistsExp = require('./expressions/attribute_exists');
 | 
					const AttributeExistsExp = require('./expressions/attribute_exists');
 | 
				
			||||||
const FieldComparisonExp = require('./expressions/field_comparison');
 | 
					const LabelComparisonExp = require('./expressions/label_comparison');
 | 
				
			||||||
const NoteCacheFulltextExp = require('./expressions/note_cache_fulltext');
 | 
					const NoteCacheFulltextExp = require('./expressions/note_cache_fulltext');
 | 
				
			||||||
const NoteContentFulltextExp = require('./expressions/note_content_fulltext');
 | 
					const NoteContentFulltextExp = require('./expressions/note_content_fulltext');
 | 
				
			||||||
const comparatorBuilder = require('./comparator_builder');
 | 
					const comparatorBuilder = require('./comparator_builder');
 | 
				
			||||||
@@ -38,17 +40,50 @@ function getExpression(tokens, parsingContext) {
 | 
				
			|||||||
    const expressions = [];
 | 
					    const expressions = [];
 | 
				
			||||||
    let op = null;
 | 
					    let op = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let i = 0; i < tokens.length; i++) {
 | 
					    let i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function parseNoteProperty() {
 | 
				
			||||||
 | 
					        if (tokens[i] !== '.') {
 | 
				
			||||||
 | 
					            parsingContext.addError('Expected "." to separate field path');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        i++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (tokens[i] === 'parent') {
 | 
				
			||||||
 | 
					            i += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new ChildOfExp(parseNoteProperty());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (tokens[i] === 'title') {
 | 
				
			||||||
 | 
					            const propertyName = tokens[i];
 | 
				
			||||||
 | 
					            const operator = tokens[i + 1];
 | 
				
			||||||
 | 
					            const comparedValue = tokens[i + 2];
 | 
				
			||||||
 | 
					            const comparator = comparatorBuilder(operator, comparedValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!comparator) {
 | 
				
			||||||
 | 
					                parsingContext.addError(`Can't find operator '${operator}'`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            i += 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new PropertyComparisonExp(propertyName, comparator);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (i = 0; i < tokens.length; i++) {
 | 
				
			||||||
        const token = tokens[i];
 | 
					        const token = tokens[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (token === '#' || token === '@') {
 | 
					        if (token === '#' || token === '~') {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (Array.isArray(token)) {
 | 
					        if (Array.isArray(token)) {
 | 
				
			||||||
            expressions.push(getExpression(token, parsingContext));
 | 
					            expressions.push(getExpression(token, parsingContext));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (token.startsWith('#') || token.startsWith('@')) {
 | 
					        else if (token.startsWith('#') || token.startsWith('~')) {
 | 
				
			||||||
            const type = token.startsWith('#') ? 'label' : 'relation';
 | 
					            const type = token.startsWith('#') ? 'label' : 'relation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            parsingContext.highlightedTokens.push(token.substr(1));
 | 
					            parsingContext.highlightedTokens.push(token.substr(1));
 | 
				
			||||||
@@ -70,7 +105,7 @@ function getExpression(tokens, parsingContext) {
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                expressions.push(new FieldComparisonExp(type, token.substr(1), comparator));
 | 
					                expressions.push(new LabelComparisonExp(type, token.substr(1), comparator));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                i += 2;
 | 
					                i += 2;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -78,11 +113,18 @@ function getExpression(tokens, parsingContext) {
 | 
				
			|||||||
                expressions.push(new AttributeExistsExp(type, token.substr(1), parsingContext.fuzzyAttributeSearch));
 | 
					                expressions.push(new AttributeExistsExp(type, token.substr(1), parsingContext.fuzzyAttributeSearch));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (['and', 'or'].includes(token.toLowerCase())) {
 | 
					        else if (token === 'note') {
 | 
				
			||||||
 | 
					            i++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            expressions.push(parseNoteProperty(tokens));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (['and', 'or'].includes(token)) {
 | 
				
			||||||
            if (!op) {
 | 
					            if (!op) {
 | 
				
			||||||
                op = token.toLowerCase();
 | 
					                op = token;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (op !== token.toLowerCase()) {
 | 
					            else if (op !== token) {
 | 
				
			||||||
                parsingContext.addError('Mixed usage of AND/OR - always use parenthesis to group AND/OR expressions.');
 | 
					                parsingContext.addError('Mixed usage of AND/OR - always use parenthesis to group AND/OR expressions.');
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user