Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion ddlmod.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ func parseDDL(strs ...string) (*ddl, error) {
}
if defaultMatches := defaultValueRegexp.FindStringSubmatch(matches[3]); len(defaultMatches) > 1 {
if strings.ToLower(defaultMatches[1]) != "null" {
columnType.DefaultValueValue = sql.NullString{String: strings.Trim(defaultMatches[1], `"`), Valid: true}
// single quotes are standard SQL string literals,
// double quotes come from tables created by older versions
columnType.DefaultValueValue = sql.NullString{String: trimQuote(defaultMatches[1]), Valid: true}
}
}

Expand Down Expand Up @@ -293,3 +295,14 @@ func (d *ddl) removeColumn(name string) bool {

return false
}

// trimQuote removes one pair of matching outer quotes from a default value
// literal, so inner quotes of values like '"x"' survive.
func trimQuote(s string) string {
if len(s) >= 2 {
if (s[0] == '\'' && s[len(s)-1] == '\'') || (s[0] == '"' && s[len(s)-1] == '"') {
return s[1 : len(s)-1]
}
}
return s
}
3 changes: 2 additions & 1 deletion sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ func (dialector Dialector) QuoteTo(writer clause.Writer, str string) {
}

func (dialector Dialector) Explain(sql string, vars ...interface{}) string {
return logger.ExplainSQL(sql, nil, `"`, vars...)
// single quotes: double quotes are identifier quoting in SQLite
return logger.ExplainSQL(sql, nil, `'`, vars...)
}

func (dialector Dialector) DataTypeOf(field *schema.Field) string {
Expand Down
78 changes: 78 additions & 0 deletions sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sqlite
import (
"database/sql"
"fmt"
"strings"
"testing"

"github.com/mattn/go-sqlite3"
Expand Down Expand Up @@ -130,3 +131,80 @@ func TestDialector(t *testing.T) {
})
}
}

func TestExplainQuotesStrings(t *testing.T) {
out := Dialector{}.Explain("SELECT * FROM t WHERE name = ?", "hello")
if !strings.Contains(out, "'hello'") {
t.Errorf("Explain must quote string literals with single quotes, got: %s", out)
}
}

type explainDefaultModel struct {
ID int
Code string `gorm:"default:hello"`
}

func (explainDefaultModel) TableName() string { return "explain_defaults" }

// GORM embeds string default values into the DDL via Dialector.Explain;
// parseDDL must strip the single quotes (and double quotes from tables
// created by older versions) so migrations stay idempotent.
func TestDefaultValueRoundTrip(t *testing.T) {
db, err := gorm.Open(Open("file:explain_defaults?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("gorm.Open: %v", err)
}
t.Cleanup(func() {
if sqlDB, err := db.DB(); err == nil {
_ = sqlDB.Close()
}
})

if err := db.AutoMigrate(&explainDefaultModel{}); err != nil {
t.Fatal(err)
}
cols, err := db.Migrator().ColumnTypes(&explainDefaultModel{})
if err != nil {
t.Fatal(err)
}
for _, c := range cols {
if c.Name() == "code" {
if dv, ok := c.DefaultValue(); !ok || dv != "hello" {
t.Errorf("DefaultValue = (%q,%v), want (hello,true)", dv, ok)
}
}
}
// second AutoMigrate must not rebuild the table
var before string
if err := db.Raw("SELECT sql FROM sqlite_master WHERE type='table' AND name='explain_defaults'").Scan(&before).Error; err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&explainDefaultModel{}); err != nil {
t.Fatal(err)
}
var after string
if err := db.Raw("SELECT sql FROM sqlite_master WHERE type='table' AND name='explain_defaults'").Scan(&after).Error; err != nil {
t.Fatal(err)
}
if before != after {
t.Errorf("DDL changed after second AutoMigrate:\n before: %s\n after: %s", before, after)
}

// double quotes from tables created by older driver versions still parse
d, err := parseDDL("CREATE TABLE `legacy` (`code` text DEFAULT \"hi\")")
if err != nil {
t.Fatal(err)
}
if dv, ok := d.columns[0].DefaultValue(); !ok || dv != "hi" {
t.Errorf("legacy DefaultValue = (%q,%v), want (hi,true)", dv, ok)
}

// only one outer quote pair is stripped; inner quotes survive
d, err = parseDDL("CREATE TABLE `q` (`code` text DEFAULT '\"x\"')")
if err != nil {
t.Fatal(err)
}
if dv, ok := d.columns[0].DefaultValue(); !ok || dv != `"x"` {
t.Errorf(`quoted DefaultValue = (%q,%v), want ("x",true)`, dv, ok)
}
}
Loading