feat(ocis): move ast and kql pkg to ocis-pkg

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2024-06-19 12:01:02 +02:00
parent 0d604dfb9b
commit 7a819412c2
23 changed files with 25 additions and 32 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ l10n-push:
.PHONY: l10n-read
l10n-read: $(GO_XGETTEXT)
go-xgettext -o $(OUTPUT_DIR)/userlog.pot --keyword=l10n.Template -s pkg/service/response.go
go-xgettext -o $(OUTPUT_DIR)/activitylog.pot --keyword=l10n.Template -s pkg/service/response.go
.PHONY: l10n-write
l10n-write:
+4 -4
View File
@@ -2,8 +2,8 @@ package config
// Log defines the available log configuration.
type Log struct {
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;ACTIVITYLOG_USERLOG_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"5.0"`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;ACTIVITYLOG_USERLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;ACTIVITYLOG_USERLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;ACTIVITYLOG_USERLOG_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"5.0"`
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;ACTIVITYLOG_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"5.0"`
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;ACTIVITYLOG_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.0"`
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;ACTIVITYLOG_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.0"`
File string `mapstructure:"file" env:"OCIS_LOG_FILE;ACTIVITYLOG_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"5.0"`
}
+2 -2
View File
@@ -15,11 +15,11 @@ import (
"github.com/cs3org/reva/v2/pkg/utils"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/ast"
"github.com/owncloud/ocis/v2/ocis-pkg/kql"
"github.com/owncloud/ocis/v2/ocis-pkg/l10n"
ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
"github.com/owncloud/ocis/v2/services/search/pkg/query/kql"
)
var (
+1 -1
View File
@@ -26,7 +26,7 @@ include ../../.make/generate.mk
.PHONY: ci-go-generate
ci-go-generate: $(PIGEON) $(MOCKERY) # CI runs ci-node-generate automatically before this target
$(MOCKERY)
$(PIGEON) -optimize-grammar -optimize-parser -o pkg/query/kql/dictionary_gen.go pkg/query/kql/dictionary.peg
$(PIGEON) -optimize-grammar -optimize-parser -o ../../ocis-pkg/kql/dictionary_gen.go ../../ocis-pkg/kql/dictionary.peg
.PHONY: ci-node-generate
ci-node-generate:
-107
View File
@@ -1,107 +0,0 @@
// Package ast provides available ast nodes.
package ast
import (
"time"
)
// Node represents abstract syntax tree node
type Node interface {
Location() *Location
}
// Position represents a specific location in the source
type Position struct {
Line int
Column int
}
// Location represents the location of a node in the AST
type Location struct {
Start Position `json:"start"`
End Position `json:"end"`
Source *string `json:"source,omitempty"`
}
// Base contains shared node attributes
// each node should inherit from this
type Base struct {
Loc *Location
}
// Location is the source location of the Node
func (b *Base) Location() *Location { return b.Loc }
// Ast represents the query - node structure as abstract syntax tree
type Ast struct {
*Base
Nodes []Node `json:"body"`
}
// StringNode represents a string value
type StringNode struct {
*Base
Key string
Value string
}
// BooleanNode represents a bool value
type BooleanNode struct {
*Base
Key string
Value bool
}
// DateTimeNode represents a time.Time value
type DateTimeNode struct {
*Base
Key string
Operator *OperatorNode
Value time.Time
}
// OperatorNode represents an operator value like
// AND, OR, NOT, =, <= ... and so on
type OperatorNode struct {
*Base
Value string
}
// GroupNode represents a collection of many grouped nodes
type GroupNode struct {
*Base
Key string
Nodes []Node
}
// NodeKey tries to return the node key
func NodeKey(n Node) string {
switch node := n.(type) {
case *StringNode:
return node.Key
case *DateTimeNode:
return node.Key
case *BooleanNode:
return node.Key
case *GroupNode:
return node.Key
default:
return ""
}
}
// NodeValue tries to return the node key
func NodeValue(n Node) interface{} {
switch node := n.(type) {
case *StringNode:
return node.Value
case *DateTimeNode:
return node.Value
case *BooleanNode:
return node.Value
case *GroupNode:
return node.Nodes
default:
return ""
}
}
@@ -1,27 +0,0 @@
// Package test provides shared test primitives for ast testing.
package test
import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
// DiffAst returns a human-readable report of the differences between two values
// by default it ignores every ast node Base field.
func DiffAst(x, y interface{}, opts ...cmp.Option) string {
return cmp.Diff(
x,
y,
append(
opts,
cmpopts.IgnoreFields(ast.Ast{}, "Base"),
cmpopts.IgnoreFields(ast.StringNode{}, "Base"),
cmpopts.IgnoreFields(ast.OperatorNode{}, "Base"),
cmpopts.IgnoreFields(ast.GroupNode{}, "Base"),
cmpopts.IgnoreFields(ast.BooleanNode{}, "Base"),
cmpopts.IgnoreFields(ast.DateTimeNode{}, "Base"),
)...,
)
}
+1 -1
View File
@@ -4,8 +4,8 @@ package bleve
import (
bQuery "github.com/blevesearch/bleve/v2/search/query"
"github.com/owncloud/ocis/v2/ocis-pkg/kql"
"github.com/owncloud/ocis/v2/services/search/pkg/query"
"github.com/owncloud/ocis/v2/services/search/pkg/query/kql"
)
// Creator is combines a Builder and a Compiler which is used to Create the query.
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"github.com/blevesearch/bleve/v2"
bleveQuery "github.com/blevesearch/bleve/v2/search/query"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
"github.com/owncloud/ocis/v2/services/search/pkg/query/kql"
"github.com/owncloud/ocis/v2/ocis-pkg/ast"
"github.com/owncloud/ocis/v2/ocis-pkg/kql"
)
var _fields = map[string]string{
@@ -5,9 +5,8 @@ import (
"time"
"github.com/blevesearch/bleve/v2/search/query"
"github.com/owncloud/ocis/v2/ocis-pkg/ast"
tAssert "github.com/stretchr/testify/assert"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
var timeMustParse = func(t *testing.T, ts string) time.Time {
+1 -1
View File
@@ -3,7 +3,7 @@ package query
import (
"fmt"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
"github.com/owncloud/ocis/v2/ocis-pkg/ast"
)
// StartsWithBinaryOperatorError records an error and the operation that caused it.
-139
View File
@@ -1,139 +0,0 @@
package kql
import (
"fmt"
"time"
"github.com/jinzhu/now"
"github.com/owncloud/ocis/v2/services/search/pkg/query"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
func toNode[T ast.Node](in interface{}) (T, error) {
var t T
out, ok := in.(T)
if !ok {
return t, fmt.Errorf("can't convert '%T' to '%T'", in, t)
}
return out, nil
}
func toNodes[T ast.Node](in interface{}) ([]T, error) {
switch v := in.(type) {
case T:
return []T{v}, nil
case []T:
return v, nil
case []*ast.OperatorNode, []*ast.DateTimeNode:
return toNodes[T](v)
case []interface{}:
var nodes []T
for _, el := range v {
node, err := toNodes[T](el)
if err != nil {
return nil, err
}
nodes = append(nodes, node...)
}
return nodes, nil
case nil:
return nil, nil
default:
var t T
return nil, fmt.Errorf("can't convert '%T' to '%T'", in, t)
}
}
func toString(in interface{}) (string, error) {
switch v := in.(type) {
case []byte:
return string(v), nil
case []interface{}:
var str string
for i := range v {
sv, err := toString(v[i])
if err != nil {
return "", err
}
str += sv
}
return str, nil
case string:
return v, nil
default:
return "", fmt.Errorf("can't convert '%T' to string", v)
}
}
func toTime(in interface{}) (time.Time, error) {
ts, err := toString(in)
if err != nil {
return time.Time{}, err
}
return now.Parse(ts)
}
func toTimeRange(in interface{}) (*time.Time, *time.Time, error) {
var from, to time.Time
value, err := toString(in)
if err != nil {
return &from, &to, &query.UnsupportedTimeRangeError{}
}
c := &now.Config{
WeekStartDay: time.Monday,
}
n := c.With(timeNow())
switch value {
case "today":
from = n.BeginningOfDay()
to = n.EndOfDay()
case "yesterday":
yesterday := n.With(n.AddDate(0, 0, -1))
from = yesterday.BeginningOfDay()
to = yesterday.EndOfDay()
case "this week":
from = n.BeginningOfWeek()
to = n.EndOfWeek()
case "last week":
lastWeek := n.With(n.AddDate(0, 0, -7))
from = lastWeek.BeginningOfWeek()
to = lastWeek.EndOfWeek()
case "last 7 days":
from = n.With(n.AddDate(0, 0, -6)).BeginningOfDay()
to = n.EndOfDay()
case "this month":
from = n.BeginningOfMonth()
to = n.EndOfMonth()
case "last month":
lastMonth := n.With(n.BeginningOfMonth().AddDate(0, 0, -1))
from = lastMonth.BeginningOfMonth()
to = lastMonth.EndOfMonth()
case "last 30 days":
from = n.With(n.AddDate(0, 0, -29)).BeginningOfDay()
to = n.EndOfDay()
case "this year":
from = n.BeginningOfYear()
to = n.EndOfYear()
case "last year":
lastYear := n.With(n.AddDate(-1, 0, 0))
from = lastYear.BeginningOfYear()
to = lastYear.EndOfYear()
}
if from.IsZero() || to.IsZero() {
return nil, nil, &query.UnsupportedTimeRangeError{}
}
return &from, &to, nil
}
-129
View File
@@ -1,129 +0,0 @@
package kql
import (
"strings"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
// connectNodes connects given nodes
func connectNodes(c Connector, nodes ...ast.Node) []ast.Node {
var connectedNodes []ast.Node
for i := range nodes {
ri := len(nodes) - 1 - i
head := nodes[ri]
if connectionNodes := connectNode(c, head, connectedNodes...); len(connectionNodes) > 0 {
connectedNodes = append(connectionNodes, connectedNodes...)
}
connectedNodes = append([]ast.Node{head}, connectedNodes...)
}
return connectedNodes
}
// connectNode connects a tip node with the rest
func connectNode(c Connector, headNode ast.Node, tailNodes ...ast.Node) []ast.Node {
var nearestNeighborNode ast.Node
var nearestNeighborOperators []*ast.OperatorNode
l:
for _, tailNode := range tailNodes {
switch node := tailNode.(type) {
case *ast.OperatorNode:
nearestNeighborOperators = append(nearestNeighborOperators, node)
default:
nearestNeighborNode = node
break l
}
}
if nearestNeighborNode == nil {
return nil
}
return c.Connect(headNode, nearestNeighborNode, nearestNeighborOperators)
}
// Connector is responsible to decide what node connections are needed
type Connector interface {
Connect(head ast.Node, neighbor ast.Node, connections []*ast.OperatorNode) []ast.Node
}
// DefaultConnector is the default node connector
type DefaultConnector struct {
sameKeyOPValue string
}
// Connect implements the Connector interface and is used to connect the nodes using
// the default logic defined by the kql spec.
func (c DefaultConnector) Connect(head ast.Node, neighbor ast.Node, connections []*ast.OperatorNode) []ast.Node {
switch head.(type) {
case *ast.OperatorNode:
return nil
}
headKey := strings.ToLower(ast.NodeKey(head))
neighborKey := strings.ToLower(ast.NodeKey(neighbor))
connection := &ast.OperatorNode{
Base: &ast.Base{Loc: &ast.Location{Source: &[]string{"implicitly operator"}[0]}},
Value: BoolAND,
}
// if the current node and the neighbor node have the same key
// the connection is of type OR
//
// spec: same
// author:"John Smith" author:"Jane Smith"
// author:"John Smith" OR author:"Jane Smith"
//
// if the nodes have NO key, the edge is a AND connection
//
// spec: same
// cat dog
// cat AND dog
// from the spec:
// To construct complex queries, you can combine multiple
// free-text expressions with KQL query operators.
// If there are multiple free-text expressions without any
// operators in between them, the query behavior is the same
// as using the AND operator.
//
// nodes inside of group node are handled differently,
// if no explicit operator given, it uses AND
//
// spec: same
// author:"John Smith" AND author:"Jane Smith"
// author:("John Smith" "Jane Smith")
if headKey == neighborKey && headKey != "" && neighborKey != "" {
connection.Value = c.sameKeyOPValue
}
// decisions based on nearest neighbor operators
for i, node := range connections {
// consider direct neighbor operator only
if i == 0 {
// no connection is necessary here because an `AND` or `OR` edge is already present
// exit
for _, skipValue := range []string{BoolOR, BoolAND} {
if node.Value == skipValue {
return nil
}
}
// if neighbor node negotiates, an AND edge is needed
//
// spec: same
// cat -dog
// cat AND NOT dog
if node.Value == BoolNOT {
connection.Value = BoolAND
}
}
}
return []ast.Node{connection}
}
@@ -1,235 +0,0 @@
{
package kql
}
////////////////////////////////////////////////////////
// ast
////////////////////////////////////////////////////////
AST <-
n:Nodes {
return buildAST(n, c.text, c.pos)
}
////////////////////////////////////////////////////////
// nodes
////////////////////////////////////////////////////////
Nodes <-
(_ Node)+
Node <-
GroupNode /
PropertyRestrictionNodes /
OperatorBooleanNodes /
FreeTextKeywordNodes
////////////////////////////////////////////////////////
// nesting
////////////////////////////////////////////////////////
GroupNode <-
k:(Char+)? (OperatorColonNode / OperatorEqualNode)? "(" v:Nodes ")" {
return buildGroupNode(k, v, c.text, c.pos)
}
////////////////////////////////////////////////////////
// property restrictions
////////////////////////////////////////////////////////
PropertyRestrictionNodes <-
YesNoPropertyRestrictionNode /
DateTimeRestrictionNode /
TextPropertyRestrictionNode
YesNoPropertyRestrictionNode <-
k:Char+ (OperatorColonNode / OperatorEqualNode) v:("true" / "false"){
return buildBooleanNode(k, v, c.text, c.pos)
}
DateTimeRestrictionNode <-
k:Char+ o:(
OperatorGreaterOrEqualNode /
OperatorLessOrEqualNode /
OperatorGreaterNode /
OperatorLessNode /
OperatorEqualNode /
OperatorColonNode
) '"'? v:(
DateTime /
FullDate /
FullTime
) '"'? {
return buildDateTimeNode(k, o, v, c.text, c.pos)
} /
k:Char+ (
OperatorEqualNode /
OperatorColonNode
) '"'? v:NaturalLanguageDateTime '"'? {
return buildNaturalLanguageDateTimeNodes(k, v, c.text, c.pos)
}
TextPropertyRestrictionNode <-
k:Char+ (OperatorColonNode / OperatorEqualNode) v:(String / [^ ()]+){
return buildStringNode(k, v, c.text, c.pos)
}
////////////////////////////////////////////////////////
// free text-keywords
////////////////////////////////////////////////////////
FreeTextKeywordNodes <-
PhraseNode /
WordNode
PhraseNode <-
OperatorColonNode? _ v:String _ OperatorColonNode? {
return buildStringNode("", v, c.text, c.pos)
}
WordNode <-
OperatorColonNode? _ v:[^ :()]+ _ OperatorColonNode? {
return buildStringNode("", v, c.text, c.pos)
}
////////////////////////////////////////////////////////
// operators
////////////////////////////////////////////////////////
OperatorBooleanNodes <-
OperatorBooleanAndNode /
OperatorBooleanNotNode /
OperatorBooleanOrNode
OperatorBooleanAndNode <-
("AND" / "+") {
return buildOperatorNode(c.text, c.pos)
}
OperatorBooleanNotNode <-
("NOT" / "-") {
return buildOperatorNode(c.text, c.pos)
}
OperatorBooleanOrNode <-
("OR") {
return buildOperatorNode(c.text, c.pos)
}
OperatorColonNode <-
":" {
return buildOperatorNode(c.text, c.pos)
}
OperatorEqualNode <-
"=" {
return buildOperatorNode(c.text, c.pos)
}
OperatorLessNode <-
"<" {
return buildOperatorNode(c.text, c.pos)
}
OperatorLessOrEqualNode <-
"<=" {
return buildOperatorNode(c.text, c.pos)
}
OperatorGreaterNode <-
">" {
return buildOperatorNode(c.text, c.pos)
}
OperatorGreaterOrEqualNode <-
">=" {
return buildOperatorNode(c.text, c.pos)
}
////////////////////////////////////////////////////////
// time
////////////////////////////////////////////////////////
TimeYear <-
Digit Digit Digit Digit {
return c.text, nil
}
TimeMonth <-
Digit Digit {
return c.text, nil
}
TimeDay <-
Digit Digit {
return c.text, nil
}
TimeHour <-
Digit Digit {
return c.text, nil
}
TimeMinute <-
Digit Digit {
return c.text, nil
}
TimeSecond <-
Digit Digit {
return c.text, nil
}
FullDate <-
TimeYear "-" TimeMonth "-" TimeDay {
return c.text, nil
}
FullTime <-
TimeHour ":" TimeMinute ":" TimeSecond ("." Digit+)? ("Z" / ("+" / "-") TimeHour ":" TimeMinute) {
return c.text, nil
}
DateTime <-
FullDate "T" FullTime {
return c.text, nil
}
NaturalLanguageDateTime <-
"today" /
"yesterday" /
"this week" /
"last week" /
"last 7 days" /
"this month" /
"last month" /
"last 30 days" /
"this year" /
"last year" {
return c.text, nil
}
////////////////////////////////////////////////////////
// misc
////////////////////////////////////////////////////////
Char <-
[A-Za-z] {
return c.text, nil
}
String <-
'"' v:[^"]* '"' {
return v, nil
}
Digit <-
[0-9] {
return c.text, nil
}
_ <-
[ \t]* {
return nil, nil
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-27
View File
@@ -1,27 +0,0 @@
/*
Package kql provides the ability to work with kql queries.
Not every aspect of the spec is implemented yet.
The language support will grow over time if needed.
The following spec parts are supported and tested:
- 2.1.2 AND Operator
- 2.1.6 NOT Operator
- 2.1.8 OR Operator
- 2.1.12 Parentheses
- 2.3.5 Date Tokens
- 3.1.11 Implicit Operator
- 3.1.12 Parentheses
- 3.1.2 AND Operator
- 3.1.6 NOT Operator
- 3.1.8 OR Operator
- 3.2.3 Implicit Operator for Property Restriction
- 3.3.1.1.1 Implicit AND Operator
- 3.3.5 Date Tokens
References:
- https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference
- https://learn.microsoft.com/en-us/openspecs/sharepoint_protocols/ms-kql/3bbf06cd-8fc1-4277-bd92-8661ccd3c9b0
- https://msopenspecs.azureedge.net/files/MS-KQL/%5bMS-KQL%5d.pdf
*/
package kql
@@ -1,11 +0,0 @@
package kql
import (
"time"
)
// PatchTimeNow is here to patch the package time now func,
// which is used in the test suite
func PatchTimeNow(t func() time.Time) {
timeNow = t
}
-209
View File
@@ -1,209 +0,0 @@
package kql
import (
"strings"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
func base(text []byte, pos position) (*ast.Base, error) {
source, err := toString(text)
if err != nil {
return nil, err
}
return &ast.Base{
Loc: &ast.Location{
Start: ast.Position{
Line: pos.line,
Column: pos.col,
},
End: ast.Position{
Line: pos.line,
Column: pos.col + len(text),
},
Source: &source,
},
}, nil
}
func buildAST(n interface{}, text []byte, pos position) (*ast.Ast, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
nodes, err := toNodes[ast.Node](n)
if err != nil {
return nil, err
}
a := &ast.Ast{
Base: b,
Nodes: connectNodes(DefaultConnector{sameKeyOPValue: BoolOR}, nodes...),
}
if err := validateAst(a); err != nil {
return nil, err
}
return a, nil
}
func buildStringNode(k, v interface{}, text []byte, pos position) (*ast.StringNode, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
key, err := toString(k)
if err != nil {
return nil, err
}
value, err := toString(v)
if err != nil {
return nil, err
}
return &ast.StringNode{
Base: b,
Key: key,
Value: value,
}, nil
}
func buildDateTimeNode(k, o, v interface{}, text []byte, pos position) (*ast.DateTimeNode, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
operator, err := toNode[*ast.OperatorNode](o)
if err != nil {
return nil, err
}
key, err := toString(k)
if err != nil {
return nil, err
}
value, err := toTime(v)
if err != nil {
return nil, err
}
return &ast.DateTimeNode{
Base: b,
Key: key,
Operator: operator,
Value: value,
}, nil
}
func buildNaturalLanguageDateTimeNodes(k, v interface{}, text []byte, pos position) ([]ast.Node, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
key, err := toString(k)
if err != nil {
return nil, err
}
from, to, err := toTimeRange(v)
if err != nil {
return nil, err
}
return []ast.Node{
&ast.DateTimeNode{
Base: b,
Value: *from,
Key: key,
Operator: &ast.OperatorNode{Value: ">="},
},
&ast.OperatorNode{Value: BoolAND},
&ast.DateTimeNode{
Base: b,
Value: *to,
Key: key,
Operator: &ast.OperatorNode{Value: "<="},
},
}, nil
}
func buildBooleanNode(k, v interface{}, text []byte, pos position) (*ast.BooleanNode, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
key, err := toString(k)
if err != nil {
return nil, err
}
value, err := toString(v)
if err != nil {
return nil, err
}
return &ast.BooleanNode{
Base: b,
Key: key,
Value: strings.ToLower(value) == "true",
}, nil
}
func buildOperatorNode(text []byte, pos position) (*ast.OperatorNode, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
value, err := toString(text)
if err != nil {
return nil, err
}
switch value {
case "+":
value = BoolAND
case "-":
value = BoolNOT
}
return &ast.OperatorNode{
Base: b,
Value: value,
}, nil
}
func buildGroupNode(k, n interface{}, text []byte, pos position) (*ast.GroupNode, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
key, _ := toString(k)
nodes, err := toNodes[ast.Node](n)
if err != nil {
return nil, err
}
gn := &ast.GroupNode{
Base: b,
Key: key,
Nodes: connectNodes(DefaultConnector{sameKeyOPValue: BoolOR}, nodes...),
}
if err := validateGroupNode(gn); err != nil {
return nil, err
}
return gn, nil
}
-3
View File
@@ -1,3 +0,0 @@
package kql
//go:generate go run github.com/mna/pigeon -optimize-grammar -optimize-parser -o dictionary_gen.go dictionary.peg
-49
View File
@@ -1,49 +0,0 @@
// Package kql provides the ability to work with kql queries.
package kql
import (
"errors"
"time"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
// The operator node value definition
const (
// BoolAND connect two nodes with "AND"
BoolAND = "AND"
// BoolOR connect two nodes with "OR"
BoolOR = "OR"
// BoolNOT connect two nodes with "NOT"
BoolNOT = "NOT"
)
// Builder implements kql Builder interface
type Builder struct{}
// Build creates an ast.Ast based on a kql query
func (b Builder) Build(q string) (*ast.Ast, error) {
f, err := Parse("", []byte(q))
if err != nil {
var list errList
errors.As(err, &list)
for _, listError := range list {
var parserError *parserError
switch {
case errors.As(listError, &parserError):
if parserError.Inner != nil {
return nil, parserError.Inner
}
return nil, listError
}
}
}
return f.(*ast.Ast), nil
}
// timeNow mirrors time.Now by default, the only reason why this exists
// is to monkey patch it from the tests. See PatchTimeNow
var timeNow = time.Now
-55
View File
@@ -1,55 +0,0 @@
package kql_test
import (
"testing"
"github.com/owncloud/ocis/v2/services/search/pkg/query"
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/kql"
)
func TestNewAST(t *testing.T) {
tests := []struct {
name string
givenQuery string
expectedError error
}{
{
name: "success",
givenQuery: "foo:bar",
},
{
name: "error",
givenQuery: kql.BoolAND,
expectedError: query.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolAND},
},
},
}
assert := tAssert.New(t)
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got, err := kql.Builder{}.Build(tt.givenQuery)
if tt.expectedError != nil {
if tt.expectedError.Error() != "" {
assert.Equal(err.Error(), tt.expectedError.Error())
} else {
assert.NotNil(err)
}
assert.Nil(got)
return
}
assert.Nil(err)
assert.NotNil(got)
})
}
}
-37
View File
@@ -1,37 +0,0 @@
package kql
import (
"github.com/owncloud/ocis/v2/services/search/pkg/query"
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
func validateAst(a *ast.Ast) error {
switch node := a.Nodes[0].(type) {
case *ast.OperatorNode:
switch node.Value {
case BoolAND, BoolOR:
return &query.StartsWithBinaryOperatorError{Node: node}
}
}
return nil
}
func validateGroupNode(n *ast.GroupNode) error {
switch node := n.Nodes[0].(type) {
case *ast.OperatorNode:
switch node.Value {
case BoolAND, BoolOR:
return &query.StartsWithBinaryOperatorError{Node: node}
}
}
if n.Key != "" {
for _, node := range n.Nodes {
if ast.NodeKey(node) != "" {
return &query.NamedGroupInvalidNodesError{Node: node}
}
}
}
return nil
}
+1 -3
View File
@@ -1,9 +1,7 @@
// Package query provides functions to work with the different search query flavours.
package query
import (
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
)
import "github.com/owncloud/ocis/v2/ocis-pkg/ast"
// Builder is the interface that wraps the basic Build method.
type Builder interface {