mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 03:16:11 +01:00
search refactoring
This commit is contained in:
@@ -1,63 +1,63 @@
|
||||
const lexer = require('../../src/services/search/lexer.js');
|
||||
const lex = require('../../src/services/search/services/lex.js');
|
||||
|
||||
describe("Lexer fulltext", () => {
|
||||
it("simple lexing", () => {
|
||||
expect(lexer("hello world").fulltextTokens.map(t => t.token))
|
||||
expect(lex("hello world").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
it("use quotes to keep words together", () => {
|
||||
expect(lexer("'hello world' my friend").fulltextTokens.map(t => t.token))
|
||||
expect(lex("'hello world' my friend").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["hello world", "my", "friend"]);
|
||||
|
||||
expect(lexer('"hello world" my friend').fulltextTokens.map(t => t.token))
|
||||
expect(lex('"hello world" my friend').fulltextTokens.map(t => t.token))
|
||||
.toEqual(["hello world", "my", "friend"]);
|
||||
|
||||
expect(lexer('`hello world` my friend').fulltextTokens.map(t => t.token))
|
||||
expect(lex('`hello world` my friend').fulltextTokens.map(t => t.token))
|
||||
.toEqual(["hello world", "my", "friend"]);
|
||||
});
|
||||
|
||||
it("you can use different quotes and other special characters inside quotes", () => {
|
||||
expect(lexer("'i can use \" or ` or #~=*' without problem").fulltextTokens.map(t => t.token))
|
||||
expect(lex("'i can use \" or ` or #~=*' without problem").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["i can use \" or ` or #~=*", "without", "problem"]);
|
||||
});
|
||||
|
||||
it("if quote is not ended then it's just one long token", () => {
|
||||
expect(lexer("'unfinished quote").fulltextTokens.map(t => t.token))
|
||||
expect(lex("'unfinished quote").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["unfinished quote"]);
|
||||
});
|
||||
|
||||
it("parenthesis and symbols in fulltext section are just normal characters", () => {
|
||||
expect(lexer("what's u=p <b(r*t)h>").fulltextTokens.map(t => t.token))
|
||||
expect(lex("what's u=p <b(r*t)h>").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["what's", "u=p", "<b(r*t)h>"]);
|
||||
});
|
||||
|
||||
it("escaping special characters", () => {
|
||||
expect(lexer("hello \\#\\~\\'").fulltextTokens.map(t => t.token))
|
||||
expect(lex("hello \\#\\~\\'").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["hello", "#~'"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Lexer expression", () => {
|
||||
it("simple attribute existence", () => {
|
||||
expect(lexer("#label ~relation").expressionTokens.map(t => t.token))
|
||||
expect(lex("#label ~relation").expressionTokens.map(t => t.token))
|
||||
.toEqual(["#label", "~relation"]);
|
||||
});
|
||||
|
||||
it("simple label operators", () => {
|
||||
expect(lexer("#label*=*text").expressionTokens.map(t => t.token))
|
||||
expect(lex("#label*=*text").expressionTokens.map(t => t.token))
|
||||
.toEqual(["#label", "*=*", "text"]);
|
||||
});
|
||||
|
||||
it("simple label operator with in quotes and without", () => {
|
||||
expect(lexer("#label*=*'text'").expressionTokens)
|
||||
expect(lex("#label*=*'text'").expressionTokens)
|
||||
.toEqual([
|
||||
{token: "#label", inQuotes: false},
|
||||
{token: "*=*", inQuotes: false},
|
||||
{token: "text", inQuotes: true}
|
||||
]);
|
||||
|
||||
expect(lexer("#label*=*text").expressionTokens)
|
||||
expect(lex("#label*=*text").expressionTokens)
|
||||
.toEqual([
|
||||
{token: "#label", inQuotes: false},
|
||||
{token: "*=*", inQuotes: false},
|
||||
@@ -66,35 +66,35 @@ describe("Lexer expression", () => {
|
||||
});
|
||||
|
||||
it("complex expressions with and, or and parenthesis", () => {
|
||||
expect(lexer(`# (#label=text OR #second=text) AND ~relation`).expressionTokens.map(t => t.token))
|
||||
expect(lex(`# (#label=text OR #second=text) AND ~relation`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["#", "(", "#label", "=", "text", "or", "#second", "=", "text", ")", "and", "~relation"]);
|
||||
});
|
||||
|
||||
it("dot separated properties", () => {
|
||||
expect(lexer(`# ~author.title = 'Hugh Howey' AND note.'book title' = 'Silo'`).expressionTokens.map(t => t.token))
|
||||
expect(lex(`# ~author.title = 'Hugh Howey' AND note.'book title' = 'Silo'`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["#", "~author", ".", "title", "=", "hugh howey", "and", "note", ".", "book title", "=", "silo"]);
|
||||
});
|
||||
|
||||
it("negation of label and relation", () => {
|
||||
expect(lexer(`#!capital ~!neighbor`).expressionTokens.map(t => t.token))
|
||||
expect(lex(`#!capital ~!neighbor`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["#!capital", "~!neighbor"]);
|
||||
});
|
||||
|
||||
it("negation of sub-expression", () => {
|
||||
expect(lexer(`# not(#capital) and note.noteId != "root"`).expressionTokens.map(t => t.token))
|
||||
expect(lex(`# not(#capital) and note.noteId != "root"`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["#", "not", "(", "#capital", ")", "and", "note", ".", "noteid", "!=", "root"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Lexer invalid queries and edge cases", () => {
|
||||
it("concatenated attributes", () => {
|
||||
expect(lexer("#label~relation").expressionTokens.map(t => t.token))
|
||||
expect(lex("#label~relation").expressionTokens.map(t => t.token))
|
||||
.toEqual(["#label", "~relation"]);
|
||||
});
|
||||
|
||||
it("spaces in attribute names and values", () => {
|
||||
// invalid but should be reported by parser as an error
|
||||
expect(lexer(`#'long label'="hello o' world" ~'long relation'`).expressionTokens.map(t => t.token))
|
||||
expect(lex(`#'long label'="hello o' world" ~'long relation'`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["#long label", "=", "hello o' world", "~long relation"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const parens = require('../../src/services/search/parens.js');
|
||||
const handleParens = require('../../src/services/search/services/handle_parens.js');
|
||||
|
||||
describe("Parens handler", () => {
|
||||
it("handles parens", () => {
|
||||
const input = ["(", "hello", ")", "and", "(", "(", "pick", "one", ")", "and", "another", ")"]
|
||||
.map(token => ({token}));
|
||||
|
||||
expect(parens(input))
|
||||
expect(handleParens(input))
|
||||
.toEqual([
|
||||
[
|
||||
{token: "hello"}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const ParsingContext = require("../../src/services/search/parsing_context.js");
|
||||
const parser = require('../../src/services/search/parser.js');
|
||||
const parse = require('../../src/services/search/services/parse.js');
|
||||
|
||||
function tokens(...args) {
|
||||
return args.map(arg => {
|
||||
@@ -17,7 +17,7 @@ function tokens(...args) {
|
||||
|
||||
describe("Parser", () => {
|
||||
it("fulltext parser without content", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens("hello", "hi"),
|
||||
expressionTokens: [],
|
||||
parsingContext: new ParsingContext({includeNoteContent: false})
|
||||
@@ -28,7 +28,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("fulltext parser with content", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens("hello", "hi"),
|
||||
expressionTokens: [],
|
||||
parsingContext: new ParsingContext({includeNoteContent: true})
|
||||
@@ -48,7 +48,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label comparison", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#mylabel", "=", "text"),
|
||||
parsingContext: new ParsingContext()
|
||||
@@ -61,7 +61,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple attribute negation", () => {
|
||||
let rootExp = parser({
|
||||
let rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#!mylabel"),
|
||||
parsingContext: new ParsingContext()
|
||||
@@ -72,7 +72,7 @@ describe("Parser", () => {
|
||||
expect(rootExp.subExpression.attributeType).toEqual("label");
|
||||
expect(rootExp.subExpression.attributeName).toEqual("mylabel");
|
||||
|
||||
rootExp = parser({
|
||||
rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("~!myrelation"),
|
||||
parsingContext: new ParsingContext()
|
||||
@@ -85,7 +85,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label AND", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#first", "=", "text", "and", "#second", "=", "text"),
|
||||
parsingContext: new ParsingContext(true)
|
||||
@@ -102,7 +102,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label AND without explicit AND", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#first", "=", "text", "#second", "=", "text"),
|
||||
parsingContext: new ParsingContext()
|
||||
@@ -119,7 +119,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label OR", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#first", "=", "text", "or", "#second", "=", "text"),
|
||||
parsingContext: new ParsingContext()
|
||||
@@ -136,7 +136,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("fulltext and simple label", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens("hello"),
|
||||
expressionTokens: tokens("#mylabel", "=", "text"),
|
||||
parsingContext: new ParsingContext()
|
||||
@@ -153,7 +153,7 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("label sub-expression", () => {
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#first", "=", "text", "or", tokens("#second", "=", "text", "and", "#third", "=", "text")),
|
||||
parsingContext: new ParsingContext()
|
||||
@@ -176,11 +176,11 @@ describe("Parser", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Invalid tokens", () => {
|
||||
describe("Invalid expressions", () => {
|
||||
it("incomplete comparison", () => {
|
||||
const parsingContext = new ParsingContext();
|
||||
|
||||
parser({
|
||||
parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#first", "="),
|
||||
parsingContext
|
||||
@@ -192,7 +192,7 @@ describe("Invalid tokens", () => {
|
||||
it("comparison between labels is impossible", () => {
|
||||
let parsingContext = new ParsingContext();
|
||||
|
||||
parser({
|
||||
parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#first", "=", "#second"),
|
||||
parsingContext
|
||||
@@ -202,7 +202,7 @@ describe("Invalid tokens", () => {
|
||||
|
||||
parsingContext = new ParsingContext();
|
||||
|
||||
parser({
|
||||
parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens("#first", "=", "note", ".", "relations", "second"),
|
||||
parsingContext
|
||||
@@ -210,7 +210,7 @@ describe("Invalid tokens", () => {
|
||||
|
||||
expect(parsingContext.error).toEqual(`Error near token "note", it's possible to compare with constant only.`);
|
||||
|
||||
const rootExp = parser({
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: [
|
||||
{ token: "#first", inQuotes: false },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const searchService = require('../../src/services/search/search.js');
|
||||
const searchService = require('../../src/services/search/services/search.js');
|
||||
const Note = require('../../src/services/note_cache/entities/note.js');
|
||||
const Branch = require('../../src/services/note_cache/entities/branch.js');
|
||||
const Attribute = require('../../src/services/note_cache/entities/attribute.js');
|
||||
|
||||
Reference in New Issue
Block a user