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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Jason Lee
						Jason Lee