query/context/query_context_helper.go (645 lines of code) (raw):
// Copyright (c) 2017-2018 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package context
import (
memCom "github.com/uber/aresdb/memstore/common"
"github.com/uber/aresdb/query/common"
"github.com/uber/aresdb/query/expr"
"github.com/uber/aresdb/utils"
"strconv"
"strings"
)
// QueryContextHelper is a helper Class to group common code
// for broker query compiler and datanode query compiler
type QueryContextHelper struct {
QCOptions QueryContextOptions
}
// NormalizeAndFilters AND filter
func (qc *QueryContextHelper) NormalizeAndFilters(filters []expr.Expr) []expr.Expr {
i := 0
for i < len(filters) {
f, _ := filters[i].(*expr.BinaryExpr)
if f != nil && f.Op == expr.AND {
filters[i] = f.LHS
filters = append(filters, f.RHS)
} else {
i++
}
}
return filters
}
// resolveColumn resolves the VarRef identifier against the schema,
// and returns the matched tableID (query scoped) and columnID (schema scoped).
func (qc *QueryContextHelper) ResolveColumn(identifier string) (int, int, error) {
tableAlias := qc.QCOptions.GetQuery().Table
column := identifier
segments := strings.SplitN(identifier, ".", 2)
if len(segments) == 2 {
tableAlias = segments[0]
column = segments[1]
}
tableID, exists := qc.QCOptions.GetTableID(tableAlias)
if !exists {
return 0, 0, utils.StackError(nil, "unknown table alias %s", tableAlias)
}
columnID, exists := qc.QCOptions.GetSchema(tableID).ColumnIDs[column]
if !exists {
return 0, 0, utils.StackError(nil, "unknown column %s for table alias %s",
column, tableAlias)
}
return tableID, columnID, nil
}
func blockNumericOpsForColumnOverFourBytes(token expr.Token, expressions ...expr.Expr) error {
if token == expr.UNARY_MINUS || token == expr.BITWISE_NOT ||
(token >= expr.ADD && token <= expr.BITWISE_LEFT_SHIFT) {
for _, expression := range expressions {
if varRef, isVarRef := expression.(*expr.VarRef); isVarRef && memCom.DataTypeBytes(varRef.DataType) > 4 {
return utils.StackError(nil, "numeric operations not supported for column over 4 bytes length, got %s", expression.String())
}
}
}
return nil
}
func blockInt64(expressions ...expr.Expr) error {
for _, expression := range expressions {
if varRef, isVarRef := expression.(*expr.VarRef); isVarRef && memCom.Int64 == varRef.DataType {
return utils.StackError(nil, "binary transformation not allowed for int64 fields, got %s", expression.String())
}
}
return nil
}
func (qc *QueryContextHelper) expandINop(e *expr.BinaryExpr) (expandedExpr expr.Expr) {
lhs, ok := e.LHS.(*expr.VarRef)
if !ok {
qc.QCOptions.SetError(utils.StackError(nil, "lhs of IN or NOT_IN must be a valid column"))
}
rhs := e.RHS
switch rhsTyped := rhs.(type) {
case *expr.Call:
expandedExpr = &expr.BooleanLiteral{Val: false}
for _, value := range rhsTyped.Args {
switch expandedExpr.(type) {
case *expr.BooleanLiteral:
expandedExpr = qc.Rewrite(&expr.BinaryExpr{
Op: expr.EQ,
LHS: lhs,
RHS: value,
}).(*expr.BinaryExpr)
default:
lastExpr := expandedExpr
expandedExpr = &expr.BinaryExpr{
Op: expr.OR,
LHS: lastExpr,
RHS: qc.Rewrite(&expr.BinaryExpr{
Op: expr.EQ,
LHS: lhs,
RHS: value,
}).(*expr.BinaryExpr),
}
}
}
break
default:
qc.QCOptions.SetError(utils.StackError(nil, "only EQ and IN operators are supported for geo fields"))
}
return
}
// Rewrite walks the expresison AST and resolves data types bottom up.
// In addition it also translates enum strings and rewrites their predicates.
func (qc *QueryContextHelper) Rewrite(expression expr.Expr) expr.Expr {
switch e := expression.(type) {
case *expr.ParenExpr:
// Strip parenthesis from the input
return e.Expr
case *expr.VarRef:
tableID, columnID, err := qc.ResolveColumn(e.Val)
if err != nil {
qc.QCOptions.SetError(err)
return expression
}
column := qc.QCOptions.GetSchema(tableID).Schema.Columns[columnID]
if column.Deleted {
qc.QCOptions.SetError(utils.StackError(nil, "column %s of table %s has been deleted",
column.Name, qc.QCOptions.GetSchema(tableID).Schema.Name))
return expression
}
dataType := qc.QCOptions.GetSchema(tableID).ValueTypeByColumn[columnID]
e.ExprType = common.DataTypeToExprType[dataType]
e.TableID = tableID
e.ColumnID = columnID
dict := qc.QCOptions.GetSchema(tableID).EnumDicts[column.Name]
e.EnumDict = dict.Dict
e.EnumReverseDict = dict.ReverseDict
e.DataType = dataType
e.IsHLLColumn = column.HLLConfig.IsHLLColumn
case *expr.UnaryExpr:
if expr.IsUUIDColumn(e.Expr) && e.Op != expr.GET_HLL_VALUE {
qc.QCOptions.SetError(utils.StackError(nil, "uuid column type only supports countdistincthll unary expression"))
return expression
}
if err := blockNumericOpsForColumnOverFourBytes(e.Op, e.Expr); err != nil {
qc.QCOptions.SetError(err)
return expression
}
e.ExprType = e.Expr.Type()
switch e.Op {
case expr.EXCLAMATION, expr.NOT, expr.IS_FALSE:
e.ExprType = expr.Boolean
// Normalize the operator.
e.Op = expr.NOT
e.Expr = expr.Cast(e.Expr, expr.Boolean)
childExpr := e.Expr
callRef, isCallRef := childExpr.(*expr.Call)
if isCallRef && callRef.Name == expr.GeographyIntersectsCallName {
qc.QCOptions.SetError(utils.StackError(nil, "Not %s condition is not allowed", expr.GeographyIntersectsCallName))
break
}
case expr.UNARY_MINUS:
// Upgrade to signed.
if e.ExprType < expr.Signed {
e.ExprType = expr.Signed
}
case expr.IS_NULL, expr.IS_NOT_NULL:
e.ExprType = expr.Boolean
case expr.IS_TRUE:
// Strip IS_TRUE if child is already boolean.
if e.Expr.Type() == expr.Boolean {
return e.Expr
}
// Rewrite to NOT(NOT(child)).
e.ExprType = expr.Boolean
e.Op = expr.NOT
e.Expr = expr.Cast(e.Expr, expr.Boolean)
return &expr.UnaryExpr{Expr: e, Op: expr.NOT, ExprType: expr.Boolean}
case expr.BITWISE_NOT:
// Cast child to unsigned.
e.ExprType = expr.Unsigned
e.Expr = expr.Cast(e.Expr, expr.Unsigned)
case expr.GET_MONTH_START, expr.GET_QUARTER_START, expr.GET_YEAR_START, expr.GET_WEEK_START:
// Cast child to unsigned.
e.ExprType = expr.Unsigned
e.Expr = expr.Cast(e.Expr, expr.Unsigned)
case expr.GET_DAY_OF_MONTH, expr.GET_DAY_OF_YEAR, expr.GET_MONTH_OF_YEAR, expr.GET_QUARTER_OF_YEAR:
// Cast child to unsigned.
e.ExprType = expr.Unsigned
e.Expr = expr.Cast(e.Expr, expr.Unsigned)
case expr.GET_HLL_VALUE:
e.ExprType = expr.Unsigned
e.Expr = expr.Cast(e.Expr, expr.Unsigned)
default:
qc.QCOptions.SetError(utils.StackError(nil, "unsupported unary expression %s", e.String()))
}
case *expr.BinaryExpr:
if err := blockNumericOpsForColumnOverFourBytes(e.Op, e.LHS, e.RHS); err != nil {
qc.QCOptions.SetError(err)
return expression
}
// TODO: @shz support int64 binary transform
if err := blockInt64(e.LHS, e.RHS); err != nil {
qc.QCOptions.SetError(err)
return expression
}
if e.Op != expr.EQ && e.Op != expr.NEQ {
_, isRHSStr := e.RHS.(*expr.StringLiteral)
_, isLHSStr := e.LHS.(*expr.StringLiteral)
if isRHSStr || isLHSStr {
qc.QCOptions.SetError(utils.StackError(nil, "string type only support EQ and NEQ operators"))
return expression
}
}
highestType := e.LHS.Type()
if e.RHS.Type() > highestType {
highestType = e.RHS.Type()
}
switch e.Op {
case expr.ADD, expr.SUB:
// Upgrade and cast to highestType.
e.ExprType = highestType
if highestType == expr.Float {
e.LHS = expr.Cast(e.LHS, expr.Float)
e.RHS = expr.Cast(e.RHS, expr.Float)
} else if e.Op == expr.SUB {
// For lhs - rhs, upgrade to signed at least.
e.ExprType = expr.Signed
}
case expr.MUL, expr.MOD:
// Upgrade and cast to highestType.
e.ExprType = highestType
e.LHS = expr.Cast(e.LHS, highestType)
e.RHS = expr.Cast(e.RHS, highestType)
case expr.DIV:
// Upgrade and cast to float.
e.ExprType = expr.Float
e.LHS = expr.Cast(e.LHS, expr.Float)
e.RHS = expr.Cast(e.RHS, expr.Float)
case expr.BITWISE_AND, expr.BITWISE_OR, expr.BITWISE_XOR,
expr.BITWISE_LEFT_SHIFT, expr.BITWISE_RIGHT_SHIFT, expr.FLOOR, expr.CONVERT_TZ:
// Cast to unsigned.
e.ExprType = expr.Unsigned
e.LHS = expr.Cast(e.LHS, expr.Unsigned)
e.RHS = expr.Cast(e.RHS, expr.Unsigned)
case expr.AND, expr.OR:
// Cast to boolean.
e.ExprType = expr.Boolean
e.LHS = expr.Cast(e.LHS, expr.Boolean)
e.RHS = expr.Cast(e.RHS, expr.Boolean)
case expr.LT, expr.LTE, expr.GT, expr.GTE:
// Cast to boolean.
e.ExprType = expr.Boolean
e.LHS = expr.Cast(e.LHS, highestType)
e.RHS = expr.Cast(e.RHS, highestType)
case expr.NEQ, expr.EQ:
// swap lhs and rhs if rhs is VarRef but lhs is not.
if _, lhsVarRef := e.LHS.(*expr.VarRef); !lhsVarRef {
if _, rhsVarRef := e.RHS.(*expr.VarRef); rhsVarRef {
e.LHS, e.RHS = e.RHS, e.LHS
}
}
e.ExprType = expr.Boolean
// Match enum = 'case' and enum != 'case'.
lhs, _ := e.LHS.(*expr.VarRef)
// rhs is bool
rhsBool, _ := e.RHS.(*expr.BooleanLiteral)
if lhs != nil && rhsBool != nil {
if (e.Op == expr.EQ && rhsBool.Val) || (e.Op == expr.NEQ && !rhsBool.Val) {
return &expr.UnaryExpr{Expr: lhs, Op: expr.IS_TRUE, ExprType: expr.Boolean}
}
return &expr.UnaryExpr{Expr: lhs, Op: expr.NOT, ExprType: expr.Boolean}
}
// rhs is string enum
rhs, _ := e.RHS.(*expr.StringLiteral)
if lhs != nil && rhs != nil && lhs.EnumDict != nil {
// Enum dictionary translation
value, exists := lhs.EnumDict[rhs.Val]
if !exists {
// Combination of nullable data with not/and/or operators on top makes
// short circuiting hard.
// To play it safe we match against an invalid value.
value = -1
}
e.RHS = &expr.NumberLiteral{Int: value, ExprType: expr.Unsigned}
} else {
// Cast to highestType.
e.LHS = expr.Cast(e.LHS, highestType)
e.RHS = expr.Cast(e.RHS, highestType)
}
if rhs != nil && e.LHS.Type() == expr.GeoPoint {
if val, err := memCom.GeoPointFromString(rhs.Val); err != nil {
qc.QCOptions.SetError(err)
} else {
e.RHS = &expr.GeopointLiteral{
Val: val,
}
}
} else if rhs != nil && e.LHS.Type() == expr.UUID {
if val, err := memCom.UUIDFromString(rhs.Val); err != nil {
qc.QCOptions.SetError(err)
} else {
e.RHS = &expr.UUIDLiteral{
Val: val,
}
}
}
case expr.IN:
return qc.expandINop(e)
case expr.NOT_IN:
return &expr.UnaryExpr{
Op: expr.NOT,
Expr: qc.expandINop(e),
}
default:
qc.QCOptions.SetError(utils.StackError(nil, "unsupported binary expression %s", e.String()))
}
case *expr.Call:
e.Name = strings.ToLower(e.Name)
switch e.Name {
case expr.ConvertTzCallName:
if len(e.Args) != 3 {
qc.QCOptions.SetError(utils.StackError(
nil, "convert_tz must have 3 arguments",
))
break
}
fromTzStringExpr, isStrLiteral := e.Args[1].(*expr.StringLiteral)
if !isStrLiteral {
qc.QCOptions.SetError(utils.StackError(nil, "2nd argument of convert_tz must be a string"))
break
}
toTzStringExpr, isStrLiteral := e.Args[2].(*expr.StringLiteral)
if !isStrLiteral {
qc.QCOptions.SetError(utils.StackError(nil, "3rd argument of convert_tz must be a string"))
break
}
fromTz, err := common.ParseTimezone(fromTzStringExpr.Val)
if err != nil {
qc.QCOptions.SetError(utils.StackError(err, "failed to rewrite convert_tz"))
break
}
toTz, err := common.ParseTimezone(toTzStringExpr.Val)
if err != nil {
qc.QCOptions.SetError(utils.StackError(err, "failed to rewrite convert_tz"))
break
}
_, fromOffsetInSeconds := utils.Now().In(fromTz).Zone()
_, toOffsetInSeconds := utils.Now().In(toTz).Zone()
offsetInSeconds := toOffsetInSeconds - fromOffsetInSeconds
return &expr.BinaryExpr{
Op: expr.ADD,
LHS: e.Args[0],
RHS: &expr.NumberLiteral{
Int: offsetInSeconds,
Expr: strconv.Itoa(offsetInSeconds),
ExprType: expr.Unsigned,
},
ExprType: expr.Unsigned,
}
case expr.CountCallName:
e.ExprType = expr.Unsigned
case expr.DayOfWeekCallName:
// dayofweek from ts: (ts / secondsInDay + 4) % 7 + 1
// ref: https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_dayofweek
if len(e.Args) != 1 {
qc.QCOptions.SetError(utils.StackError(nil, "dayofweek takes exactly 1 argument"))
break
}
tsExpr := e.Args[0]
return &expr.BinaryExpr{
Op: expr.ADD,
ExprType: expr.Unsigned,
RHS: &expr.NumberLiteral{
Int: 1,
Expr: "1",
ExprType: expr.Unsigned,
},
LHS: &expr.BinaryExpr{
Op: expr.MOD,
ExprType: expr.Unsigned,
RHS: &expr.NumberLiteral{
Int: common.DaysPerWeek,
Expr: strconv.Itoa(common.DaysPerWeek),
ExprType: expr.Unsigned,
},
LHS: &expr.BinaryExpr{
Op: expr.ADD,
ExprType: expr.Unsigned,
RHS: &expr.NumberLiteral{
// offset for
Int: common.WeekdayOffset,
Expr: strconv.Itoa(common.WeekdayOffset),
ExprType: expr.Unsigned,
},
LHS: &expr.BinaryExpr{
Op: expr.DIV,
ExprType: expr.Unsigned,
RHS: &expr.NumberLiteral{
Int: common.SecondsPerDay,
Expr: strconv.Itoa(common.SecondsPerDay),
ExprType: expr.Unsigned,
},
LHS: tsExpr,
},
},
},
}
// no-op, this will be over written
case expr.FromUnixTimeCallName:
// for now, only the following format is allowed for backward compatibility
// from_unixtime(time_col / 1000)
timeColumnDivideErrMsg := "from_unixtime must be time column / 1000"
timeColDivide, isBinary := e.Args[0].(*expr.BinaryExpr)
if !isBinary || timeColDivide.Op != expr.DIV {
qc.QCOptions.SetError(utils.StackError(nil, timeColumnDivideErrMsg))
break
}
divisor, isLiteral := timeColDivide.RHS.(*expr.NumberLiteral)
if !isLiteral || divisor.Int != 1000 {
qc.QCOptions.SetError(utils.StackError(nil, timeColumnDivideErrMsg))
break
}
if par, isParen := timeColDivide.LHS.(*expr.ParenExpr); isParen {
timeColDivide.LHS = par.Expr
}
timeColExpr, isVarRef := timeColDivide.LHS.(*expr.VarRef)
if !isVarRef {
qc.QCOptions.SetError(utils.StackError(nil, timeColumnDivideErrMsg))
break
}
return timeColExpr
case expr.HourCallName:
if len(e.Args) != 1 {
qc.QCOptions.SetError(utils.StackError(nil, "hour takes exactly 1 argument"))
break
}
// hour(ts) = (ts % secondsInDay) / secondsInHour
return &expr.BinaryExpr{
Op: expr.DIV,
ExprType: expr.Unsigned,
LHS: &expr.BinaryExpr{
Op: expr.MOD,
LHS: e.Args[0],
RHS: &expr.NumberLiteral{
Expr: strconv.Itoa(common.SecondsPerDay),
Int: common.SecondsPerDay,
ExprType: expr.Unsigned,
},
},
RHS: &expr.NumberLiteral{
Expr: strconv.Itoa(common.SecondsPerHour),
Int: common.SecondsPerHour,
ExprType: expr.Unsigned,
},
}
// list of literals, no need to cast it for now.
case expr.ListCallName:
case expr.GeographyIntersectsCallName:
if len(e.Args) != 2 {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 2 argument for %s, but got %s", e.Name, e.String()))
break
}
lhsRef, isVarRef := e.Args[0].(*expr.VarRef)
if !isVarRef || (lhsRef.DataType != memCom.GeoShape && lhsRef.DataType != memCom.GeoPoint) {
qc.QCOptions.SetError(utils.StackError(
nil, "expect argument to be a valid geo shape or geo point column for %s, but got %s of type %s",
e.Name, e.Args[0].String(), memCom.DataTypeName[lhsRef.DataType]))
break
}
lhsGeoPoint := lhsRef.DataType == memCom.GeoPoint
rhsRef, isVarRef := e.Args[1].(*expr.VarRef)
if !isVarRef || (rhsRef.DataType != memCom.GeoShape && rhsRef.DataType != memCom.GeoPoint) {
qc.QCOptions.SetError(utils.StackError(
nil, "expect argument to be a valid geo shape or geo point column for %s, but got %s of type %s",
e.Name, e.Args[1].String(), memCom.DataTypeName[rhsRef.DataType]))
break
}
rhsGeoPoint := rhsRef.DataType == memCom.GeoPoint
if lhsGeoPoint == rhsGeoPoint {
qc.QCOptions.SetError(utils.StackError(
nil, "expect exactly one geo shape column and one geo point column for %s, got %s",
e.Name, e.String()))
break
}
// Switch geo point so that lhs is geo shape and rhs is geo point
if lhsGeoPoint {
e.Args[0], e.Args[1] = e.Args[1], e.Args[0]
}
e.ExprType = expr.Boolean
case expr.HexCallName:
if len(e.Args) != 1 {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 1 argument for %s, but got %s", e.Name, e.String()))
break
}
colRef, isVarRef := e.Args[0].(*expr.VarRef)
if !isVarRef || colRef.DataType != memCom.UUID {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 1 argument to be a valid uuid column for %s, but got %s of type %s",
e.Name, e.Args[0].String(), memCom.DataTypeName[colRef.DataType]))
break
}
e.ExprType = e.Args[0].Type()
case expr.CountDistinctHllCallName:
if len(e.Args) != 1 {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 1 argument for %s, but got %s", e.Name, e.String()))
break
}
colRef, isVarRef := e.Args[0].(*expr.VarRef)
if !isVarRef {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 1 argument to be a column for %s", e.Name))
break
}
e.Name = expr.HllCallName
// 1. noop when column itself is hll column
// 2. compute hll on the fly when column is not hll column
if !colRef.IsHLLColumn {
e.Args[0] = &expr.UnaryExpr{
Op: expr.GET_HLL_VALUE,
Expr: colRef,
ExprType: expr.Unsigned,
}
}
e.ExprType = expr.Unsigned
case expr.HllCallName:
if len(e.Args) != 1 {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 1 argument for %s, but got %s", e.Name, e.String()))
break
}
colRef, isVarRef := e.Args[0].(*expr.VarRef)
if !isVarRef || colRef.DataType != memCom.Uint32 {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 1 argument to be a valid hll column for %s, but got %s of type %s",
e.Name, e.Args[0].String(), memCom.DataTypeName[colRef.DataType]))
break
}
e.ExprType = e.Args[0].Type()
case expr.SumCallName, expr.MinCallName, expr.MaxCallName, expr.AvgCallName:
if len(e.Args) != 1 {
qc.QCOptions.SetError(utils.StackError(
nil, "expect 1 argument for %s, but got %s", e.Name, e.String()))
break
}
// For avg, the expression type should always be float.
if e.Name == expr.AvgCallName {
e.Args[0] = expr.Cast(e.Args[0], expr.Float)
}
e.ExprType = e.Args[0].Type()
case expr.LengthCallName, expr.ContainsCallName, expr.ElementAtCallName:
// validate first argument
if len(e.Args) == 0 {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s requires arguments", e.Name))
break
}
firstArg := e.Args[0]
vr, ok := firstArg.(*expr.VarRef)
if !ok || !memCom.IsArrayType(vr.DataType) {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s requires first argument to be array type column, but got %s", e.Name, firstArg))
}
if e.Name == expr.LengthCallName {
if len(e.Args) != 1 {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s takes exactly 1 argument", e.Name))
break
}
return &expr.UnaryExpr{
Op: expr.ARRAY_LENGTH,
ExprType: expr.Unsigned,
Expr: vr,
}
} else if e.Name == expr.ContainsCallName {
if len(e.Args) != 2 {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s takes exactly 2 arguments", e.Name))
break
}
secondArg := e.Args[1]
var literalExpr expr.Expr
// build rhs literal
t := memCom.GetElementDataType(vr.DataType)
switch t {
case memCom.Bool:
ok := false
literalExpr, ok = secondArg.(*expr.BooleanLiteral)
if !ok {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s argument type mismatch", e.Name))
break
}
case memCom.SmallEnum, memCom.BigEnum:
if qc.QCOptions.IsDataOnly() {
// if the request is from broker, it should be already a number literal
literalExpr, ok = secondArg.(*expr.NumberLiteral)
if !ok {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s argument type mismatch, expecting number literal", e.Name))
}
break
}
strLiteral, ok := secondArg.(*expr.StringLiteral)
if !ok {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s argument type mismatch, expecting string literal", e.Name))
break
}
if vr.EnumDict != nil {
// Enum dictionary translation
value, exists := vr.EnumDict[strLiteral.Val]
if !exists {
// Combination of nullable data with not/and/or operators on top makes
// short circuiting hard.
// To play it safe we match against an invalid value.
value = -1
}
literalExpr = &expr.NumberLiteral{Int: value, ExprType: expr.Unsigned}
}
case memCom.GeoPoint:
strLiteral, ok := secondArg.(*expr.StringLiteral)
if !ok {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s argument type mismatch, expecting string literal", e.Name))
break
}
val, err := memCom.GeoPointFromString(strLiteral.Val)
if err != nil {
qc.QCOptions.SetError(err)
break
}
literalExpr = &expr.GeopointLiteral{
Val: val,
}
case memCom.UUID:
strLiteral, ok := secondArg.(*expr.StringLiteral)
if !ok {
qc.QCOptions.SetError(utils.StackError(nil, "array function %s needs uuid string literal", e.Name))
break
}
val, err := memCom.UUIDFromString(strLiteral.Val)
if err != nil {
qc.QCOptions.SetError(err)
break
}
literalExpr = &expr.UUIDLiteral{
Val: val,
}
case memCom.Uint8, memCom.Uint16, memCom.Uint32, memCom.Int8, memCom.Int16, memCom.Int32, memCom.Float32:
ok := false
literalExpr, ok = secondArg.(*expr.NumberLiteral)
if !ok {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s argument type mismatch", e.Name))
}
}
return &expr.BinaryExpr{
Op: expr.ARRAY_CONTAINS,
ExprType: expr.Boolean,
LHS: vr,
RHS: literalExpr,
}
} else if e.Name == expr.ElementAtCallName {
if len(e.Args) != 2 {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s takes exactly 2 arguments", e.Name))
break
}
if _, ok := e.Args[1].(*expr.NumberLiteral); !ok {
qc.QCOptions.SetError(utils.StackError(
nil, "array function %s takes array type column and an index", e.Name))
}
return &expr.BinaryExpr{
Op: expr.ARRAY_ELEMENT_AT,
ExprType: common.DataTypeToExprType[memCom.GetElementDataType(vr.DataType)],
LHS: vr,
RHS: e.Args[1],
}
}
default:
qc.QCOptions.SetError(utils.StackError(nil, "unknown function %s", e.Name))
}
case *expr.Case:
highestType := e.Else.Type()
for _, whenThen := range e.WhenThens {
if whenThen.Then.Type() > highestType {
highestType = whenThen.Then.Type()
}
}
// Cast else and thens to highestType, cast whens to boolean.
e.Else = expr.Cast(e.Else, highestType)
for i, whenThen := range e.WhenThens {
whenThen.When = expr.Cast(whenThen.When, expr.Boolean)
whenThen.Then = expr.Cast(whenThen.Then, highestType)
e.WhenThens[i] = whenThen
}
e.ExprType = highestType
}
return expression
}