mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-03 03:28:03 -05:00
130 lines
3.4 KiB
Go
130 lines
3.4 KiB
Go
package kql
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/opencloud-eu/opencloud/pkg/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}
|
|
}
|