mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-06 04:09:40 -06:00
* enhancement: use kql as default search query language * enhancement: add support for unicode search queries * fix: escape bleve field query whitespace * fix: search related acceptance tests * enhancement: remove legacy search query language * enhancement: add support for kql dateTime restriction node types * chore: bump web to v8.0.0-alpha.2 * fix: failing search api test * enhancement: search bleve query compiler use DateRangeQuery as DateTimeNode counterpart * enhancement: support for colon operators in dateTime kql queries
129 lines
3.9 KiB
Go
129 lines
3.9 KiB
Go
package kql
|
|
|
|
import (
|
|
"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
|
|
)
|
|
|
|
var implicitOperatorNodeSource = "implicitly operator"
|
|
var operatorNodeAnd = ast.OperatorNode{Base: &ast.Base{Loc: &ast.Location{Source: &implicitOperatorNodeSource}}, Value: BoolAND}
|
|
var operatorNodeOr = ast.OperatorNode{Base: &ast.Base{Loc: &ast.Location{Source: &implicitOperatorNodeSource}}, Value: BoolOR}
|
|
|
|
// NormalizeNodes Populate the implicit logical operators in the ast
|
|
//
|
|
// https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference#constructing-free-text-queries-using-kql
|
|
// If there are multiple free-text expressions without any operators in between them, the query behavior is the same as using the AND operator.
|
|
// "John Smith" "Jane Smith"
|
|
// This functionally is the same as using the AND Boolean operator, as follows:
|
|
// "John Smith" AND "Jane Smith"
|
|
//
|
|
// https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference#using-multiple-property-restrictions-within-a-kql-query
|
|
// When you use multiple instances of the same property restriction, matches are based on the union of the property restrictions in the KQL query.
|
|
// author:"John Smith" author:"Jane Smith"
|
|
// This functionally is the same as using the OR Boolean operator, as follows:
|
|
// author:"John Smith" OR author:"Jane Smith"
|
|
//
|
|
// When you use different property restrictions, matches are based on an intersection of the property restrictions in the KQL query, as follows:
|
|
// author:"John Smith" filetype:docx
|
|
// This is the same as using the AND Boolean operator, as follows:
|
|
// author:"John Smith" AND filetype:docx
|
|
//
|
|
// https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference#grouping-property-restrictions-within-a-kql-query
|
|
// author:("John Smith" "Jane Smith")
|
|
// This is the same as using the AND Boolean operator, as follows:
|
|
// author:"John Smith" AND author:"Jane Smith"
|
|
func NormalizeNodes(nodes []ast.Node) ([]ast.Node, error) {
|
|
res := make([]ast.Node, 0, len(nodes))
|
|
var currentNode ast.Node
|
|
var prevKey, currentKey *string
|
|
var operator *ast.OperatorNode
|
|
for _, node := range nodes {
|
|
switch n := node.(type) {
|
|
case *ast.StringNode:
|
|
if prevKey == nil {
|
|
prevKey = &n.Key
|
|
res = append(res, node)
|
|
continue
|
|
}
|
|
currentNode = n
|
|
currentKey = &n.Key
|
|
case *ast.DateTimeNode:
|
|
if prevKey == nil {
|
|
prevKey = &n.Key
|
|
res = append(res, node)
|
|
continue
|
|
}
|
|
currentNode = n
|
|
currentKey = &n.Key
|
|
case *ast.BooleanNode:
|
|
if prevKey == nil {
|
|
prevKey = &n.Key
|
|
res = append(res, node)
|
|
continue
|
|
}
|
|
currentNode = n
|
|
currentKey = &n.Key
|
|
case *ast.GroupNode:
|
|
var err error
|
|
n.Nodes, err = NormalizeNodes(n.Nodes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if prevKey == nil {
|
|
prevKey = &n.Key
|
|
res = append(res, n)
|
|
continue
|
|
}
|
|
currentNode = n
|
|
currentKey = &n.Key
|
|
case *ast.OperatorNode:
|
|
if n.Value == BoolNOT {
|
|
if prevKey == nil {
|
|
res = append(res, n)
|
|
} else {
|
|
operator = n
|
|
}
|
|
} else {
|
|
if prevKey == nil {
|
|
return nil, &StartsWithBinaryOperatorError{Op: n.Value}
|
|
}
|
|
prevKey = nil
|
|
res = append(res, node)
|
|
}
|
|
default:
|
|
prevKey = nil
|
|
res = append(res, node)
|
|
}
|
|
if prevKey != nil && currentKey != nil {
|
|
if *prevKey == *currentKey && *prevKey != "" {
|
|
res = append(res, &operatorNodeOr)
|
|
} else {
|
|
res = append(res, &operatorNodeAnd)
|
|
}
|
|
if operator != nil {
|
|
res = append(res, operator)
|
|
operator = nil
|
|
}
|
|
res = append(res, currentNode)
|
|
|
|
prevKey = currentKey
|
|
currentNode = nil
|
|
currentKey = nil
|
|
continue
|
|
}
|
|
}
|
|
|
|
return trimOrphan(res), nil
|
|
}
|
|
|
|
func trimOrphan(nodes []ast.Node) []ast.Node {
|
|
offset := len(nodes)
|
|
for i := len(nodes) - 1; i >= 0; i-- {
|
|
if _, ok := nodes[i].(*ast.OperatorNode); ok {
|
|
offset--
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return nodes[:offset]
|
|
}
|