milvus/internal/parser/planparserv2/parser_visitor.go
cai.zhang 19346fa389
feat: Geospatial Data Type and GIS Function support for milvus (#44547)
issue: #43427

This pr's main goal is merge #37417 to milvus 2.5 without conflicts.

# Main Goals

1. Create and describe collections with geospatial type
2. Insert geospatial data into the insert binlog
3. Load segments containing geospatial data into memory
4. Enable query and search can display  geospatial data
5. Support using GIS funtions like ST_EQUALS in query
6. Support R-Tree index for geometry type

# Solution

1. **Add Type**: Modify the Milvus core by adding a Geospatial type in
both the C++ and Go code layers, defining the Geospatial data structure
and the corresponding interfaces.
2. **Dependency Libraries**: Introduce necessary geospatial data
processing libraries. In the C++ source code, use Conan package
management to include the GDAL library. In the Go source code, add the
go-geom library to the go.mod file.
3. **Protocol Interface**: Revise the Milvus protocol to provide
mechanisms for Geospatial message serialization and deserialization.
4. **Data Pipeline**: Facilitate interaction between the client and
proxy using the WKT format for geospatial data. The proxy will convert
all data into WKB format for downstream processing, providing column
data interfaces, segment encapsulation, segment loading, payload
writing, and cache block management.
5. **Query Operators**: Implement simple display and support for filter
queries. Initially, focus on filtering based on spatial relationships
for a single column of geospatial literal values, providing parsing and
execution for query expressions.Now only support brutal search
7. **Client Modification**: Enable the client to handle user input for
geospatial data and facilitate end-to-end testing.Check the modification
in pymilvus.

---------

Signed-off-by: Yinwei Li <yinwei.li@zilliz.com>
Signed-off-by: Cai Zhang <cai.zhang@zilliz.com>
Co-authored-by: ZhuXi <150327960+Yinwei-Yu@users.noreply.github.com>
2025-09-28 19:43:05 +08:00

1979 lines
56 KiB
Go

package planparserv2
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/antlr4-go/antlr/v4"
"github.com/cockroachdb/errors"
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
parser "github.com/milvus-io/milvus/internal/parser/planparserv2/generated"
"github.com/milvus-io/milvus/pkg/v2/proto/planpb"
"github.com/milvus-io/milvus/pkg/v2/util/typeutil"
)
type ParserVisitorArgs struct {
TimezonePreference []string
}
type ParserVisitor struct {
parser.BasePlanVisitor
schema *typeutil.SchemaHelper
args *ParserVisitorArgs
}
func NewParserVisitor(schema *typeutil.SchemaHelper, args *ParserVisitorArgs) *ParserVisitor {
return &ParserVisitor{schema: schema, args: args}
}
// VisitParens unpack the parentheses.
func (v *ParserVisitor) VisitParens(ctx *parser.ParensContext) interface{} {
return ctx.Expr().Accept(v)
}
func (v *ParserVisitor) translateIdentifier(identifier string) (*ExprWithType, error) {
identifier = decodeUnicode(identifier)
field, err := v.schema.GetFieldFromNameDefaultJSON(identifier)
if err != nil {
return nil, err
}
var nestedPath []string
if identifier != field.Name {
nestedPath = append(nestedPath, identifier)
}
if field.DataType == schemapb.DataType_Text {
return nil, fmt.Errorf("filter on text field (%s) is not supported yet", field.Name)
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_ColumnExpr{
ColumnExpr: &planpb.ColumnExpr{
Info: &planpb.ColumnInfo{
FieldId: field.FieldID,
DataType: field.DataType,
IsPrimaryKey: field.IsPrimaryKey,
IsAutoID: field.AutoID,
NestedPath: nestedPath,
IsPartitionKey: field.IsPartitionKey,
IsClusteringKey: field.IsClusteringKey,
ElementType: field.GetElementType(),
Nullable: field.GetNullable(),
},
},
},
},
dataType: field.DataType,
nodeDependent: true,
}, nil
}
// VisitIdentifier translates expr to column plan.
func (v *ParserVisitor) VisitIdentifier(ctx *parser.IdentifierContext) interface{} {
identifier := ctx.GetText()
expr, err := v.translateIdentifier(identifier)
if err != nil {
return err
}
return expr
}
// VisitBoolean translates expr to GenericValue.
func (v *ParserVisitor) VisitBoolean(ctx *parser.BooleanContext) interface{} {
literal := ctx.BooleanConstant().GetText()
b, err := strconv.ParseBool(literal)
if err != nil {
return err
}
return &ExprWithType{
dataType: schemapb.DataType_Bool,
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: NewBool(b),
},
},
},
nodeDependent: true,
}
}
// VisitInteger translates expr to GenericValue.
func (v *ParserVisitor) VisitInteger(ctx *parser.IntegerContext) interface{} {
literal := ctx.IntegerConstant().GetText()
i, err := strconv.ParseInt(literal, 0, 64)
if err != nil {
return err
}
return &ExprWithType{
dataType: schemapb.DataType_Int64,
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: NewInt(i),
},
},
},
nodeDependent: true,
}
}
// VisitFloating translates expr to GenericValue.
func (v *ParserVisitor) VisitFloating(ctx *parser.FloatingContext) interface{} {
literal := ctx.FloatingConstant().GetText()
f, err := strconv.ParseFloat(literal, 64)
if err != nil {
return err
}
return &ExprWithType{
dataType: schemapb.DataType_Double,
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: NewFloat(f),
},
},
},
nodeDependent: true,
}
}
// VisitString translates expr to GenericValue.
func (v *ParserVisitor) VisitString(ctx *parser.StringContext) interface{} {
pattern, err := convertEscapeSingle(ctx.GetText())
if err != nil {
return err
}
return &ExprWithType{
dataType: schemapb.DataType_VarChar,
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: NewString(pattern),
},
},
},
nodeDependent: true,
}
}
func checkDirectComparisonBinaryField(columnInfo *planpb.ColumnInfo) error {
if typeutil.IsArrayType(columnInfo.GetDataType()) && len(columnInfo.GetNestedPath()) == 0 {
return errors.New("can not comparisons array fields directly")
}
return nil
}
// VisitAddSub translates expr to arithmetic plan.
func (v *ParserVisitor) VisitAddSub(ctx *parser.AddSubContext) interface{} {
var err error
left := ctx.Expr(0).Accept(v)
if err = getError(left); err != nil {
return err
}
right := ctx.Expr(1).Accept(v)
if err = getError(right); err != nil {
return err
}
leftValueExpr, rightValueExpr := getValueExpr(left), getValueExpr(right)
if leftValueExpr != nil && rightValueExpr != nil {
if isTemplateExpr(leftValueExpr) || isTemplateExpr(rightValueExpr) {
return fmt.Errorf("placeholder was not supported between two constants with operator: %s", ctx.GetOp().GetText())
}
leftValue, rightValue := leftValueExpr.GetValue(), rightValueExpr.GetValue()
switch ctx.GetOp().GetTokenType() {
case parser.PlanParserADD:
n, err := Add(leftValue, rightValue)
if err != nil {
return err
}
return n
case parser.PlanParserSUB:
n, err := Subtract(leftValue, rightValue)
if err != nil {
return err
}
return n
default:
return fmt.Errorf("unexpected op: %s", ctx.GetOp().GetText())
}
}
leftExpr, rightExpr := getExpr(left), getExpr(right)
reverse := true
if leftValueExpr == nil {
reverse = false
}
if leftExpr == nil || rightExpr == nil {
return fmt.Errorf("invalid arithmetic expression, left: %s, op: %s, right: %s", ctx.Expr(0).GetText(), ctx.GetOp(), ctx.Expr(1).GetText())
}
if err = checkDirectComparisonBinaryField(toColumnInfo(leftExpr)); err != nil {
return err
}
if err = checkDirectComparisonBinaryField(toColumnInfo(rightExpr)); err != nil {
return err
}
var dataType schemapb.DataType
if leftExpr.expr.GetIsTemplate() {
dataType = rightExpr.dataType
} else if rightExpr.expr.GetIsTemplate() {
dataType = leftExpr.dataType
} else {
if err := canArithmetic(leftExpr.dataType, getArrayElementType(leftExpr), rightExpr.dataType, getArrayElementType(rightExpr), reverse); err != nil {
return fmt.Errorf("'%s' %s", arithNameMap[ctx.GetOp().GetTokenType()], err.Error())
}
dataType, err = calcDataType(leftExpr, rightExpr, reverse)
if err != nil {
return err
}
}
expr := &planpb.Expr{
Expr: &planpb.Expr_BinaryArithExpr{
BinaryArithExpr: &planpb.BinaryArithExpr{
Left: leftExpr.expr,
Right: rightExpr.expr,
Op: arithExprMap[ctx.GetOp().GetTokenType()],
},
},
IsTemplate: leftExpr.expr.GetIsTemplate() || rightExpr.expr.GetIsTemplate(),
}
return &ExprWithType{
expr: expr,
dataType: dataType,
nodeDependent: true,
}
}
// VisitMulDivMod translates expr to arithmetic plan.
func (v *ParserVisitor) VisitMulDivMod(ctx *parser.MulDivModContext) interface{} {
var err error
left := ctx.Expr(0).Accept(v)
if err := getError(left); err != nil {
return err
}
right := ctx.Expr(1).Accept(v)
if err := getError(right); err != nil {
return err
}
leftValueExpr, rightValueExpr := getValueExpr(left), getValueExpr(right)
if leftValueExpr != nil && rightValueExpr != nil {
if isTemplateExpr(leftValueExpr) || isTemplateExpr(rightValueExpr) {
return fmt.Errorf("placeholder was not supported between two constants with operator: %s", ctx.GetOp().GetText())
}
leftValue, rightValue := getGenericValue(left), getGenericValue(right)
switch ctx.GetOp().GetTokenType() {
case parser.PlanParserMUL:
n, err := Multiply(leftValue, rightValue)
if err != nil {
return err
}
return n
case parser.PlanParserDIV:
n, err := Divide(leftValue, rightValue)
if err != nil {
return err
}
return n
case parser.PlanParserMOD:
n, err := Modulo(leftValue, rightValue)
if err != nil {
return err
}
return n
default:
return fmt.Errorf("unexpected op: %s", ctx.GetOp().GetText())
}
}
leftExpr, rightExpr := getExpr(left), getExpr(right)
reverse := true
if leftValueExpr == nil {
reverse = false
}
if leftExpr == nil || rightExpr == nil {
return fmt.Errorf("invalid arithmetic expression, left: %s, op: %s, right: %s", ctx.Expr(0).GetText(), ctx.GetOp(), ctx.Expr(1).GetText())
}
if err := checkDirectComparisonBinaryField(toColumnInfo(leftExpr)); err != nil {
return err
}
if err := checkDirectComparisonBinaryField(toColumnInfo(rightExpr)); err != nil {
return err
}
var dataType schemapb.DataType
if leftExpr.expr.GetIsTemplate() {
dataType = rightExpr.dataType
} else if rightExpr.expr.GetIsTemplate() {
dataType = leftExpr.dataType
} else {
if err := canArithmetic(leftExpr.dataType, getArrayElementType(leftExpr), rightExpr.dataType, getArrayElementType(rightExpr), reverse); err != nil {
return fmt.Errorf("'%s' %s", arithNameMap[ctx.GetOp().GetTokenType()], err.Error())
}
if err = checkValidModArith(arithExprMap[ctx.GetOp().GetTokenType()], leftExpr.dataType, getArrayElementType(leftExpr), rightExpr.dataType, getArrayElementType(rightExpr)); err != nil {
return err
}
dataType, err = calcDataType(leftExpr, rightExpr, reverse)
if err != nil {
return err
}
}
expr := &planpb.Expr{
Expr: &planpb.Expr_BinaryArithExpr{
BinaryArithExpr: &planpb.BinaryArithExpr{
Left: leftExpr.expr,
Right: rightExpr.expr,
Op: arithExprMap[ctx.GetOp().GetTokenType()],
},
},
IsTemplate: leftExpr.expr.GetIsTemplate() || rightExpr.expr.GetIsTemplate(),
}
return &ExprWithType{
expr: expr,
dataType: dataType,
nodeDependent: true,
}
}
// VisitEquality translates expr to compare/range plan.
func (v *ParserVisitor) VisitEquality(ctx *parser.EqualityContext) interface{} {
left := ctx.Expr(0).Accept(v)
if err := getError(left); err != nil {
return err
}
right := ctx.Expr(1).Accept(v)
if err := getError(right); err != nil {
return err
}
leftValueExpr, rightValueExpr := getValueExpr(left), getValueExpr(right)
if leftValueExpr != nil && rightValueExpr != nil {
if isTemplateExpr(leftValueExpr) || isTemplateExpr(rightValueExpr) {
return fmt.Errorf("placeholder was not supported between two constants with operator: %s", ctx.GetOp().GetText())
}
leftValue, rightValue := leftValueExpr.GetValue(), rightValueExpr.GetValue()
var ret *ExprWithType
switch ctx.GetOp().GetTokenType() {
case parser.PlanParserEQ:
ret = Equal(leftValue, rightValue)
case parser.PlanParserNE:
ret = NotEqual(leftValue, rightValue)
default:
return fmt.Errorf("unexpected op: %s", ctx.GetOp().GetText())
}
if ret == nil {
return fmt.Errorf("comparison operations cannot be applied to two incompatible operands: %s", ctx.GetText())
}
return ret
}
leftExpr, rightExpr := getExpr(left), getExpr(right)
expr, err := HandleCompare(ctx.GetOp().GetTokenType(), leftExpr, rightExpr)
if err != nil {
return err
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
// VisitRelational translates expr to range/compare plan.
func (v *ParserVisitor) VisitRelational(ctx *parser.RelationalContext) interface{} {
left := ctx.Expr(0).Accept(v)
if err := getError(left); err != nil {
return err
}
right := ctx.Expr(1).Accept(v)
if err := getError(right); err != nil {
return err
}
leftValueExpr, rightValueExpr := getValueExpr(left), getValueExpr(right)
if leftValueExpr != nil && rightValueExpr != nil {
if isTemplateExpr(leftValueExpr) || isTemplateExpr(rightValueExpr) {
return fmt.Errorf("placeholder was not supported between two constants with operator: %s", ctx.GetOp().GetText())
}
leftValue, rightValue := getGenericValue(left), getGenericValue(right)
var ret *ExprWithType
switch ctx.GetOp().GetTokenType() {
case parser.PlanParserLT:
ret = Less(leftValue, rightValue)
case parser.PlanParserLE:
ret = LessEqual(leftValue, rightValue)
case parser.PlanParserGT:
ret = Greater(leftValue, rightValue)
case parser.PlanParserGE:
ret = GreaterEqual(leftValue, rightValue)
default:
return fmt.Errorf("unexpected op: %s", ctx.GetOp().GetText())
}
if ret == nil {
return fmt.Errorf("comparison operations cannot be applied to two incompatible operands: %s", ctx.GetText())
}
return ret
}
leftExpr, rightExpr := getExpr(left), getExpr(right)
if err := checkDirectComparisonBinaryField(toColumnInfo(leftExpr)); err != nil {
return err
}
if err := checkDirectComparisonBinaryField(toColumnInfo(rightExpr)); err != nil {
return err
}
expr, err := HandleCompare(ctx.GetOp().GetTokenType(), leftExpr, rightExpr)
if err != nil {
return err
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
// VisitLike handles match operations.
func (v *ParserVisitor) VisitLike(ctx *parser.LikeContext) interface{} {
left := ctx.Expr().Accept(v)
if err := getError(left); err != nil {
return err
}
leftExpr := getExpr(left)
if leftExpr == nil {
return errors.New("the left operand of like is invalid")
}
column := toColumnInfo(leftExpr)
if column == nil {
return errors.New("like operation on complicated expr is unsupported")
}
if err := checkDirectComparisonBinaryField(column); err != nil {
return err
}
if !typeutil.IsStringType(leftExpr.dataType) && !typeutil.IsJSONType(leftExpr.dataType) &&
!(typeutil.IsArrayType(leftExpr.dataType) && typeutil.IsStringType(column.GetElementType())) {
return errors.New("like operation on non-string or no-json field is unsupported")
}
pattern, err := convertEscapeSingle(ctx.StringLiteral().GetText())
if err != nil {
return err
}
op, operand, err := translatePatternMatch(pattern)
if err != nil {
return err
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_UnaryRangeExpr{
UnaryRangeExpr: &planpb.UnaryRangeExpr{
ColumnInfo: column,
Op: op,
Value: NewString(operand),
},
},
},
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitTextMatch(ctx *parser.TextMatchContext) interface{} {
identifier := ctx.Identifier().GetText()
column, err := v.translateIdentifier(identifier)
if err != nil {
return err
}
columnInfo := toColumnInfo(column)
if !typeutil.IsStringType(column.dataType) {
return errors.New("text match operation on non-string is unsupported")
}
if column.dataType == schemapb.DataType_Text {
return errors.New("text match operation on text field is not supported yet")
}
if !v.schema.IsFieldTextMatchEnabled(columnInfo.FieldId) {
return fmt.Errorf("field \"%s\" does not enable match", identifier)
}
queryText, err := convertEscapeSingle(ctx.StringLiteral().GetText())
if err != nil {
return err
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_UnaryRangeExpr{
UnaryRangeExpr: &planpb.UnaryRangeExpr{
ColumnInfo: columnInfo,
Op: planpb.OpType_TextMatch,
Value: NewString(queryText),
},
},
},
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitPhraseMatch(ctx *parser.PhraseMatchContext) interface{} {
identifier := ctx.Identifier().GetText()
column, err := v.translateIdentifier(identifier)
if err != nil {
return err
}
columnInfo := toColumnInfo(column)
if !typeutil.IsStringType(column.dataType) {
return errors.New("phrase match operation on non-string is unsupported")
}
if !v.schema.IsFieldTextMatchEnabled(columnInfo.FieldId) {
return fmt.Errorf("field \"%s\" does not enable match", identifier)
}
queryText, err := convertEscapeSingle(ctx.StringLiteral().GetText())
if err != nil {
return err
}
var slop int64 = 0
if ctx.Expr() != nil {
slopExpr := ctx.Expr().Accept(v)
slopValueExpr := getValueExpr(slopExpr)
if slopValueExpr == nil || slopValueExpr.GetValue() == nil {
return fmt.Errorf("\"slop\" should be a const integer expression with \"uint32\" value. \"slop\" expression passed: %s", ctx.Expr().GetText())
}
slop = slopValueExpr.GetValue().GetInt64Val()
if slop < 0 {
return fmt.Errorf("\"slop\" should not be a negative interger. \"slop\" passed: %s", ctx.Expr().GetText())
}
if slop > math.MaxUint32 {
return fmt.Errorf("\"slop\" exceeds the range of \"uint32\". \"slop\" expression passed: %s", ctx.Expr().GetText())
}
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_UnaryRangeExpr{
UnaryRangeExpr: &planpb.UnaryRangeExpr{
ColumnInfo: toColumnInfo(column),
Op: planpb.OpType_PhraseMatch,
Value: NewString(queryText),
ExtraValues: []*planpb.GenericValue{NewInt(slop)},
},
},
},
dataType: schemapb.DataType_Bool,
}
}
func isRandomSampleExpr(expr *ExprWithType) bool {
return expr.expr.GetRandomSampleExpr() != nil
}
const EPSILON = 1e-10
func (v *ParserVisitor) VisitRandomSample(ctx *parser.RandomSampleContext) interface{} {
if ctx.Expr() == nil {
return fmt.Errorf("sample factor missed: %s", ctx.GetText())
}
floatExpr := ctx.Expr().Accept(v)
if err := getError(floatExpr); err != nil {
return fmt.Errorf("cannot parse expression: %s, error: %s", ctx.Expr().GetText(), err)
}
floatValueExpr := getValueExpr(floatExpr)
if floatValueExpr == nil || floatValueExpr.GetValue() == nil {
return fmt.Errorf("\"float factor\" should be a const float expression: \"float factor\" passed: %s", ctx.Expr().GetText())
}
sampleFactor := floatValueExpr.GetValue().GetFloatVal()
if sampleFactor <= 0+EPSILON || sampleFactor >= 1-EPSILON {
return fmt.Errorf("the sample factor should be between 0 and 1 and not too close to 0 or 1(the difference should be larger than 1e-10), but got %s", ctx.Expr().GetText())
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_RandomSampleExpr{
RandomSampleExpr: &planpb.RandomSampleExpr{
SampleFactor: float32(sampleFactor),
Predicate: nil,
},
},
},
dataType: schemapb.DataType_Bool,
}
}
// VisitTerm translates expr to term plan.
func (v *ParserVisitor) VisitTerm(ctx *parser.TermContext) interface{} {
child := ctx.Expr(0).Accept(v)
if err := getError(child); err != nil {
return err
}
if childValue := getGenericValue(child); childValue != nil {
return fmt.Errorf("'term' can only be used on non-const expression, but got: %s", ctx.Expr(0).GetText())
}
childExpr := getExpr(child)
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil {
return fmt.Errorf("'term' can only be used on single field, but got: %s", ctx.Expr(0).GetText())
}
dataType := columnInfo.GetDataType()
if typeutil.IsArrayType(dataType) && len(columnInfo.GetNestedPath()) != 0 {
dataType = columnInfo.GetElementType()
}
term := ctx.Expr(1).Accept(v)
if getError(term) != nil {
return term
}
valueExpr := getValueExpr(term)
var placeholder string
var isTemplate bool
var values []*planpb.GenericValue
if valueExpr.GetValue() == nil && valueExpr.GetTemplateVariableName() != "" {
placeholder = valueExpr.GetTemplateVariableName()
values = nil
isTemplate = true
} else {
elementValue := valueExpr.GetValue()
if elementValue == nil {
return fmt.Errorf("value '%s' in list cannot be a non-const expression", ctx.Expr(1).GetText())
}
if !IsArray(elementValue) {
return fmt.Errorf("the right-hand side of 'in' must be a list, but got: %s", ctx.Expr(1).GetText())
}
array := elementValue.GetArrayVal().GetArray()
values = make([]*planpb.GenericValue, len(array))
for i, e := range array {
castedValue, err := castValue(dataType, e)
if err != nil {
return fmt.Errorf("value '%s' in list cannot be casted to %s", e.String(), dataType.String())
}
values[i] = castedValue
}
}
expr := &planpb.Expr{
Expr: &planpb.Expr_TermExpr{
TermExpr: &planpb.TermExpr{
ColumnInfo: columnInfo,
Values: values,
TemplateVariableName: placeholder,
},
},
IsTemplate: isTemplate,
}
if ctx.GetOp() != nil {
expr = &planpb.Expr{
Expr: &planpb.Expr_UnaryExpr{
UnaryExpr: &planpb.UnaryExpr{
Op: planpb.UnaryExpr_Not,
Child: expr,
},
},
IsTemplate: isTemplate,
}
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) getChildColumnInfo(identifier, child antlr.TerminalNode) (*planpb.ColumnInfo, error) {
if identifier != nil {
childExpr, err := v.translateIdentifier(identifier.GetText())
if err != nil {
return nil, err
}
return toColumnInfo(childExpr), nil
}
return v.getColumnInfoFromJSONIdentifier(child.GetText())
}
// VisitCall parses the expr to call plan.
func (v *ParserVisitor) VisitCall(ctx *parser.CallContext) interface{} {
functionName := strings.ToLower(ctx.Identifier().GetText())
numParams := len(ctx.AllExpr())
funcParameters := make([]*planpb.Expr, 0, numParams)
for _, param := range ctx.AllExpr() {
paramExpr := param.Accept(v)
if err := getError(paramExpr); err != nil {
return err
}
funcParameters = append(funcParameters, getExpr(param.Accept(v)).expr)
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_CallExpr{
CallExpr: &planpb.CallExpr{
FunctionName: functionName,
FunctionParameters: funcParameters,
},
},
},
dataType: schemapb.DataType_Bool,
}
}
// VisitRange translates expr to range plan.
func (v *ParserVisitor) VisitRange(ctx *parser.RangeContext) interface{} {
columnInfo, err := v.getChildColumnInfo(ctx.Identifier(), ctx.JSONIdentifier())
if err != nil {
return err
}
if columnInfo == nil {
return fmt.Errorf("range operations are only supported on single fields now, got: %s", ctx.Expr(1).GetText())
}
if err := checkDirectComparisonBinaryField(columnInfo); err != nil {
return err
}
lower := ctx.Expr(0).Accept(v)
upper := ctx.Expr(1).Accept(v)
if err := getError(lower); err != nil {
return err
}
if err := getError(upper); err != nil {
return err
}
lowerValueExpr, upperValueExpr := getValueExpr(lower), getValueExpr(upper)
if lowerValueExpr == nil {
return fmt.Errorf("lowerbound cannot be a non-const expression: %s", ctx.Expr(0).GetText())
}
if upperValueExpr == nil {
return fmt.Errorf("upperbound cannot be a non-const expression: %s", ctx.Expr(1).GetText())
}
fieldDataType := columnInfo.GetDataType()
if typeutil.IsArrayType(columnInfo.GetDataType()) {
fieldDataType = columnInfo.GetElementType()
}
lowerValue := lowerValueExpr.GetValue()
upperValue := upperValueExpr.GetValue()
if !isTemplateExpr(lowerValueExpr) {
if lowerValue, err = castRangeValue(fieldDataType, lowerValue); err != nil {
return err
}
}
if !isTemplateExpr(upperValueExpr) {
if upperValue, err = castRangeValue(fieldDataType, upperValue); err != nil {
return err
}
}
lowerInclusive := ctx.GetOp1().GetTokenType() == parser.PlanParserLE
upperInclusive := ctx.GetOp2().GetTokenType() == parser.PlanParserLE
if !isTemplateExpr(lowerValueExpr) && !isTemplateExpr(upperValueExpr) {
if !(lowerInclusive && upperInclusive) {
if getGenericValue(GreaterEqual(lowerValue, upperValue)).GetBoolVal() {
return errors.New("invalid range: lowerbound is greater than upperbound")
}
} else {
if getGenericValue(Greater(lowerValue, upperValue)).GetBoolVal() {
return errors.New("invalid range: lowerbound is greater than upperbound")
}
}
}
expr := &planpb.Expr{
Expr: &planpb.Expr_BinaryRangeExpr{
BinaryRangeExpr: &planpb.BinaryRangeExpr{
ColumnInfo: columnInfo,
LowerInclusive: lowerInclusive,
UpperInclusive: upperInclusive,
LowerValue: lowerValue,
UpperValue: upperValue,
LowerTemplateVariableName: lowerValueExpr.GetTemplateVariableName(),
UpperTemplateVariableName: upperValueExpr.GetTemplateVariableName(),
},
},
IsTemplate: isTemplateExpr(lowerValueExpr) || isTemplateExpr(upperValueExpr),
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
// VisitReverseRange parses the expression like "1 > a > 0".
func (v *ParserVisitor) VisitReverseRange(ctx *parser.ReverseRangeContext) interface{} {
columnInfo, err := v.getChildColumnInfo(ctx.Identifier(), ctx.JSONIdentifier())
if err != nil {
return err
}
if columnInfo == nil {
return fmt.Errorf("range operations are only supported on single fields now, got: %s", ctx.Expr(1).GetText())
}
if err := checkDirectComparisonBinaryField(columnInfo); err != nil {
return err
}
lower := ctx.Expr(1).Accept(v)
upper := ctx.Expr(0).Accept(v)
if err := getError(lower); err != nil {
return err
}
if err := getError(upper); err != nil {
return err
}
lowerValueExpr, upperValueExpr := getValueExpr(lower), getValueExpr(upper)
if lowerValueExpr == nil {
return fmt.Errorf("lowerbound cannot be a non-const expression: %s", ctx.Expr(0).GetText())
}
if upperValueExpr == nil {
return fmt.Errorf("upperbound cannot be a non-const expression: %s", ctx.Expr(1).GetText())
}
fieldDataType := columnInfo.GetDataType()
if typeutil.IsArrayType(columnInfo.GetDataType()) {
fieldDataType = columnInfo.GetElementType()
}
lowerValue := lowerValueExpr.GetValue()
upperValue := upperValueExpr.GetValue()
if !isTemplateExpr(lowerValueExpr) {
if lowerValue, err = castRangeValue(fieldDataType, lowerValue); err != nil {
return err
}
}
if !isTemplateExpr(upperValueExpr) {
if upperValue, err = castRangeValue(fieldDataType, upperValue); err != nil {
return err
}
}
lowerInclusive := ctx.GetOp2().GetTokenType() == parser.PlanParserGE
upperInclusive := ctx.GetOp1().GetTokenType() == parser.PlanParserGE
if !isTemplateExpr(lowerValueExpr) && !isTemplateExpr(upperValueExpr) {
if !(lowerInclusive && upperInclusive) {
if getGenericValue(GreaterEqual(lowerValue, upperValue)).GetBoolVal() {
return errors.New("invalid range: lowerbound is greater than upperbound")
}
} else {
if getGenericValue(Greater(lowerValue, upperValue)).GetBoolVal() {
return errors.New("invalid range: lowerbound is greater than upperbound")
}
}
}
expr := &planpb.Expr{
Expr: &planpb.Expr_BinaryRangeExpr{
BinaryRangeExpr: &planpb.BinaryRangeExpr{
ColumnInfo: columnInfo,
LowerInclusive: lowerInclusive,
UpperInclusive: upperInclusive,
LowerValue: lowerValue,
UpperValue: upperValue,
LowerTemplateVariableName: lowerValueExpr.GetTemplateVariableName(),
UpperTemplateVariableName: upperValueExpr.GetTemplateVariableName(),
},
},
IsTemplate: isTemplateExpr(lowerValueExpr) || isTemplateExpr(upperValueExpr),
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
// VisitUnary unpack the +expr to expr.
func (v *ParserVisitor) VisitUnary(ctx *parser.UnaryContext) interface{} {
child := ctx.Expr().Accept(v)
if err := getError(child); err != nil {
return err
}
childValue := getGenericValue(child)
if childValue != nil {
switch ctx.GetOp().GetTokenType() {
case parser.PlanParserADD:
return child
case parser.PlanParserSUB:
return Negative(childValue)
case parser.PlanParserNOT:
n, err := Not(childValue)
if err != nil {
return err
}
return n
default:
return fmt.Errorf("unexpected op: %s", ctx.GetOp().GetText())
}
}
childExpr := getExpr(child)
if childExpr == nil {
return errors.New("failed to parse unary expressions")
}
if isRandomSampleExpr(childExpr) {
return errors.New("random sample expression cannot be used in unary expression")
}
if err := checkDirectComparisonBinaryField(toColumnInfo(childExpr)); err != nil {
return err
}
switch ctx.GetOp().GetTokenType() {
case parser.PlanParserADD:
return childExpr
case parser.PlanParserNOT:
if !canBeExecuted(childExpr) {
return fmt.Errorf("%s op can only be applied on boolean expression", unaryLogicalNameMap[parser.PlanParserNOT])
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_UnaryExpr{
UnaryExpr: &planpb.UnaryExpr{
Op: unaryLogicalOpMap[parser.PlanParserNOT],
Child: childExpr.expr,
},
},
},
dataType: schemapb.DataType_Bool,
}
default:
return fmt.Errorf("unexpected op: %s", ctx.GetOp().GetText())
}
}
// VisitLogicalOr apply logical or to two boolean expressions.
func (v *ParserVisitor) VisitLogicalOr(ctx *parser.LogicalOrContext) interface{} {
left := ctx.Expr(0).Accept(v)
if err := getError(left); err != nil {
return err
}
right := ctx.Expr(1).Accept(v)
if err := getError(right); err != nil {
return err
}
leftValue, rightValue := getGenericValue(left), getGenericValue(right)
if leftValue != nil && rightValue != nil {
n, err := Or(leftValue, rightValue)
if err != nil {
return err
}
return n
}
if leftValue != nil || rightValue != nil {
return errors.New("'or' can only be used between boolean expressions")
}
var leftExpr *ExprWithType
var rightExpr *ExprWithType
leftExpr = getExpr(left)
rightExpr = getExpr(right)
if isRandomSampleExpr(leftExpr) || isRandomSampleExpr(rightExpr) {
return errors.New("random sample expression cannot be used in logical and expression")
}
if !canBeExecuted(leftExpr) || !canBeExecuted(rightExpr) {
return errors.New("'or' can only be used between boolean expressions")
}
expr := &planpb.Expr{
Expr: &planpb.Expr_BinaryExpr{
BinaryExpr: &planpb.BinaryExpr{
Left: leftExpr.expr,
Right: rightExpr.expr,
Op: planpb.BinaryExpr_LogicalOr,
},
},
IsTemplate: leftExpr.expr.GetIsTemplate() || rightExpr.expr.GetIsTemplate(),
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
// VisitLogicalAnd apply logical and to two boolean expressions.
func (v *ParserVisitor) VisitLogicalAnd(ctx *parser.LogicalAndContext) interface{} {
left := ctx.Expr(0).Accept(v)
if err := getError(left); err != nil {
return err
}
right := ctx.Expr(1).Accept(v)
if err := getError(right); err != nil {
return err
}
leftValue, rightValue := getGenericValue(left), getGenericValue(right)
if leftValue != nil && rightValue != nil {
n, err := And(leftValue, rightValue)
if err != nil {
return err
}
return n
}
if leftValue != nil || rightValue != nil {
return errors.New("'and' can only be used between boolean expressions")
}
var leftExpr *ExprWithType
var rightExpr *ExprWithType
leftExpr = getExpr(left)
rightExpr = getExpr(right)
if isRandomSampleExpr(leftExpr) {
return errors.New("random sample expression can only be the last expression in the logical and expression")
}
if !canBeExecuted(leftExpr) || !canBeExecuted(rightExpr) {
return errors.New("'and' can only be used between boolean expressions")
}
var expr *planpb.Expr
if isRandomSampleExpr(rightExpr) {
randomSampleExpr := rightExpr.expr.GetRandomSampleExpr()
randomSampleExpr.Predicate = leftExpr.expr
expr = &planpb.Expr{
Expr: &planpb.Expr_RandomSampleExpr{
RandomSampleExpr: randomSampleExpr,
},
}
} else {
expr = &planpb.Expr{
Expr: &planpb.Expr_BinaryExpr{
BinaryExpr: &planpb.BinaryExpr{
Left: leftExpr.expr,
Right: rightExpr.expr,
Op: planpb.BinaryExpr_LogicalAnd,
},
},
IsTemplate: leftExpr.expr.GetIsTemplate() || rightExpr.expr.GetIsTemplate(),
}
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
// VisitBitXor not supported.
func (v *ParserVisitor) VisitBitXor(ctx *parser.BitXorContext) interface{} {
return fmt.Errorf("BitXor is not supported: %s", ctx.GetText())
}
// VisitBitAnd not supported.
func (v *ParserVisitor) VisitBitAnd(ctx *parser.BitAndContext) interface{} {
return fmt.Errorf("BitAnd is not supported: %s", ctx.GetText())
}
// VisitPower parses power expression.
func (v *ParserVisitor) VisitPower(ctx *parser.PowerContext) interface{} {
left := ctx.Expr(0).Accept(v)
if err := getError(left); err != nil {
return err
}
right := ctx.Expr(1).Accept(v)
if err := getError(right); err != nil {
return err
}
leftValue, rightValue := getGenericValue(left), getGenericValue(right)
if leftValue != nil && rightValue != nil {
return Power(leftValue, rightValue)
}
return fmt.Errorf("power can only apply on constants: %s", ctx.GetText())
}
// VisitShift unsupported.
func (v *ParserVisitor) VisitShift(ctx *parser.ShiftContext) interface{} {
return fmt.Errorf("shift is not supported: %s", ctx.GetText())
}
// VisitBitOr unsupported.
func (v *ParserVisitor) VisitBitOr(ctx *parser.BitOrContext) interface{} {
return fmt.Errorf("BitOr is not supported: %s", ctx.GetText())
}
// getColumnInfoFromJSONIdentifier parse JSON field name and JSON nested path.
// input: user["name"]["first"],
// output: if user is JSON field name, and fieldID is 102
/*
&planpb.ColumnInfo{
FieldId: 102,
DataType: JSON,
NestedPath: []string{"name", "first"},
}, nil
*/
// if user is not JSON field name, and $SYS_META fieldID is 102:
/*
&planpb.ColumnInfo{
FieldId: 102,
DataType: JSON,
NestedPath: []string{"user", "name", "first"},
}, nil
*/
// input: user,
// output: if user is JSON field name, return error.
// if user is not JSON field name, and $SYS_META fieldID is 102:
/*
&planpb.ColumnInfo{
FieldId: 102,
DataType: JSON,
NestedPath: []string{"user"},
}, nil
*/
// More tests refer to plan_parser_v2_test.go::Test_JSONExpr
func (v *ParserVisitor) getColumnInfoFromJSONIdentifier(identifier string) (*planpb.ColumnInfo, error) {
identifier = decodeUnicode(identifier)
fieldName := strings.Split(identifier, "[")[0]
nestedPath := make([]string, 0)
field, err := v.schema.GetFieldFromNameDefaultJSON(fieldName)
if err != nil {
return nil, err
}
if field.GetDataType() != schemapb.DataType_JSON &&
field.GetDataType() != schemapb.DataType_Array {
errMsg := fmt.Sprintf("%s data type not supported accessed with []", field.GetDataType())
return nil, fmt.Errorf("%s", errMsg)
}
if fieldName != field.Name {
nestedPath = append(nestedPath, fieldName)
}
jsonKeyStr := identifier[len(fieldName):]
ss := strings.Split(jsonKeyStr, "][")
for i := 0; i < len(ss); i++ {
path := strings.Trim(ss[i], "[]")
if path == "" {
return nil, fmt.Errorf("invalid identifier: %s", identifier)
}
if (strings.HasPrefix(path, "\"") && strings.HasSuffix(path, "\"")) ||
(strings.HasPrefix(path, "'") && strings.HasSuffix(path, "'")) {
path = path[1 : len(path)-1]
if path == "" {
return nil, fmt.Errorf("invalid identifier: %s", identifier)
}
if typeutil.IsArrayType(field.DataType) {
return nil, errors.New("can only access array field with integer index")
}
} else if _, err := strconv.ParseInt(path, 10, 64); err != nil {
return nil, fmt.Errorf("json key must be enclosed in double quotes or single quotes: \"%s\"", path)
}
nestedPath = append(nestedPath, path)
}
return &planpb.ColumnInfo{
FieldId: field.FieldID,
DataType: field.DataType,
NestedPath: nestedPath,
ElementType: field.GetElementType(),
}, nil
}
func (v *ParserVisitor) VisitJSONIdentifier(ctx *parser.JSONIdentifierContext) interface{} {
field, err := v.getColumnInfoFromJSONIdentifier(ctx.JSONIdentifier().GetText())
if err != nil {
return err
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_ColumnExpr{
ColumnExpr: &planpb.ColumnExpr{
Info: &planpb.ColumnInfo{
FieldId: field.GetFieldId(),
DataType: field.GetDataType(),
NestedPath: field.GetNestedPath(),
ElementType: field.GetElementType(),
},
},
},
},
dataType: field.GetDataType(),
nodeDependent: true,
}
}
func (v *ParserVisitor) VisitExists(ctx *parser.ExistsContext) interface{} {
child := ctx.Expr().Accept(v)
if err := getError(child); err != nil {
return err
}
columnInfo := toColumnInfo(child.(*ExprWithType))
if columnInfo == nil {
return fmt.Errorf(
"exists operations are only supported on single fields now, got: %s", ctx.Expr().GetText())
}
if columnInfo.GetDataType() != schemapb.DataType_JSON {
return fmt.Errorf(
"exists operations are only supportted on json field, got:%s", columnInfo.GetDataType())
}
if len(columnInfo.GetNestedPath()) == 0 {
return fmt.Errorf(
"exists operations are only supportted on json key")
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_ExistsExpr{
ExistsExpr: &planpb.ExistsExpr{
Info: &planpb.ColumnInfo{
FieldId: columnInfo.GetFieldId(),
DataType: columnInfo.GetDataType(),
NestedPath: columnInfo.GetNestedPath(),
},
},
},
},
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitArray(ctx *parser.ArrayContext) interface{} {
allExpr := ctx.AllExpr()
array := make([]*planpb.GenericValue, len(allExpr))
dType := schemapb.DataType_None
sameType := true
for i := 0; i < len(allExpr); i++ {
element := allExpr[i].Accept(v)
if err := getError(element); err != nil {
return err
}
elementValue := getGenericValue(element)
if elementValue == nil {
return fmt.Errorf("array element type must be generic value, but got: %s", allExpr[i].GetText())
}
array[i] = elementValue
if dType == schemapb.DataType_None {
dType = element.(*ExprWithType).dataType
} else if dType != element.(*ExprWithType).dataType {
sameType = false
}
}
if !sameType {
dType = schemapb.DataType_None
}
return &ExprWithType{
dataType: schemapb.DataType_Array,
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: &planpb.GenericValue{
Val: &planpb.GenericValue_ArrayVal{
ArrayVal: &planpb.Array{
Array: array,
SameType: sameType,
ElementType: dType,
},
},
},
},
},
},
nodeDependent: true,
}
}
func (v *ParserVisitor) VisitEmptyArray(ctx *parser.EmptyArrayContext) interface{} {
return &ExprWithType{
dataType: schemapb.DataType_Array,
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: &planpb.GenericValue{
Val: &planpb.GenericValue_ArrayVal{
ArrayVal: &planpb.Array{
Array: nil,
SameType: true,
ElementType: schemapb.DataType_None,
},
},
},
},
},
},
nodeDependent: true,
}
}
func (v *ParserVisitor) VisitIsNotNull(ctx *parser.IsNotNullContext) interface{} {
column, err := v.getChildColumnInfo(ctx.Identifier(), ctx.JSONIdentifier())
if err != nil {
return err
}
if len(column.NestedPath) != 0 {
// convert json not null expr to exists expr, eg: json['a'] is not null -> exists json['a']
expr := &planpb.Expr{
Expr: &planpb.Expr_ExistsExpr{
ExistsExpr: &planpb.ExistsExpr{
Info: &planpb.ColumnInfo{
FieldId: column.FieldId,
DataType: column.DataType,
NestedPath: column.NestedPath,
},
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
expr := &planpb.Expr{
Expr: &planpb.Expr_NullExpr{
NullExpr: &planpb.NullExpr{
ColumnInfo: column,
Op: planpb.NullExpr_IsNotNull,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitIsNull(ctx *parser.IsNullContext) interface{} {
column, err := v.getChildColumnInfo(ctx.Identifier(), ctx.JSONIdentifier())
if err != nil {
return err
}
if len(column.NestedPath) != 0 {
// convert json is null expr to not exists expr, eg: json['a'] is null -> not exists json['a']
expr := &planpb.Expr{
Expr: &planpb.Expr_ExistsExpr{
ExistsExpr: &planpb.ExistsExpr{
Info: &planpb.ColumnInfo{
FieldId: column.FieldId,
DataType: column.DataType,
NestedPath: column.NestedPath,
},
},
},
}
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_UnaryExpr{
UnaryExpr: &planpb.UnaryExpr{
Op: unaryLogicalOpMap[parser.PlanParserNOT],
Child: expr,
},
},
},
dataType: schemapb.DataType_Bool,
}
}
expr := &planpb.Expr{
Expr: &planpb.Expr_NullExpr{
NullExpr: &planpb.NullExpr{
ColumnInfo: column,
Op: planpb.NullExpr_IsNull,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitJSONContains(ctx *parser.JSONContainsContext) interface{} {
field := ctx.Expr(0).Accept(v)
if err := getError(field); err != nil {
return err
}
columnInfo := toColumnInfo(field.(*ExprWithType))
if columnInfo == nil ||
(!typeutil.IsJSONType(columnInfo.GetDataType()) && !typeutil.IsArrayType(columnInfo.GetDataType())) {
return fmt.Errorf(
"contains operation are only supported on json or array fields now, got: %s", ctx.Expr(0).GetText())
}
element := ctx.Expr(1).Accept(v)
if err := getError(element); err != nil {
return err
}
elementExpr := getValueExpr(element)
if elementExpr == nil {
return fmt.Errorf(
"contains operation are only supported explicitly specified element, got: %s", ctx.Expr(1).GetText())
}
var elements []*planpb.GenericValue
if !isTemplateExpr(elementExpr) {
elements = make([]*planpb.GenericValue, 1)
elementValue := elementExpr.GetValue()
if err := checkContainsElement(field.(*ExprWithType), planpb.JSONContainsExpr_Contains, elementValue); err != nil {
return err
}
elements[0] = elementValue
}
expr := &planpb.Expr{
Expr: &planpb.Expr_JsonContainsExpr{
JsonContainsExpr: &planpb.JSONContainsExpr{
ColumnInfo: columnInfo,
Elements: elements,
Op: planpb.JSONContainsExpr_Contains,
ElementsSameType: true,
TemplateVariableName: elementExpr.GetTemplateVariableName(),
},
},
IsTemplate: isTemplateExpr(elementExpr),
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitJSONContainsAll(ctx *parser.JSONContainsAllContext) interface{} {
field := ctx.Expr(0).Accept(v)
if err := getError(field); err != nil {
return err
}
columnInfo := toColumnInfo(field.(*ExprWithType))
if columnInfo == nil ||
(!typeutil.IsJSONType(columnInfo.GetDataType()) && !typeutil.IsArrayType(columnInfo.GetDataType())) {
return fmt.Errorf(
"contains_all operation are only supported on json or array fields now, got: %s", ctx.Expr(0).GetText())
}
element := ctx.Expr(1).Accept(v)
if err := getError(element); err != nil {
return err
}
elementExpr := getValueExpr(element)
if elementExpr == nil {
return fmt.Errorf(
"contains_all operation are only supported explicitly specified element, got: %s", ctx.Expr(1).GetText())
}
var elements []*planpb.GenericValue
var sameType bool
if !isTemplateExpr(elementExpr) {
elementValue := elementExpr.GetValue()
if err := checkContainsElement(field.(*ExprWithType), planpb.JSONContainsExpr_ContainsAll, elementValue); err != nil {
return err
}
elements = elementValue.GetArrayVal().GetArray()
sameType = elementValue.GetArrayVal().GetSameType()
}
expr := &planpb.Expr{
Expr: &planpb.Expr_JsonContainsExpr{
JsonContainsExpr: &planpb.JSONContainsExpr{
ColumnInfo: columnInfo,
Elements: elements,
Op: planpb.JSONContainsExpr_ContainsAll,
ElementsSameType: sameType,
TemplateVariableName: elementExpr.GetTemplateVariableName(),
},
},
IsTemplate: isTemplateExpr(elementExpr),
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitJSONContainsAny(ctx *parser.JSONContainsAnyContext) interface{} {
field := ctx.Expr(0).Accept(v)
if err := getError(field); err != nil {
return err
}
columnInfo := toColumnInfo(field.(*ExprWithType))
if columnInfo == nil ||
(!typeutil.IsJSONType(columnInfo.GetDataType()) && !typeutil.IsArrayType(columnInfo.GetDataType())) {
return fmt.Errorf(
"contains_any operation are only supported on json or array fields now, got: %s", ctx.Expr(0).GetText())
}
element := ctx.Expr(1).Accept(v)
if err := getError(element); err != nil {
return err
}
valueExpr := getValueExpr(element)
if valueExpr == nil {
return fmt.Errorf(
"contains_any operation are only supported explicitly specified element, got: %s", ctx.Expr(1).GetText())
}
var elements []*planpb.GenericValue
var sameType bool
if !isTemplateExpr(valueExpr) {
elementValue := valueExpr.GetValue()
if err := checkContainsElement(field.(*ExprWithType), planpb.JSONContainsExpr_ContainsAny, elementValue); err != nil {
return err
}
elements = elementValue.GetArrayVal().GetArray()
sameType = elementValue.GetArrayVal().GetSameType()
}
expr := &planpb.Expr{
Expr: &planpb.Expr_JsonContainsExpr{
JsonContainsExpr: &planpb.JSONContainsExpr{
ColumnInfo: columnInfo,
Elements: elements,
Op: planpb.JSONContainsExpr_ContainsAny,
ElementsSameType: sameType,
TemplateVariableName: valueExpr.GetTemplateVariableName(),
},
},
IsTemplate: isTemplateExpr(valueExpr),
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitArrayLength(ctx *parser.ArrayLengthContext) interface{} {
columnInfo, err := v.getChildColumnInfo(ctx.Identifier(), ctx.JSONIdentifier())
if err != nil {
return err
}
if columnInfo == nil ||
(!typeutil.IsJSONType(columnInfo.GetDataType()) && !typeutil.IsArrayType(columnInfo.GetDataType())) {
return fmt.Errorf(
"array_length operation are only supported on json or array fields now, got: %s", ctx.GetText())
}
expr := &planpb.Expr{
Expr: &planpb.Expr_BinaryArithExpr{
BinaryArithExpr: &planpb.BinaryArithExpr{
Left: &planpb.Expr{
Expr: &planpb.Expr_ColumnExpr{
ColumnExpr: &planpb.ColumnExpr{
Info: columnInfo,
},
},
},
Right: nil,
Op: planpb.ArithOpType_ArrayLength,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Int64,
nodeDependent: true,
}
}
func (v *ParserVisitor) VisitTemplateVariable(ctx *parser.TemplateVariableContext) interface{} {
return &ExprWithType{
expr: &planpb.Expr{
Expr: &planpb.Expr_ValueExpr{
ValueExpr: &planpb.ValueExpr{
Value: nil,
TemplateVariableName: ctx.Identifier().GetText(),
},
},
IsTemplate: true,
},
}
}
func (v *ParserVisitor) VisitSTEuqals(ctx *parser.STEuqalsContext) interface{} {
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"STEuqals operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err := checkValidWKT(wktString); err != nil {
return err
}
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString,
Op: planpb.GISFunctionFilterExpr_Equals,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitSTTouches(ctx *parser.STTouchesContext) interface{} {
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"STTouches operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err := checkValidWKT(wktString); err != nil {
return err
}
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString,
Op: planpb.GISFunctionFilterExpr_Touches,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitSTOverlaps(ctx *parser.STOverlapsContext) interface{} {
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"STOverlaps operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err := checkValidWKT(wktString); err != nil {
return err
}
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString,
Op: planpb.GISFunctionFilterExpr_Overlaps,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitSTCrosses(ctx *parser.STCrossesContext) interface{} {
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"STCrosses operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err := checkValidWKT(wktString); err != nil {
return err
}
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString,
Op: planpb.GISFunctionFilterExpr_Crosses,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitSTContains(ctx *parser.STContainsContext) interface{} {
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"STContains operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err := checkValidWKT(wktString); err != nil {
return err
}
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString,
Op: planpb.GISFunctionFilterExpr_Contains,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitSTIntersects(ctx *parser.STIntersectsContext) interface{} {
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"STIntersects operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err := checkValidWKT(wktString); err != nil {
return err
}
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString,
Op: planpb.GISFunctionFilterExpr_Intersects,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitSTWithin(ctx *parser.STWithinContext) interface{} {
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"STWithin operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err := checkValidWKT(wktString); err != nil {
return err
}
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString,
Op: planpb.GISFunctionFilterExpr_Within,
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitSTDWithin(ctx *parser.STDWithinContext) interface{} {
// Process the geometry field identifier
childExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
if err != nil {
return err
}
columnInfo := toColumnInfo(childExpr)
if columnInfo == nil ||
(!typeutil.IsGeometryType(columnInfo.GetDataType())) {
return fmt.Errorf(
"ST_DWITHIN operation are only supported on geometry fields now, got: %s", ctx.GetText())
}
// Process the WKT string
element := ctx.StringLiteral().GetText()
wktString := element[1 : len(element)-1] // Remove surrounding quotes
if err = checkValidPoint(wktString); err != nil {
return err
}
// Process the distance expression (can be int or float)
distanceExpr := ctx.Expr().Accept(v)
if err := getError(distanceExpr); err != nil {
return err
}
// Extract distance value - must be a constant expression
distanceValueExpr := getValueExpr(distanceExpr)
if distanceValueExpr == nil {
return fmt.Errorf("distance parameter must be a constant numeric value, got: %s", ctx.Expr().GetText())
}
var distance float64
genericValue := distanceValueExpr.GetValue()
if genericValue == nil {
return fmt.Errorf("invalid distance value: %s", ctx.Expr().GetText())
}
// Handle both integer and floating point values using type assertion
switch val := genericValue.GetVal().(type) {
case *planpb.GenericValue_Int64Val:
distance = float64(val.Int64Val)
case *planpb.GenericValue_FloatVal:
distance = val.FloatVal
default:
return fmt.Errorf("distance parameter must be a numeric value (int or float), got: %s", ctx.Expr().GetText())
}
if distance < 0 {
return fmt.Errorf("distance parameter must be non-negative, got: %f", distance)
}
// Create the GIS function expression using the bounding box
expr := &planpb.Expr{
Expr: &planpb.Expr_GisfunctionFilterExpr{
GisfunctionFilterExpr: &planpb.GISFunctionFilterExpr{
ColumnInfo: columnInfo,
WktString: wktString, // Use bounding box instead of original point
Op: planpb.GISFunctionFilterExpr_DWithin,
Distance: distance, // Keep distance for reference
},
},
}
return &ExprWithType{
expr: expr,
dataType: schemapb.DataType_Bool,
}
}
func (v *ParserVisitor) VisitTimestamptzCompare(ctx *parser.TimestamptzCompareContext) interface{} {
colExpr, err := v.translateIdentifier(ctx.Identifier().GetText())
identifier := ctx.Identifier().Accept(v)
if err != nil {
return fmt.Errorf("can not translate identifier: %s", identifier)
}
if colExpr.dataType != schemapb.DataType_Timestamptz {
return fmt.Errorf("field '%s' is not a timestamptz datatype", identifier)
}
arithOp := planpb.ArithOpType_Unknown
interval := &planpb.Interval{}
if ctx.GetOp1() != nil {
arithOp = arithExprMap[ctx.GetOp1().GetTokenType()]
rawIntervalStr := ctx.GetInterval_string().GetText()
unquotedIntervalStr, err := convertEscapeSingle(rawIntervalStr)
if err != nil {
return fmt.Errorf("can not convert interval string: %s", rawIntervalStr)
}
interval, err = parseISODuration(unquotedIntervalStr)
if err != nil {
return err
}
}
rawCompareStr := ctx.GetCompare_string().GetText()
unquotedCompareStr, err := convertEscapeSingle(rawCompareStr)
if err != nil {
return fmt.Errorf("can not convert compare string: %s", rawCompareStr)
}
compareOp := cmpOpMap[ctx.GetOp2().GetTokenType()]
timestamptzInt64, err := parseISOWithTimezone(unquotedCompareStr, v.args.TimezonePreference)
if err != nil {
return err
}
newExpr := &planpb.Expr{
Expr: &planpb.Expr_TimestamptzArithCompareExpr{
TimestamptzArithCompareExpr: &planpb.TimestamptzArithCompareExpr{
TimestamptzColumn: toColumnInfo(colExpr),
ArithOp: arithOp,
Interval: interval,
CompareOp: compareOp,
CompareValue: &planpb.GenericValue{
Val: &planpb.GenericValue_Int64Val{Int64Val: timestamptzInt64},
},
},
},
}
return &ExprWithType{
expr: newExpr,
dataType: schemapb.DataType_Bool,
}
}