diff --git a/doc/md/schema-edges.md b/doc/md/schema-edges.md index dcab1f0ad..c3fcba0c2 100755 --- a/doc/md/schema-edges.md +++ b/doc/md/schema-edges.md @@ -977,9 +977,20 @@ func (User) Edges() []ent.Edge { edge.To("pets", Pet.Type). // Set the column name in the "pets" table for O2M relationship. StorageKey(edge.Column("owner_id")), + edge.To("cars", Car.Type). + // Set the symbol of the foreign-key constraint for O2M relationship. + StorageKey(edge.Symbol("cars_owner_id")), edge.To("friends", User.Type). - // Set the join-table and the column names for M2M relationship. + // Set the join-table, and the column names for a M2M relationship. StorageKey(edge.Table("friends"), edge.Columns("user_id", "friend_id")), + edge.To("groups", Group.Type). + // Set the join-table, its column names and the symbols + // of the foreign-key constraints for M2M relationship. + StorageKey( + edge.Table("groups"), + edge.Columns("user_id", "group_id"), + edge.Symbols("groups_id1", "groups_id2") + ), } } ``` diff --git a/entc/gen/graph.go b/entc/gen/graph.go index a0ccfe1c6..287c3ea9d 100644 --- a/entc/gen/graph.go +++ b/entc/gen/graph.go @@ -426,7 +426,7 @@ func (g *Graph) Tables() (all []*schema.Table) { OnDelete: deleteAction(e), Columns: []*schema.Column{column}, RefColumns: []*schema.Column{ref.PrimaryKey[0]}, - Symbol: fmt.Sprintf("%s_%s_%s", owner.Name, ref.Name, e.Name), + Symbol: fkSymbol(e, owner, ref), }) case M2O: ref, owner := tables[e.Type.Table()], tables[e.Rel.Table] @@ -438,7 +438,7 @@ func (g *Graph) Tables() (all []*schema.Table) { OnDelete: deleteAction(e), Columns: []*schema.Column{column}, RefColumns: []*schema.Column{ref.PrimaryKey[0]}, - Symbol: fmt.Sprintf("%s_%s_%s", owner.Name, ref.Name, e.Name), + Symbol: fkSymbol(e, owner, ref), }) case M2M: t1, t2 := tables[n.Table()], tables[e.Type.Table()] @@ -452,6 +452,7 @@ func (g *Graph) Tables() (all []*schema.Table) { c2.Type = ref.Type.Type c2.Size = ref.size() } + s1, s2 := fkSymbols(e, c1, c2) all = append(all, &schema.Table{ Name: e.Rel.Table, Columns: []*schema.Column{c1, c2}, @@ -462,14 +463,14 @@ func (g *Graph) Tables() (all []*schema.Table) { OnDelete: schema.Cascade, Columns: []*schema.Column{c1}, RefColumns: []*schema.Column{t1.PrimaryKey[0]}, - Symbol: fmt.Sprintf("%s_%s", e.Rel.Table, c1.Name), + Symbol: s1, }, { RefTable: t2, OnDelete: schema.Cascade, Columns: []*schema.Column{c2}, RefColumns: []*schema.Column{t2.PrimaryKey[0]}, - Symbol: fmt.Sprintf("%s_%s", e.Rel.Table, c2.Name), + Symbol: s2, }, }, }) @@ -493,6 +494,30 @@ func mayAddColumn(t *schema.Table, c *schema.Column) { } } +// fkSymbol returns the symbol of the foreign-key constraint for edges of type O2M, M2O and O2O. +// It returns the symbol of the storage-key if it was provided, and generate custom one otherwise. +func fkSymbol(e *Edge, ownerT, refT *schema.Table) string { + if k, _ := e.StorageKey(); k != nil && len(k.Symbols) == 1 { + return k.Symbols[0] + } + return fmt.Sprintf("%s_%s_%s", ownerT.Name, refT.Name, e.Name) +} + +// fkSymbols is like fkSymbol but for M2M edges. +func fkSymbols(e *Edge, c1, c2 *schema.Column) (string, string) { + s1 := fmt.Sprintf("%s_%s", e.Rel.Table, c1.Name) + s2 := fmt.Sprintf("%s_%s", e.Rel.Table, c2.Name) + if k, _ := e.StorageKey(); k != nil { + if len(k.Symbols) > 0 { + s1 = k.Symbols[0] + } + if len(k.Symbols) > 1 { + s2 = k.Symbols[1] + } + } + return s1, s2 +} + // deleteAction returns the referential action for DELETE operations of the given edge. func deleteAction(e *Edge) schema.ReferenceOption { action := schema.SetNull diff --git a/entc/integration/migrate/entv2/migrate/schema.go b/entc/integration/migrate/entv2/migrate/schema.go index 442c157c1..0240e2172 100644 --- a/entc/integration/migrate/entv2/migrate/schema.go +++ b/entc/integration/migrate/entv2/migrate/schema.go @@ -106,7 +106,7 @@ var ( PrimaryKey: []*schema.Column{PetsColumns[0]}, ForeignKeys: []*schema.ForeignKey{ { - Symbol: "pets_users_pets", + Symbol: "user_pet_id", Columns: []*schema.Column{PetsColumns[1]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, @@ -157,13 +157,13 @@ var ( PrimaryKey: []*schema.Column{FriendsColumns[0], FriendsColumns[1]}, ForeignKeys: []*schema.ForeignKey{ { - Symbol: "friends_user", + Symbol: "user_friend_id1", Columns: []*schema.Column{FriendsColumns[0]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.Cascade, }, { - Symbol: "friends_friend", + Symbol: "user_friend_id2", Columns: []*schema.Column{FriendsColumns[1]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.Cascade, diff --git a/entc/integration/migrate/entv2/schema/media.go b/entc/integration/migrate/entv2/schema/media.go index 5eef8ba99..d75b47c77 100644 --- a/entc/integration/migrate/entv2/schema/media.go +++ b/entc/integration/migrate/entv2/schema/media.go @@ -28,6 +28,7 @@ func (Media) Fields() []ent.Field { // Indexes of the Media. func (Media) Indexes() []ent.Index { return []ent.Index{ - index.Fields("source", "source_uri").Unique(), + index.Fields("source", "source_uri"). + Unique(), } } diff --git a/entc/integration/migrate/entv2/schema/user.go b/entc/integration/migrate/entv2/schema/user.go index 9b4290c53..72f41a4cb 100644 --- a/entc/integration/migrate/entv2/schema/user.go +++ b/entc/integration/migrate/entv2/schema/user.go @@ -100,10 +100,14 @@ func (User) Edges() []ent.Edge { edge.To("car", Car.Type), // New edges to added. edge.To("pets", Pet.Type). - StorageKey(edge.Column("owner_id")). + StorageKey(edge.Column("owner_id"), edge.Symbol("user_pet_id")). Unique(), edge.To("friends", User.Type). - StorageKey(edge.Table("friends"), edge.Columns("user", "friend")), + StorageKey( + edge.Table("friends"), + edge.Columns("user", "friend"), + edge.Symbols("user_friend_id1", "user_friend_id2"), + ), } } @@ -122,7 +126,6 @@ type Car struct { func (Car) Edges() []ent.Edge { return []ent.Edge{ - // Car now can have more than 1 owner (not unique anymore). edge.From("owner", User.Type). Ref("car"). Unique(), diff --git a/entc/integration/migrate/migrate_test.go b/entc/integration/migrate/migrate_test.go index 880ee9348..6dcacb8ac 100644 --- a/entc/integration/migrate/migrate_test.go +++ b/entc/integration/migrate/migrate_test.go @@ -103,6 +103,12 @@ func TestSQLite(t *testing.T) { ContainsFold(t, client) } +func TestStorageKey(t *testing.T) { + require.Equal(t, "user_pet_id", migratev2.PetsTable.ForeignKeys[0].Symbol) + require.Equal(t, "user_friend_id1", migratev2.FriendsTable.ForeignKeys[0].Symbol) + require.Equal(t, "user_friend_id2", migratev2.FriendsTable.ForeignKeys[1].Symbol) +} + func V1ToV2(t *testing.T, dialect string, clientv1 *entv1.Client, clientv2 *entv2.Client) { ctx := context.Background() diff --git a/schema/edge/edge.go b/schema/edge/edge.go index 1bc54af3f..c2781813d 100644 --- a/schema/edge/edge.go +++ b/schema/edge/edge.go @@ -194,6 +194,7 @@ func (b *inverseBuilder) Descriptor() *Descriptor { // StorageKey holds the configuration for edge storage-key. type StorageKey struct { Table string // Table or label. + Symbols []string // Symbols/names of the foreign-key constraints. Columns []string // Foreign-key columns. } @@ -207,6 +208,24 @@ func Table(name string) StorageOption { } } +// Symbol sets the symbol/name of the foreign-key constraint for O2O, O2M and M2O edges. +// Note that, for M2M edges (2 columns and 2 constraints), use the edge.Symbols option. +func Symbol(symbol string) StorageOption { + return func(key *StorageKey) { + key.Symbols = []string{symbol} + } +} + +// Symbols sets the symbol/name of the foreign-key constraints for M2M edges. +// The 1st column defines the name of the "To" edge, and the 2nd defines +// the name of the "From" edge (inverse edge). +// Note that, for O2O, O2M and M2O edges, use the edge.Symbol option. +func Symbols(to, from string) StorageOption { + return func(key *StorageKey) { + key.Symbols = []string{to, from} + } +} + // Column sets the foreign-key column name option for O2O, O2M and M2O edges. // Note that, for M2M edges (2 columns), use the edge.Columns option. func Column(name string) StorageOption { diff --git a/schema/edge/edge_test.go b/schema/edge/edge_test.go index 0f7c0ef47..8d011b10f 100644 --- a/schema/edge/edge_test.go +++ b/schema/edge/edge_test.go @@ -86,13 +86,13 @@ func TestEdge(t *testing.T) { from = edge.To("following", User.Type). StructTag("following"). - StorageKey(edge.Table("user_followers"), edge.Columns("following_id", "followers_id")). + StorageKey(edge.Table("user_followers"), edge.Columns("following_id", "followers_id"), edge.Symbol("users_followers")). From("followers"). StructTag("followers"). Descriptor() assert.Equal("followers", from.Tag) assert.Equal("following", from.Ref.Tag) - assert.Equal(edge.StorageKey{Table: "user_followers", Columns: []string{"following_id", "followers_id"}}, *from.Ref.StorageKey) + assert.Equal(edge.StorageKey{Table: "user_followers", Symbols: []string{"users_followers"}, Columns: []string{"following_id", "followers_id"}}, *from.Ref.StorageKey) } type GQL struct {