Skip to content

Commit a66b992

Browse files
author
Januth Nimnal
committed
Detect slice/array element kinds in Eq/Neq via reflection
1 parent b3d3bf2 commit a66b992

2 files changed

Lines changed: 78 additions & 6 deletions

File tree

‎clause/expression.go‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,29 @@ type Eq struct {
234234
Value interface{}
235235
}
236236

237+
// isValueList reports whether value should be rendered as an IN (...) list
238+
// rather than as a single bind variable.
239+
//
240+
// Any slice or array is a list, except for byte slices (including named byte
241+
// slice types) which databases bind as a single binary value.
242+
func isValueList(value interface{}) bool {
243+
if _, ok := value.(driver.Valuer); ok {
244+
return false
245+
}
246+
247+
rv := reflect.ValueOf(value)
248+
switch rv.Kind() {
249+
case reflect.Slice, reflect.Array:
250+
return rv.Type().Elem().Kind() != reflect.Uint8
251+
default:
252+
return false
253+
}
254+
}
255+
237256
func (eq Eq) Build(builder Builder) {
238257
builder.WriteQuoted(eq.Column)
239258

240-
switch eq.Value.(type) {
241-
case []string, []int, []int32, []int64, []uint, []uint32, []uint64, []interface{}:
259+
if isValueList(eq.Value) {
242260
rv := reflect.ValueOf(eq.Value)
243261
if rv.Len() == 0 {
244262
builder.WriteString(" IN (NULL)")
@@ -252,7 +270,7 @@ func (eq Eq) Build(builder Builder) {
252270
}
253271
builder.WriteByte(')')
254272
}
255-
default:
273+
} else {
256274
if eqNil(eq.Value) {
257275
builder.WriteString(" IS NULL")
258276
} else {
@@ -272,8 +290,7 @@ type Neq Eq
272290
func (neq Neq) Build(builder Builder) {
273291
builder.WriteQuoted(neq.Column)
274292

275-
switch neq.Value.(type) {
276-
case []string, []int, []int32, []int64, []uint, []uint32, []uint64, []interface{}:
293+
if isValueList(neq.Value) {
277294
builder.WriteString(" NOT IN (")
278295
rv := reflect.ValueOf(neq.Value)
279296
for i := 0; i < rv.Len(); i++ {
@@ -283,7 +300,7 @@ func (neq Neq) Build(builder Builder) {
283300
builder.AddVar(builder, rv.Index(i).Interface())
284301
}
285302
builder.WriteByte(')')
286-
default:
303+
} else {
287304
if eqNil(neq.Value) {
288305
builder.WriteString(" IS NOT NULL")
289306
} else {

‎clause/expression_test.go‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ func TestNamedExpr(t *testing.T) {
160160
}
161161
}
162162

163+
type namedFloat64s []float64
164+
type namedBytes []byte
165+
163166
func TestExpression(t *testing.T) {
164167
column := "column-name"
165168
results := []struct {
@@ -214,6 +217,58 @@ func TestExpression(t *testing.T) {
214217
clause.Eq{Column: column, Value: []string{}},
215218
},
216219
Result: "`column-name` IN (NULL)",
220+
}, {
221+
// slice element types other than the hard-coded ones in Eq/Neq must
222+
// still render as an IN/NOT IN list, not `= (?,?)`
223+
Expressions: []clause.Expression{
224+
clause.Eq{Column: column, Value: []float64{1.5, 2.5}},
225+
},
226+
ExpectedVars: []interface{}{1.5, 2.5},
227+
Result: "`column-name` IN (?,?)",
228+
}, {
229+
Expressions: []clause.Expression{
230+
clause.Neq{Column: column, Value: []float64{1.5, 2.5}},
231+
},
232+
ExpectedVars: []interface{}{1.5, 2.5},
233+
Result: "`column-name` NOT IN (?,?)",
234+
}, {
235+
Expressions: []clause.Expression{
236+
clause.Eq{Column: column, Value: []int8{1, 2}},
237+
},
238+
ExpectedVars: []interface{}{int8(1), int8(2)},
239+
Result: "`column-name` IN (?,?)",
240+
}, {
241+
Expressions: []clause.Expression{
242+
clause.Eq{Column: column, Value: []bool{true, false}},
243+
},
244+
ExpectedVars: []interface{}{true, false},
245+
Result: "`column-name` IN (?,?)",
246+
}, {
247+
// a named slice type must behave the same as its underlying type
248+
Expressions: []clause.Expression{
249+
clause.Eq{Column: column, Value: namedFloat64s{1.5, 2.5}},
250+
},
251+
ExpectedVars: []interface{}{1.5, 2.5},
252+
Result: "`column-name` IN (?,?)",
253+
}, {
254+
// []byte is a single value, never a list
255+
Expressions: []clause.Expression{
256+
clause.Eq{Column: column, Value: []byte{1, 2}},
257+
},
258+
ExpectedVars: []interface{}{[]byte{1, 2}},
259+
Result: "`column-name` = ?",
260+
}, {
261+
Expressions: []clause.Expression{
262+
clause.Eq{Column: column, Value: namedBytes{1, 2}},
263+
},
264+
ExpectedVars: []interface{}{namedBytes{1, 2}},
265+
Result: "`column-name` = ?",
266+
}, {
267+
// an empty list of any element type must still be IN (NULL)
268+
Expressions: []clause.Expression{
269+
clause.Eq{Column: column, Value: []float64{}},
270+
},
271+
Result: "`column-name` IN (NULL)",
217272
}, {
218273
Expressions: []clause.Expression{
219274
clause.Eq{Column: clause.Expr{SQL: "SUM(?)", Vars: []interface{}{clause.Column{Name: "id"}}}, Value: 100},

0 commit comments

Comments
 (0)