Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 15bebe8

Browse files
authored
ent: support schema view (#4157)
1 parent 5cd2ede commit 15bebe8

Some content is hidden

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

95 files changed

+14607
-31
lines changed

dialect/entsql/annotation.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
package entsql
66

7-
import "entgo.io/ent/schema"
7+
import (
8+
"errors"
9+
"fmt"
10+
11+
"entgo.io/ent/dialect/sql"
12+
"entgo.io/ent/schema"
13+
)
814

915
// Annotation is a builtin schema annotation for attaching
1016
// SQL metadata to schema objects for both codegen and runtime.
@@ -158,6 +164,27 @@ type Annotation struct {
158164
// }
159165
//
160166
Skip bool `json:"skip,omitempty"`
167+
168+
// ViewAs allows defining a view for the schema. For example:
169+
//
170+
// entsql.Annotation{
171+
// View: "SELECT name FROM users",
172+
// }
173+
ViewAs string `json:"view_as,omitempty"`
174+
175+
// ViewFor allows defining a view for the schema per dialect. For example:
176+
//
177+
// entsql.Annotation{
178+
// ViewFor: map[string]string{
179+
// dialect.MySQL: "...",
180+
// dialect.Postgres: "...",
181+
// },
182+
// }
183+
ViewFor map[string]string `json:"view_for,omitempty"`
184+
185+
// error occurs during annotation build. This field is not
186+
// serialized to JSON and used only by the codegen loader.
187+
err error
161188
}
162189

163190
// Name describes the annotation name.
@@ -232,6 +259,35 @@ func Skip() *Annotation {
232259
return &Annotation{Skip: true}
233260
}
234261

262+
// View specifies the definition of a view.
263+
func View(as string) *Annotation {
264+
return &Annotation{ViewAs: as}
265+
}
266+
267+
// ViewFor specifies the definition of a view.
268+
func ViewFor(dialect string, as func(*sql.Selector)) *Annotation {
269+
b := sql.Dialect(dialect).Select()
270+
as(b)
271+
switch q, args := b.Query(); {
272+
case len(args) > 0:
273+
return &Annotation{
274+
err: fmt.Errorf("entsql: view query should not contain arguments. got: %d", len(args)),
275+
}
276+
case q == "":
277+
return &Annotation{
278+
err: errors.New("entsql: view query is empty"),
279+
}
280+
case b.Err() != nil:
281+
return &Annotation{
282+
err: b.Err(),
283+
}
284+
default:
285+
return &Annotation{
286+
ViewFor: map[string]string{dialect: q},
287+
}
288+
}
289+
}
290+
235291
// Default specifies a literal default value of a column. Note that using
236292
// this option overrides the default behavior of the code-generation.
237293
//
@@ -376,9 +432,28 @@ func (a Annotation) Merge(other schema.Annotation) schema.Annotation {
376432
if ant.Skip {
377433
a.Skip = true
378434
}
435+
if v := ant.ViewAs; v != "" {
436+
a.ViewAs = v
437+
}
438+
if vf := ant.ViewFor; len(vf) > 0 {
439+
if a.ViewFor == nil {
440+
a.ViewFor = make(map[string]string)
441+
}
442+
for dialect, view := range vf {
443+
a.ViewFor[dialect] = view
444+
}
445+
}
446+
if ant.err != nil {
447+
a.err = errors.Join(a.err, ant.err)
448+
}
379449
return a
380450
}
381451

452+
// Err returns the error that occurred during annotation build, if any.
453+
func (a Annotation) Err() error {
454+
return a.err
455+
}
456+
382457
var _ interface {
383458
schema.Annotation
384459
schema.Merger

dialect/sql/builder.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,15 @@ type Selector struct {
21872187
lock *LockOptions
21882188
}
21892189

2190+
// New returns a new Selector with the same dialect and context.
2191+
func (s *Selector) New() *Selector {
2192+
c := Dialect(s.dialect).Select()
2193+
if s.ctx != nil {
2194+
c = c.WithContext(s.ctx)
2195+
}
2196+
return c
2197+
}
2198+
21902199
// WithContext sets the context into the *Selector.
21912200
func (s *Selector) WithContext(ctx context.Context) *Selector {
21922201
if ctx == nil {
@@ -2240,6 +2249,11 @@ func (s *Selector) Select(columns ...string) *Selector {
22402249
return s
22412250
}
22422251

2252+
// SelectDistinct selects distinct columns.
2253+
func (s *Selector) SelectDistinct(columns ...string) *Selector {
2254+
return s.Select(columns...).Distinct()
2255+
}
2256+
22432257
// AppendSelect appends additional columns to the SELECT statement.
22442258
func (s *Selector) AppendSelect(columns ...string) *Selector {
22452259
for i := range columns {
@@ -2626,6 +2640,10 @@ const (
26262640

26272641
// Union appends the UNION (DISTINCT) clause to the query.
26282642
func (s *Selector) Union(t TableView) *Selector {
2643+
if s1, ok := t.(*Selector); ok && s == s1 {
2644+
s.AddError(errors.New("self UNION is not supported. Create a clone or a new selector instead"))
2645+
return s
2646+
}
26292647
s.setOps = append(s.setOps, setOp{
26302648
Type: setOpTypeUnion,
26312649
TableView: t,

dialect/sql/schema/atlas.go

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,11 @@ func (a *Atlas) StateReader(tables ...*Table) migrate.StateReaderFunc {
572572
if err != nil {
573573
return nil, err
574574
}
575-
return &schema.Realm{Schemas: []*schema.Schema{{Tables: ts}}}, nil
575+
vs, err := a.views(tables)
576+
if err != nil {
577+
return nil, err
578+
}
579+
return &schema.Realm{Schemas: []*schema.Schema{{Tables: ts, Views: vs}}}, nil
576580
}
577581
}
578582

@@ -878,8 +882,14 @@ func (d *db) ExecContext(ctx context.Context, query string, args ...any) (sql.Re
878882

879883
// tables converts an Ent table slice to an atlas table slice
880884
func (a *Atlas) tables(tables []*Table) ([]*schema.Table, error) {
881-
ts := make([]*schema.Table, len(tables))
882-
for i, et := range tables {
885+
var (
886+
byT = make(map[*Table]*schema.Table)
887+
ts = make([]*schema.Table, 0, len(tables))
888+
)
889+
for _, et := range tables {
890+
if et.View {
891+
continue
892+
}
883893
at := schema.NewTable(et.Name)
884894
if et.Comment != "" {
885895
at.SetComment(et.Comment)
@@ -898,10 +908,14 @@ func (a *Atlas) tables(tables []*Table) ([]*schema.Table, error) {
898908
if err := a.aIndexes(et, at); err != nil {
899909
return nil, err
900910
}
901-
ts[i] = at
911+
ts = append(ts, at)
912+
byT[et] = at
902913
}
903-
for i, t1 := range tables {
904-
t2 := ts[i]
914+
for _, t1 := range tables {
915+
if t1.View {
916+
continue
917+
}
918+
t2 := byT[t1]
905919
for _, fk1 := range t1.ForeignKeys {
906920
fk2 := schema.NewForeignKey(fk1.Symbol).
907921
SetTable(t2).
@@ -938,6 +952,30 @@ func (a *Atlas) tables(tables []*Table) ([]*schema.Table, error) {
938952
return ts, nil
939953
}
940954

955+
// tables converts an Ent table slice to an atlas table slice
956+
func (a *Atlas) views(tables []*Table) ([]*schema.View, error) {
957+
vs := make([]*schema.View, 0, len(tables))
958+
for _, et := range tables {
959+
// Not a view, or the view defined externally.
960+
if !et.View || et.Annotation == nil || (et.Annotation.ViewAs == "" && et.Annotation.ViewFor[a.dialect] == "") {
961+
continue
962+
}
963+
def := et.Annotation.ViewFor[a.dialect]
964+
if def == "" {
965+
def = et.Annotation.ViewAs
966+
}
967+
av := schema.NewView(et.Name, def)
968+
if et.Comment != "" {
969+
av.SetComment(et.Comment)
970+
}
971+
if err := a.aVColumns(et, av); err != nil {
972+
return nil, err
973+
}
974+
vs = append(vs, av)
975+
}
976+
return vs, nil
977+
}
978+
941979
func (a *Atlas) aColumns(et *Table, at *schema.Table) error {
942980
for _, c1 := range et.Columns {
943981
c2 := schema.NewColumn(c1.Name).
@@ -965,6 +1003,27 @@ func (a *Atlas) aColumns(et *Table, at *schema.Table) error {
9651003
return nil
9661004
}
9671005

1006+
func (a *Atlas) aVColumns(et *Table, at *schema.View) error {
1007+
for _, c1 := range et.Columns {
1008+
c2 := schema.NewColumn(c1.Name).
1009+
SetNull(c1.Nullable)
1010+
if c1.Collation != "" {
1011+
c2.SetCollation(c1.Collation)
1012+
}
1013+
if c1.Comment != "" {
1014+
c2.SetComment(c1.Comment)
1015+
}
1016+
if err := a.sqlDialect.atTypeC(c1, c2); err != nil {
1017+
return err
1018+
}
1019+
if err := a.atDefault(c1, c2); err != nil {
1020+
return err
1021+
}
1022+
at.AddColumns(c2)
1023+
}
1024+
return nil
1025+
}
1026+
9681027
func (a *Atlas) atDefault(c1 *Column, c2 *schema.Column) error {
9691028
if c1.Default == nil || !a.sqlDialect.supportsDefault(c1) {
9701029
return nil

dialect/sql/schema/schema.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Table struct {
3838
ForeignKeys []*ForeignKey
3939
Annotation *entsql.Annotation
4040
Comment string
41+
View bool // Indicate the table is a view.
4142
}
4243

4344
// NewTable returns a new table with the given name.
@@ -48,6 +49,13 @@ func NewTable(name string) *Table {
4849
}
4950
}
5051

52+
// NewView returns a new view with the given name.
53+
func NewView(name string) *Table {
54+
t := NewTable(name)
55+
t.View = true
56+
return t
57+
}
58+
5159
// SetComment sets the table comment.
5260
func (t *Table) SetComment(c string) *Table {
5361
t.Comment = c

ent.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ type (
123123
// }
124124
//
125125
// Deprecated: the Config object predates the schema.Annotation method and it
126-
// is planned be removed in v0.5.0. New code should use Annotations instead.
126+
// is planned to be removed in future versions. New code should use Annotations
127+
// instead.
127128
//
128129
// func (T) Annotations() []schema.Annotation {
129130
// return []schema.Annotation{
@@ -209,6 +210,20 @@ type (
209210
Schema struct {
210211
Interface
211212
}
213+
214+
// A View only schema describes an entity that all its operations
215+
// are limited to read-only. For example, a database view.
216+
//
217+
// Users that wants to define a view schema should embed the View
218+
// struct in their schema as follows:
219+
//
220+
// type V struct {
221+
// ent.View
222+
// }
223+
//
224+
View struct {
225+
Schema
226+
}
212227
)
213228

214229
// Fields of the schema.
@@ -238,6 +253,13 @@ func (Schema) Policy() Policy { return nil }
238253
// Annotations of the schema.
239254
func (Schema) Annotations() []schema.Annotation { return nil }
240255

256+
// Viewer is an interface that wraps the view method.
257+
// Implemented by the View struct.
258+
type Viewer interface{ view() }
259+
260+
// view is a dummy method to distinguish between Schema and View.
261+
func (View) view() {}
262+
241263
type (
242264
// Value represents a dynamic value returned by mutations or queries.
243265
Value any

entc/gen/graph.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,17 @@ func NewGraph(c *Config, schemas ...*load.Schema) (g *Graph, err error) {
184184
return
185185
}
186186

187+
// MutableNodes returns the list of nodes that are mutable. i.e., not views.
188+
func (g *Graph) MutableNodes() []*Type {
189+
nodes := make([]*Type, 0, len(g.Nodes))
190+
for _, n := range g.Nodes {
191+
if !n.IsView() {
192+
nodes = append(nodes, n)
193+
}
194+
}
195+
return nodes
196+
}
197+
187198
// defaultIDType holds the default value for IDType.
188199
var defaultIDType = &field.TypeInfo{Type: field.TypeInt}
189200

@@ -232,6 +243,9 @@ func generate(g *Graph) error {
232243
for _, n := range g.Nodes {
233244
assets.addDir(filepath.Join(g.Config.Target, n.PackageDir()))
234245
for _, tmpl := range Templates {
246+
if tmpl.Cond != nil && !tmpl.Cond(n) {
247+
continue
248+
}
235249
b := bytes.NewBuffer(nil)
236250
if err := templates.ExecuteTemplate(b, tmpl.Name, n); err != nil {
237251
return fmt.Errorf("execute template %q: %w", tmpl.Name, err)
@@ -617,7 +631,7 @@ func (g *Graph) edgeSchemas() error {
617631
// Tables returns the schema definitions of SQL tables for the graph.
618632
func (g *Graph) Tables() (all []*schema.Table, err error) {
619633
tables := make(map[string]*schema.Table)
620-
for _, n := range g.Nodes {
634+
for _, n := range g.MutableNodes() {
621635
table := schema.NewTable(n.Table()).
622636
SetComment(n.sqlComment())
623637
if n.HasOneFieldID() {
@@ -764,6 +778,32 @@ func (g *Graph) Tables() (all []*schema.Table, err error) {
764778
return
765779
}
766780

781+
// Views returns all schema views
782+
func (g *Graph) Views() (views []*schema.Table, err error) {
783+
for _, n := range g.Nodes {
784+
if !n.IsView() {
785+
continue
786+
}
787+
view := schema.NewView(n.Table()).
788+
SetComment(n.sqlComment())
789+
switch ant := n.EntSQL(); {
790+
case ant == nil:
791+
case ant.Skip:
792+
continue
793+
default:
794+
view.SetAnnotation(ant).SetSchema(ant.Schema)
795+
}
796+
for _, f := range n.Fields {
797+
if a := f.EntSQL(); a != nil && a.Skip {
798+
continue
799+
}
800+
view.AddColumn(f.Column())
801+
}
802+
views = append(views, view)
803+
}
804+
return
805+
}
806+
767807
// mayAddColumn adds the given column if it does not already exist in the table.
768808
func mayAddColumn(t *schema.Table, c *schema.Column) {
769809
if !t.HasColumn(c.Name) {

0 commit comments

Comments
 (0)