mirror of
https://github.com/owncloud/ocis
synced 2026-04-26 01:35:25 +02:00
* enhancement: add more kql spec tests and simplify ast normalization * enhancement: kql parser error if query starts with AND * enhancement: add kql docs and support for date and time only dateTimeRestriction queries * enhancement: add the ability to decide how kql nodes get connected connecting nodes (with edges) seem straight forward when not using group, the default connection for nodes with the same node is always OR. THis only applies for first level nodes, for grouped nodes it is defined differently. The KQL docs are saying, nodes inside a grouped node, with the same key are connected by a AND edge. * enhancement: explicit error handling for falsy group nodes and queries with leading binary operator * enhancement: use optimized grammar for kql parser and toolify pigeon * enhancement: simplify error handling * fix: kql implicit 'AND' and 'OR' follows the ms html spec instead of the pdf spec
1063 lines
28 KiB
Go
1063 lines
28 KiB
Go
package kql_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/araddon/dateparse"
|
|
tAssert "github.com/stretchr/testify/assert"
|
|
|
|
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
|
|
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast/test"
|
|
"github.com/owncloud/ocis/v2/services/search/pkg/query/kql"
|
|
)
|
|
|
|
var mustParseTime = func(t *testing.T, ts string) time.Time {
|
|
tp, err := dateparse.ParseLocal(ts)
|
|
if err != nil {
|
|
t.Fatalf("time.Parse(...) error = %v", err)
|
|
}
|
|
|
|
return tp
|
|
}
|
|
|
|
var mustJoin = func(v []string) string {
|
|
return strings.Join(v, " ")
|
|
}
|
|
|
|
var FullDictionary = []string{
|
|
`federated search`,
|
|
`federat* search`,
|
|
`search fed*`,
|
|
`author:"John Smith"`,
|
|
`filetype:docx`,
|
|
`filename:budget.xlsx`,
|
|
`author: "John Smith"`,
|
|
`author :"John Smith"`,
|
|
`author : "John Smith"`,
|
|
`author "John Smith"`,
|
|
`author "John Smith"`,
|
|
`author:Shakespear`,
|
|
`author:Paul`,
|
|
`author:Shakesp*`,
|
|
`title:"Advanced Search"`,
|
|
`title:"Advanced Sear*"`,
|
|
`title:"Advan* Search"`,
|
|
`title:"*anced Search"`,
|
|
`author:"John Smith" OR author:"Jane Smith"`,
|
|
`author:"John Smith" AND filetype:docx`,
|
|
`author:("John Smith" "Jane Smith")`,
|
|
`author:("John Smith" OR "Jane Smith")`,
|
|
`(DepartmentId:* OR RelatedHubSites:*) AND contentclass:sts_site NOT IsHubSite:false`,
|
|
`author:"John Smith" (filetype:docx title:"Advanced Search")`,
|
|
}
|
|
|
|
func TestParse(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
skip bool
|
|
givenQuery string
|
|
expectedAst *ast.Ast
|
|
expectedError error
|
|
}{
|
|
// SPEC //////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// https://msopenspecs.azureedge.net/files/MS-KQL/%5bMS-KQL%5d.pdf
|
|
// https://learn.microsoft.com/en-us/openspecs/sharepoint_protocols/ms-kql/3bbf06cd-8fc1-4277-bd92-8661ccd3c9b0
|
|
// https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference
|
|
//
|
|
// ++
|
|
// 2.1.2 AND Operator
|
|
// 3.1.2 AND Operator
|
|
{
|
|
name: `cat AND dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `AND`,
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolAND},
|
|
},
|
|
},
|
|
{
|
|
name: `AND cat AND dog`,
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolAND},
|
|
},
|
|
},
|
|
// ++
|
|
// 2.1.6 NOT Operator
|
|
// 3.1.6 NOT Operator
|
|
{
|
|
name: `cat NOT dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `NOT dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
// ++
|
|
// 2.1.8 OR Operator
|
|
// 3.1.8 OR Operator
|
|
{
|
|
name: `cat OR dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `OR`,
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolOR},
|
|
},
|
|
},
|
|
{
|
|
name: `OR cat AND dog`,
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolOR},
|
|
},
|
|
},
|
|
// ++
|
|
// 3.1.11 Implicit Operator
|
|
{
|
|
name: `cat dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat AND (dog OR fox)`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "fox"},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat (dog OR fox)`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "fox"},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
// ++
|
|
// 2.1.12 Parentheses
|
|
// 3.1.12 Parentheses
|
|
{
|
|
name: `(cat OR dog) AND fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "dog"},
|
|
}},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
// ++
|
|
// 3.2.3 Implicit Operator for Property Restriction
|
|
{
|
|
name: `author:"John Smith" filetype:docx`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "filetype", Value: "docx"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `author:"John Smith" AND filetype:docx`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "filetype", Value: "docx"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `author:"John Smith" author:"Jane Smith"`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "author", Value: "Jane Smith"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `author:"John Smith" OR author:"Jane Smith"`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "author", Value: "Jane Smith"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat filetype:docx`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "filetype", Value: "docx"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat AND filetype:docx`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "filetype", Value: "docx"},
|
|
},
|
|
},
|
|
},
|
|
// ++
|
|
// 3.3.1.1.1 Implicit AND Operator
|
|
{
|
|
name: `cat +dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat AND dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat -dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat AND NOT dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat +dog -fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat AND dog AND NOT fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat dog +fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `fox OR (fox AND (cat OR dog))`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "fox"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "fox"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "dog"},
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat dog -fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `(NOT fox) AND (cat OR dog)`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "fox"},
|
|
}},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "dog"},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat +dog -fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `(NOT fox) AND (dog OR (dog AND cat))`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "fox"},
|
|
}},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "cat"},
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
// ++
|
|
// 2.3.5 Date Tokens
|
|
// 3.3.5 Date Tokens
|
|
{
|
|
name: `Modified:2023-09-05`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.DateTimeNode{
|
|
Key: "Modified",
|
|
Operator: &ast.OperatorNode{Value: ":"},
|
|
Value: mustParseTime(t, "2023-09-05"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `Modified:"2008-01-29"`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.DateTimeNode{
|
|
Key: "Modified",
|
|
Operator: &ast.OperatorNode{Value: ":"},
|
|
Value: mustParseTime(t, "2008-01-29"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `Modified:today`,
|
|
skip: true,
|
|
},
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
// everything else
|
|
{
|
|
name: "FullDictionary",
|
|
givenQuery: mustJoin(FullDictionary),
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "federated"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "search"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "federat*"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "search"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "search"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "fed*"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "filetype", Value: "docx"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "filename", Value: "budget.xlsx"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "author"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "author"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "author"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "author"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "author"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "author", Value: "Shakespear"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "author", Value: "Paul"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "author", Value: "Shakesp*"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "title", Value: "Advanced Search"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "title", Value: "Advanced Sear*"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "title", Value: "Advan* Search"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "title", Value: "*anced Search"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "author", Value: "Jane Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "filetype", Value: "docx"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{
|
|
Key: "author",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "Jane Smith"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.GroupNode{
|
|
Key: "author",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "Jane Smith"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "DepartmentId", Value: "*"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "RelatedHubSites", Value: "*"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "contentclass", Value: "sts_site"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.BooleanNode{Key: "IsHubSite", Value: false},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "filetype", Value: "docx"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "title", Value: "Advanced Search"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Group",
|
|
givenQuery: mustJoin([]string{
|
|
`(name:"moby di*" OR tag:bestseller) AND tag:book NOT tag:read`,
|
|
`author:("John Smith" Jane)`,
|
|
`author:("John Smith" OR Jane)`,
|
|
}),
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "name", Value: "moby di*"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "tag", Value: "bestseller"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "tag", Value: "book"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Key: "tag", Value: "read"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.GroupNode{
|
|
Key: "author",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "Jane"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.GroupNode{
|
|
Key: "author",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "Jane"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `author:("John Smith" Jane) author:"Jack" AND author:"Oggy"`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Key: "author",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "Jane"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "author", Value: "Jack"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Key: "author", Value: "Oggy"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `author:("John Smith" OR Jane)`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Key: "author",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "Jane"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `NOT "John Smith" NOT Jane`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "Jane"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `NOT author:"John Smith" NOT author:"Jane Smith" NOT tag:sifi`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Key: "author", Value: "John Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Key: "author", Value: "Jane Smith"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Key: "tag", Value: "sifi"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `scope:"<uuid>/new folder/subfolder" file`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{
|
|
Key: "scope",
|
|
Value: "<uuid>/new folder/subfolder",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Value: "file",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: ` 😂 "*😀 😁*" name:😂💁👌🎍😍 name:😂💁👌 😍`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{
|
|
Value: "😂",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Value: "*😀 😁*",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Key: "name",
|
|
Value: "😂💁👌🎍😍",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{
|
|
Key: "name",
|
|
Value: "😂💁👌",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Value: "😍",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "DateTimeRestrictionNode",
|
|
givenQuery: mustJoin([]string{
|
|
`Mtime:"2023-09-05T08:42:11.23554+02:00"`,
|
|
`Mtime:2023-09-05T08:42:11.23554+02:00`,
|
|
`Mtime="2023-09-05T08:42:11.23554+02:00"`,
|
|
`Mtime=2023-09-05T08:42:11.23554+02:00`,
|
|
`Mtime<"2023-09-05T08:42:11.23554+02:00"`,
|
|
`Mtime<2023-09-05T08:42:11.23554+02:00`,
|
|
`Mtime<="2023-09-05T08:42:11.23554+02:00"`,
|
|
`Mtime<=2023-09-05T08:42:11.23554+02:00`,
|
|
`Mtime>"2023-09-05T08:42:11.23554+02:00"`,
|
|
`Mtime>2023-09-05T08:42:11.23554+02:00`,
|
|
`Mtime>="2023-09-05T08:42:11.23554+02:00"`,
|
|
`Mtime>=2023-09-05T08:42:11.23554+02:00`,
|
|
}),
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: ":"},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: ":"},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: "="},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: "="},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: "<"},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: "<"},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: "<="},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: "<="},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: ">"},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: ">"},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: ">="},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.DateTimeNode{
|
|
Key: "Mtime",
|
|
Operator: &ast.OperatorNode{Value: ">="},
|
|
Value: mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "id",
|
|
givenQuery: mustJoin([]string{
|
|
`id:b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c`,
|
|
`ID:b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c`,
|
|
}),
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{
|
|
Key: "id",
|
|
Value: "b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{
|
|
Key: "ID",
|
|
Value: "b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "animal:(cat dog turtle)",
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Key: "animal",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{
|
|
Value: "cat",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Value: "dog",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Value: "turtle",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "(cat dog turtle)",
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{
|
|
Value: "cat",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Value: "dog",
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{
|
|
Value: "turtle",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "animal:(mammal:cat mammal:dog reptile:turtle)",
|
|
expectedError: kql.NamedGroupInvalidNodesError{
|
|
Node: &ast.StringNode{Key: "mammal", Value: "cat"},
|
|
},
|
|
},
|
|
{
|
|
name: "animal:(cat mammal:dog turtle)",
|
|
expectedError: kql.NamedGroupInvalidNodesError{
|
|
Node: &ast.StringNode{Key: "mammal", Value: "dog"},
|
|
},
|
|
},
|
|
{
|
|
name: "animal:(AND cat)",
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolAND},
|
|
},
|
|
},
|
|
{
|
|
name: "animal:(OR cat)",
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolOR},
|
|
},
|
|
},
|
|
{
|
|
name: "(AND cat)",
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolAND},
|
|
},
|
|
},
|
|
{
|
|
name: "(OR cat)",
|
|
expectedError: kql.StartsWithBinaryOperatorError{
|
|
Node: &ast.OperatorNode{Value: kql.BoolOR},
|
|
},
|
|
},
|
|
{
|
|
name: `cat dog`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `cat dog fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `(cat dog) fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `(mammal:cat mammal:dog) fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Key: "mammal", Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "mammal", Value: "dog"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `mammal:(cat dog) fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Key: "mammal",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `mammal:(cat dog) mammal:fox`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Key: "mammal",
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "cat"},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.StringNode{Value: "dog"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Key: "mammal", Value: "fox"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `title:((Advanced OR Search OR Query) -"Advanced Search Query")`,
|
|
expectedAst: &ast.Ast{
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Key: "title",
|
|
Nodes: []ast.Node{
|
|
&ast.GroupNode{
|
|
Nodes: []ast.Node{
|
|
&ast.StringNode{Value: "Advanced"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "Search"},
|
|
&ast.OperatorNode{Value: kql.BoolOR},
|
|
&ast.StringNode{Value: "Query"},
|
|
},
|
|
},
|
|
&ast.OperatorNode{Value: kql.BoolAND},
|
|
&ast.OperatorNode{Value: kql.BoolNOT},
|
|
&ast.StringNode{Value: "Advanced Search Query"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert := tAssert.New(t)
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.skip {
|
|
t.Skip()
|
|
}
|
|
|
|
q := tt.name
|
|
|
|
if tt.givenQuery != "" {
|
|
q = tt.givenQuery
|
|
}
|
|
|
|
parsedAST, err := kql.Builder{}.Build(q)
|
|
|
|
if tt.expectedError != nil {
|
|
if tt.expectedError.Error() != "" {
|
|
assert.Equal(err.Error(), tt.expectedError.Error())
|
|
} else {
|
|
assert.NotNil(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if diff := test.DiffAst(tt.expectedAst, parsedAST); diff != "" {
|
|
t.Fatalf("AST mismatch \nquery: '%s' \n(-expected +got): %s", q, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkParse(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for n := 0; n < b.N; n++ {
|
|
if _, err := kql.Parse("", []byte(strings.Join(FullDictionary, " "))); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|