Files
ocis/ocis-pkg/kql/factory.go
Paul Faure 4b7349037f feat(kql): support numeric range queries (>=, <=, >, <)
Add NumericRestrictionNode to the KQL PEG grammar so that range
operators work with numeric values, not just DateTimes. This enables
queries like `size>=1048576`, `photo.iso>=100`, `photo.fNumber>=2.8`,
and `photo.focalLength<50`.

Changes:
- ast: add NumericNode with Key, Operator, Value (float64)
- kql/dictionary.peg: add NumericRestrictionNode and Number rules
- kql/factory.go: add buildNumericNode()
- kql/cast.go: add toFloat64()
- bleve/compiler.go: compile NumericNode to NumericRangeQuery

Fixes: #12093

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:51:08 -05:00

239 lines
4.0 KiB
Go

package kql
import (
"strings"
"github.com/owncloud/ocis/v2/ocis-pkg/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 buildNumericNode(k, o, v interface{}, text []byte, pos position) (*ast.NumericNode, 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 := toFloat64(v)
if err != nil {
return nil, err
}
return &ast.NumericNode{
Base: b,
Key: key,
Operator: operator,
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
}