diff --git a/go/mysql/sql_error.go b/go/mysql/sql_error.go index 73a18d5ae35..7e672f436c7 100644 --- a/go/mysql/sql_error.go +++ b/go/mysql/sql_error.go @@ -194,6 +194,7 @@ var stateToMysqlCode = map[vterrors.State]struct { vterrors.CantDoThisInTransaction: {num: ERCantDoThisDuringAnTransaction, state: SSCantDoThisDuringAnTransaction}, vterrors.RequiresPrimaryKey: {num: ERRequiresPrimaryKey, state: SSClientError}, vterrors.NoSuchSession: {num: ERUnknownComError, state: SSNetError}, + vterrors.OperandColumns: {num: EROperandColumns, state: SSWrongNumberOfColumns}, } func init() { diff --git a/go/vt/sqlparser/parse_test.go b/go/vt/sqlparser/parse_test.go index 941e40888b8..087a2ba765e 100644 --- a/go/vt/sqlparser/parse_test.go +++ b/go/vt/sqlparser/parse_test.go @@ -3255,6 +3255,27 @@ func TestCreateTable(t *testing.T) { } } +func TestOne(t *testing.T) { + testOne := struct { + input, output string + }{ + input: "", + output: "", + } + if testOne.input == "" { + return + } + sql := strings.TrimSpace(testOne.input) + tree, err := Parse(sql) + require.NoError(t, err) + got := String(tree) + expected := testOne.output + if expected == "" { + expected = sql + } + require.Equal(t, expected, got) +} + func TestCreateTableLike(t *testing.T) { normal := "create table a like b" testCases := []struct { diff --git a/go/vt/vterrors/state.go b/go/vt/vterrors/state.go index eb1657ae537..4f7d7cf2860 100644 --- a/go/vt/vterrors/state.go +++ b/go/vt/vterrors/state.go @@ -49,6 +49,7 @@ const ( WrongNumberOfColumnsInSelect CantDoThisInTransaction RequiresPrimaryKey + OperandColumns // not found BadDb diff --git a/go/vt/vtgate/evalengine/comparisons.go b/go/vt/vtgate/evalengine/comparisons.go index 22350605efa..7be6653fe15 100644 --- a/go/vt/vtgate/evalengine/comparisons.go +++ b/go/vt/vtgate/evalengine/comparisons.go @@ -28,7 +28,6 @@ type ( // when evaluating the whole comparison ComparisonOp interface { Evaluate(left, right EvalResult) (EvalResult, error) - IsTrue(left, right EvalResult) (bool, error) Type() querypb.Type String() string } @@ -126,19 +125,28 @@ func evalResultsAreDateAndNumeric(l, r EvalResult) bool { // For more details on comparison expression evaluation and type conversion: // - https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html -func executeComparison(lVal, rVal EvalResult) (int, error) { +func nullSafeExecuteComparison(lVal, rVal EvalResult) (comp int, isNull bool, err error) { + lVal = foldSingleLenTuples(lVal) + rVal = foldSingleLenTuples(rVal) + if hasNullEvalResult(lVal, rVal) { + return 0, true, nil + } switch { case evalResultsAreStrings(lVal, rVal): - return compareStrings(lVal, rVal) + comp, err = compareStrings(lVal, rVal) + return comp, false, err case evalResultsAreSameNumericType(lVal, rVal), needsDecimalHandling(lVal, rVal): - return compareNumeric(lVal, rVal) + comp, err = compareNumeric(lVal, rVal) + return comp, false, err case evalResultsAreDates(lVal, rVal): - return compareDates(lVal, rVal) + comp, err = compareDates(lVal, rVal) + return comp, false, err case evalResultsAreDateAndString(lVal, rVal): - return compareDateAndString(lVal, rVal) + comp, err = compareDateAndString(lVal, rVal) + return comp, false, err case evalResultsAreDateAndNumeric(lVal, rVal): // TODO: support comparison between a date and a numeric value @@ -146,7 +154,14 @@ func executeComparison(lVal, rVal EvalResult) (int, error) { // - select 1 where 20210101 = cast("2021-01-01" as date) // - select 1 where 2021210101 = cast("2021-01-01" as date) // - select 1 where 104200 = cast("10:42:00" as time) - return 0, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "cannot compare a date with a numeric value") + return 0, false, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "cannot compare a date with a numeric value") + + case lVal.typ == querypb.Type_TUPLE && rVal.typ == querypb.Type_TUPLE: + return compareTuples(lVal, rVal) + case lVal.typ == querypb.Type_TUPLE: + return 0, false, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.OperandColumns, "Operand should contain %d column(s)", len(lVal.tupleResults)) + case rVal.typ == querypb.Type_TUPLE: + return 0, false, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.OperandColumns, "Operand should contain 1 column(s)") default: // Quoting MySQL Docs: @@ -156,10 +171,18 @@ func executeComparison(lVal, rVal EvalResult) (int, error) { // comparison of floating-point numbers." // // https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html - return compareNumeric(makeFloat(lVal), makeFloat(rVal)) + comp, err = compareNumeric(makeFloat(lVal), makeFloat(rVal)) + return comp, false, err } } +func foldSingleLenTuples(val EvalResult) EvalResult { + if val.typ == querypb.Type_TUPLE && len(val.tupleResults) == 1 { + val = val.tupleResults[0] + } + return val +} + // Evaluate implements the Expr interface func (c *ComparisonExpr) Evaluate(env ExpressionEnv) (EvalResult, error) { if c.Op == nil { @@ -171,13 +194,6 @@ func (c *ComparisonExpr) Evaluate(env ExpressionEnv) (EvalResult, error) { return EvalResult{}, err } - if hasNullEvalResult(lVal, rVal) { - // Comparison operation NullSafeEqual (<=>) does not care if one or two sides are NULL - if _, isNullsafe := c.Op.(*NullSafeEqualOp); !isNullsafe { - // If a side of the comparison is NULL, result will always be NULL - return resultNull, nil - } - } return c.Op.Evaluate(lVal, rVal) } @@ -193,19 +209,17 @@ func (c *ComparisonExpr) String() string { // Evaluate implements the ComparisonOp interface func (e *EqualOp) Evaluate(left, right EvalResult) (EvalResult, error) { - if out, err := e.IsTrue(left, right); err != nil || !out { - return resultFalse, err - } - return resultTrue, nil -} - -// IsTrue implements the ComparisonOp interface -func (e *EqualOp) IsTrue(left, right EvalResult) (bool, error) { - numeric, err := executeComparison(left, right) + numeric, isNull, err := nullSafeExecuteComparison(left, right) if err != nil { - return false, err + return EvalResult{}, err } - return numeric == 0, nil + if isNull { + return resultNull, err + } + if numeric == 0 { + return resultTrue, nil + } + return resultFalse, nil } // Type implements the ComparisonOp interface @@ -220,19 +234,17 @@ func (e *EqualOp) String() string { // Evaluate implements the ComparisonOp interface func (n *NotEqualOp) Evaluate(left, right EvalResult) (EvalResult, error) { - if out, err := n.IsTrue(left, right); err != nil || !out { - return resultFalse, err - } - return resultTrue, nil -} - -// IsTrue implements the ComparisonOp interface -func (n *NotEqualOp) IsTrue(left, right EvalResult) (bool, error) { - numeric, err := executeComparison(left, right) + numeric, isNull, err := nullSafeExecuteComparison(left, right) if err != nil { - return false, err + return EvalResult{}, err + } + if isNull { + return resultNull, err + } + if numeric != 0 { + return resultTrue, nil } - return numeric != 0, nil + return resultFalse, nil } // Type implements the ComparisonOp interface @@ -250,11 +262,6 @@ func (n *NullSafeEqualOp) Evaluate(left, right EvalResult) (EvalResult, error) { panic("implement me") } -// IsTrue implements the ComparisonOp interface -func (n *NullSafeEqualOp) IsTrue(left, right EvalResult) (bool, error) { - return false, nil -} - // Type implements the ComparisonOp interface func (n *NullSafeEqualOp) Type() querypb.Type { return querypb.Type_INT32 @@ -267,19 +274,17 @@ func (n *NullSafeEqualOp) String() string { // Evaluate implements the ComparisonOp interface func (l *LessThanOp) Evaluate(left, right EvalResult) (EvalResult, error) { - if out, err := l.IsTrue(left, right); err != nil || !out { - return resultFalse, err - } - return resultTrue, nil -} - -// IsTrue implements the ComparisonOp interface -func (l *LessThanOp) IsTrue(left, right EvalResult) (bool, error) { - numeric, err := executeComparison(left, right) + numeric, isNull, err := nullSafeExecuteComparison(left, right) if err != nil { - return false, err + return EvalResult{}, err + } + if isNull { + return resultNull, err + } + if numeric < 0 { + return resultTrue, nil } - return numeric < 0, nil + return resultFalse, nil } // Type implements the ComparisonOp interface @@ -294,19 +299,17 @@ func (l *LessThanOp) String() string { // Evaluate implements the ComparisonOp interface func (l *LessEqualOp) Evaluate(left, right EvalResult) (EvalResult, error) { - if out, err := l.IsTrue(left, right); err != nil || !out { - return resultFalse, err - } - return resultTrue, nil -} - -// IsTrue implements the ComparisonOp interface -func (l *LessEqualOp) IsTrue(left, right EvalResult) (bool, error) { - numeric, err := executeComparison(left, right) + numeric, isNull, err := nullSafeExecuteComparison(left, right) if err != nil { - return false, err + return EvalResult{}, err } - return numeric <= 0, nil + if isNull { + return resultNull, err + } + if numeric <= 0 { + return resultTrue, nil + } + return resultFalse, nil } // Type implements the ComparisonOp interface @@ -321,19 +324,17 @@ func (l *LessEqualOp) String() string { // Evaluate implements the ComparisonOp interface func (g *GreaterThanOp) Evaluate(left, right EvalResult) (EvalResult, error) { - if out, err := g.IsTrue(left, right); err != nil || !out { - return resultFalse, err - } - return resultTrue, nil -} - -// IsTrue implements the ComparisonOp interface -func (g *GreaterThanOp) IsTrue(left, right EvalResult) (bool, error) { - numeric, err := executeComparison(left, right) + numeric, isNull, err := nullSafeExecuteComparison(left, right) if err != nil { - return false, err + return EvalResult{}, err + } + if isNull { + return resultNull, err } - return numeric > 0, nil + if numeric > 0 { + return resultTrue, nil + } + return resultFalse, nil } // Type implements the ComparisonOp interface @@ -348,19 +349,17 @@ func (g *GreaterThanOp) String() string { // Evaluate implements the ComparisonOp interface func (g *GreaterEqualOp) Evaluate(left, right EvalResult) (EvalResult, error) { - if out, err := g.IsTrue(left, right); err != nil || !out { - return resultFalse, err - } - return resultTrue, nil -} - -// IsTrue implements the ComparisonOp interface -func (g *GreaterEqualOp) IsTrue(left, right EvalResult) (bool, error) { - numeric, err := executeComparison(left, right) + numeric, isNull, err := nullSafeExecuteComparison(left, right) if err != nil { - return false, err + return EvalResult{}, err + } + if isNull { + return resultNull, err } - return numeric >= 0, nil + if numeric >= 0 { + return resultTrue, nil + } + return resultFalse, nil } // Type implements the ComparisonOp interface @@ -375,12 +374,24 @@ func (g *GreaterEqualOp) String() string { // Evaluate implements the ComparisonOp interface func (i *InOp) Evaluate(left, right EvalResult) (EvalResult, error) { - panic("implement me") -} - -// IsTrue implements the ComparisonOp interface -func (i *InOp) IsTrue(left, right EvalResult) (bool, error) { - return false, nil + if right.typ != querypb.Type_TUPLE { + return EvalResult{}, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "rhs of an In operation should be a tuple") + } + returnValue := resultFalse + for _, result := range right.tupleResults { + res, err := (&EqualOp{}).Evaluate(left, result) + if err != nil { + return EvalResult{}, err + } + if res.typ == querypb.Type_NULL_TYPE { + returnValue = resultNull + continue + } + if sqltypes.IsIntegral(res.typ) && res.ival == 1 { + return resultTrue, nil + } + } + return returnValue, nil } // Type implements the ComparisonOp interface @@ -395,12 +406,9 @@ func (i *InOp) String() string { // Evaluate implements the ComparisonOp interface func (n *NotInOp) Evaluate(left, right EvalResult) (EvalResult, error) { - panic("implement me") -} - -// IsTrue implements the ComparisonOp interface -func (n *NotInOp) IsTrue(left, right EvalResult) (bool, error) { - return false, nil + res, err := (&InOp{}).Evaluate(left, right) + res.ival = 1 - res.ival + return res, err } // Type implements the ComparisonOp interface @@ -418,11 +426,6 @@ func (l *LikeOp) Evaluate(left, right EvalResult) (EvalResult, error) { panic("implement me") } -// IsTrue implements the ComparisonOp interface -func (l *LikeOp) IsTrue(left, right EvalResult) (bool, error) { - return false, nil -} - // Type implements the ComparisonOp interface func (l *LikeOp) Type() querypb.Type { return querypb.Type_INT32 @@ -438,11 +441,6 @@ func (n *NotLikeOp) Evaluate(left, right EvalResult) (EvalResult, error) { panic("implement me") } -// IsTrue implements the ComparisonOp interface -func (n *NotLikeOp) IsTrue(left, right EvalResult) (bool, error) { - return false, nil -} - // Type implements the ComparisonOp interface func (n *NotLikeOp) Type() querypb.Type { return querypb.Type_INT32 @@ -458,11 +456,6 @@ func (r *RegexpOp) Evaluate(left, right EvalResult) (EvalResult, error) { panic("implement me") } -// IsTrue implements the ComparisonOp interface -func (r *RegexpOp) IsTrue(left, right EvalResult) (bool, error) { - return false, nil -} - // Type implements the ComparisonOp interface func (r *RegexpOp) Type() querypb.Type { return querypb.Type_INT32 @@ -478,11 +471,6 @@ func (n *NotRegexpOp) Evaluate(left, right EvalResult) (EvalResult, error) { panic("implement me") } -// IsTrue implements the ComparisonOp interface -func (n *NotRegexpOp) IsTrue(left, right EvalResult) (bool, error) { - return false, nil -} - // Type implements the ComparisonOp interface func (n *NotRegexpOp) Type() querypb.Type { return querypb.Type_INT32 diff --git a/go/vt/vtgate/evalengine/comparisons_test.go b/go/vt/vtgate/evalengine/comparisons_test.go index a576515e73d..24d389486eb 100644 --- a/go/vt/vtgate/evalengine/comparisons_test.go +++ b/go/vt/vtgate/evalengine/comparisons_test.go @@ -68,7 +68,6 @@ func (tc testCase) run(t *testing.T) { } else if tc.out != nil && !*tc.out { require.EqualValues(t, 0, got.ival) } else { - require.EqualValues(t, 0, got.ival) require.EqualValues(t, sqltypes.Null, got.typ) } } else { @@ -894,3 +893,119 @@ func TestCompareStrings(t *testing.T) { }) } } + +// TestInOp tests the In operator comparisons +func TestInOp(t *testing.T) { + tests := []testCase{ + { + name: "integer In tuple", + v1: NewLiteralInt(52), v2: Tuple{NewLiteralInt(52), NewLiteralInt(54)}, + out: &T, + op: &InOp{}, + }, { + name: "integer not In tuple", + v1: NewLiteralInt(51), v2: Tuple{NewLiteralInt(52), NewLiteralInt(54)}, + out: &F, + op: &InOp{}, + }, { + name: "integer In tuple - single value", + v1: NewLiteralInt(52), v2: Tuple{NewLiteralInt(52)}, + out: &T, + op: &InOp{}, + }, { + name: "integer not In tuple - single value", + v1: NewLiteralInt(51), v2: Tuple{NewLiteralInt(52)}, + out: &F, + op: &InOp{}, + }, { + name: "integer not In tuple - no value", + v1: NewLiteralInt(51), v2: Tuple{}, + out: &F, + op: &InOp{}, + }, { + name: "integer not In tuple - null value", + v1: NewLiteralInt(51), v2: Tuple{Null{}}, + out: nil, + op: &InOp{}, + }, { + name: "integer not In tuple but with Null inside", + v1: NewLiteralInt(52), v2: Tuple{Null{}, NewLiteralInt(51), NewLiteralInt(54), Null{}}, + out: nil, + op: &InOp{}, + }, { + name: "integer In tuple with null inside", + v1: NewLiteralInt(52), v2: Tuple{Null{}, NewLiteralInt(52), NewLiteralInt(54)}, + out: &T, + op: &InOp{}, + }, { + name: "Null In tuple", + v1: Null{}, v2: Tuple{Null{}, NewLiteralInt(52), NewLiteralInt(54)}, + out: nil, + op: &InOp{}, + }, + } + + for i, tcase := range tests { + t.Run(fmt.Sprintf("%d %s", i, tcase.name), func(t *testing.T) { + tcase.run(t) + }) + } +} + +// TestNotInOp tests the NotIn operator comparisons +func TestNotInOp(t *testing.T) { + tests := []testCase{ + { + name: "integer In tuple", + v1: NewLiteralInt(52), v2: Tuple{NewLiteralInt(52), NewLiteralInt(54)}, + out: &F, + op: &NotInOp{}, + }, { + name: "integer not In tuple", + v1: NewLiteralInt(51), v2: Tuple{NewLiteralInt(52), NewLiteralInt(54)}, + out: &T, + op: &NotInOp{}, + }, { + name: "integer In tuple - single value", + v1: NewLiteralInt(52), v2: Tuple{NewLiteralInt(52)}, + out: &F, + op: &NotInOp{}, + }, { + name: "integer not In tuple - single value", + v1: NewLiteralInt(51), v2: Tuple{NewLiteralInt(52)}, + out: &T, + op: &NotInOp{}, + }, { + name: "integer not In tuple - no value", + v1: NewLiteralInt(51), v2: Tuple{}, + out: &T, + op: &NotInOp{}, + }, { + name: "integer not In tuple - null value", + v1: NewLiteralInt(51), v2: Tuple{Null{}}, + out: nil, + op: &NotInOp{}, + }, { + name: "integer not In tuple but with Null inside", + v1: NewLiteralInt(52), v2: Tuple{Null{}, NewLiteralInt(51), NewLiteralInt(54), Null{}}, + out: nil, + op: &NotInOp{}, + }, { + name: "integer In tuple with null inside", + v1: NewLiteralInt(52), v2: Tuple{Null{}, NewLiteralInt(52), NewLiteralInt(54)}, + out: &F, + op: &NotInOp{}, + }, { + name: "Null In tuple", + v1: Null{}, v2: Tuple{Null{}, NewLiteralInt(52), NewLiteralInt(54)}, + out: nil, + op: &NotInOp{}, + }, + } + + for i, tcase := range tests { + t.Run(fmt.Sprintf("%d %s", i, tcase.name), func(t *testing.T) { + tcase.run(t) + }) + } +} diff --git a/go/vt/vtgate/evalengine/convert.go b/go/vt/vtgate/evalengine/convert.go index 2979a1ac293..9447f104726 100644 --- a/go/vt/vtgate/evalengine/convert.go +++ b/go/vt/vtgate/evalengine/convert.go @@ -152,7 +152,18 @@ func Convert(e sqlparser.Expr, lookup ConverterLookup) (Expr, error) { Left: left, Right: right, }, nil - + case sqlparser.ValTuple: + var res Tuple + for _, expr := range node { + convertedExpr, err := Convert(expr, lookup) + if err != nil { + return nil, err + } + res = append(res, convertedExpr) + } + return res, nil + case *sqlparser.NullVal: + return Null{}, nil } return nil, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "%s: %T", ErrConvertExprNotSupported, e) } diff --git a/go/vt/vtgate/evalengine/convert_test.go b/go/vt/vtgate/evalengine/convert_test.go index 740707ecd0a..b350d155312 100644 --- a/go/vt/vtgate/evalengine/convert_test.go +++ b/go/vt/vtgate/evalengine/convert_test.go @@ -70,6 +70,45 @@ func TestEvaluate(t *testing.T) { }, { expression: ":float_bind_variable", expected: sqltypes.NewFloat64(2.2), + }, { + expression: "42 in (41, 42)", + expected: sqltypes.NewInt32(1), + }, { + expression: "42 in (41, 43)", + expected: sqltypes.NewInt32(0), + }, { + expression: "42 in (null, 41, 43)", + expected: NULL, + }, { + expression: "(1,2) in ((1,2), (2,3))", + expected: sqltypes.NewInt32(1), + }, { + expression: "(1,2) = (1,2)", + expected: sqltypes.NewInt32(1), + }, { + expression: "1 = 'sad'", + expected: sqltypes.NewInt32(0), + }, { + expression: "(1,2) = (1,3)", + expected: sqltypes.NewInt32(0), + }, { + expression: "(1,2) = (1,null)", + expected: NULL, + }, { + expression: "(1,2) in ((4,2), (2,3))", + expected: sqltypes.NewInt32(0), + }, { + expression: "(1,2) in ((1,null), (2,3))", + expected: NULL, + }, { + expression: "(1,2) in ((3,2), (2,3), null)", + expected: NULL, + }, { + expression: "(1,(1,2,3),(1,(1,2),4),2) = (1,(1,2,3),(1,(1,2),4),2)", + expected: sqltypes.NewInt32(1), + }, { + expression: "(1,(1,2,3),(1,(1,NULL),4),2) = (1,(1,2,3),(1,(1,2),4),2)", + expected: NULL, }} for _, test := range tests { diff --git a/go/vt/vtgate/evalengine/evalengine.go b/go/vt/vtgate/evalengine/evalengine.go index aa4e5735ab9..f14b17fdb29 100644 --- a/go/vt/vtgate/evalengine/evalengine.go +++ b/go/vt/vtgate/evalengine/evalengine.go @@ -416,6 +416,24 @@ func compareDateAndString(l, r EvalResult) (int, error) { return compareGoTimes(lTime, rTime) } +func compareTuples(lVal EvalResult, rVal EvalResult) (int, bool, error) { + if len(lVal.tupleResults) != len(rVal.tupleResults) { + return 0, false, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.OperandColumns, "Operand should contain %d column(s)", len(lVal.tupleResults)) + } + hasSeenNull := false + for idx, lResult := range lVal.tupleResults { + rResult := rVal.tupleResults[idx] + res, isNull, err := nullSafeExecuteComparison(lResult, rResult) + if isNull { + hasSeenNull = true + } + if res != 0 || err != nil { + return res, false, err + } + } + return 0, hasSeenNull, nil +} + func compareGoTimes(lTime, rTime time.Time) (int, error) { if lTime.Before(rTime) { return -1, nil diff --git a/go/vt/vtgate/evalengine/expressions.go b/go/vt/vtgate/evalengine/expressions.go index 6f9e7503cf3..83dfa590f0f 100644 --- a/go/vt/vtgate/evalengine/expressions.go +++ b/go/vt/vtgate/evalengine/expressions.go @@ -19,6 +19,7 @@ package evalengine import ( "fmt" "strconv" + "strings" "vitess.io/vitess/go/mysql/collations" @@ -31,12 +32,13 @@ import ( type ( EvalResult struct { - typ querypb.Type - ival int64 - uval uint64 - fval float64 - bytes []byte - collation collations.ID + typ querypb.Type + ival int64 + uval uint64 + fval float64 + bytes []byte + collation collations.ID + tupleResults []EvalResult } // ExpressionEnv contains the environment that the expression @@ -67,6 +69,7 @@ type ( Offset int Collation collations.ID } + Tuple []Expr ) var _ Expr = (*Null)(nil) @@ -75,6 +78,7 @@ var _ Expr = (*BindVariable)(nil) var _ Expr = (*Column)(nil) var _ Expr = (*BinaryExpr)(nil) var _ Expr = (*ComparisonExpr)(nil) +var _ Expr = (Tuple)(nil) // Value allows for retrieval of the value we expose for public consumption func (e EvalResult) Value() sqltypes.Value { @@ -152,6 +156,20 @@ func (l *Literal) Evaluate(ExpressionEnv) (EvalResult, error) { return eval, nil } +// Evaluate implements the Expr interface +func (t Tuple) Evaluate(env ExpressionEnv) (EvalResult, error) { + var res EvalResult + res.typ = querypb.Type_TUPLE + for _, expr := range t { + evalRes, err := expr.Evaluate(env) + if err != nil { + return EvalResult{}, err + } + res.tupleResults = append(res.tupleResults, evalRes) + } + return res, nil +} + // Evaluate implements the Expr interface func (b *BindVariable) Evaluate(env ExpressionEnv) (EvalResult, error) { val, ok := env.BindVars[b.Key] @@ -189,6 +207,11 @@ func (l *Literal) Type(ExpressionEnv) (querypb.Type, error) { return l.Val.typ, nil } +// Type implements the Expr interface +func (t Tuple) Type(env ExpressionEnv) (querypb.Type, error) { + return querypb.Type_TUPLE, nil +} + // Type implements the Expr interface func (c *Column) Type(ExpressionEnv) (querypb.Type, error) { return sqltypes.Float64, nil @@ -204,6 +227,15 @@ func (l *Literal) String() string { return l.Val.Value().String() } +// String implements the Expr interface +func (t Tuple) String() string { + var stringSlice []string + for _, expr := range t { + stringSlice = append(stringSlice, expr.String()) + } + return "(" + strings.Join(stringSlice, ",") + ")" +} + // String implements the Expr interface func (c *Column) String() string { return fmt.Sprintf("column %d from the input", c.Offset)