Skip to content

Commit 205217f

Browse files
committed
Merge branch 'orig'
2 parents 963e2cc + 397ec6f commit 205217f

File tree

6 files changed

+209
-43
lines changed

6 files changed

+209
-43
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
vendor/
1+
.idea/

ddlmod.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,26 @@ import (
1313

1414
var (
1515
sqliteSeparator = "`|\"|'|\t"
16-
indexRegexp = regexp.MustCompile(fmt.Sprintf("(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\\w\\d-]+[%v]? ON (.*)$", sqliteSeparator, sqliteSeparator))
17-
tableRegexp = regexp.MustCompile(fmt.Sprintf("(?is)(CREATE TABLE [%v]?[\\w\\d-]+[%v]?)(?: \\((.*)\\))?", sqliteSeparator, sqliteSeparator))
16+
indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]? ON (.*)$`, sqliteSeparator, sqliteSeparator))
17+
tableRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)(CREATE TABLE [%v]?[\w\d-]+[%v]?)(?:\s*\((.*)\))?`, sqliteSeparator, sqliteSeparator))
1818
separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator))
19-
columnsRegexp = regexp.MustCompile(fmt.Sprintf("\\([%v]?([\\w\\d]+)[%v]?(?:,[%v]?([\\w\\d]+)[%v]){0,}\\)", sqliteSeparator, sqliteSeparator, sqliteSeparator, sqliteSeparator))
20-
columnRegexp = regexp.MustCompile(fmt.Sprintf("^[%v]?([\\w\\d]+)[%v]?\\s+([\\w\\(\\)\\d]+)(.*)$", sqliteSeparator, sqliteSeparator))
21-
defaultValueRegexp = regexp.MustCompile("(?i) DEFAULT \\(?(.+)?\\)?( |COLLATE|GENERATED|$)")
19+
columnsRegexp = regexp.MustCompile(fmt.Sprintf(`[(,][%v]?(\w+)[%v]?`, sqliteSeparator, sqliteSeparator))
20+
columnRegexp = regexp.MustCompile(fmt.Sprintf(`^[%v]?([\w\d]+)[%v]?\s+([\w\(\)\d]+)(.*)$`, sqliteSeparator, sqliteSeparator))
21+
defaultValueRegexp = regexp.MustCompile(`(?i) DEFAULT \(?(.+)?\)?( |COLLATE|GENERATED|$)`)
2222
regRealDataType = regexp.MustCompile(`[^\d](\d+)[^\d]?`)
2323
)
2424

25+
func getAllColumns(s string) []string {
26+
allMatches := columnsRegexp.FindAllStringSubmatch(s, -1)
27+
columns := make([]string, 0, len(allMatches))
28+
for _, matches := range allMatches {
29+
if len(matches) > 1 {
30+
columns = append(columns, matches[1])
31+
}
32+
}
33+
return columns
34+
}
35+
2536
type ddl struct {
2637
head string
2738
fields []string
@@ -98,15 +109,12 @@ func parseDDL(strs ...string) (*ddl, error) {
98109
}
99110

100111
if strings.HasPrefix(fUpper, "PRIMARY KEY") {
101-
matches := columnsRegexp.FindStringSubmatch(f)
102-
if len(matches) > 1 {
103-
for _, name := range matches[1:] {
104-
for idx, column := range result.columns {
105-
if column.NameValue.String == name {
106-
column.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
107-
result.columns[idx] = column
108-
break
109-
}
112+
for _, name := range getAllColumns(f) {
113+
for idx, column := range result.columns {
114+
if column.NameValue.String == name {
115+
column.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
116+
result.columns[idx] = column
117+
break
110118
}
111119
}
112120
}
@@ -151,10 +159,10 @@ func parseDDL(strs ...string) (*ddl, error) {
151159
}
152160
}
153161
} else if matches := indexRegexp.FindStringSubmatch(str); len(matches) > 0 {
154-
if columns := columnsRegexp.FindStringSubmatch(matches[1]); len(columns) == 1 {
162+
for _, column := range getAllColumns(matches[1]) {
155163
for idx, c := range result.columns {
156-
if c.NameValue.String == columns[0] {
157-
c.UniqueValue = sql.NullBool{Bool: true, Valid: true}
164+
if c.NameValue.String == column {
165+
c.UniqueValue = sql.NullBool{Bool: strings.ToUpper(strings.Fields(str)[1]) == "UNIQUE", Valid: true}
158166
result.columns[idx] = c
159167
}
160168
}

ddlmod_test.go

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestParseDDL(t *testing.T) {
2020
"CREATE UNIQUE INDEX `idx_profiles_refer` ON `profiles`(`text`)",
2121
}, 6, []migrator.ColumnType{
2222
{NameValue: sql.NullString{String: "id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, PrimaryKeyValue: sql.NullBool{Bool: true, Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, DefaultValueValue: sql.NullString{Valid: false}},
23-
{NameValue: sql.NullString{String: "text", Valid: true}, DataTypeValue: sql.NullString{String: "varchar", Valid: true}, LengthValue: sql.NullInt64{Int64: 500, Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, DefaultValueValue: sql.NullString{String: "hello", Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
23+
{NameValue: sql.NullString{String: "text", Valid: true}, DataTypeValue: sql.NullString{String: "varchar", Valid: true}, LengthValue: sql.NullInt64{Int64: 500, Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, DefaultValueValue: sql.NullString{String: "hello", Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Bool: true, Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
2424
{NameValue: sql.NullString{String: "age", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{String: "18", Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
2525
{NameValue: sql.NullString{String: "user_id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{Valid: false}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
2626
},
@@ -56,11 +56,47 @@ func TestParseDDL(t *testing.T) {
5656
ColumnTypeValue: sql.NullString{String: "int", Valid: true},
5757
NullableValue: sql.NullBool{Bool: false, Valid: true},
5858
DefaultValueValue: sql.NullString{Valid: false},
59-
UniqueValue: sql.NullBool{Valid: true},
59+
UniqueValue: sql.NullBool{Bool: true, Valid: true},
6060
PrimaryKeyValue: sql.NullBool{Valid: true},
6161
},
6262
},
6363
},
64+
{
65+
"unique index",
66+
[]string{
67+
"CREATE TABLE `test-b` (`field` integer NOT NULL)",
68+
"CREATE UNIQUE INDEX `idx_uq` ON `test-b`(`field`) WHERE field = 0",
69+
},
70+
1,
71+
[]migrator.ColumnType{
72+
{
73+
NameValue: sql.NullString{String: "field", Valid: true},
74+
DataTypeValue: sql.NullString{String: "integer", Valid: true},
75+
ColumnTypeValue: sql.NullString{String: "integer", Valid: true},
76+
PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true},
77+
UniqueValue: sql.NullBool{Bool: true, Valid: true},
78+
NullableValue: sql.NullBool{Bool: false, Valid: true},
79+
},
80+
},
81+
},
82+
{
83+
"non-unique index",
84+
[]string{
85+
"CREATE TABLE `test-c` (`field` integer NOT NULL)",
86+
"CREATE INDEX `idx_uq` ON `test-b`(`field`) WHERE field = 0",
87+
},
88+
1,
89+
[]migrator.ColumnType{
90+
{
91+
NameValue: sql.NullString{String: "field", Valid: true},
92+
DataTypeValue: sql.NullString{String: "integer", Valid: true},
93+
ColumnTypeValue: sql.NullString{String: "integer", Valid: true},
94+
PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true},
95+
UniqueValue: sql.NullBool{Bool: false, Valid: true},
96+
NullableValue: sql.NullBool{Bool: false, Valid: true},
97+
},
98+
},
99+
},
64100
}
65101

66102
for _, p := range params {
@@ -80,6 +116,75 @@ func TestParseDDL(t *testing.T) {
80116
}
81117
}
82118

119+
func TestParseDDL_Whitespaces(t *testing.T) {
120+
testColumns := []migrator.ColumnType{
121+
{
122+
NameValue: sql.NullString{String: "id", Valid: true},
123+
DataTypeValue: sql.NullString{String: "integer", Valid: true},
124+
ColumnTypeValue: sql.NullString{String: "integer", Valid: true},
125+
NullableValue: sql.NullBool{Bool: false, Valid: true},
126+
DefaultValueValue: sql.NullString{Valid: false},
127+
UniqueValue: sql.NullBool{Bool: true, Valid: true},
128+
PrimaryKeyValue: sql.NullBool{Bool: true, Valid: true},
129+
},
130+
{
131+
NameValue: sql.NullString{String: "dark_mode", Valid: true},
132+
DataTypeValue: sql.NullString{String: "numeric", Valid: true},
133+
ColumnTypeValue: sql.NullString{String: "numeric", Valid: true},
134+
NullableValue: sql.NullBool{Valid: true},
135+
DefaultValueValue: sql.NullString{String: "true", Valid: true},
136+
UniqueValue: sql.NullBool{Bool: false, Valid: true},
137+
PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true},
138+
},
139+
}
140+
141+
params := []struct {
142+
name string
143+
sql []string
144+
nFields int
145+
columns []migrator.ColumnType
146+
}{
147+
{
148+
"with_newline",
149+
[]string{"CREATE TABLE `users`\n(\nid integer primary key unique,\ndark_mode numeric DEFAULT true)"},
150+
2,
151+
testColumns,
152+
},
153+
{
154+
"with_newline_2",
155+
[]string{"CREATE TABLE `users` (\n\nid integer primary key unique,\ndark_mode numeric DEFAULT true)"},
156+
2,
157+
testColumns,
158+
},
159+
{
160+
"with_missing_space",
161+
[]string{"CREATE TABLE `users`(id integer primary key unique, dark_mode numeric DEFAULT true)"},
162+
2,
163+
testColumns,
164+
},
165+
{
166+
"with_many_spaces",
167+
[]string{"CREATE TABLE `users` (id integer primary key unique, dark_mode numeric DEFAULT true)"},
168+
2,
169+
testColumns,
170+
},
171+
}
172+
for _, p := range params {
173+
t.Run(p.name, func(t *testing.T) {
174+
ddl, err := parseDDL(p.sql...)
175+
176+
if err != nil {
177+
panic(err.Error())
178+
}
179+
180+
if len(ddl.fields) != p.nFields {
181+
t.Fatalf("fields length doesn't match: expect: %v, got %v", p.nFields, len(ddl.fields))
182+
}
183+
tests.AssertEqual(t, ddl.columns, p.columns)
184+
})
185+
}
186+
}
187+
83188
func TestParseDDL_error(t *testing.T) {
84189
params := []struct {
85190
name string

error_translator.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package sqlite
2+
3+
import (
4+
"encoding/json"
5+
6+
"gorm.io/gorm"
7+
)
8+
9+
// The error codes to map sqlite errors to gorm errors, here is a reference about error codes for sqlite https://www.sqlite.org/rescode.html.
10+
var errCodes = map[int]error{
11+
1555: gorm.ErrDuplicatedKey,
12+
2067: gorm.ErrDuplicatedKey,
13+
787: gorm.ErrForeignKeyViolated,
14+
}
15+
16+
type ErrMessage struct {
17+
Code int `json:"Code"`
18+
ExtendedCode int `json:"ExtendedCode"`
19+
SystemErrno int `json:"SystemErrno"`
20+
}
21+
22+
// Translate it will translate the error to native gorm errors.
23+
// We are not using go-sqlite3 error type intentionally here because it will need the CGO_ENABLED=1 and cross-C-compiler.
24+
func (dialector Dialector) Translate(err error) error {
25+
parsedErr, marshalErr := json.Marshal(err)
26+
if marshalErr != nil {
27+
return err
28+
}
29+
30+
var errMsg ErrMessage
31+
unmarshalErr := json.Unmarshal(parsedErr, &errMsg)
32+
if unmarshalErr != nil {
33+
return err
34+
}
35+
36+
if translatedErr, found := errCodes[errMsg.ExtendedCode]; found {
37+
return translatedErr
38+
}
39+
return err
40+
}

migrator.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -271,37 +271,40 @@ func (m Migrator) BuildIndexOptions(opts []schema.IndexOption, stmt *gorm.Statem
271271

272272
func (m Migrator) CreateIndex(value interface{}, name string) error {
273273
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
274-
if idx := stmt.Schema.LookIndex(name); idx != nil {
275-
opts := m.BuildIndexOptions(idx.Fields, stmt)
276-
values := []interface{}{clause.Column{Name: idx.Name}, clause.Table{Name: stmt.Table}, opts}
274+
if stmt.Schema != nil {
275+
if idx := stmt.Schema.LookIndex(name); idx != nil {
276+
opts := m.BuildIndexOptions(idx.Fields, stmt)
277+
values := []interface{}{clause.Column{Name: idx.Name}, clause.Table{Name: stmt.Table}, opts}
277278

278-
createIndexSQL := "CREATE "
279-
if idx.Class != "" {
280-
createIndexSQL += idx.Class + " "
281-
}
282-
createIndexSQL += "INDEX ?"
279+
createIndexSQL := "CREATE "
280+
if idx.Class != "" {
281+
createIndexSQL += idx.Class + " "
282+
}
283+
createIndexSQL += "INDEX ?"
283284

284-
if idx.Type != "" {
285-
createIndexSQL += " USING " + idx.Type
286-
}
287-
createIndexSQL += " ON ??"
285+
if idx.Type != "" {
286+
createIndexSQL += " USING " + idx.Type
287+
}
288+
createIndexSQL += " ON ??"
288289

289-
if idx.Where != "" {
290-
createIndexSQL += " WHERE " + idx.Where
291-
}
290+
if idx.Where != "" {
291+
createIndexSQL += " WHERE " + idx.Where
292+
}
292293

293-
return m.DB.Exec(createIndexSQL, values...).Error
294+
return m.DB.Exec(createIndexSQL, values...).Error
295+
}
294296
}
295-
296297
return fmt.Errorf("failed to create index with name %v", name)
297298
})
298299
}
299300

300301
func (m Migrator) HasIndex(value interface{}, name string) bool {
301302
var count int
302303
m.RunWithValue(value, func(stmt *gorm.Statement) error {
303-
if idx := stmt.Schema.LookIndex(name); idx != nil {
304-
name = idx.Name
304+
if stmt.Schema != nil {
305+
if idx := stmt.Schema.LookIndex(name); idx != nil {
306+
name = idx.Name
307+
}
305308
}
306309

307310
if name != "" {
@@ -319,6 +322,9 @@ func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error
319322
var sql string
320323
m.DB.Raw("SELECT sql FROM sqlite_master WHERE type = ? AND tbl_name = ? AND name = ?", "index", stmt.Table, oldName).Row().Scan(&sql)
321324
if sql != "" {
325+
if err := m.DropIndex(value, oldName); err != nil {
326+
return err
327+
}
322328
return m.DB.Exec(strings.Replace(sql, oldName, newName, 1)).Error
323329
}
324330
return fmt.Errorf("failed to find index with name %v", oldName)
@@ -327,8 +333,10 @@ func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error
327333

328334
func (m Migrator) DropIndex(value interface{}, name string) error {
329335
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
330-
if idx := stmt.Schema.LookIndex(name); idx != nil {
331-
name = idx.Name
336+
if stmt.Schema != nil {
337+
if idx := stmt.Schema.LookIndex(name); idx != nil {
338+
name = idx.Name
339+
}
332340
}
333341

334342
return m.DB.Exec("DROP INDEX ?", clause.Column{Name: name}).Error
@@ -390,7 +398,7 @@ func (m Migrator) recreateTable(value interface{}, tablePtr *string,
390398
return nil
391399
}
392400

393-
tableReg, err := regexp.Compile(" ('|`|\"| )" + table + "('|`|\"| ) ")
401+
tableReg, err := regexp.Compile("\\s*('|`|\")?\\b" + table + "\\b('|`|\")?\\s*")
394402
if err != nil {
395403
return err
396404
}

sqlite.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,12 @@ func (dialector Dialector) DataTypeOf(field *schema.Field) string {
180180
case schema.String:
181181
return "text"
182182
case schema.Time:
183-
return "datetime"
183+
// Distinguish between schema.Time and tag time
184+
if val, ok := field.TagSettings["TYPE"]; ok {
185+
return val
186+
} else {
187+
return "datetime"
188+
}
184189
case schema.Bytes:
185190
return "blob"
186191
}

0 commit comments

Comments
 (0)