fix #213 support typed nil in interpolation

This commit is contained in:
Huan Du
2025-08-11 18:32:23 +08:00
parent 6587b1a288
commit e47fcc5fb9
2 changed files with 119 additions and 3 deletions
+13 -3
View File
@@ -618,12 +618,22 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
buf = append(buf, "', 'YYYY-MM-DD HH24:MI:SS.FF')"...)
}
case fmt.Stringer:
buf = quoteStringValue(buf, v.String(), flavor)
default:
primative := reflect.ValueOf(arg)
// Handle typed nil values (e.g. (*string)(nil), (*time.Time)(nil))
// This check must come before fmt.Stringer check since nil pointers may implement interfaces
if !primative.IsValid() || (primative.Kind() == reflect.Ptr && primative.IsNil()) {
buf = append(buf, "NULL"...)
return buf, nil
}
// Check for fmt.Stringer after nil pointer check
if stringer, ok := arg.(fmt.Stringer); ok {
buf = quoteStringValue(buf, stringer.String(), flavor)
return buf, nil
}
switch k := primative.Kind(); k {
case reflect.Bool:
switch flavor {
+106
View File
@@ -389,3 +389,109 @@ func TestFlavorInterpolate(t *testing.T) {
})
}
}
func TestTypedNilInterpolation(t *testing.T) {
a := assert.New(t)
// Test various typed nil pointers
cases := []struct {
name string
flavor Flavor
sql string
args []interface{}
expected string
}{
{
name: "string pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*string)(nil)},
expected: "SELECT NULL",
},
{
name: "int pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*int)(nil)},
expected: "SELECT NULL",
},
{
name: "float64 pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*float64)(nil)},
expected: "SELECT NULL",
},
{
name: "time.Time pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*time.Time)(nil)},
expected: "SELECT NULL",
},
{
name: "byte slice pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*[]byte)(nil)},
expected: "SELECT NULL",
},
{
name: "PostgreSQL string pointer nil",
flavor: PostgreSQL,
sql: "SELECT $1",
args: []interface{}{(*string)(nil)},
expected: "SELECT NULL",
},
{
name: "SQLite int pointer nil",
flavor: SQLite,
sql: "SELECT ?",
args: []interface{}{(*int)(nil)},
expected: "SELECT NULL",
},
{
name: "Oracle float pointer nil",
flavor: Oracle,
sql: "SELECT :1",
args: []interface{}{(*float64)(nil)},
expected: "SELECT NULL",
},
{
name: "mixed nil types",
flavor: MySQL,
sql: "SELECT ?, ?, ?, ?",
args: []interface{}{(*string)(nil), nil, (*int)(nil), (*[]byte)(nil)},
expected: "SELECT NULL, NULL, NULL, NULL",
},
{
name: "interface pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*fmt.Stringer)(nil)},
expected: "SELECT NULL",
},
{
name: "slice pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*[]int)(nil)},
expected: "SELECT NULL",
},
{
name: "map pointer nil",
flavor: MySQL,
sql: "SELECT ?",
args: []interface{}{(*map[string]int)(nil)},
expected: "SELECT NULL",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
query, err := tc.flavor.Interpolate(tc.sql, tc.args)
a.NilError(err)
a.Equal(tc.expected, query)
})
}
}