Repository: go-gorm/gorm
Pull request #6503 (2023-08-04 02:58:41)

fix(clause): clause equal empty array
<!--
Make sure these boxes checked before submitting your pull request.

For significant changes, please open an issue to make an agreement on an implementation design/plan first before starting it.
-->

- [ ] Do only one thing
- [ ] Non breaking API changes
- [ ] Tested

### What did this pull request do?
when the value of `clause.Eq` is an empty array, the SQL should be `IN (NULL)`
<!--
provide a general description of the code changes in your pull request
-->

### User Case Description

<!-- Your use case -->



Patch:
diff --git a/clause/expression.go b/clause/expression.go
index 92ac7f2..8d01052 100644
--- a/clause/expression.go
+++ b/clause/expression.go
@@ -246,15 +246,19 @@ func (eq Eq) Build(builder Builder) {
 
 	switch eq.Value.(type) {
 	case []string, []int, []int32, []int64, []uint, []uint32, []uint64, []interface{}:
-		builder.WriteString(" IN (")
 		rv := reflect.ValueOf(eq.Value)
-		for i := 0; i < rv.Len(); i++ {
-			if i > 0 {
-				builder.WriteByte(',')
+		if rv.Len() == 0 {
+			builder.WriteString(" IN (NULL)")
+		} else {
+			builder.WriteString(" IN (")
+			for i := 0; i < rv.Len(); i++ {
+				if i > 0 {
+					builder.WriteByte(',')
+				}
+				builder.AddVar(builder, rv.Index(i).Interface())
 			}
-			builder.AddVar(builder, rv.Index(i).Interface())
+			builder.WriteByte(')')
 		}
-		builder.WriteByte(')')
 	default:
 		if eqNil(eq.Value) {
 			builder.WriteString(" IS NULL")
diff --git a/logger/sql.go b/logger/sql.go
index 1521c1f..13e5d95 100644
--- a/logger/sql.go
+++ b/logger/sql.go
@@ -28,8 +28,10 @@ func isPrintable(s string) bool {
 	return true
 }
 
+// A list of Go types that should be converted to SQL primitives
 var convertibleTypes = []reflect.Type{reflect.TypeOf(time.Time{}), reflect.TypeOf(false), reflect.TypeOf([]byte{})}
 
+// RegEx matches only numeric values
 var numericPlaceholderRe = regexp.MustCompile(`\$\d+\$`)
 
 // ExplainSQL generate SQL string with given parameters, the generated SQL is expected to be used in logger, execute it might introduce a SQL injection vulnerability
diff --git a/migrator/migrator.go b/migrator/migrator.go
index de60f91..b15a43e 100644
--- a/migrator/migrator.go
+++ b/migrator/migrator.go
@@ -16,8 +16,17 @@ import (
 	"gorm.io/gorm/schema"
 )
 
+// This regular expression seeks to find a sequence of digits (\d+) among zero or more non-digit characters (\D*),
+// with a possible trailing non-digit character (\D?).
+
+// For example, values that can pass this regular expression are:
+// - "123"
+// - "abc456"
+// -"%$#@789"
 var regFullDataType = regexp.MustCompile(`\D*(\d+)\D?`)
 
+// TODO:? Create const vars for raw sql queries ?
+
 // Migrator m struct
 type Migrator struct {
 	Config
diff --git a/prepare_stmt.go b/prepare_stmt.go
index 10fefc3..9d98c86 100644
--- a/prepare_stmt.go
+++ b/prepare_stmt.go
@@ -30,15 +30,19 @@ func NewPreparedStmtDB(connPool ConnPool) *PreparedStmtDB {
 	}
 }
 
-func (db *PreparedStmtDB) GetDBConn() (*sql.DB, error) {
-	if dbConnector, ok := db.ConnPool.(GetDBConnector); ok && dbConnector != nil {
-		return dbConnector.GetDBConn()
-	}
-
+func (db *PreparedStmtDB) GetDBConnWithContext(gormdb *DB) (*sql.DB, error) {
 	if sqldb, ok := db.ConnPool.(*sql.DB); ok {
 		return sqldb, nil
 	}
 
+	if connector, ok := db.ConnPool.(GetDBConnectorWithContext); ok && connector != nil {
+		return connector.GetDBConnWithContext(gormdb)
+	}
+
+	if dbConnector, ok := db.ConnPool.(GetDBConnector); ok && dbConnector != nil {
+		return dbConnector.GetDBConn()
+	}
+
 	return nil, ErrInvalidDB
 }
 
@@ -54,15 +58,15 @@ func (db *PreparedStmtDB) Close() {
 	}
 }
 
-func (db *PreparedStmtDB) Reset() {
-	db.Mux.Lock()
-	defer db.Mux.Unlock()
+func (sdb *PreparedStmtDB) Reset() {
+	sdb.Mux.Lock()
+	defer sdb.Mux.Unlock()
 
-	for _, stmt := range db.Stmts {
+	for _, stmt := range sdb.Stmts {
 		go stmt.Close()
 	}
-	db.PreparedSQL = make([]string, 0, 100)
-	db.Stmts = make(map[string]*Stmt)
+	sdb.PreparedSQL = make([]string, 0, 100)
+	sdb.Stmts = make(map[string]*Stmt)
 }
 
 func (db *PreparedStmtDB) prepare(ctx context.Context, conn ConnPool, isTransaction bool, query string) (Stmt, error) {


Test patch:
diff --git a/clause/expression_test.go b/clause/expression_test.go
index aaede61..b997bf1 100644
--- a/clause/expression_test.go
+++ b/clause/expression_test.go
@@ -199,6 +199,11 @@ func TestExpression(t *testing.T) {
 		},
 		ExpectedVars: []interface{}{"a", "b"},
 		Result:       "`column-name` NOT IN (?,?)",
+	}, {
+		Expressions: []clause.Expression{
+			clause.Eq{Column: column, Value: []string{}},
+		},
+		Result: "`column-name` IN (NULL)",
 	}, {
 		Expressions: []clause.Expression{
 			clause.Eq{Column: clause.Expr{SQL: "SUM(?)", Vars: []interface{}{clause.Column{Name: "id"}}}, Value: 100},
diff --git a/logger/sql_test.go b/logger/sql_test.go
index e4a7274..d9afe39 100644
--- a/logger/sql_test.go
+++ b/logger/sql_test.go
@@ -101,7 +101,6 @@ func TestExplainSQL(t *testing.T) {
 			Vars:          []interface{}{"jinzhu", 1, 0.1753607109, true, []byte("12345"), tt, &tt, nil, "w@g.\"com", myrole, pwd, &js, &es},
 			Result:        fmt.Sprintf(`create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass, json_struct, example_struct) values ("jinzhu", 1, 0.1753607109, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.\"com", "admin", "pass", %v, %v)`, format(jsVal, `"`), format(esVal, `"`)),
 		},
-
 	}
 
 	for idx, r := range results {
diff --git a/tests/go.mod b/tests/go.mod
index 147d0a7..7a89ee0 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -22,9 +22,9 @@ require (
 	github.com/jackc/pgx/v5 v5.4.2 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/mattn/go-sqlite3 v1.14.17 // indirect
-	github.com/microsoft/go-mssqldb v1.4.0 // indirect
-	golang.org/x/crypto v0.11.0 // indirect
-	golang.org/x/text v0.11.0 // indirect
+	github.com/microsoft/go-mssqldb v1.5.0 // indirect
+	golang.org/x/crypto v0.12.0 // indirect
+	golang.org/x/text v0.12.0 // indirect
 )
 
 replace gorm.io/gorm => ../