@@ -16,6 +16,7 @@ package expression
1616
1717import (
1818 "fmt"
19+ "github.com/dolthub/vitess/go/mysql"
1920
2021 "github.com/dolthub/go-mysql-server/sql"
2122 "github.com/dolthub/go-mysql-server/sql/hash"
@@ -59,65 +60,120 @@ func NewInTuple(left sql.Expression, right sql.Expression) *InTuple {
5960 return & InTuple {BinaryExpressionStub {left , right }}
6061}
6162
63+ // validateAndEvalRightTuple will evaluate the right tuple, check if leftType and the right Tuple are comparable,
64+ // determine what type to use to compare the two sides, and indicate if right Tuple contains any NULL elements.
65+ // The NULL handling for IN expressions is tricky. According to
66+ // https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_in:
67+ // To comply with the SQL standard, IN() returns NULL not only if the expression on the left hand side is NULL, but
68+ // also if no match is found in the list and one of the expressions in the list is NULL.
69+ func validateAndEvalRightTuple (ctx * sql.Context , lType sql.Type , right Tuple , row sql.Row ) ([]any , sql.Type , bool , error ) {
70+ // If left is StringType and ANY of the right is NumberType, then we should use Double Type for comparison
71+ // If left is NumberType and ANT of the left is StringType, then we should use Double Type for comparison
72+ lColCount := types .NumColumns (lType )
73+ lIsNumType := types .IsNumber (lType )
74+ lIsStrType := types .IsText (lType )
75+ var rHasNumType , rHasStrType , rHasNull bool
76+ rVals := make ([]any , len (right ))
77+ for i , el := range right {
78+ rType := el .Type ()
79+
80+ // Nested tuples must have the same number of columns
81+ rColCount := types .NumColumns (rType )
82+ if rColCount != lColCount {
83+ return nil , nil , false , sql .ErrInvalidOperandColumns .New (lColCount , types .NumColumns (el .Type ()))
84+ }
85+
86+ if types .IsNumber (rType ) {
87+ rHasNumType = true
88+ } else if types .IsText (rType ) {
89+ rHasStrType = true
90+ }
91+
92+ // Null elements are not hashed into the Tuple Map
93+ if types .IsNullType (rType ) {
94+ rHasNull = true
95+ continue
96+ }
97+ v , err := el .Eval (ctx , row )
98+ if err != nil {
99+ return nil , nil , false , err
100+ }
101+ if v == nil {
102+ rHasNull = true
103+ continue
104+ }
105+
106+ rVals [i ] = v
107+ }
108+
109+ var cmpType sql.Type
110+ if (lIsStrType && rHasNumType ) || (lIsNumType && rHasStrType ) {
111+ cmpType = types .Float64
112+ } else if types .IsEnum (lType ) || types .IsSet (lType ) || types .IsText (lType ) {
113+ cmpType = lType
114+ } else {
115+ cmpType = types .GetCompareType (lType , right [0 ].Type ())
116+ }
117+
118+ return rVals , cmpType , rHasNull , nil
119+ }
120+
62121// Eval implements the Expression interface.
63122func (in * InTuple ) Eval (ctx * sql.Context , row sql.Row ) (interface {}, error ) {
64- leftElems := types .NumColumns (in .Left ().Type ())
65- originalLeft , err := in .Left ().Eval (ctx , row )
123+ leftVal , err := in .Left ().Eval (ctx , row )
66124 if err != nil {
67125 return nil , err
68126 }
69- if originalLeft == nil {
127+ if leftVal == nil {
70128 return nil , nil
71129 }
72130
73- // The NULL handling for IN expressions is tricky. According to
74- // https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_in:
75- // To comply with the SQL standard, IN() returns NULL not only if the expression on the left hand side is NULL, but
76- // also if no match is found in the list and one of the expressions in the list is NULL.
77- rightNull := false
78-
79- switch right := in .Right ().(type ) {
80- case Tuple :
81- for _ , el := range right {
82- if types .NumColumns (el .Type ()) != leftElems {
83- return nil , sql .ErrInvalidOperandColumns .New (leftElems , types .NumColumns (el .Type ()))
84- }
85- }
131+ right , isTuple := in .Right ().(Tuple )
132+ if ! isTuple {
133+ return nil , ErrUnsupportedInOperand .New (right )
134+ }
86135
87- leftLit := NewLiteral (originalLeft , in .Left ().Type ())
88- for _ , el := range right {
89- originalRight , err := el .Eval (ctx , row )
90- if err != nil {
91- return nil , err
92- }
136+ lType := in .Left ().Type ()
137+ rVals , cmpType , rHasNull , err := validateAndEvalRightTuple (ctx , lType , right , row )
138+ if err != nil {
139+ return nil , err
140+ }
93141
94- if ! rightNull && originalRight == nil {
95- rightNull = true
96- continue
97- }
142+ lv , _ , lErr := cmpType .Convert (ctx , leftVal )
143+ if lErr != nil {
144+ if ! sql .ErrTruncatedIncorrect .Is (lErr ) {
145+ return nil , lErr
146+ }
147+ ctx .Warn (mysql .ERTruncatedWrongValue , "%s" , lErr .Error ())
148+ }
98149
99- comp := newComparison ( leftLit , NewLiteral ( originalRight , el . Type ()))
100- l , r , compareType , err := comp . castLeftAndRight ( ctx , originalLeft , originalRight )
101- if err != nil {
102- return nil , err
103- }
104- cmp , err := compareType . Compare ( ctx , l , r )
105- if err != nil {
106- return nil , err
150+ for _ , rVal := range rVals {
151+ if rVal == nil {
152+ continue
153+ }
154+ rv , _ , rErr := cmpType . Convert ( ctx , rVal )
155+ if rErr != nil {
156+ if ! sql . ErrTruncatedIncorrect . Is ( rErr ) {
157+ return nil , rErr
107158 }
108- if cmp == 0 {
109- return true , nil
159+ ctx .Warn (mysql .ERTruncatedWrongValue , "%s" , rErr .Error ())
160+ }
161+ cmp , cErr := cmpType .Compare (ctx , lv , rv )
162+ if cErr != nil {
163+ if ! sql .ErrTruncatedIncorrect .Is (cErr ) {
164+ return nil , cErr
110165 }
166+ ctx .Warn (mysql .ERTruncatedWrongValue , "%s" , cErr .Error ())
111167 }
112-
113- if rightNull {
114- return nil , nil
168+ if cmp == 0 {
169+ return true , nil
115170 }
116-
117- return false , nil
118- default :
119- return nil , ErrUnsupportedInOperand .New (right )
120171 }
172+ if rHasNull {
173+ return nil , nil
174+ }
175+
176+ return false , nil
121177}
122178
123179// WithChildren implements the Expression interface.
@@ -191,60 +247,15 @@ func newInMap(ctx *sql.Context, lType sql.Type, right Tuple) (map[uint64]struct{
191247 if len (right ) == 0 {
192248 return nil , nil , false , nil
193249 }
194-
195- // If left is StringType and ANY of the right is NumberType, then we should use Double Type for comparison
196- // If left is NumberType and ANT of the left is StringType, then we should use Double Type for comparison
197- lColumnCount := types .NumColumns (lType )
198- lIsNumType := types .IsNumber (lType )
199- lIsStrType := types .IsText (lType )
200- var rHasNumType , rHasStrType , rHasNull bool
201- rVals := make ([]any , len (right ))
202- for i , el := range right {
203- rType := el .Type ()
204-
205- // Nested tuples must have the same number of columns
206- rColumnCount := types .NumColumns (rType )
207- if rColumnCount != lColumnCount {
208- return nil , nil , false , sql .ErrInvalidOperandColumns .New (lColumnCount , rColumnCount )
209- }
210-
211- if types .IsNumber (rType ) {
212- rHasNumType = true
213- } else if types .IsText (rType ) {
214- rHasStrType = true
215- }
216-
217- // Null elements are not hashed into the Tuple Map
218- if types .IsNullType (rType ) {
219- rHasNull = true
220- continue
221- }
222- v , err := el .Eval (ctx , sql.Row {})
223- if err != nil {
224- return nil , nil , false , err
225- }
226- if v == nil {
227- rHasNull = true
228- continue
229- }
230-
231- rVals [i ] = v
232- }
233-
234- var cmpType sql.Type
235- if (lIsStrType && rHasNumType ) || (lIsNumType && rHasStrType ) {
236- cmpType = types .Float64
237- } else if types .IsEnum (lType ) || types .IsSet (lType ) || types .IsText (lType ) {
238- cmpType = lType
239- } else {
240- cmpType = types .GetCompareType (lType , right [0 ].Type ())
250+ rVals , cmpType , rHasNull , err := validateAndEvalRightTuple (ctx , lType , right , nil )
251+ if err != nil {
252+ return nil , nil , false , err
241253 }
242-
243254 elements := make (map [uint64 ]struct {})
244255 for _ , v := range rVals {
245- key , err := hash .HashOfSimple (ctx , v , cmpType )
246- if err != nil {
247- return nil , nil , false , err
256+ key , hErr := hash .HashOfSimple (ctx , v , cmpType )
257+ if hErr != nil {
258+ return nil , nil , false , hErr
248259 }
249260 elements [key ] = struct {}{}
250261 }
0 commit comments