Skip to content

Commit edfdbb0

Browse files
author
Miguel Molina
committed
allow circular dependencies in the schema migration to some extent
This PR allows the usage of circular dependencies in the schema for which migrations are going to be generated. This was a mandatory feature since otherwise you'd have no way of using self-refs and inverse relationships of any kind. With this, the behaviour should be working as expected.
1 parent 356d0a6 commit edfdbb0

File tree

2 files changed

+75
-30
lines changed

2 files changed

+75
-30
lines changed

generator/migration.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,18 @@ type TableSchema struct {
8989
Columns []*ColumnSchema
9090
}
9191

92-
func (s *TableSchema) relationships() []string {
92+
type relationship struct {
93+
name string
94+
inverse bool
95+
}
96+
97+
func (s *TableSchema) relationships() []relationship {
9398
var rels = make(map[string]struct{})
94-
var result []string
99+
var result []relationship
95100
for _, c := range s.Columns {
96101
if c.Reference != nil {
97102
if _, ok := rels[c.Reference.Table]; !ok {
98-
result = append(result, c.Reference.Table)
103+
result = append(result, relationship{c.Reference.Table, c.Reference.inverse})
99104
rels[c.Reference.Table] = struct{}{}
100105
}
101106
}
@@ -228,7 +233,8 @@ type Reference struct {
228233
// Table is the referenced table.
229234
Table string
230235
// Column is the referenced column.
231-
Column string
236+
Column string
237+
inverse bool
232238
}
233239

234240
func (r *Reference) Equals(r2 *Reference) bool {
@@ -275,7 +281,15 @@ func (cs ChangeSet) sorted(dropIndex, createIndex map[string]*TableSchema) (Chan
275281
createTables[c.Name] = c
276282
if rels := createIndex[c.Name].relationships(); len(rels) > 0 {
277283
for _, r := range rels {
278-
createGraph.dependsOn(r, c.Name)
284+
if r.name == c.Name {
285+
continue
286+
}
287+
288+
if r.inverse {
289+
createGraph.dependsOn(c.Name, r.name)
290+
} else {
291+
createGraph.dependsOn(r.name, c.Name)
292+
}
279293
}
280294
} else {
281295
createGraph.add(c.Name)
@@ -284,7 +298,15 @@ func (cs ChangeSet) sorted(dropIndex, createIndex map[string]*TableSchema) (Chan
284298
dropTables[c.Name] = c
285299
if rels := dropIndex[c.Name].relationships(); len(rels) > 0 {
286300
for _, r := range rels {
287-
dropGraph.dependsOn(r, c.Name)
301+
if r.name == c.Name {
302+
continue
303+
}
304+
305+
if r.inverse {
306+
dropGraph.dependsOn(c.Name, r.name)
307+
} else {
308+
dropGraph.dependsOn(r.name, c.Name)
309+
}
288310
}
289311
} else {
290312
dropGraph.add(c.Name)
@@ -870,9 +892,9 @@ func (t *packageTransformer) transformRef(f *Field) (*Reference, error) {
870892
return nil, fmt.Errorf("kallax: unable to find table for type %s in field %s of model %s. Is the model type part of the generation input?", typ, f.Name, f.Model.Name)
871893
}
872894

873-
return &Reference{Table: table, Column: t.pkIndex[table].ColumnName()}, nil
895+
return &Reference{Table: table, Column: t.pkIndex[table].ColumnName(), inverse: true}, nil
874896
} else if f.Kind == Relationship {
875-
return &Reference{Table: f.Model.Table, Column: f.Model.ID.ColumnName()}, nil
897+
return &Reference{Table: f.Model.Table, Column: f.Model.ID.ColumnName(), inverse: false}, nil
876898
}
877899

878900
return nil, nil

generator/migration_test.go

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ func TestNewMigration(t *testing.T) {
2121
require.Equal(t, migration.Lock, new)
2222
}
2323

24+
func TestNewMigration_SelfRef(t *testing.T) {
25+
old := mkSchema()
26+
new := mkSchema(mkTable(
27+
"selfref",
28+
mkCol("id", SerialColumn, true, false, nil),
29+
mkCol("parent_id", BigIntColumn, false, false, mkRef("selfref", "id", true)),
30+
mkCol("child_id", BigIntColumn, false, false, mkRef("selfref", "id", false)),
31+
))
32+
33+
_, err := NewMigration(old, new)
34+
require.NoError(t, err)
35+
}
36+
2437
var table1 = mkTable(
2538
"table",
2639
mkCol("id", SerialColumn, true, true, nil),
@@ -29,7 +42,7 @@ var table1 = mkTable(
2942

3043
var table2 = mkTable(
3144
"table2",
32-
mkCol("table_id", SerialColumn, false, true, mkRef("table", "id")),
45+
mkCol("table_id", SerialColumn, false, true, mkRef("table", "id", false)),
3346
mkCol("num", NumericColumn(20), false, false, nil),
3447
)
3548

@@ -198,31 +211,31 @@ func TestColumnSchemaDiff(t *testing.T) {
198211
{
199212
"ref added",
200213
mkCol("foo", TextColumn, false, false, nil),
201-
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar")),
214+
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar", false)),
202215
true,
203216
},
204217
{
205218
"ref removed",
206-
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar")),
219+
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar", false)),
207220
mkCol("foo", TextColumn, false, false, nil),
208221
true,
209222
},
210223
{
211224
"ref table changed",
212-
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar")),
213-
mkCol("foo", TextColumn, false, false, mkRef("bar", "bar")),
225+
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar", false)),
226+
mkCol("foo", TextColumn, false, false, mkRef("bar", "bar", false)),
214227
true,
215228
},
216229
{
217230
"ref col changed",
218-
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar")),
219-
mkCol("foo", TextColumn, false, false, mkRef("foo", "foo")),
231+
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar", false)),
232+
mkCol("foo", TextColumn, false, false, mkRef("foo", "foo", false)),
220233
true,
221234
},
222235
{
223236
"ref col unchanged",
224-
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar")),
225-
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar")),
237+
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar", false)),
238+
mkCol("foo", TextColumn, false, false, mkRef("foo", "bar", false)),
226239
false,
227240
},
228241
{
@@ -368,25 +381,25 @@ func TestColumnSchemaEquals(t *testing.T) {
368381
{
369382
"one of the references is nil",
370383
mkCol("foo", TextColumn, false, false, nil),
371-
mkCol("foo", TextColumn, false, false, mkRef("a", "b")),
384+
mkCol("foo", TextColumn, false, false, mkRef("a", "b", false)),
372385
false,
373386
},
374387
{
375388
"reference table does not match",
376-
mkCol("foo", TextColumn, false, false, mkRef("a", "b")),
377-
mkCol("foo", TextColumn, false, false, mkRef("b", "b")),
389+
mkCol("foo", TextColumn, false, false, mkRef("a", "b", false)),
390+
mkCol("foo", TextColumn, false, false, mkRef("b", "b", false)),
378391
false,
379392
},
380393
{
381394
"reference column does not match",
382-
mkCol("foo", TextColumn, false, false, mkRef("a", "b")),
383-
mkCol("foo", TextColumn, false, false, mkRef("a", "a")),
395+
mkCol("foo", TextColumn, false, false, mkRef("a", "b", false)),
396+
mkCol("foo", TextColumn, false, false, mkRef("a", "a", false)),
384397
false,
385398
},
386399
{
387400
"equal with reference",
388-
mkCol("foo", TextColumn, false, false, mkRef("a", "b")),
389-
mkCol("foo", TextColumn, false, false, mkRef("a", "b")),
401+
mkCol("foo", TextColumn, false, false, mkRef("a", "b", false)),
402+
mkCol("foo", TextColumn, false, false, mkRef("a", "b", false)),
390403
true,
391404
},
392405
{
@@ -407,7 +420,7 @@ func TestChangeSetSorted(t *testing.T) {
407420
mkTable("table2"),
408421
mkTable(
409422
"table1",
410-
mkCol("foo", SerialColumn, false, false, mkRef("table2", "bar")),
423+
mkCol("foo", SerialColumn, false, false, mkRef("table2", "bar", false)),
411424
),
412425
mkTable("table3"),
413426
)
@@ -416,7 +429,7 @@ func TestChangeSetSorted(t *testing.T) {
416429
mkTable("table4"),
417430
mkTable(
418431
"table5",
419-
mkCol("foo", SerialColumn, false, false, mkRef("table4", "bar")),
432+
mkCol("foo", SerialColumn, false, false, mkRef("table4", "bar", false)),
420433
),
421434
)
422435
cs := SchemaDiff(old, new)
@@ -486,6 +499,16 @@ func (s *PackageTransformerSuite) SetupTest() {
486499
s.Require().NoError(err)
487500
}
488501

502+
func (s *PackageTransformerSuite) TestMigrationCircularDep() {
503+
require := s.Require()
504+
schema, err := s.t.transform(s.pkg)
505+
require.NoError(err)
506+
require.NotNil(schema)
507+
508+
_, err = NewMigration(mkSchema(), schema)
509+
require.NoError(err)
510+
}
511+
489512
func (s *PackageTransformerSuite) TestTransform() {
490513
require := s.Require()
491514
schema, err := s.t.transform(s.pkg)
@@ -498,14 +521,14 @@ func (s *PackageTransformerSuite) TestTransform() {
498521
mkCol("id", SerialColumn, true, false, nil),
499522
mkCol("color", ColumnType("char(6)"), false, false, nil),
500523
mkCol("background", TextColumn, false, false, nil),
501-
mkCol("user_id", UUIDColumn, false, false, mkRef("users", "id")),
524+
mkCol("user_id", UUIDColumn, false, false, mkRef("users", "id", true)),
502525
mkCol("spouse", UUIDColumn, false, false, nil),
503526
),
504527
mkTable(
505528
"metadata",
506529
mkCol("id", SerialColumn, true, false, nil),
507530
mkCol("metadata", JSONBColumn, false, false, nil),
508-
mkCol("profile_id", BigIntColumn, false, false, mkRef("profiles", "id")),
531+
mkCol("profile_id", BigIntColumn, false, false, mkRef("profiles", "id", false)),
509532
),
510533
mkTable(
511534
"users",
@@ -589,6 +612,6 @@ func mkCol(name string, typ ColumnType, pk, notNull bool, ref *Reference) *Colum
589612
return &ColumnSchema{name, typ, pk, ref, notNull}
590613
}
591614

592-
func mkRef(table, col string) *Reference {
593-
return &Reference{table, col}
615+
func mkRef(table, col string, inverse bool) *Reference {
616+
return &Reference{table, col, inverse}
594617
}

0 commit comments

Comments
 (0)