updated vendor
This commit is contained in:
parent
577cf4d482
commit
5e9ce5a306
8 changed files with 1306 additions and 5 deletions
14
vendor/github.com/bcampbell/qs/LICENSE
generated
vendored
Normal file
14
vendor/github.com/bcampbell/qs/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
Copyright 2015 Ben Campbell
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
77
vendor/github.com/bcampbell/qs/README.md
generated
vendored
Normal file
77
vendor/github.com/bcampbell/qs/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
[](https://godoc.org/github.com/bcampbell/qs)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
qs implements a query language for the [Bleve](http://www.blevesearch.com/)
|
||||||
|
text indexer library.
|
||||||
|
|
||||||
|
The query syntax is aiming to be about the same as those used by
|
||||||
|
[Lucene](http://lucene.apache.org/core/5_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description)
|
||||||
|
and [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax).
|
||||||
|
|
||||||
|
The full syntax is described in [syntax.md](syntax.md).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
import "github.com/bcampbell/qs"
|
||||||
|
|
||||||
|
query, err := qs.Parse("grapefruit lemon orange lime")
|
||||||
|
|
||||||
|
Or, to create a parse which uses AND as the default operator:
|
||||||
|
|
||||||
|
p := qs.Parser{DefaultOp: qs.AND}
|
||||||
|
query,err := p.Parse("grapefruit lemon orange lime")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Quick Examples
|
||||||
|
|
||||||
|
Documents containing the term `grapefruit`:
|
||||||
|
|
||||||
|
grapefruit
|
||||||
|
|
||||||
|
There is support for boolean AND, OR and NOT:
|
||||||
|
|
||||||
|
grapefruit OR lemon
|
||||||
|
grapefruit AND lemon
|
||||||
|
grapefruit AND NOT lemon
|
||||||
|
|
||||||
|
Phrases are indicated with quotes:
|
||||||
|
|
||||||
|
"navel orange"
|
||||||
|
|
||||||
|
The default boolean operator is OR, so these two are equivalent when using the default parser:
|
||||||
|
|
||||||
|
grapefruit lemon
|
||||||
|
grapefruit OR lemon
|
||||||
|
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
tags:citrus
|
||||||
|
headline:"How to Make the Perfect Negroni"
|
||||||
|
|
||||||
|
Grouping:
|
||||||
|
|
||||||
|
(lemon AND lime) OR (orange AND grapefruit)
|
||||||
|
|
||||||
|
Boosting term relevance:
|
||||||
|
|
||||||
|
fruit grapefruit^2
|
||||||
|
|
||||||
|
Inclusive ranges in square brackets, Exclusive ranges in curly brackets:
|
||||||
|
|
||||||
|
date:[2012-01-01 TO 2012-12-31]
|
||||||
|
count:{0 TO 100}
|
||||||
|
|
||||||
|
Wildcard markers `*` (any character sequence, 0 or more) and `?` (any single character)
|
||||||
|
eg to match `foot`, `fort`, `fret` etc:
|
||||||
|
|
||||||
|
f??t
|
||||||
|
|
||||||
|
eg to match `ant`, `anteater`, `antidisestablishmentarianism` etc:
|
||||||
|
|
||||||
|
ant*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
17
vendor/github.com/bcampbell/qs/doc.go
generated
vendored
Normal file
17
vendor/github.com/bcampbell/qs/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
Package qs is a query language parser for Bleve (http://www.blevesearch.com).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "github.com/bcampbell/qs"
|
||||||
|
|
||||||
|
query, err := qs.Parse("grapefruit lemon orange lime")
|
||||||
|
|
||||||
|
Or, to create a parse which uses AND as the default operator:
|
||||||
|
|
||||||
|
p := qs.Parser{DefaultOp: qs.AND}
|
||||||
|
query,err := p.Parse("grapefruit lemon orange lime")
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
package qs
|
||||||
251
vendor/github.com/bcampbell/qs/lex.go
generated
vendored
Normal file
251
vendor/github.com/bcampbell/qs/lex.go
generated
vendored
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
package qs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: handle escaping special characters:
|
||||||
|
|
||||||
|
type tokType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
tEOF tokType = iota
|
||||||
|
tERROR
|
||||||
|
tLITERAL
|
||||||
|
tQUOTED
|
||||||
|
tPLUS
|
||||||
|
tMINUS
|
||||||
|
tCOLON
|
||||||
|
tEQUAL
|
||||||
|
tGREATER
|
||||||
|
tLESS
|
||||||
|
tOR
|
||||||
|
tAND
|
||||||
|
tNOT
|
||||||
|
tLPAREN
|
||||||
|
tRPAREN
|
||||||
|
tLSQUARE
|
||||||
|
tRSQUARE
|
||||||
|
tLBRACE
|
||||||
|
tRBRACE
|
||||||
|
tTO
|
||||||
|
tBOOST
|
||||||
|
tFUZZY
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t tokType) String() string {
|
||||||
|
|
||||||
|
tokTypes := map[tokType]string{
|
||||||
|
tEOF: "tEOF",
|
||||||
|
tERROR: "tERROR",
|
||||||
|
tLITERAL: "tLITERAL",
|
||||||
|
tQUOTED: "tQUOTED",
|
||||||
|
tOR: "tOR",
|
||||||
|
tAND: "tAND",
|
||||||
|
tNOT: "tNOT",
|
||||||
|
tTO: "tTO",
|
||||||
|
tPLUS: "tPLUS",
|
||||||
|
tMINUS: "tMINUS",
|
||||||
|
tCOLON: "tCOLON",
|
||||||
|
tEQUAL: "tEQUAL",
|
||||||
|
tGREATER: "tGREATER",
|
||||||
|
tLESS: "tLESS",
|
||||||
|
tLPAREN: "tLPAREN",
|
||||||
|
tRPAREN: "tRPAREN",
|
||||||
|
tLSQUARE: "tLSQUARE",
|
||||||
|
tRSQUARE: "tRSQUARE",
|
||||||
|
tLBRACE: "tLBRACE",
|
||||||
|
tRBRACE: "tRBRACE",
|
||||||
|
tBOOST: "tBOOST",
|
||||||
|
tFUZZY: "tFUZZY",
|
||||||
|
}
|
||||||
|
return tokTypes[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
// some single-rune tokens
|
||||||
|
var singles = map[rune]tokType{
|
||||||
|
'(': tLPAREN,
|
||||||
|
')': tRPAREN,
|
||||||
|
'[': tLSQUARE,
|
||||||
|
']': tRSQUARE,
|
||||||
|
'{': tLBRACE,
|
||||||
|
'}': tRBRACE,
|
||||||
|
':': tCOLON,
|
||||||
|
'+': tPLUS,
|
||||||
|
'-': tMINUS,
|
||||||
|
'=': tEQUAL,
|
||||||
|
'>': tGREATER,
|
||||||
|
'<': tLESS,
|
||||||
|
}
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
typ tokType
|
||||||
|
val string
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateFn func(*lexer) stateFn
|
||||||
|
|
||||||
|
type lexer struct {
|
||||||
|
input string
|
||||||
|
tokens []token
|
||||||
|
pos int
|
||||||
|
prevpos int
|
||||||
|
start int
|
||||||
|
}
|
||||||
|
|
||||||
|
// lex takes an input string and breaks it up into an array of tokens.
|
||||||
|
// The last token will be an tEOF, unless an error occurs, in which case
|
||||||
|
// it will be a tERROR.
|
||||||
|
func lex(input string) []token {
|
||||||
|
l := &lexer{
|
||||||
|
input: input,
|
||||||
|
tokens: []token{},
|
||||||
|
}
|
||||||
|
// run state machine - each state returns the next state, or nil when finished
|
||||||
|
for state := lexDefault; state != nil; {
|
||||||
|
state = state(l)
|
||||||
|
}
|
||||||
|
return l.tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) next() rune {
|
||||||
|
l.prevpos = l.pos
|
||||||
|
if l.eof() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.pos += w
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
func (l *lexer) eof() bool {
|
||||||
|
return l.pos >= len(l.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
func (l *lexer) backup() {
|
||||||
|
l.pos = l.prevpos
|
||||||
|
}
|
||||||
|
func (l *lexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) emit(t tokType) {
|
||||||
|
l.tokens = append(l.tokens, token{t, l.input[l.start:l.pos], l.start})
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) emitError(msg string) {
|
||||||
|
l.tokens = append(l.tokens, token{tERROR, msg, l.start})
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexDefault(l *lexer) stateFn {
|
||||||
|
// skip space
|
||||||
|
for {
|
||||||
|
if l.eof() {
|
||||||
|
l.emit(tEOF)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r := l.next()
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
l.backup()
|
||||||
|
l.ignore()
|
||||||
|
if r == '~' || r == '^' {
|
||||||
|
return lexSuffix
|
||||||
|
}
|
||||||
|
if typ, got := singles[r]; got {
|
||||||
|
l.next()
|
||||||
|
l.emit(typ)
|
||||||
|
return lexDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
if r == '"' || r == '\'' {
|
||||||
|
return lexQuoted
|
||||||
|
}
|
||||||
|
|
||||||
|
return lexText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexText(l *lexer) stateFn {
|
||||||
|
// non-space characters which terminate a literal
|
||||||
|
//stopChars := `&|:(){}[]^~`
|
||||||
|
stopChars := `:(){}[]^~`
|
||||||
|
|
||||||
|
for {
|
||||||
|
if l.eof() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r := l.next()
|
||||||
|
if unicode.IsSpace(r) || strings.ContainsRune(stopChars, r) {
|
||||||
|
l.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch l.input[l.start:l.pos] {
|
||||||
|
case "OR":
|
||||||
|
l.emit(tOR)
|
||||||
|
case "AND":
|
||||||
|
l.emit(tAND)
|
||||||
|
case "NOT":
|
||||||
|
l.emit(tNOT)
|
||||||
|
case "TO":
|
||||||
|
l.emit(tTO)
|
||||||
|
default:
|
||||||
|
l.emit(tLITERAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lexDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexQuoted(l *lexer) stateFn {
|
||||||
|
q := l.next()
|
||||||
|
for {
|
||||||
|
if l.eof() {
|
||||||
|
l.emitError("unclosed quote")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r := l.next()
|
||||||
|
if r == q {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(tQUOTED)
|
||||||
|
return lexDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexSuffix(l *lexer) stateFn {
|
||||||
|
kind := l.next() // '^' or '~'
|
||||||
|
|
||||||
|
// number (optional)
|
||||||
|
for {
|
||||||
|
if l.eof() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r := l.next()
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
l.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !strings.ContainsRune("0123456789.", r) {
|
||||||
|
l.emitError("bad number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case '~':
|
||||||
|
l.emit(tFUZZY)
|
||||||
|
case '^':
|
||||||
|
l.emit(tBOOST)
|
||||||
|
}
|
||||||
|
return lexDefault
|
||||||
|
}
|
||||||
621
vendor/github.com/bcampbell/qs/parse.go
generated
vendored
Normal file
621
vendor/github.com/bcampbell/qs/parse.go
generated
vendored
Normal file
|
|
@ -0,0 +1,621 @@
|
||||||
|
package qs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/blevesearch/bleve"
|
||||||
|
"github.com/blevesearch/bleve/search/query"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseError is the error type returned by Parse()
|
||||||
|
type ParseError struct {
|
||||||
|
// Pos is the character position where the error occured
|
||||||
|
Pos int
|
||||||
|
// Msg is a description of the error
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe ParseError) Error() string { return fmt.Sprintf("%d: %s", pe.Pos, pe.Msg) }
|
||||||
|
|
||||||
|
type OpType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OR OpType = 0
|
||||||
|
AND = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
tokens []token
|
||||||
|
pos int
|
||||||
|
// DefaultOp is used when no explict OR or AND is present
|
||||||
|
// ie: foo bar => foo OR bar | foo AND bar
|
||||||
|
// TODO: not sure AND/OR is the right terminology (but it's what others use)
|
||||||
|
// Doesn't actually use OR or AND. AND is treated as an implied '+' prefix
|
||||||
|
DefaultOp OpType
|
||||||
|
|
||||||
|
// Loc is the location to use for parsing dates in range queries.
|
||||||
|
// If nil, UTC is assumed.
|
||||||
|
Loc *time.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// context is used to hold settings active within a given scope during parsing
|
||||||
|
// (TODO: maybe this should just be absorbed back into the Parser struct instead?)
|
||||||
|
type context struct {
|
||||||
|
// field is the name of the field currently in scope (or "")
|
||||||
|
field string
|
||||||
|
fieldPos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse takes a query string and turns it into a bleve Query.
|
||||||
|
//
|
||||||
|
// Returned errors are type ParseError, which includes the position
|
||||||
|
// of the offending part of the input string.
|
||||||
|
//
|
||||||
|
// BNF(ish) query syntax:
|
||||||
|
// exprList = expr1*
|
||||||
|
// expr1 = expr2 {"OR" expr2}
|
||||||
|
// expr2 = expr3 {"AND" expr3}
|
||||||
|
// expr3 = {"NOT"} expr4
|
||||||
|
// expr4 = {("+"|"-")} expr5
|
||||||
|
// expr5 = {field} part {boost}
|
||||||
|
// part = lit {"~" number} | range | "(" exprList ")"
|
||||||
|
// field = lit ":"
|
||||||
|
// range = ("["|"}") {lit} "TO" {lit} ("]"|"}")
|
||||||
|
// relational = ("<"|">"|"<="|">=") lit
|
||||||
|
// boost = "^" number
|
||||||
|
//
|
||||||
|
// (where lit is a string, quoted string or number)
|
||||||
|
func (p *Parser) Parse(q string) (query.Query, error) {
|
||||||
|
p.tokens = lex(q)
|
||||||
|
ctx := context{field: ""}
|
||||||
|
return p.parseExprList(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse takes a query string and turns it into a bleve Query using
|
||||||
|
// the default Parser.
|
||||||
|
// Returned errors are type ParseError, which includes the position
|
||||||
|
// of the offending part of the input string.
|
||||||
|
func Parse(q string) (query.Query, error) {
|
||||||
|
p := Parser{DefaultOp: OR}
|
||||||
|
return p.Parse(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek looks at the next token without consuming it.
|
||||||
|
// peeks beyond the end of the token stream will return EOF
|
||||||
|
func (p *Parser) peek() token {
|
||||||
|
if p.pos < len(p.tokens) {
|
||||||
|
tok := p.tokens[p.pos]
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
return token{typ: tEOF}
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup steps back one position in the token stream
|
||||||
|
func (p *Parser) backup() {
|
||||||
|
p.pos -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// next fetches the next token in the stream
|
||||||
|
func (p *Parser) next() token {
|
||||||
|
if p.pos < len(p.tokens) {
|
||||||
|
tok := p.tokens[p.pos]
|
||||||
|
p.pos += 1
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
p.pos += 1 // to make sure backup() works
|
||||||
|
return token{typ: tEOF}
|
||||||
|
}
|
||||||
|
|
||||||
|
// starting point
|
||||||
|
// exprList = expr1*
|
||||||
|
func (p *Parser) parseExprList(ctx context) (query.Query, error) {
|
||||||
|
// <empty>
|
||||||
|
if p.peek().typ == tEOF {
|
||||||
|
return bleve.NewMatchNoneQuery(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
must := []query.Query{}
|
||||||
|
mustNot := []query.Query{}
|
||||||
|
should := []query.Query{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
tok := p.peek()
|
||||||
|
if tok.typ == tEOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// slightly kludgy...
|
||||||
|
if tok.typ == tRPAREN {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix, q, err := p.parseExpr1(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch prefix {
|
||||||
|
case tPLUS:
|
||||||
|
must = append(must, q)
|
||||||
|
case tMINUS:
|
||||||
|
mustNot = append(mustNot, q)
|
||||||
|
default:
|
||||||
|
if p.DefaultOp == AND {
|
||||||
|
must = append(must, q)
|
||||||
|
} else { // OR
|
||||||
|
should = append(should, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// some obvious shortcuts
|
||||||
|
total := len(must) + len(mustNot) + len(should)
|
||||||
|
if total == 0 {
|
||||||
|
return bleve.NewMatchNoneQuery(), nil
|
||||||
|
}
|
||||||
|
if total == 1 && len(must) == 1 {
|
||||||
|
return must[0], nil
|
||||||
|
}
|
||||||
|
if total == 1 && len(should) == 1 {
|
||||||
|
return should[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// no shortcuts - go with the full-fat version
|
||||||
|
q := bleve.NewBooleanQuery()
|
||||||
|
if len(must) > 0 {
|
||||||
|
q.AddMust(must...)
|
||||||
|
}
|
||||||
|
if len(should) > 0 {
|
||||||
|
q.AddShould(should...)
|
||||||
|
}
|
||||||
|
if len(mustNot) > 0 {
|
||||||
|
q.AddMustNot(mustNot...)
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExpr1 handles OR expressions
|
||||||
|
//
|
||||||
|
// expr1 = expr2 {"OR" expr2}
|
||||||
|
func (p *Parser) parseExpr1(ctx context) (tokType, query.Query, error) {
|
||||||
|
|
||||||
|
queries := []query.Query{}
|
||||||
|
prefixes := []tokType{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
prefix, q, err := p.parseExpr2(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return tEOF, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixes = append(prefixes, prefix)
|
||||||
|
queries = append(queries, q)
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
if tok.typ != tOR {
|
||||||
|
p.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let single, non-OR expressions bubble upward, prefix intact
|
||||||
|
if len(queries) == 1 {
|
||||||
|
return prefixes[0], queries[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KLUDGINESS - prefixes on terms in OR expressions
|
||||||
|
// we'll ignore "+" and treat "-" as NOT
|
||||||
|
// eg:
|
||||||
|
// `+alice OR -bob OR chuck` => `alice OR (NOT bob) OR chuck`
|
||||||
|
for i, _ := range queries {
|
||||||
|
if prefixes[i] == tMINUS {
|
||||||
|
q := bleve.NewBooleanQuery()
|
||||||
|
q.AddMustNot(queries[i])
|
||||||
|
queries[i] = q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tEOF, bleve.NewDisjunctionQuery(queries...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExpr2 handles AND expressions
|
||||||
|
//
|
||||||
|
// expr2 = expr3 {"AND" expr3}
|
||||||
|
func (p *Parser) parseExpr2(ctx context) (tokType, query.Query, error) {
|
||||||
|
|
||||||
|
queries := []query.Query{}
|
||||||
|
prefixes := []tokType{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
prefix, q, err := p.parseExpr3(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return tEOF, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixes = append(prefixes, prefix)
|
||||||
|
queries = append(queries, q)
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
if tok.typ != tAND {
|
||||||
|
p.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let single, non-AND expressions bubble upward, prefix intact
|
||||||
|
if len(queries) == 1 {
|
||||||
|
return prefixes[0], queries[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KLUDGINESS - prefixes on terms in AND expressions
|
||||||
|
// we'll ignore "+" and treat "-" as NOT
|
||||||
|
// eg:
|
||||||
|
// `+alice AND -bob AND chuck` => `alice AND (NOT bob) AND chuck`
|
||||||
|
for i, _ := range queries {
|
||||||
|
if prefixes[i] == tMINUS {
|
||||||
|
q := bleve.NewBooleanQuery()
|
||||||
|
q.AddMustNot(queries[i])
|
||||||
|
queries[i] = q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tEOF, bleve.NewConjunctionQuery(queries...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExpr3 handles NOT expressions
|
||||||
|
//
|
||||||
|
// expr3 = {"NOT"} expr4
|
||||||
|
func (p *Parser) parseExpr3(ctx context) (tokType, query.Query, error) {
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
if tok.typ != tNOT {
|
||||||
|
p.backup()
|
||||||
|
// just let the lower, non-NOT expression bubble up with its prefix
|
||||||
|
return p.parseExpr4(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix, q, err := p.parseExpr4(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return tEOF, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// KLUDGINESS - prefixes on terms in NOT expressions:
|
||||||
|
// `NOT -bob` => `bob`
|
||||||
|
// `NOT +bob` => `NOT bob`
|
||||||
|
if prefix != tMINUS {
|
||||||
|
q2 := bleve.NewBooleanQuery()
|
||||||
|
q2.AddMustNot(q)
|
||||||
|
q = q2
|
||||||
|
}
|
||||||
|
return tEOF, q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here's where all the prefix-bubbling-up begins...
|
||||||
|
// expr4 = {("+"|"-")} expr5
|
||||||
|
func (p *Parser) parseExpr4(ctx context) (tokType, query.Query, error) {
|
||||||
|
var prefix tokType
|
||||||
|
tok := p.next()
|
||||||
|
switch tok.typ {
|
||||||
|
case tMINUS, tPLUS:
|
||||||
|
prefix = tok.typ
|
||||||
|
default:
|
||||||
|
p.backup()
|
||||||
|
prefix = tEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
q, err := p.parseExpr5(ctx)
|
||||||
|
return prefix, q, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// expr5 = {field} part {boost}
|
||||||
|
func (p *Parser) parseExpr5(ctx context) (query.Query, error) {
|
||||||
|
|
||||||
|
fldpos := p.peek().pos
|
||||||
|
fld, err := p.parseField()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fld != "" {
|
||||||
|
if ctx.field != "" {
|
||||||
|
return nil, ParseError{fldpos, fmt.Sprintf("'%s:' clashes with '%s:'", fld, ctx.field)}
|
||||||
|
}
|
||||||
|
ctx.field = fld
|
||||||
|
ctx.fieldPos = fldpos
|
||||||
|
}
|
||||||
|
|
||||||
|
q, err := p.parsePart(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse (optional) suffix
|
||||||
|
boostpos := p.peek().pos
|
||||||
|
boost, err := p.parseBoostSuffix()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if boost > 0 {
|
||||||
|
if boostable, ok := q.(query.BoostableQuery); ok {
|
||||||
|
boostable.SetBoost(boost)
|
||||||
|
} else {
|
||||||
|
return nil, ParseError{boostpos, "can't specify a boost value here"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// part = lit {"~" number} | range | "(" exprList ")"
|
||||||
|
func (p *Parser) parsePart(ctx context) (query.Query, error) {
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
|
||||||
|
// lit
|
||||||
|
if tok.typ == tLITERAL {
|
||||||
|
var q query.Query
|
||||||
|
if strings.ContainsAny(tok.val, "*?") {
|
||||||
|
q = bleve.NewWildcardQuery(tok.val)
|
||||||
|
} else {
|
||||||
|
if p.peek().typ == tFUZZY {
|
||||||
|
fuzziness, err := p.parseFuzzySuffix()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fuzz := bleve.NewFuzzyQuery(tok.val)
|
||||||
|
fuzz.SetFuzziness(fuzziness)
|
||||||
|
q = fuzz
|
||||||
|
} else {
|
||||||
|
q = bleve.NewMatchPhraseQuery(tok.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.field != "" {
|
||||||
|
if fieldable, ok := q.(query.FieldableQuery); ok {
|
||||||
|
fieldable.SetField(ctx.field)
|
||||||
|
} else {
|
||||||
|
return nil, ParseError{ctx.fieldPos, "unexpected field"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
if tok.typ == tQUOTED {
|
||||||
|
// strip quotes (ugh)
|
||||||
|
txt := string(tok.val[1 : len(tok.val)-1])
|
||||||
|
/*
|
||||||
|
if strings.ContainsAny(txt, "*?") {
|
||||||
|
return nil, ParseError{tok.pos, "wildcards not supported in phrases"}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
q := bleve.NewMatchPhraseQuery(txt)
|
||||||
|
if ctx.field != "" {
|
||||||
|
q.SetField(ctx.field)
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// | "(" exprList ")"
|
||||||
|
if tok.typ == tLPAREN {
|
||||||
|
q, err := p.parseExprList(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tok = p.next()
|
||||||
|
if tok.typ != tRPAREN {
|
||||||
|
return nil, ParseError{tok.pos, "missing )"}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// | range
|
||||||
|
if tok.typ == tLSQUARE || tok.typ == tLBRACE {
|
||||||
|
p.backup()
|
||||||
|
q, err := p.parseRange(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// | relational
|
||||||
|
if tok.typ == tGREATER || tok.typ == tLESS {
|
||||||
|
p.backup()
|
||||||
|
q, err := p.parseRelational(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if tok.typ == tERROR {
|
||||||
|
return nil, ParseError{tok.pos, tok.val}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ParseError{tok.pos, fmt.Sprintf("unexpected %s", tok.val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns >0 if there is a value given
|
||||||
|
func (p *Parser) parseBoostSuffix() (float64, error) {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.typ != tBOOST {
|
||||||
|
p.backup()
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := tok.val[1:]
|
||||||
|
if v == "" {
|
||||||
|
return 1.0, nil
|
||||||
|
}
|
||||||
|
boost, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ParseError{tok.pos, "bad boost value"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return boost, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
func (p *Parser) parseFuzzySuffix() (int, error) {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.typ != tFUZZY {
|
||||||
|
return 0, ParseError{tok.pos, "expected ~"}
|
||||||
|
}
|
||||||
|
|
||||||
|
v := tok.val[1:]
|
||||||
|
if v == "" {
|
||||||
|
return 1, nil // Default fuzziness is 1
|
||||||
|
}
|
||||||
|
fuzz, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ParseError{tok.pos, "bad fuzziness value"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fuzz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse (optional) field specifier
|
||||||
|
// [ lit ":" ]
|
||||||
|
// returns field name or "" if not a field
|
||||||
|
func (p *Parser) parseField() (string, error) {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.typ != tLITERAL {
|
||||||
|
// not a field
|
||||||
|
p.backup()
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
field := tok.val
|
||||||
|
|
||||||
|
tok = p.next()
|
||||||
|
if tok.typ != tCOLON {
|
||||||
|
// oop, not a field after all!
|
||||||
|
p.backup()
|
||||||
|
p.backup()
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return field, nil // it's OK
|
||||||
|
}
|
||||||
|
|
||||||
|
// range = ("["|"}") {lit} "TO" {lit} ("]"|"}")
|
||||||
|
func (p *Parser) parseRange(ctx context) (query.Query, error) {
|
||||||
|
|
||||||
|
var minVal, maxVal string
|
||||||
|
var minInclusive, maxInclusive bool
|
||||||
|
openTok := p.next()
|
||||||
|
switch openTok.typ {
|
||||||
|
case tLSQUARE:
|
||||||
|
minInclusive = true
|
||||||
|
case tLBRACE:
|
||||||
|
minInclusive = false
|
||||||
|
default:
|
||||||
|
return nil, ParseError{openTok.pos, "expected range"}
|
||||||
|
}
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
switch tok.typ {
|
||||||
|
case tLITERAL:
|
||||||
|
minVal = tok.val
|
||||||
|
case tQUOTED:
|
||||||
|
minVal = tok.val[1 : len(tok.val)-1]
|
||||||
|
case tTO:
|
||||||
|
p.backup()
|
||||||
|
// empty start
|
||||||
|
default:
|
||||||
|
return nil, ParseError{tok.pos, fmt.Sprintf("unexpected %s", tok.val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
tok = p.next()
|
||||||
|
if tok.typ != tTO {
|
||||||
|
return nil, ParseError{tok.pos, "expected TO"}
|
||||||
|
}
|
||||||
|
|
||||||
|
tok = p.next()
|
||||||
|
switch tok.typ {
|
||||||
|
case tLITERAL:
|
||||||
|
maxVal = tok.val
|
||||||
|
case tQUOTED:
|
||||||
|
maxVal = tok.val[1 : len(tok.val)-1]
|
||||||
|
case tRSQUARE:
|
||||||
|
p.backup() // empty end value
|
||||||
|
case tRBRACE:
|
||||||
|
p.backup() // empty end value
|
||||||
|
default:
|
||||||
|
return nil, ParseError{tok.pos, fmt.Sprintf("unexpected %s", tok.val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTok := p.next()
|
||||||
|
switch closeTok.typ {
|
||||||
|
case tRSQUARE:
|
||||||
|
maxInclusive = true
|
||||||
|
case tRBRACE:
|
||||||
|
maxInclusive = false
|
||||||
|
default:
|
||||||
|
return nil, ParseError{closeTok.pos, "expected ] or }"}
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := newRangeParams(minVal, maxVal, minInclusive, maxInclusive, p.Loc)
|
||||||
|
q, err := rp.generate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, ParseError{openTok.pos, err.Error()}
|
||||||
|
}
|
||||||
|
if ctx.field != "" {
|
||||||
|
if fieldable, ok := q.(query.FieldableQuery); ok {
|
||||||
|
fieldable.SetField(ctx.field)
|
||||||
|
} else {
|
||||||
|
return nil, ParseError{ctx.fieldPos, "unexpected field"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRelational handles greaterthan/lessthan etc...
|
||||||
|
// Implemented as a range.
|
||||||
|
// relational = ("<"|">"|"<="|">=") lit
|
||||||
|
func (p *Parser) parseRelational(ctx context) (query.Query, error) {
|
||||||
|
|
||||||
|
var minVal, maxVal string
|
||||||
|
var minInclusive, maxInclusive bool
|
||||||
|
|
||||||
|
rel := p.next()
|
||||||
|
if rel.typ != tGREATER && rel.typ != tLESS {
|
||||||
|
return nil, ParseError{rel.pos, "expected > or <"}
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := p.next()
|
||||||
|
if eq.typ != tEQUAL {
|
||||||
|
p.backup()
|
||||||
|
}
|
||||||
|
|
||||||
|
var val string
|
||||||
|
tok := p.next()
|
||||||
|
switch tok.typ {
|
||||||
|
case tLITERAL:
|
||||||
|
val = tok.val
|
||||||
|
case tQUOTED:
|
||||||
|
val = tok.val[1 : len(tok.val)-1]
|
||||||
|
default:
|
||||||
|
return nil, ParseError{tok.pos, fmt.Sprintf("unexpected %s", tok.val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rel.typ == tGREATER {
|
||||||
|
minVal = val
|
||||||
|
minInclusive = (eq.typ == tEQUAL)
|
||||||
|
} else { // if rel.typ == tLESS
|
||||||
|
maxVal = val
|
||||||
|
maxInclusive = (eq.typ == tEQUAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := newRangeParams(minVal, maxVal, minInclusive, maxInclusive, p.Loc)
|
||||||
|
q, err := rp.generate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, ParseError{rel.pos, err.Error()}
|
||||||
|
}
|
||||||
|
if ctx.field != "" {
|
||||||
|
if fieldable, ok := q.(query.FieldableQuery); ok {
|
||||||
|
fieldable.SetField(ctx.field)
|
||||||
|
} else {
|
||||||
|
return nil, ParseError{ctx.fieldPos, "unexpected field"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
117
vendor/github.com/bcampbell/qs/range.go
generated
vendored
Normal file
117
vendor/github.com/bcampbell/qs/range.go
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package qs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/blevesearch/bleve"
|
||||||
|
"github.com/blevesearch/bleve/search/query"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper stuff for setting up range queries
|
||||||
|
|
||||||
|
// returns time, precision
|
||||||
|
// TODO: support other precisions? "YYYY-MM", "YYYY-MM-DDThh:mm:ss" etc?
|
||||||
|
func parseTime(in string, loc *time.Location) (time.Time, string) {
|
||||||
|
t, err := time.ParseInLocation("2006-01-02", in, loc)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, ""
|
||||||
|
}
|
||||||
|
return t, "day"
|
||||||
|
}
|
||||||
|
|
||||||
|
type rangeParams struct {
|
||||||
|
min, max *string
|
||||||
|
minInclusive, maxInclusive *bool
|
||||||
|
// need location for parsing times
|
||||||
|
loc *time.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRangeParams(minVal, maxVal string, minInc, maxInc bool, loc *time.Location) *rangeParams {
|
||||||
|
rp := &rangeParams{}
|
||||||
|
if minVal != "" {
|
||||||
|
rp.min = &minVal
|
||||||
|
rp.minInclusive = &minInc
|
||||||
|
}
|
||||||
|
if maxVal != "" {
|
||||||
|
rp.max = &maxVal
|
||||||
|
rp.maxInclusive = &maxInc
|
||||||
|
}
|
||||||
|
if loc == nil {
|
||||||
|
loc = time.UTC
|
||||||
|
}
|
||||||
|
rp.loc = loc
|
||||||
|
return rp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *rangeParams) numericArgs() (bool, *float64, *float64) {
|
||||||
|
var f1, f2 *float64
|
||||||
|
if rp.min != nil {
|
||||||
|
f, err := strconv.ParseFloat(*rp.min, 64)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
f1 = &f
|
||||||
|
}
|
||||||
|
if rp.max != nil {
|
||||||
|
f, err := strconv.ParseFloat(*rp.max, 64)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
f2 = &f
|
||||||
|
}
|
||||||
|
return true, f1, f2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *rangeParams) dateArgs() (bool, time.Time, time.Time) {
|
||||||
|
var truthy bool = true
|
||||||
|
var falsey bool = false
|
||||||
|
var t1, t2 time.Time
|
||||||
|
var prec string
|
||||||
|
if rp.min != nil {
|
||||||
|
t1, prec = parseTime(*rp.min, rp.loc)
|
||||||
|
switch prec {
|
||||||
|
case "day":
|
||||||
|
if !*rp.minInclusive {
|
||||||
|
// add 1 day and make inclusive
|
||||||
|
t1 = t1.AddDate(0, 0, 1)
|
||||||
|
rp.minInclusive = &truthy
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false, t1, t2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rp.max != nil {
|
||||||
|
t2, prec = parseTime(*rp.max, rp.loc)
|
||||||
|
switch prec {
|
||||||
|
case "day":
|
||||||
|
if *rp.maxInclusive {
|
||||||
|
// add 1 day and change to exclusive
|
||||||
|
t2 = t2.AddDate(0, 0, 1)
|
||||||
|
rp.maxInclusive = &falsey
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false, t1, t2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, t1, t2
|
||||||
|
}
|
||||||
|
|
||||||
|
// try and build a query from the given params
|
||||||
|
func (rp *rangeParams) generate() (query.Query, error) {
|
||||||
|
if rp.min == nil && rp.max == nil {
|
||||||
|
return nil, fmt.Errorf("empty range")
|
||||||
|
}
|
||||||
|
isNumeric, f1, f2 := rp.numericArgs()
|
||||||
|
if isNumeric {
|
||||||
|
return bleve.NewNumericRangeInclusiveQuery(f1, f2, rp.minInclusive, rp.maxInclusive), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isDate, t1, t2 := rp.dateArgs()
|
||||||
|
if isDate {
|
||||||
|
return bleve.NewDateRangeInclusiveQuery(t1, t2, rp.minInclusive, rp.maxInclusive), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not numeric")
|
||||||
|
|
||||||
|
}
|
||||||
202
vendor/github.com/bcampbell/qs/syntax.md
generated
vendored
Normal file
202
vendor/github.com/bcampbell/qs/syntax.md
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
# Query String Syntax
|
||||||
|
|
||||||
|
## Basics
|
||||||
|
|
||||||
|
Single words are treated as simple terms. The results will include
|
||||||
|
documents containing those terms, eg:
|
||||||
|
|
||||||
|
embiggen
|
||||||
|
cromulent
|
||||||
|
|
||||||
|
Terms are separated by spaces, so:
|
||||||
|
|
||||||
|
navel orange
|
||||||
|
|
||||||
|
Will match both documents containing "navel" and documents containing "orange",
|
||||||
|
which is probably not what was intended. The user was probably looking for
|
||||||
|
documents about "navel oranges" rather than those about "orange navels".
|
||||||
|
|
||||||
|
To search for phrases - multiple words, matched in order - enclose them in double quotes.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
"navel orange"
|
||||||
|
|
||||||
|
## Boolean Operators
|
||||||
|
|
||||||
|
### `OR`
|
||||||
|
|
||||||
|
The `OR` operator returns documents matched by terms on either side of it.
|
||||||
|
It is the default operator, meaning that the following two queries are
|
||||||
|
considered to be equivalent:
|
||||||
|
|
||||||
|
orange OR "navel orange"
|
||||||
|
orange "navel orange"
|
||||||
|
|
||||||
|
Note that the query parser can be set up to use `AND` as the default operator instead of `OR`.
|
||||||
|
|
||||||
|
### `AND`
|
||||||
|
|
||||||
|
'`AND`' returns documents which match the terms on *both* sides.
|
||||||
|
|
||||||
|
orange AND citrus
|
||||||
|
|
||||||
|
### `+`
|
||||||
|
|
||||||
|
'`+`' is the 'required' operator. A term prefixed with '`+`' must exist in matching
|
||||||
|
documents.
|
||||||
|
|
||||||
|
For example, to return documents definitely containing "orange" and maybe also
|
||||||
|
containing "fruit":
|
||||||
|
|
||||||
|
fruit +orange
|
||||||
|
|
||||||
|
|
||||||
|
### `NOT`
|
||||||
|
|
||||||
|
`NOT` excludes documents that match the following term.
|
||||||
|
|
||||||
|
For example, to match documents containing "orange" but not "paint":
|
||||||
|
|
||||||
|
orange NOT paint
|
||||||
|
|
||||||
|
|
||||||
|
### `-`
|
||||||
|
|
||||||
|
Prefixing a term with '`-`' will "prohibit" it.
|
||||||
|
|
||||||
|
For example, to match documents containing "orange" but not "paint":
|
||||||
|
|
||||||
|
orange -paint
|
||||||
|
|
||||||
|
|
||||||
|
### Precedence
|
||||||
|
|
||||||
|
`-`, `+` and `NOT` take precedence over `AND`, which takes precedence over `OR`.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
lemon OR orange AND citrus
|
||||||
|
|
||||||
|
is treated as:
|
||||||
|
|
||||||
|
lemon OR (orange AND citrus)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Field Scoping
|
||||||
|
|
||||||
|
You can control which fields are matched by prefixing the name of a field separated by a colon.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
genus:citrus
|
||||||
|
headline:"How to Make the Perfect Negroni"
|
||||||
|
tags:(fruit OR paint)
|
||||||
|
|
||||||
|
If no field is specified, the default `_all` is used, unless overridden in the index mapping.
|
||||||
|
|
||||||
|
|
||||||
|
## Grouping
|
||||||
|
|
||||||
|
Parentheses can be used to group sub queries.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
(shaddock OR pomelo OR pamplemousse) AND (family:rutaceae AND NOT genus:fortunella) AND colour:(greenish OR yellowish)
|
||||||
|
|
||||||
|
## Wildcards
|
||||||
|
|
||||||
|
Within an individual term, partial matches can be described using wildcard characters:
|
||||||
|
|
||||||
|
`?` to match any single character
|
||||||
|
|
||||||
|
`*` To match and sequence of zero or more characters
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
qu?ck bro*
|
||||||
|
|
||||||
|
|
||||||
|
## Fuzziness
|
||||||
|
|
||||||
|
A fuzzy query is a term query that matches terms within a given [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance).
|
||||||
|
The edit distance is the number of single-character edits (insertions, deletions or substitutions) allowed.
|
||||||
|
|
||||||
|
To specify a fuzzy query, use the tilde sign (`~`), optionally followed by the edit distance you'll accept.
|
||||||
|
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
colour~1
|
||||||
|
|
||||||
|
to match "`colour`" or "`color`" (or "`zolour`" or "`colours`"... but not "`colors`", as the `~1` allows only a single character change).
|
||||||
|
|
||||||
|
If no number is specified, the default value is 1, so the following are equivalent:
|
||||||
|
|
||||||
|
wibble~
|
||||||
|
wibble~1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Boosting
|
||||||
|
|
||||||
|
Boosting a term doesn't affect the set of matching documents, but it does
|
||||||
|
indicate that that term should be assigned greater importance when the
|
||||||
|
results are being scored by relevance.
|
||||||
|
|
||||||
|
To boost a term, you use the boost operator '`^`' followed by a number
|
||||||
|
indicating the relative importance of the term.
|
||||||
|
|
||||||
|
For example, in the following query, "grapefruit" should be considered twice
|
||||||
|
as important as "lime":
|
||||||
|
|
||||||
|
grapefruit^2 lime
|
||||||
|
|
||||||
|
You can boost more complex sub queries:
|
||||||
|
|
||||||
|
(grapefruit OR orange)^2 "navel orange"^4 genus:citrus
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Ranges
|
||||||
|
|
||||||
|
Inclusive ranges can be described with square braces and `TO`, eg:
|
||||||
|
|
||||||
|
num:[1 TO 5]
|
||||||
|
date:[2010-01-01 TO 2010-01-31]
|
||||||
|
|
||||||
|
Dates must be in `YYYY-MM-DD` form.
|
||||||
|
|
||||||
|
Exclusive ranges are supported using curly braces. So, these are equivalent:
|
||||||
|
|
||||||
|
score:{0 TO 10}
|
||||||
|
score:[1 TO 9]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
You can mix inclusive and exclusive endpoints, eg:
|
||||||
|
|
||||||
|
pubdate:[2010-01-01 TO 2011-01-01}
|
||||||
|
shoesize:{0 TO 16]
|
||||||
|
byte:[0 TO 256}
|
||||||
|
|
||||||
|
You can have open ranges by leaving off either endpoint:
|
||||||
|
|
||||||
|
pubdate:[2000-01-01 TO ]
|
||||||
|
temp:[TO 100}
|
||||||
|
|
||||||
|
|
||||||
|
NOTE: ranges currently work only on numeric and date fields.
|
||||||
|
|
||||||
|
|
||||||
|
## Relational Operators
|
||||||
|
|
||||||
|
You can perform numeric comparisons using the `>`, `>=`, `<`, and `<=` operators.
|
||||||
|
These are equivalent to using the above range syntax with unbounded ranges.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
score:>=100
|
||||||
|
score:[ TO 100]
|
||||||
|
|
||||||
|
|
||||||
12
vendor/modules.txt
vendored
12
vendor/modules.txt
vendored
|
|
@ -4,16 +4,19 @@ git.giftfish.de/ston1th/godrop/v2
|
||||||
git.giftfish.de/ston1th/jwt/v3
|
git.giftfish.de/ston1th/jwt/v3
|
||||||
# github.com/RoaringBitmap/roaring v0.4.17
|
# github.com/RoaringBitmap/roaring v0.4.17
|
||||||
github.com/RoaringBitmap/roaring
|
github.com/RoaringBitmap/roaring
|
||||||
|
# github.com/bcampbell/qs v0.0.0-20161004022730-15caa99abd01
|
||||||
|
github.com/bcampbell/qs
|
||||||
# github.com/blevesearch/bleve v0.8.0
|
# github.com/blevesearch/bleve v0.8.0
|
||||||
github.com/blevesearch/bleve
|
github.com/blevesearch/bleve
|
||||||
github.com/blevesearch/bleve/analysis/analyzer/custom
|
github.com/blevesearch/bleve/analysis/analyzer/custom
|
||||||
github.com/blevesearch/bleve/analysis/token/edgengram
|
github.com/blevesearch/bleve/analysis/token/edgengram
|
||||||
github.com/blevesearch/bleve/analysis/token/lowercase
|
github.com/blevesearch/bleve/analysis/token/lowercase
|
||||||
github.com/blevesearch/bleve/analysis/tokenizer/unicode
|
github.com/blevesearch/bleve/analysis/tokenizer/unicode
|
||||||
|
github.com/blevesearch/bleve/document
|
||||||
github.com/blevesearch/bleve/mapping
|
github.com/blevesearch/bleve/mapping
|
||||||
|
github.com/blevesearch/bleve/search/query
|
||||||
github.com/blevesearch/bleve/analysis
|
github.com/blevesearch/bleve/analysis
|
||||||
github.com/blevesearch/bleve/analysis/datetime/optional
|
github.com/blevesearch/bleve/analysis/datetime/optional
|
||||||
github.com/blevesearch/bleve/document
|
|
||||||
github.com/blevesearch/bleve/index
|
github.com/blevesearch/bleve/index
|
||||||
github.com/blevesearch/bleve/index/scorch
|
github.com/blevesearch/bleve/index/scorch
|
||||||
github.com/blevesearch/bleve/index/store
|
github.com/blevesearch/bleve/index/store
|
||||||
|
|
@ -26,19 +29,18 @@ github.com/blevesearch/bleve/search/collector
|
||||||
github.com/blevesearch/bleve/search/facet
|
github.com/blevesearch/bleve/search/facet
|
||||||
github.com/blevesearch/bleve/search/highlight
|
github.com/blevesearch/bleve/search/highlight
|
||||||
github.com/blevesearch/bleve/search/highlight/highlighter/html
|
github.com/blevesearch/bleve/search/highlight/highlighter/html
|
||||||
github.com/blevesearch/bleve/search/query
|
|
||||||
github.com/blevesearch/bleve/size
|
github.com/blevesearch/bleve/size
|
||||||
github.com/blevesearch/bleve/analysis/analyzer/standard
|
|
||||||
github.com/blevesearch/bleve/geo
|
github.com/blevesearch/bleve/geo
|
||||||
github.com/blevesearch/bleve/analysis/datetime/flexible
|
|
||||||
github.com/blevesearch/bleve/numeric
|
github.com/blevesearch/bleve/numeric
|
||||||
|
github.com/blevesearch/bleve/analysis/analyzer/standard
|
||||||
|
github.com/blevesearch/bleve/search/searcher
|
||||||
|
github.com/blevesearch/bleve/analysis/datetime/flexible
|
||||||
github.com/blevesearch/bleve/index/scorch/mergeplan
|
github.com/blevesearch/bleve/index/scorch/mergeplan
|
||||||
github.com/blevesearch/bleve/index/scorch/segment
|
github.com/blevesearch/bleve/index/scorch/segment
|
||||||
github.com/blevesearch/bleve/index/scorch/segment/zap
|
github.com/blevesearch/bleve/index/scorch/segment/zap
|
||||||
github.com/blevesearch/bleve/search/highlight/format/html
|
github.com/blevesearch/bleve/search/highlight/format/html
|
||||||
github.com/blevesearch/bleve/search/highlight/fragmenter/simple
|
github.com/blevesearch/bleve/search/highlight/fragmenter/simple
|
||||||
github.com/blevesearch/bleve/search/highlight/highlighter/simple
|
github.com/blevesearch/bleve/search/highlight/highlighter/simple
|
||||||
github.com/blevesearch/bleve/search/searcher
|
|
||||||
github.com/blevesearch/bleve/analysis/lang/en
|
github.com/blevesearch/bleve/analysis/lang/en
|
||||||
github.com/blevesearch/bleve/search/scorer
|
github.com/blevesearch/bleve/search/scorer
|
||||||
github.com/blevesearch/bleve/analysis/token/porter
|
github.com/blevesearch/bleve/analysis/token/porter
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue