Leichter und leistungsstarker Lucene-ähnlicher Parser, Serialisierer und Suchmaschine.
Ursprünglich wurde Liqe entwickelt, um die Roarr-Protokollfilterung über CLI zu ermöglichen. Seitdem verfeinere ich dieses Projekt als Hobby/intellektuelle Übung. Ich habe gesehen, dass es von verschiedenen CLI- und Webanwendungen übernommen wird, die eine erweiterte Suche erfordern. Meines Wissens handelt es sich derzeit um den vollständigsten Lucene-ähnlichen Syntaxparser und Serialisierer in JavaScript sowie um eine kompatible In-Memory-Suchmaschine.
Zu den Liqe-Anwendungsfällen gehören:
Beachten Sie, dass der Liqe AST als öffentliche API behandelt wird, d. h. man könnte einen eigenen Suchmechanismus implementieren, der die Liqe-Abfragesprache (LQL) verwendet.
import {
filter ,
highlight ,
parse ,
test ,
} from 'liqe' ;
const persons = [
{
height : 180 ,
name : 'John Morton' ,
} ,
{
height : 175 ,
name : 'David Barker' ,
} ,
{
height : 170 ,
name : 'Thomas Castro' ,
} ,
] ;
Filtern Sie eine Sammlung:
filter ( parse ( 'height:>170' ) , persons ) ;
// [
// {
// height: 180,
// name: 'John Morton',
// },
// {
// height: 175,
// name: 'David Barker',
// },
// ]
Testen Sie ein einzelnes Objekt:
test ( parse ( 'name:John' ) , persons [ 0 ] ) ;
// true
test ( parse ( 'name:David' ) , persons [ 0 ] ) ;
// false
Markieren Sie passende Felder und Teilzeichenfolgen:
highlight ( parse ( 'name:john' ) , persons [ 0 ] ) ;
// [
// {
// path: 'name',
// query: /(John)/,
// }
// ]
highlight ( parse ( 'height:180' ) , persons [ 0 ] ) ;
// [
// {
// path: 'height',
// }
// ]
Liqe verwendet die Liqe Query Language (LQL), die stark von Lucene inspiriert ist, diese jedoch auf verschiedene Weise erweitert, um ein leistungsfähigeres Sucherlebnis zu ermöglichen.
# search for "foo" term anywhere in the document (case insensitive)
foo
# search for "foo" term anywhere in the document (case sensitive)
'foo'
"foo"
# search for "foo" term in `name` field
name :foo
# search for "foo" term in `full name` field
'full name' : foo
"full name" : foo
# search for "foo" term in `first` field, member of `name`, i.e.
# matches {name: {first: 'foo'}}
name . first :foo
# search using regex
name :/foo /
name :/ foo / o
# search using wildcard
name :foo * bar
name :foo? bar
# boolean search
member :true
member :false
# null search
member :null
# search for age =, >, >=, <, <=
height : = 100
height :>100
height :>=100
height :<100
height :<=100
# search for height in range (inclusive, exclusive)
height : [ 100 TO 200 ]
height : { 100 TO 200 }
# boolean operators
name :foo AND height : = 100
name :foo OR name : bar
# unary operators
NOT foo
- foo
NOT foo : bar
- foo :bar
name :foo AND NOT ( bio :bar OR bio : baz )
# implicit AND boolean operator
name :foo height : = 100
# grouping
name :foo AND ( bio :bar OR bio : baz )
Suchen Sie in einem beliebigen Feld nach dem Wort „foo“ (ohne Berücksichtigung der Groß- und Kleinschreibung).
foo
Suchen Sie im name
nach dem Wort „foo“.
name :foo
Suchen Sie nach name
, die mit /foo/i
-Regex übereinstimmen.
name :/foo / i
Suchen Sie nach name
, die mit f*o
-Platzhaltermuster übereinstimmen.
name :f * o
Suchen Sie nach name
, die mit f?o
Platzhaltermuster übereinstimmen.
name :f? o
Suchen Sie im name
nach der Phrase „foo bar“ (Groß-/Kleinschreibung beachten).
name :"foo bar"
Suchen Sie im height
nach einem Wert gleich 100.
height : = 100
Suchen Sie im height
nach einem Wert größer als 100.
height :>100
Suchen Sie im height
nach einem Wert größer oder gleich 100.
height :>=100
Suchen Sie im height
nach einem Wert größer oder gleich 100 und kleiner oder gleich 200.
height : [ 100 TO 200 ]
Suchen Sie im height
nach Werten größer als 100 und kleiner als 200.
height : { 100 TO 200 }
Suchen Sie im name
nach einem beliebigen Wort, das mit „foo“ beginnt.
name :foo *
Suchen Sie im name
nach einem beliebigen Wort, das mit „foo“ beginnt und mit „bar“ endet.
name :foo * bar
Suchen Sie im name
nach einem beliebigen Wort, das mit „foo“ beginnt, gefolgt von einem einzelnen beliebigen Zeichen.
name :foo?
Suchen Sie im name
nach einem beliebigen Wort, das mit „foo“ beginnt, gefolgt von einem einzelnen beliebigen Zeichen und sofort mit „bar“ endet.
name :foo? bar
Suchen Sie im name
nach der Phrase „foo bar“ UND im bio
Feld nach der Phrase „quick fox“.
name :"foo bar" AND bio : "quick fox"
Suchen Sie entweder nach der Phrase „foo bar“ im name
UND der Phrase „quick fox“ im bio
Feld oder nach dem Wort „fox“ im name
.
( name :"foo bar" AND bio : "quick fox" ) OR name : fox
Serializer ermöglicht die Rückkonvertierung von Liqe-Tokens in die ursprüngliche Suchabfrage.
import {
parse ,
serialize ,
} from 'liqe' ;
const tokens = parse ( 'foo:bar' ) ;
// {
// expression: {
// location: {
// start: 4,
// },
// quoted: false,
// type: 'LiteralExpression',
// value: 'bar',
// },
// field: {
// location: {
// start: 0,
// },
// name: 'foo',
// path: ['foo'],
// quoted: false,
// type: 'Field',
// },
// location: {
// start: 0,
// },
// operator: {
// location: {
// start: 3,
// },
// operator: ':',
// type: 'ComparisonOperator',
// },
// type: 'Tag',
// }
serialize ( tokens ) ;
// 'foo:bar'
import {
type BooleanOperatorToken ,
type ComparisonOperatorToken ,
type EmptyExpression ,
type FieldToken ,
type ImplicitBooleanOperatorToken ,
type ImplicitFieldToken ,
type LiteralExpressionToken ,
type LogicalExpressionToken ,
type RangeExpressionToken ,
type RegexExpressionToken ,
type TagToken ,
type UnaryOperatorToken ,
} from 'liqe' ;
Es gibt 11 AST-Token, die eine analysierte Liqe-Abfrage beschreiben.
Wenn Sie einen Serialisierer erstellen, müssen Sie alle implementieren, um alle möglichen Abfrageeingaben vollständig abzudecken. Ein Beispiel finden Sie im integrierten Serialisierer.
import {
isSafeUnquotedExpression ,
} from 'liqe' ;
/**
* Determines if an expression requires quotes.
* Use this if you need to programmatically manipulate the AST
* before using a serializer to convert the query back to text.
*/
isSafeUnquotedExpression ( expression : string ) : boolean ;
Die folgenden Lucene-Fähigkeiten werden nicht unterstützt:
Im Falle eines Syntaxfehlers löst Liqe SyntaxError
aus.
import {
parse ,
SyntaxError ,
} from 'liqe' ;
try {
parse ( 'foo bar' ) ;
} catch ( error ) {
if ( error instanceof SyntaxError ) {
console . error ( {
// Syntax error at line 1 column 5
message : error . message ,
// 4
offset : error . offset ,
// 1
offset : error . line ,
// 5
offset : error . column ,
} ) ;
} else {
throw error ;
}
}
Erwägen Sie die Verwendung highlight-words
Pakets, um Liqe-Übereinstimmungen hervorzuheben.
Wenn Sie den Parser ändern möchten, verwenden Sie npm run watch
um den Compiler im Überwachungsmodus auszuführen.
Bevor Sie Änderungen vornehmen, erfassen Sie den aktuellen Benchmark auf Ihrem Computer mit npm run benchmark
. Führen Sie den Benchmark erneut aus, nachdem Sie Änderungen vorgenommen haben. Stellen Sie vor dem Festschreiben von Änderungen sicher, dass die Leistung nicht beeinträchtigt wird.