Repository: go-gorm/gorm
Pull request #6417 (2023-06-16 03:08:39)

fix schema GetIdentityFieldValuesMap interface or ptr
<!--
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.
-->

- [ x ] Do only one thing
- [ x ] Non breaking API changes
- [ x ] Tested

### What did this pull request do?

我的使用场景遇到了 类型为 interface 的问题

### User Case Description

使用 reflect 构建的 model 对象在 Find 中 Preload 失效

https://github.com/go-gorm/gorm/issues/6416



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/go.sum b/go.sum
index fb4240e..bd6104c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,4 @@
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
-github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
diff --git a/gorm.go b/gorm.go
index 9297850..3219387 100644
--- a/gorm.go
+++ b/gorm.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
+	"reflect"
 	"sort"
 	"sync"
 	"time"
@@ -374,9 +375,11 @@ func (db *DB) AddError(err error) error {
 // DB returns `*sql.DB`
 func (db *DB) DB() (*sql.DB, error) {
 	connPool := db.ConnPool
-
-	if connector, ok := connPool.(GetDBConnectorWithContext); ok && connector != nil {
-		return connector.GetDBConnWithContext(db)
+	if db.Statement != nil && db.Statement.ConnPool != nil {
+		connPool = db.Statement.ConnPool
+	}
+	if tx, ok := connPool.(*sql.Tx); ok && tx != nil {
+		return (*sql.DB)(reflect.ValueOf(tx).Elem().FieldByName("db").UnsafePointer()), nil
 	}
 
 	if dbConnector, ok := connPool.(GetDBConnector); ok && dbConnector != nil {
@@ -399,11 +402,12 @@ func (db *DB) getInstance() *DB {
 		if db.clone == 1 {
 			// clone with new statement
 			tx.Statement = &Statement{
-				DB:       tx,
-				ConnPool: db.Statement.ConnPool,
-				Context:  db.Statement.Context,
-				Clauses:  map[string]clause.Clause{},
-				Vars:     make([]interface{}, 0, 8),
+				DB:        tx,
+				ConnPool:  db.Statement.ConnPool,
+				Context:   db.Statement.Context,
+				Clauses:   map[string]clause.Clause{},
+				Vars:      make([]interface{}, 0, 8),
+				SkipHooks: db.Statement.SkipHooks,
 			}
 		} else {
 			// with clone statement
diff --git a/interfaces.go b/interfaces.go
index 1950d74..3bcc3d5 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -77,12 +77,6 @@ type GetDBConnector interface {
 	GetDBConn() (*sql.DB, error)
 }
 
-// GetDBConnectorWithContext represents SQL db connector which takes into
-// account the current database context
-type GetDBConnectorWithContext interface {
-	GetDBConnWithContext(db *DB) (*sql.DB, error)
-}
-
 // Rows rows interface
 type Rows interface {
 	Columns() ([]string, error)
diff --git a/logger/sql.go b/logger/sql.go
index bcacc7c..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
@@ -93,8 +95,10 @@ func ExplainSQL(sql string, numericPlaceholder *regexp.Regexp, escaper string, a
 			}
 		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
 			vars[idx] = utils.ToString(v)
-		case float64, float32:
-			vars[idx] = fmt.Sprintf("%.6f", v)
+		case float32:
+			vars[idx] = strconv.FormatFloat(float64(v), 'f', -1, 32)
+		case float64:
+			vars[idx] = strconv.FormatFloat(v, 'f', -1, 64)
 		case string:
 			vars[idx] = escaper + strings.ReplaceAll(v, escaper, "\\"+escaper) + escaper
 		default:
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..aa94462 100644
--- a/prepare_stmt.go
+++ b/prepare_stmt.go
@@ -31,14 +31,14 @@ func NewPreparedStmtDB(connPool ConnPool) *PreparedStmtDB {
 }
 
 func (db *PreparedStmtDB) GetDBConn() (*sql.DB, error) {
-	if dbConnector, ok := db.ConnPool.(GetDBConnector); ok && dbConnector != nil {
-		return dbConnector.GetDBConn()
-	}
-
 	if sqldb, ok := db.ConnPool.(*sql.DB); ok {
 		return sqldb, nil
 	}
 
+	if dbConnector, ok := db.ConnPool.(GetDBConnector); ok && dbConnector != nil {
+		return dbConnector.GetDBConn()
+	}
+
 	return nil, ErrInvalidDB
 }
 
@@ -54,15 +54,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) {
@@ -127,6 +127,19 @@ func (db *PreparedStmtDB) BeginTx(ctx context.Context, opt *sql.TxOptions) (Conn
 		tx, err := beginner.BeginTx(ctx, opt)
 		return &PreparedStmtTX{PreparedStmtDB: db, Tx: tx}, err
 	}
+
+	beginner, ok := db.ConnPool.(ConnPoolBeginner)
+	if !ok {
+		return nil, ErrInvalidTransaction
+	}
+
+	connPool, err := beginner.BeginTx(ctx, opt)
+	if err != nil {
+		return nil, err
+	}
+	if tx, ok := connPool.(Tx); ok {
+		return &PreparedStmtTX{PreparedStmtDB: db, Tx: tx}, nil
+	}
 	return nil, ErrInvalidTransaction
 }
 
@@ -172,6 +185,10 @@ type PreparedStmtTX struct {
 	PreparedStmtDB *PreparedStmtDB
 }
 
+func (db *PreparedStmtTX) GetDBConn() (*sql.DB, error) {
+	return db.PreparedStmtDB.GetDBConn()
+}
+
 func (tx *PreparedStmtTX) Commit() error {
 	if tx.Tx != nil && !reflect.ValueOf(tx.Tx).IsNil() {
 		return tx.Tx.Commit()
diff --git a/schema/utils.go b/schema/utils.go
index 65d012e..7fdda18 100644
--- a/schema/utils.go
+++ b/schema/utils.go
@@ -115,6 +115,11 @@ func GetIdentityFieldValuesMap(ctx context.Context, reflectValue reflect.Value,
 		notZero, zero bool
 	)
 
+	if reflectValue.Kind() == reflect.Ptr ||
+		reflectValue.Kind() == reflect.Interface {
+		reflectValue = reflectValue.Elem()
+	}
+
 	switch reflectValue.Kind() {
 	case reflect.Struct:
 		results = [][]interface{}{make([]interface{}, len(fields))}


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 c5b181a..a82fa54 100644
--- a/logger/sql_test.go
+++ b/logger/sql_test.go
@@ -57,43 +57,55 @@ func TestExplainSQL(t *testing.T) {
 			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 			NumericRegexp: nil,
 			Vars:          []interface{}{"jinzhu", 1, 999.99, true, []byte("12345"), tt, &tt, nil, "w@g.\"com", myrole, pwd},
-			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.990000, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.\"com", "admin", "pass")`,
+			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.99, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.\"com", "admin", "pass")`,
 		},
 		{
 			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 			NumericRegexp: nil,
 			Vars:          []interface{}{"jinzhu?", 1, 999.99, true, []byte("12345"), tt, &tt, nil, "w@g.\"com", myrole, pwd},
-			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu?", 1, 999.990000, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.\"com", "admin", "pass")`,
+			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu?", 1, 999.99, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.\"com", "admin", "pass")`,
 		},
 		{
 			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values (@p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11)",
 			NumericRegexp: regexp.MustCompile(`@p(\d+)`),
 			Vars:          []interface{}{"jinzhu", 1, 999.99, true, []byte("12345"), tt, &tt, nil, "w@g.com", myrole, pwd},
-			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.990000, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.com", "admin", "pass")`,
+			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.99, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.com", "admin", "pass")`,
 		},
 		{
 			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ($3, $4, $1, $2, $7, $8, $5, $6, $9, $10, $11)",
 			NumericRegexp: regexp.MustCompile(`\$(\d+)`),
 			Vars:          []interface{}{999.99, true, "jinzhu", 1, &tt, nil, []byte("12345"), tt, "w@g.com", myrole, pwd},
-			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.990000, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.com", "admin", "pass")`,
+			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.99, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.com", "admin", "pass")`,
 		},
 		{
 			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values (@p1, @p11, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10)",
 			NumericRegexp: regexp.MustCompile(`@p(\d+)`),
 			Vars:          []interface{}{"jinzhu", 999.99, true, []byte("12345"), tt, &tt, nil, "w@g.com", myrole, pwd, 1},
-			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.990000, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.com", "admin", "pass")`,
+			Result:        `create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass) values ("jinzhu", 1, 999.99, true, "12345", "2020-02-23 11:10:10", "2020-02-23 11:10:10", NULL, "w@g.com", "admin", "pass")`,
 		},
 		{
 			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass, json_struct, example_struct) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 			NumericRegexp: nil,
 			Vars:          []interface{}{"jinzhu", 1, 999.99, 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, 999.990000, 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, `"`)),
+			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, 999.99, 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, `"`)),
 		},
 		{
 			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass, json_struct, example_struct) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 			NumericRegexp: nil,
 			Vars:          []interface{}{"jinzhu", 1, 999.99, 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, 999.990000, 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, `"`)),
+			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, 999.99, 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, `"`)),
+		},
+		{
+			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass, json_struct, example_struct) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+			NumericRegexp: nil,
+			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, `"`)),
+		},
+		{
+			SQL:           "create table users (name, age, height, actived, bytes, create_at, update_at, deleted_at, email, role, pass, json_struct, example_struct) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+			NumericRegexp: nil,
+			Vars:          []interface{}{"jinzhu", 1, float32(999.99), 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, 999.99, 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, `"`)),
 		},
 	}
 
diff --git a/tests/error_translator_test.go b/tests/error_translator_test.go
index f6c7067..ee54300 100644
--- a/tests/error_translator_test.go
+++ b/tests/error_translator_test.go
@@ -44,6 +44,8 @@ func TestSupportedDialectorWithErrDuplicatedKey(t *testing.T) {
 		return
 	}
 
+	DB.Migrator().DropTable(&City{})
+
 	if err = db.AutoMigrate(&City{}); err != nil {
 		t.Fatalf("failed to migrate cities table, got error: %v", err)
 	}
@@ -58,3 +60,52 @@ func TestSupportedDialectorWithErrDuplicatedKey(t *testing.T) {
 		t.Fatalf("expected err: %v got err: %v", gorm.ErrDuplicatedKey, err)
 	}
 }
+
+func TestSupportedDialectorWithErrForeignKeyViolated(t *testing.T) {
+	tidbSkip(t, "not support the foreign key feature")
+
+	type City struct {
+		gorm.Model
+		Name string `gorm:"unique"`
+	}
+
+	type Museum struct {
+		gorm.Model
+		Name   string `gorm:"unique"`
+		CityID uint
+		City   City `gorm:"Constraint:OnUpdate:CASCADE,OnDelete:CASCADE;FOREIGNKEY:CityID;References:ID"`
+	}
+
+	db, err := OpenTestConnection(&gorm.Config{TranslateError: true})
+	if err != nil {
+		t.Fatalf("failed to connect database, got error %v", err)
+	}
+
+	dialectors := map[string]bool{"sqlite": true, "postgres": true, "mysql": true, "sqlserver": true}
+	if supported, found := dialectors[db.Dialector.Name()]; !(found && supported) {
+		return
+	}
+
+	DB.Migrator().DropTable(&City{}, &Museum{})
+
+	if err = db.AutoMigrate(&City{}, &Museum{}); err != nil {
+		t.Fatalf("failed to migrate countries & cities tables, got error: %v", err)
+	}
+
+	city := City{Name: "Amsterdam"}
+
+	err = db.Create(&city).Error
+	if err != nil {
+		t.Fatalf("failed to create city: %v", err)
+	}
+
+	err = db.Create(&Museum{Name: "Eye Filmmuseum", CityID: city.ID}).Error
+	if err != nil {
+		t.Fatalf("failed to create museum: %v", err)
+	}
+
+	err = db.Create(&Museum{Name: "Dungeon", CityID: 123}).Error
+	if !errors.Is(err, gorm.ErrForeignKeyViolated) {
+		t.Fatalf("expected err: %v got err: %v", gorm.ErrForeignKeyViolated, err)
+	}
+}
diff --git a/tests/go.mod b/tests/go.mod
index 0b38b9d..7a89ee0 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -1,18 +1,30 @@
 module gorm.io/gorm/tests
 
-go 1.16
+go 1.18
 
 require (
 	github.com/google/uuid v1.3.0
-	github.com/jackc/pgx/v5 v5.3.1 // indirect
 	github.com/jinzhu/now v1.1.5
-	github.com/lib/pq v1.10.8
-	github.com/mattn/go-sqlite3 v1.14.16 // indirect
-	gorm.io/driver/mysql v1.5.0
-	gorm.io/driver/postgres v1.5.0
-	gorm.io/driver/sqlite v1.5.0
-	gorm.io/driver/sqlserver v1.5.1
-	gorm.io/gorm v1.25.1
+	github.com/lib/pq v1.10.9
+	gorm.io/driver/mysql v1.5.2-0.20230612053416-48b6526a21f0
+	gorm.io/driver/postgres v1.5.3-0.20230607070428-18bc84b75196
+	gorm.io/driver/sqlite v1.5.2
+	gorm.io/driver/sqlserver v1.5.2-0.20230613072041-6e2cde390b0a
+	gorm.io/gorm v1.25.2
+)
+
+require (
+	github.com/go-sql-driver/mysql v1.7.1 // indirect
+	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
+	github.com/golang-sql/sqlexp v0.1.0 // indirect
+	github.com/jackc/pgpassfile v1.0.0 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+	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.5.0 // indirect
+	golang.org/x/crypto v0.12.0 // indirect
+	golang.org/x/text v0.12.0 // indirect
 )
 
 replace gorm.io/gorm => ../
diff --git a/tests/helper_test.go b/tests/helper_test.go
index c34e357..1a4874e 100644
--- a/tests/helper_test.go
+++ b/tests/helper_test.go
@@ -265,6 +265,10 @@ func isTiDB() bool {
 	return os.Getenv("GORM_DIALECT") == "tidb"
 }
 
+func isMysql() bool {
+	return os.Getenv("GORM_DIALECT") == "mysql"
+}
+
 func db(unscoped bool) *gorm.DB {
 	if unscoped {
 		return DB.Unscoped()
diff --git a/tests/migrate_test.go b/tests/migrate_test.go
index 69f8641..849e2b7 100644
--- a/tests/migrate_test.go
+++ b/tests/migrate_test.go
@@ -1598,3 +1598,48 @@ func TestMigrateExistingBoolColumnPG(t *testing.T) {
 		}
 	}
 }
+
+func TestTableType(t *testing.T) {
+	// currently it is only supported for mysql driver
+	if !isMysql() {
+		return
+	}
+
+	const tblName = "cities"
+	const tblSchema = "gorm"
+	const tblType = "BASE TABLE"
+	const tblComment = "foobar comment"
+
+	type City struct {
+		gorm.Model
+		Name string `gorm:"unique"`
+	}
+
+	DB.Migrator().DropTable(&City{})
+
+	if err := DB.Set("gorm:table_options", fmt.Sprintf("ENGINE InnoDB COMMENT '%s'", tblComment)).AutoMigrate(&City{}); err != nil {
+		t.Fatalf("failed to migrate cities tables, got error: %v", err)
+	}
+
+	tableType, err := DB.Table("cities").Migrator().TableType(&City{})
+	if err != nil {
+		t.Fatalf("failed to get table type, got error %v", err)
+	}
+
+	if tableType.Schema() != tblSchema {
+		t.Fatalf("expected tblSchema to be %s but got %s", tblSchema, tableType.Schema())
+	}
+
+	if tableType.Name() != tblName {
+		t.Fatalf("expected table name to be %s but got %s", tblName, tableType.Name())
+	}
+
+	if tableType.Type() != tblType {
+		t.Fatalf("expected table type to be %s but got %s", tblType, tableType.Type())
+	}
+
+	comment, ok := tableType.Comment()
+	if !ok || comment != tblComment {
+		t.Fatalf("expected comment %s got %s", tblComment, comment)
+	}
+}
diff --git a/tests/query_test.go b/tests/query_test.go
index b6bd073..5728378 100644
--- a/tests/query_test.go
+++ b/tests/query_test.go
@@ -2,6 +2,7 @@ package tests_test
 
 import (
 	"database/sql"
+	"database/sql/driver"
 	"fmt"
 	"reflect"
 	"regexp"
@@ -658,6 +659,18 @@ func TestOrWithAllFields(t *testing.T) {
 	}
 }
 
+type Int64 int64
+
+func (v Int64) Value() (driver.Value, error) {
+	return v - 1, nil
+}
+
+func (f *Int64) Scan(v interface{}) error {
+	y := v.(int64)
+	*f = Int64(y + 1)
+	return nil
+}
+
 func TestPluck(t *testing.T) {
 	users := []*User{
 		GetUser("pluck-user1", Config{}),
@@ -685,6 +698,11 @@ func TestPluck(t *testing.T) {
 		t.Errorf("got error when pluck id: %v", err)
 	}
 
+	var ids2 []Int64
+	if err := DB.Model(User{}).Where("name like ?", "pluck-user%").Pluck("id", &ids2).Error; err != nil {
+		t.Errorf("got error when pluck id: %v", err)
+	}
+
 	for idx, name := range names {
 		if name != users[idx].Name {
 			t.Errorf("Unexpected result on pluck name, got %+v", names)
@@ -697,6 +715,12 @@ func TestPluck(t *testing.T) {
 		}
 	}
 
+	for idx, id := range ids2 {
+		if int(id) != int(users[idx].ID+1) {
+			t.Errorf("Unexpected result on pluck id, got %+v", ids)
+		}
+	}
+
 	var times []time.Time
 	if err := DB.Model(User{}).Where("name like ?", "pluck-user%").Pluck("created_at", &times).Error; err != nil {
 		t.Errorf("got error when pluck time: %v", err)
diff --git a/tests/tests_test.go b/tests/tests_test.go
index 0167d40..47c2a7c 100644
--- a/tests/tests_test.go
+++ b/tests/tests_test.go
@@ -89,7 +89,7 @@ func OpenTestConnection(cfg *gorm.Config) (db *gorm.DB, err error) {
 		db, err = gorm.Open(mysql.Open(dbDSN), cfg)
 	default:
 		log.Println("testing sqlite3...")
-		db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db")), cfg)
+		db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db?_foreign_keys=on")), cfg)
 	}
 
 	if err != nil {