Skip to content

Commit d5ba72c

Browse files
author
Sergey Podgornyy
committed
Export indexes and foreign keys
1 parent a389afa commit d5ba72c

10 files changed

+158
-117
lines changed

Diff for: README.md

+45
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,50 @@ var migrations = []migrator.Migration{
8484
return s
8585
},
8686
},
87+
{
88+
Name: "19700101_0003_rename_foreign_key",
89+
Up: func() migrator.Schema {
90+
var s migrator.Schema
91+
92+
keyName := migrator.BuildForeignNameOnTable("comments", "post_id")
93+
newKeyName := migrator.BuildForeignNameOnTable("comments", "article_id")
94+
95+
s.AlterTable("comments", migrator.TableCommands{
96+
migrator.DropForeignCommand(keyName),
97+
migrator.DropIndexCommand(keyName),
98+
migrator.RenameColumnCommand{"post_id", "article_id"},
99+
migrator.AddIndexCommand{newKeyName, []string{"article_id"}},
100+
migrator.AddForeignCommand{migrator.Foreign{
101+
Key: newKeyName,
102+
Column: "article_id",
103+
Reference: "id",
104+
On: "posts",
105+
}},
106+
})
107+
108+
return s
109+
},
110+
Down: func() migrator.Schema {
111+
var s migrator.Schema
112+
113+
keyName := migrator.BuildForeignNameOnTable("comments", "article_id")
114+
newKeyName := migrator.BuildForeignNameOnTable("comments", "post_id")
115+
116+
s.AlterTable("comments", migrator.TableCommands{
117+
migrator.DropForeignCommand(keyName),
118+
migrator.DropIndexCommand(keyName),
119+
migrator.RenameColumnCommand{"article_id", "post_id"},
120+
migrator.AddIndexCommand{newKeyName, []string{"post_id"}},
121+
migrator.AddForeignCommand{migrator.Foreign{
122+
Key: newKeyName,
123+
Column: "post_id",
124+
Reference: "id",
125+
On: "posts",
126+
}},
127+
})
128+
129+
return s
130+
},
87131
}
88132

89133
m := migrator.Migrator{Pool: migrations}
@@ -113,6 +157,7 @@ After the first migration run, `migrations` table will be created:
113157
+----+-------------------------------------+-------+----------------------------+
114158
| 1 | 19700101_0001_create_posts_table | 1 | 2020-06-27 00:00:00.000000 |
115159
| 2 | 19700101_0002_create_comments_table | 1 | 2020-06-27 00:00:00.000000 |
160+
| 3 | 19700101_0003_rename_foreign_key | 1 | 2020-06-27 00:00:00.000000 |
116161
+----+-------------------------------------+-------+----------------------------+
117162
```
118163

Diff for: foreign.go

+21-15
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"strings"
66
)
77

8-
type foreigns []foreign
8+
type foreigns []Foreign
99

1010
func (f foreigns) render() string {
1111
values := []string{}
@@ -17,31 +17,37 @@ func (f foreigns) render() string {
1717
return strings.Join(values, ", ")
1818
}
1919

20-
type foreign struct {
21-
key string
22-
column string
23-
reference string // reference field
24-
on string // reference table
25-
onUpdate string
26-
onDelete string
20+
// Foreign represents an instance to handle foreign key interactions
21+
type Foreign struct {
22+
Key string
23+
Column string
24+
Reference string // reference field
25+
On string // reference table
26+
OnUpdate string
27+
OnDelete string
2728
}
2829

29-
func (f foreign) render() string {
30-
if f.key == "" || f.column == "" || f.on == "" || f.reference == "" {
30+
func (f Foreign) render() string {
31+
if f.Key == "" || f.Column == "" || f.On == "" || f.Reference == "" {
3132
return ""
3233
}
3334

34-
sql := fmt.Sprintf("CONSTRAINT `%s` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`)", f.key, f.column, f.on, f.reference)
35-
if referenceOptions.has(strings.ToUpper(f.onDelete)) {
36-
sql += " ON DELETE " + strings.ToUpper(f.onDelete)
35+
sql := fmt.Sprintf("CONSTRAINT `%s` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`)", f.Key, f.Column, f.On, f.Reference)
36+
if referenceOptions.has(strings.ToUpper(f.OnDelete)) {
37+
sql += " ON DELETE " + strings.ToUpper(f.OnDelete)
3738
}
38-
if referenceOptions.has(strings.ToUpper(f.onUpdate)) {
39-
sql += " ON UPDATE " + strings.ToUpper(f.onUpdate)
39+
if referenceOptions.has(strings.ToUpper(f.OnUpdate)) {
40+
sql += " ON UPDATE " + strings.ToUpper(f.OnUpdate)
4041
}
4142

4243
return sql
4344
}
4445

46+
// BuildForeignNameOnTable builds a name for the foreign key on the table
47+
func BuildForeignNameOnTable(table string, column string) string {
48+
return table + "_" + column + "_foreign"
49+
}
50+
4551
var referenceOptions = list{"SET NULL", "CASCADE", "RESTRICT", "NO ACTION", "SET DEFAULT"}
4652

4753
type list []string

Diff for: foreign_test.go

+14-10
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ import (
88

99
func TestForeigns(t *testing.T) {
1010
t.Run("it returns empty on empty keys", func(t *testing.T) {
11-
f := foreigns{foreign{}}
11+
f := foreigns{Foreign{}}
1212

1313
assert.Equal(t, "", f.render())
1414
})
1515

1616
t.Run("it renders row from one foreign", func(t *testing.T) {
17-
f := foreigns{foreign{key: "idx_foreign", column: "test_id", reference: "id", on: "tests"}}
17+
f := foreigns{Foreign{Key: "idx_foreign", Column: "test_id", Reference: "id", On: "tests"}}
1818

1919
assert.Equal(t, "CONSTRAINT `idx_foreign` FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`)", f.render())
2020
})
2121

2222
t.Run("it renders row from multiple foreigns", func(t *testing.T) {
2323
f := foreigns{
24-
foreign{key: "idx_foreign", column: "test_id", reference: "id", on: "tests"},
25-
foreign{key: "foreign_idx", column: "random_id", reference: "id", on: "randoms"},
24+
Foreign{Key: "idx_foreign", Column: "test_id", Reference: "id", On: "tests"},
25+
Foreign{Key: "foreign_idx", Column: "random_id", Reference: "id", On: "randoms"},
2626
}
2727

2828
assert.Equal(
@@ -35,38 +35,42 @@ func TestForeigns(t *testing.T) {
3535

3636
func TestForeign(t *testing.T) {
3737
t.Run("it builds base constraint", func(t *testing.T) {
38-
f := foreign{key: "foreign_idx", column: "test_id", reference: "id", on: "tests"}
38+
f := Foreign{Key: "foreign_idx", Column: "test_id", Reference: "id", On: "tests"}
3939

4040
assert.Equal(t, "CONSTRAINT `foreign_idx` FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`)", f.render())
4141
})
4242

4343
t.Run("it builds contraint with on_update", func(t *testing.T) {
44-
f := foreign{key: "foreign_idx", column: "test_id", reference: "id", on: "tests", onUpdate: "no action"}
44+
f := Foreign{Key: "foreign_idx", Column: "test_id", Reference: "id", On: "tests", OnUpdate: "no action"}
4545

4646
assert.Equal(t, "CONSTRAINT `foreign_idx` FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`) ON UPDATE NO ACTION", f.render())
4747
})
4848

4949
t.Run("it builds contraint without invalid on_update", func(t *testing.T) {
50-
f := foreign{key: "foreign_idx", column: "test_id", reference: "id", on: "tests", onUpdate: "null"}
50+
f := Foreign{Key: "foreign_idx", Column: "test_id", Reference: "id", On: "tests", OnUpdate: "null"}
5151

5252
assert.Equal(t, "CONSTRAINT `foreign_idx` FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`)", f.render())
5353
})
5454

5555
t.Run("it builds contraint with on_update", func(t *testing.T) {
56-
f := foreign{key: "foreign_idx", column: "test_id", reference: "id", on: "tests", onDelete: "set default"}
56+
f := Foreign{Key: "foreign_idx", Column: "test_id", Reference: "id", On: "tests", OnDelete: "set default"}
5757

5858
assert.Equal(t, "CONSTRAINT `foreign_idx` FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`) ON DELETE SET DEFAULT", f.render())
5959
})
6060

6161
t.Run("it builds contraint without invalid on_update", func(t *testing.T) {
62-
f := foreign{key: "foreign_idx", column: "test_id", reference: "id", on: "tests", onDelete: "default"}
62+
f := Foreign{Key: "foreign_idx", Column: "test_id", Reference: "id", On: "tests", OnDelete: "default"}
6363

6464
assert.Equal(t, "CONSTRAINT `foreign_idx` FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`)", f.render())
6565
})
6666

6767
t.Run("it builds full contraint", func(t *testing.T) {
68-
f := foreign{key: "foreign_idx", column: "test_id", reference: "id", on: "tests", onUpdate: "cascade", onDelete: "restrict"}
68+
f := Foreign{Key: "foreign_idx", Column: "test_id", Reference: "id", On: "tests", OnUpdate: "cascade", OnDelete: "restrict"}
6969

7070
assert.Equal(t, "CONSTRAINT `foreign_idx` FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE", f.render())
7171
})
7272
}
73+
74+
func TestBuildForeignIndexNameOnTable(t *testing.T) {
75+
assert.Equal(t, "table_test_foreign", BuildForeignNameOnTable("table", "test"))
76+
}

Diff for: key.go

+18-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package migrator
22

33
import "strings"
44

5-
type keys []key
5+
type keys []Key
66

77
func (k keys) render() string {
88
values := []string{}
@@ -17,31 +17,37 @@ func (k keys) render() string {
1717
return strings.Join(values, ", ")
1818
}
1919

20-
type key struct {
21-
name string
22-
typ string // primary, unique
23-
columns []string
20+
// Key represents an instance to handle key (index) interactions
21+
type Key struct {
22+
Name string
23+
Type string // primary, unique
24+
Columns []string
2425
}
2526

2627
var keyTypes = list{"PRIMARY", "UNIQUE"}
2728

28-
func (k key) render() string {
29-
if len(k.columns) == 0 {
29+
func (k Key) render() string {
30+
if len(k.Columns) == 0 {
3031
return ""
3132
}
3233

3334
sql := ""
34-
if keyTypes.has(strings.ToUpper(k.typ)) {
35-
sql += strings.ToUpper(k.typ) + " "
35+
if keyTypes.has(strings.ToUpper(k.Type)) {
36+
sql += strings.ToUpper(k.Type) + " "
3637
}
3738

3839
sql += "KEY"
3940

40-
if k.name != "" {
41-
sql += " `" + k.name + "`"
41+
if k.Name != "" {
42+
sql += " `" + k.Name + "`"
4243
}
4344

44-
sql += " (`" + strings.Join(k.columns, "`, `") + "`)"
45+
sql += " (`" + strings.Join(k.Columns, "`, `") + "`)"
4546

4647
return sql
4748
}
49+
50+
// BuildUniqueKeyNameOnTable builds a name for the foreign key on the table
51+
func BuildUniqueKeyNameOnTable(table string, columns ...string) string {
52+
return table + "_" + strings.Join(columns, "_") + "_unique"
53+
}

Diff for: key_test.go

+19-9
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ import (
88

99
func TestKeys(t *testing.T) {
1010
t.Run("it returns empty on empty keys", func(t *testing.T) {
11-
k := keys{key{}}
11+
k := keys{Key{}}
1212

1313
assert.Equal(t, "", k.render())
1414
})
1515

1616
t.Run("it renders row from one key", func(t *testing.T) {
17-
k := keys{key{columns: []string{"test_id"}}}
17+
k := keys{Key{Columns: []string{"test_id"}}}
1818

1919
assert.Equal(t, "KEY (`test_id`)", k.render())
2020
})
2121

2222
t.Run("it renders row from multiple keys", func(t *testing.T) {
2323
k := keys{
24-
key{columns: []string{"test_id"}},
25-
key{columns: []string{"random_id"}},
24+
Key{Columns: []string{"test_id"}},
25+
Key{Columns: []string{"random_id"}},
2626
}
2727

2828
assert.Equal(
@@ -35,32 +35,42 @@ func TestKeys(t *testing.T) {
3535

3636
func TestKey(t *testing.T) {
3737
t.Run("it returns empty on empty keys", func(t *testing.T) {
38-
k := key{}
38+
k := Key{}
3939

4040
assert.Equal(t, "", k.render())
4141
})
4242

4343
t.Run("it skips type if it is not in valid list", func(t *testing.T) {
44-
k := key{typ: "random", columns: []string{"test_id"}}
44+
k := Key{Type: "random", Columns: []string{"test_id"}}
4545

4646
assert.Equal(t, "KEY (`test_id`)", k.render())
4747
})
4848

4949
t.Run("it renders with type", func(t *testing.T) {
50-
k := key{typ: "primary", columns: []string{"test_id"}}
50+
k := Key{Type: "primary", Columns: []string{"test_id"}}
5151

5252
assert.Equal(t, "PRIMARY KEY (`test_id`)", k.render())
5353
})
5454

5555
t.Run("it renders with multiple columns", func(t *testing.T) {
56-
k := key{typ: "unique", columns: []string{"test_id", "random_id"}}
56+
k := Key{Type: "unique", Columns: []string{"test_id", "random_id"}}
5757

5858
assert.Equal(t, "UNIQUE KEY (`test_id`, `random_id`)", k.render())
5959
})
6060

6161
t.Run("it renders with name", func(t *testing.T) {
62-
k := key{name: "random_idx", columns: []string{"test_id"}}
62+
k := Key{Name: "random_idx", Columns: []string{"test_id"}}
6363

6464
assert.Equal(t, "KEY `random_idx` (`test_id`)", k.render())
6565
})
6666
}
67+
68+
func TestBuildUniqueIndexName(t *testing.T) {
69+
t.Run("It builds name from one column", func(t *testing.T) {
70+
assert.Equal(t, "table_test_unique", BuildUniqueKeyNameOnTable("table", "test"))
71+
})
72+
73+
t.Run("it builds name from multiple columns", func(t *testing.T) {
74+
assert.Equal(t, "table_test_again_unique", BuildUniqueKeyNameOnTable("table", "test", "again"))
75+
})
76+
}

Diff for: schema_command_test.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ func TestCreateTableCommand(t *testing.T) {
5252
t.Run("it renders indexes", func(t *testing.T) {
5353
tb := Table{
5454
Name: "test",
55-
indexes: []key{
56-
{name: "idx_rand", columns: []string{"id"}},
57-
{columns: []string{"id", "name"}},
55+
indexes: []Key{
56+
{Name: "idx_rand", Columns: []string{"id"}},
57+
{Columns: []string{"id", "name"}},
5858
},
5959
}
6060
c := createTableCommand{tb}
@@ -74,9 +74,9 @@ func TestCreateTableCommand(t *testing.T) {
7474
t.Run("it renders foreigns", func(t *testing.T) {
7575
tb := Table{
7676
Name: "test",
77-
foreigns: []foreign{
78-
{key: "idx_foreign", column: "test_id", reference: "id", on: "tests"},
79-
{key: "foreign_idx", column: "random_id", reference: "id", on: "randoms"},
77+
foreigns: []Foreign{
78+
{Key: "idx_foreign", Column: "test_id", Reference: "id", On: "tests"},
79+
{Key: "foreign_idx", Column: "random_id", Reference: "id", On: "randoms"},
8080
},
8181
}
8282
c := createTableCommand{tb}
@@ -145,13 +145,13 @@ func TestCreateTableCommand(t *testing.T) {
145145
{"test", testColumnType("random thing")},
146146
{"random", testColumnType("another thing")},
147147
},
148-
indexes: []key{
149-
{name: "idx_rand", columns: []string{"id"}},
150-
{columns: []string{"id", "name"}},
148+
indexes: []Key{
149+
{Name: "idx_rand", Columns: []string{"id"}},
150+
{Columns: []string{"id", "name"}},
151151
},
152-
foreigns: []foreign{
153-
{key: "idx_foreign", column: "test_id", reference: "id", on: "tests"},
154-
{key: "foreign_idx", column: "random_id", reference: "id", on: "randoms"},
152+
foreigns: []Foreign{
153+
{Key: "idx_foreign", Column: "test_id", Reference: "id", On: "tests"},
154+
{Key: "foreign_idx", Column: "random_id", Reference: "id", On: "randoms"},
155155
},
156156
Engine: "MyISAM",
157157
Charset: "rand",

0 commit comments

Comments
 (0)