mirror of https://github.com/go-gorm/gorm.git
Add ToSQL support to generate SQL string. (#4787)
* Add db.ToSQL method for generate SQL string. * Improve sql builder test for all dialects. Improve assertEqualSQL test helper for ignore quotes in SQL.
This commit is contained in:
parent
9635d25150
commit
8de266b4a7
15
gorm.go
15
gorm.go
|
@ -441,3 +441,18 @@ func (db *DB) Use(plugin Plugin) error {
|
||||||
db.Plugins[name] = plugin
|
db.Plugins[name] = plugin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSQL for generate SQL string.
|
||||||
|
//
|
||||||
|
// db.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
// return tx.Model(&User{}).Where(&User{Name: "foo", Age: 20})
|
||||||
|
// .Limit(10).Offset(5)
|
||||||
|
// .Order("name ASC")
|
||||||
|
// .First(&User{})
|
||||||
|
// })
|
||||||
|
func (db *DB) ToSQL(queryFn func(tx *DB) *DB) string {
|
||||||
|
tx := queryFn(db.Session(&Session{DryRun: true}))
|
||||||
|
stmt := tx.Statement
|
||||||
|
|
||||||
|
return db.Dialector.Explain(stmt.SQL.String(), stmt.Vars...)
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
. "gorm.io/gorm/utils/tests"
|
. "gorm.io/gorm/utils/tests"
|
||||||
|
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRow(t *testing.T) {
|
func TestRow(t *testing.T) {
|
||||||
|
@ -287,3 +289,136 @@ func TestFromWithJoins(t *testing.T) {
|
||||||
t.Errorf("The first join condition is over written instead of combining")
|
t.Errorf("The first join condition is over written instead of combining")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToSQL(t *testing.T) {
|
||||||
|
// By default DB.DryRun should false
|
||||||
|
if DB.DryRun {
|
||||||
|
t.Fatal("Failed expect DB.DryRun to be false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if DB.Dialector.Name() == "sqlserver" {
|
||||||
|
t.Skip("Skip SQL Server for this test, because it too difference with other dialects.")
|
||||||
|
}
|
||||||
|
|
||||||
|
date, _ := time.Parse("2006-01-02", "2021-10-18")
|
||||||
|
|
||||||
|
// find
|
||||||
|
sql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10`, sql)
|
||||||
|
|
||||||
|
// after model chagned
|
||||||
|
if DB.Statement.DryRun || DB.DryRun {
|
||||||
|
t.Fatal("Failed expect DB.DryRun and DB.Statement.ToSQL to be false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if DB.Statement.SQL.String() != "" {
|
||||||
|
t.Fatal("Failed expect DB.Statement.SQL to be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// first
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Where(&User{Name: "foo", Age: 20}).Limit(10).Offset(5).Order("name ASC").First(&User{})
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `SELECT * FROM "users" WHERE "users"."name" = 'foo' AND "users"."age" = 20 AND "users"."deleted_at" IS NULL ORDER BY name ASC,"users"."id" LIMIT 1 OFFSET 5`, sql)
|
||||||
|
|
||||||
|
// last and unscoped
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Unscoped().Where(&User{Name: "bar", Age: 12}).Limit(10).Offset(5).Order("name ASC").Last(&User{})
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `SELECT * FROM "users" WHERE "users"."name" = 'bar' AND "users"."age" = 12 ORDER BY name ASC,"users"."id" DESC LIMIT 1 OFFSET 5`, sql)
|
||||||
|
|
||||||
|
// create
|
||||||
|
user := &User{Name: "foo", Age: 20}
|
||||||
|
user.CreatedAt = date
|
||||||
|
user.UpdatedAt = date
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Create(user)
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `INSERT INTO "users" ("created_at","updated_at","deleted_at","name","age","birthday","company_id","manager_id","active") VALUES ('2021-10-18 00:00:00','2021-10-18 00:00:00',NULL,'foo',20,NULL,NULL,NULL,false) RETURNING "id"`, sql)
|
||||||
|
|
||||||
|
// save
|
||||||
|
user = &User{Name: "foo", Age: 20}
|
||||||
|
user.CreatedAt = date
|
||||||
|
user.UpdatedAt = date
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Save(user)
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `INSERT INTO "users" ("created_at","updated_at","deleted_at","name","age","birthday","company_id","manager_id","active") VALUES ('2021-10-18 00:00:00','2021-10-18 00:00:00',NULL,'foo',20,NULL,NULL,NULL,false) RETURNING "id"`, sql)
|
||||||
|
|
||||||
|
// updates
|
||||||
|
user = &User{Name: "bar", Age: 22}
|
||||||
|
user.CreatedAt = date
|
||||||
|
user.UpdatedAt = date
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Where("id = ?", 100).Updates(user)
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `UPDATE "users" SET "created_at"='2021-10-18 00:00:00',"updated_at"='2021-10-18 19:50:09.438',"name"='bar',"age"=22 WHERE id = 100 AND "users"."deleted_at" IS NULL`, sql)
|
||||||
|
|
||||||
|
// update
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Where("id = ?", 100).Update("name", "Foo bar")
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `UPDATE "users" SET "name"='Foo bar',"updated_at"='2021-10-18 19:50:09.438' WHERE id = 100 AND "users"."deleted_at" IS NULL`, sql)
|
||||||
|
|
||||||
|
// UpdateColumn
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Where("id = ?", 100).UpdateColumn("name", "Foo bar")
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `UPDATE "users" SET "name"='Foo bar' WHERE id = 100 AND "users"."deleted_at" IS NULL`, sql)
|
||||||
|
|
||||||
|
// UpdateColumns
|
||||||
|
sql = DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Model(&User{}).Where("id = ?", 100).UpdateColumns(User{Name: "Foo", Age: 100})
|
||||||
|
})
|
||||||
|
assertEqualSQL(t, `UPDATE "users" SET "name"='Foo',"age"=100 WHERE id = 100 AND "users"."deleted_at" IS NULL`, sql)
|
||||||
|
|
||||||
|
// after model chagned
|
||||||
|
if DB.Statement.DryRun || DB.DryRun {
|
||||||
|
t.Fatal("Failed expect DB.DryRun and DB.Statement.ToSQL to be false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertEqualSQL for assert that the sql is equal, this method will ignore quote, and dialect speicals.
|
||||||
|
func assertEqualSQL(t *testing.T, expected string, actually string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// replace SQL quote, convert into postgresql like ""
|
||||||
|
expected = replaceQuoteInSQL(expected)
|
||||||
|
actually = replaceQuoteInSQL(actually)
|
||||||
|
|
||||||
|
// ignore updated_at value, becase it's generated in Gorm inernal, can't to mock value on update.
|
||||||
|
var updatedAtRe = regexp.MustCompile(`(?i)"updated_at"=".+?"`)
|
||||||
|
actually = updatedAtRe.ReplaceAllString(actually, `"updated_at"=?`)
|
||||||
|
expected = updatedAtRe.ReplaceAllString(expected, `"updated_at"=?`)
|
||||||
|
|
||||||
|
// ignore RETURNING "id" (only in PostgreSQL)
|
||||||
|
var returningRe = regexp.MustCompile(`(?i)RETURNING "id"`)
|
||||||
|
actually = returningRe.ReplaceAllString(actually, ``)
|
||||||
|
expected = returningRe.ReplaceAllString(expected, ``)
|
||||||
|
|
||||||
|
actually = strings.TrimSpace(actually)
|
||||||
|
expected = strings.TrimSpace(expected)
|
||||||
|
|
||||||
|
if actually != expected {
|
||||||
|
t.Fatalf("\nexpected: %s\nactually: %s", expected, actually)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceQuoteInSQL(sql string) string {
|
||||||
|
// convert single quote into double quote
|
||||||
|
sql = strings.Replace(sql, `'`, `"`, -1)
|
||||||
|
|
||||||
|
// convert dialect speical quote into double quote
|
||||||
|
switch DB.Dialector.Name() {
|
||||||
|
case "postgres":
|
||||||
|
sql = strings.Replace(sql, `"`, `"`, -1)
|
||||||
|
case "mysql", "sqlite":
|
||||||
|
sql = strings.Replace(sql, "`", `"`, -1)
|
||||||
|
case "sqlserver":
|
||||||
|
sql = strings.Replace(sql, `'`, `"`, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue