Skip to content

Commit 9aba4ae

Browse files
authored
entc/gen: initial work for multi-schema migration using atlasgo.io (ent#3821)
1 parent 50938a5 commit 9aba4ae

File tree

116 files changed

+15265
-203
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+15265
-203
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@ jobs:
2525
lint:
2626
runs-on: ubuntu-latest
2727
steps:
28-
- uses: actions/checkout@v3
29-
- uses: actions/setup-go@v3
28+
- uses: actions/checkout@v4
29+
- uses: actions/setup-go@v4
3030
with:
31-
go-version: 1.19
31+
go-version: '1.21'
3232
- name: Run linters
33-
uses: golangci/golangci-lint-action@v3.4.0
33+
uses: golangci/golangci-lint-action@v3
3434
with:
35-
version: v1.50.1 # The next version requires Go 1.20
35+
args: --verbose
3636

3737
unit:
3838
runs-on: ubuntu-latest
3939
strategy:
4040
matrix:
41-
go: ['1.19', '1.20']
41+
go: ['1.20', '1.21']
4242
steps:
4343
- uses: actions/checkout@v3
4444
- uses: actions/setup-go@v3
@@ -75,7 +75,7 @@ jobs:
7575
- uses: actions/checkout@v3
7676
- uses: actions/setup-go@v3
7777
with:
78-
go-version: '1.20'
78+
go-version: '1.21'
7979
- uses: actions/cache@v3
8080
with:
8181
path: ~/go/pkg/mod
@@ -266,7 +266,7 @@ jobs:
266266
- uses: actions/checkout@v3
267267
- uses: actions/setup-go@v3
268268
with:
269-
go-version: '1.20'
269+
go-version: '1.21'
270270
- uses: actions/cache@v3
271271
with:
272272
path: ~/go/pkg/mod
@@ -446,7 +446,7 @@ jobs:
446446
fetch-depth: 0
447447
- uses: actions/setup-go@v3
448448
with:
449-
go-version: '1.20'
449+
go-version: '1.21'
450450
- uses: actions/cache@v3
451451
with:
452452
path: ~/go/pkg/mod

.golangci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ linters:
2020
enable:
2121
- asciicheck
2222
- bodyclose
23-
- depguard
2423
- dogsled
2524
- dupl
2625
- errcheck

dialect/sql/schema/schema.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ func (t *Table) SetComment(c string) *Table {
5454
return t
5555
}
5656

57+
// SetSchema sets the table schema.
58+
func (t *Table) SetSchema(s string) *Table {
59+
t.Schema = s
60+
return t
61+
}
62+
5763
// AddPrimary adds a new primary key to the table.
5864
func (t *Table) AddPrimary(c *Column) *Table {
5965
c.Key = PrimaryKey

entc/gen/feature.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ var (
7373
// multiple databases.
7474
FeatureSchemaConfig = Feature{
7575
Name: "sql/schemaconfig",
76-
Stage: Experimental,
76+
Stage: Stable,
7777
Default: false,
7878
Description: "Allows alternate schema names for each ent model. Useful if SQL tables are spread out against multiple databases",
7979
GraphTemplates: []GraphTemplate{
@@ -87,6 +87,14 @@ var (
8787
},
8888
}
8989

90+
// featureMultiSchema indicates that ent/schema is annotated with multiple schemas.
91+
// This feature-flag is enabled by default by the storage driver and exists to pass
92+
// this info to the templates.
93+
featureMultiSchema = Feature{
94+
Name: "sql/multischema",
95+
Stage: Beta,
96+
}
97+
9098
// FeatureLock provides a feature-flag for sql locking extension.
9199
FeatureLock = Feature{
92100
Name: "sql/lock",
@@ -134,6 +142,7 @@ var (
134142
FeatureNamedEdges,
135143
FeatureSnapshot,
136144
FeatureSchemaConfig,
145+
featureMultiSchema,
137146
FeatureLock,
138147
FeatureModifier,
139148
FeatureExecQuery,

entc/gen/func.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ var (
4444
"quote": quote,
4545
"base": filepath.Base,
4646
"keys": keys,
47+
"indexOf": indexOf,
4748
"join": join,
4849
"joinWords": joinWords,
4950
"isNil": isNil,
@@ -96,6 +97,16 @@ func joinWords(words []string, maxSize int) string {
9697
return b.String()
9798
}
9899

100+
// indexOf returns the index of the given string in the slice.
101+
func indexOf(s []string, v string) int {
102+
for i, x := range s {
103+
if x == v {
104+
return i
105+
}
106+
}
107+
return -1
108+
}
109+
99110
// quote only strings.
100111
func quote(v any) any {
101112
if s, ok := v.(string); ok {

entc/gen/graph.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ func NewGraph(c *Config, schemas ...*load.Schema) (g *Graph, err error) {
175175
check(g.edgeSchemas(), "resolving edges")
176176
aliases(g)
177177
g.defaults()
178+
if c.Storage != nil && c.Storage.Init != nil {
179+
check(c.Storage.Init(g), "storage driver init")
180+
}
178181
return
179182
}
180183

@@ -617,14 +620,24 @@ func (g *Graph) Tables() (all []*schema.Table, err error) {
617620
if n.HasOneFieldID() {
618621
table.AddPrimary(n.ID.PK())
619622
}
620-
table.SetAnnotation(n.EntSQL())
623+
ant := n.EntSQL()
624+
if ant != nil {
625+
table.SetAnnotation(ant).SetSchema(ant.Schema)
626+
}
621627
for _, f := range n.Fields {
622628
if !f.IsEdgeField() {
623629
table.AddColumn(f.Column())
624630
}
625631
}
626-
tables[table.Name] = table
627-
all = append(all, table)
632+
switch {
633+
case tables[table.Name] == nil:
634+
tables[table.Name] = table
635+
all = append(all, table)
636+
case tables[table.Name].Schema != table.Schema:
637+
return nil, fmt.Errorf("cannot use the same table name %q in different schemas: %q, %q", table.Name, tables[table.Name].Schema, table.Schema)
638+
default:
639+
return nil, fmt.Errorf("duplicate table name %q in schema %q", table.Name, table.Schema)
640+
}
628641
}
629642
for _, n := range g.Nodes {
630643
// Foreign key and its reference, or a join table.
@@ -683,9 +696,22 @@ func (g *Graph) Tables() (all []*schema.Table, err error) {
683696
c2.Type = ref.Type.Type
684697
c2.Size = ref.size()
685698
}
699+
ant := e.EntSQL()
686700
s1, s2 := fkSymbols(e, c1, c2)
687701
all = append(all, &schema.Table{
688-
Name: e.Rel.Table,
702+
Name: e.Rel.Table,
703+
// Search for edge annotation, or
704+
// default to edge owner annotation.
705+
Schema: func() string {
706+
if ant != nil && ant.Schema != "" {
707+
return ant.Schema
708+
}
709+
if ant := n.EntSQL(); ant != nil && ant.Schema != "" {
710+
return ant.Schema
711+
}
712+
return ""
713+
}(),
714+
Annotation: ant,
689715
Columns: []*schema.Column{c1, c2},
690716
PrimaryKey: []*schema.Column{c1, c2},
691717
ForeignKeys: []*schema.ForeignKey{

entc/gen/graph_test.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,18 @@ func TestGraph_Gen(t *testing.T) {
434434
Storage: drivers[0],
435435
Templates: []*Template{external, skipped},
436436
IDType: &field.TypeInfo{Type: field.TypeInt},
437-
Features: AllFeatures,
437+
Features: []Feature{
438+
FeaturePrivacy,
439+
FeatureIntercept,
440+
FeatureEntQL,
441+
FeatureNamedEdges,
442+
FeatureSnapshot,
443+
FeatureSchemaConfig,
444+
FeatureLock,
445+
FeatureModifier,
446+
FeatureExecQuery,
447+
FeatureUpsert,
448+
},
438449
}, schemas...)
439450
require.NoError(err)
440451
require.NotNil(graph)
@@ -482,7 +493,18 @@ func TestGraph_Gen(t *testing.T) {
482493
Storage: drivers[0],
483494
Templates: []*Template{external, skipped},
484495
IDType: &field.TypeInfo{Type: field.TypeInt},
485-
Features: AllFeatures,
496+
Features: []Feature{
497+
FeaturePrivacy,
498+
FeatureIntercept,
499+
FeatureEntQL,
500+
FeatureNamedEdges,
501+
FeatureSnapshot,
502+
FeatureSchemaConfig,
503+
FeatureLock,
504+
FeatureModifier,
505+
FeatureExecQuery,
506+
FeatureUpsert,
507+
},
486508
}, schemas...)
487509
require.NoError(err)
488510
require.NotNil(graph)

entc/gen/storage.go

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package gen
77
import (
88
"fmt"
99
"reflect"
10+
"sort"
11+
"strings"
1012

1113
"entgo.io/ent/dialect/gremlin/graph/dsl"
1214
"entgo.io/ent/dialect/sql"
@@ -34,14 +36,15 @@ func (m SchemaMode) Support(mode SchemaMode) bool { return m&mode != 0 }
3436

3537
// Storage driver type for codegen.
3638
type Storage struct {
37-
Name string // storage name.
38-
Builder reflect.Type // query builder type.
39-
Dialects []string // supported dialects.
40-
IdentName string // identifier name (fields and funcs).
41-
Imports []string // import packages needed.
42-
SchemaMode SchemaMode // schema mode support.
43-
Ops func(*Field) []Op // storage specific operations.
44-
OpCode func(Op) string // operation code for predicates.
39+
Name string // storage name.
40+
Builder reflect.Type // query builder type.
41+
Dialects []string // supported dialects.
42+
IdentName string // identifier name (fields and funcs).
43+
Imports []string // import packages needed.
44+
SchemaMode SchemaMode // schema mode support.
45+
Ops func(*Field) []Op // storage specific operations.
46+
OpCode func(Op) string // operation code for predicates.
47+
Init func(*Graph) error // optional init function.
4548
}
4649

4750
// StorageDrivers holds the storage driver options for entc.
@@ -66,6 +69,30 @@ var drivers = []*Storage{
6669
return nil
6770
},
6871
OpCode: opCodes(sqlCode[:]),
72+
Init: func(g *Graph) error {
73+
var with, without []string
74+
for _, n := range g.Nodes {
75+
if s, err := n.TableSchema(); err == nil && s != "" {
76+
with = append(with, n.Name)
77+
} else {
78+
without = append(without, n.Name)
79+
}
80+
}
81+
switch {
82+
case len(with) == 0:
83+
return nil
84+
case len(without) > 0:
85+
return fmt.Errorf("missing schema annotation for %s", strings.Join(without, ", "))
86+
default:
87+
if !g.featureEnabled(FeatureSchemaConfig) {
88+
g.Features = append(g.Features, FeatureSchemaConfig)
89+
}
90+
if !g.featureEnabled(featureMultiSchema) {
91+
g.Features = append(g.Features, featureMultiSchema)
92+
}
93+
return nil
94+
}
95+
},
6996
},
7097
{
7198
Name: "gremlin",
@@ -82,6 +109,7 @@ var drivers = []*Storage{
82109
},
83110
SchemaMode: Unique,
84111
OpCode: opCodes(gremlinCode[:]),
112+
Init: func(*Graph) error { return nil }, // Noop.
85113
},
86114
}
87115

@@ -126,3 +154,51 @@ func opCodes(codes []string) func(Op) string {
126154
return o.Name()
127155
}
128156
}
157+
158+
// TableSchemas returns all table schemas in ent/schema (intentionally exported).
159+
func (g *Graph) TableSchemas() ([]string, error) {
160+
all := make(map[string]struct{})
161+
for _, n := range g.Nodes {
162+
s, err := n.TableSchema()
163+
if err != nil {
164+
return nil, err
165+
}
166+
all[s] = struct{}{}
167+
for _, e := range n.Edges {
168+
// {{- if and $e.M2M (not $e.Inverse) (not $e.Through) }}
169+
if e.M2M() && !e.IsInverse() && e.Through == nil {
170+
s, err := e.TableSchema()
171+
if err != nil {
172+
return nil, err
173+
}
174+
all[s] = struct{}{}
175+
}
176+
}
177+
}
178+
names := make([]string, 0, len(all))
179+
for s := range all {
180+
names = append(names, s)
181+
}
182+
sort.Strings(names)
183+
return names, nil
184+
}
185+
186+
// TableSchema returns the schema name of where the type table resides (intentionally exported).
187+
func (t *Type) TableSchema() (string, error) {
188+
switch ant := t.EntSQL(); {
189+
case ant == nil || ant.Schema == "":
190+
return "", fmt.Errorf("atlas: missing schema annotation for node %q", t.Name)
191+
default:
192+
return ant.Schema, nil
193+
}
194+
}
195+
196+
// TableSchema returns the schema name of where the type table resides (intentionally exported).
197+
func (e *Edge) TableSchema() (string, error) {
198+
switch ant := e.EntSQL(); {
199+
case ant == nil || ant.Schema == "":
200+
return e.Owner.TableSchema()
201+
default:
202+
return ant.Schema, nil
203+
}
204+
}

entc/gen/template.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ var (
170170
"client/additional/*",
171171
"client/additional/*/*",
172172
"config/*/*",
173+
"config/*/*/*",
173174
"create/additional/*",
174175
"delete/additional/*",
175176
"dialect/*/*/*/spec/*",
@@ -179,6 +180,7 @@ var (
179180
"dialect/*/query/selector/*",
180181
"dialect/sql/create/additional/*",
181182
"dialect/sql/create_bulk/additional/*",
183+
"dialect/sql/meta/constants/*",
182184
"dialect/sql/model/additional/*",
183185
"dialect/sql/model/edges/*",
184186
"dialect/sql/model/edges/fields/additional/*",

0 commit comments

Comments
 (0)