mirror of
https://github.com/owncloud/ocis
synced 2026-04-25 17:25:21 +02:00
* feat(search): introduce search query package With the increasing complexity of how we organize our resources, the search must also be able to find them using entity properties. The query package provides the necessary functionality to do this. This makes it possible to search for resources via KQL, the microsoft spec is largely covered and can be used for this. In the current state, the legacy query language is still used, in a future update this will be deprecated and KQL will become the standard
121 lines
3.7 KiB
Go
121 lines
3.7 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.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]
|
|
}
|