Skip to content

Commit

Permalink
entc/gen: add feature-flag to set unique backref when their inverse i…
Browse files Browse the repository at this point in the history
…s loaded (ent#3953)
  • Loading branch information
a8m authored Feb 24, 2024
1 parent d4560fa commit 7284481
Show file tree
Hide file tree
Showing 87 changed files with 463 additions and 579 deletions.
11 changes: 11 additions & 0 deletions entc/gen/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ var (
Description: "NamedEdges provides an API for eager-loading edges with dynamic names",
}

// FeatureBidiEdgeRefs provides a feature-flag for sql dialect to set two-way
// references when loading (unique) edges. Note, users that use the standard
// encoding/json.MarshalJSON should detach the circular references before marshaling.
FeatureBidiEdgeRefs = Feature{
Name: "bidiedges",
Stage: Experimental,
Default: false,
Description: "This features guides Ent to set two-way references when loading (O2M/O2O) edges",
}

// FeatureSnapshot stores a snapshot of ent/schema and auto-solve merge-conflict (issue #852).
FeatureSnapshot = Feature{
Name: "schema/snapshot",
Expand Down Expand Up @@ -140,6 +150,7 @@ var (
FeatureIntercept,
FeatureEntQL,
FeatureNamedEdges,
FeatureBidiEdgeRefs,
FeatureSnapshot,
FeatureSchemaConfig,
FeatureLock,
Expand Down
16 changes: 15 additions & 1 deletion entc/gen/template/dialect/sql/query.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,21 @@ func ({{ $receiver }} *{{ $builder }}) sqlAll(ctx context.Context, hooks ...quer
if query := {{ $receiver }}.{{ $e.EagerLoadField }}; query != nil {
if err := {{ $receiver }}.load{{ $e.StructField }}(ctx, query, nodes, {{ if $e.Unique }}nil{{ else }}
func(n *{{ $.Name }}){ n.Edges.{{ $e.StructField }} = []*{{ $e.Type.Name }}{} }{{ end }},
func(n *{{ $.Name }}, e *{{ $e.Type.Name }}){ n.Edges.{{ $e.StructField }} = {{ if $e.Unique }}e{{ else }}append(n.Edges.{{ $e.StructField }}, e){{ end }} }); err != nil {
{{- $lhs := printf "n.Edges.%s" $e.StructField }}
{{- $rhs := print "e" }}{{- if not $e.Unique }}{{ $rhs = printf "append(%s, e)" $lhs }}{{ end }}
{{- if and ($.FeatureEnabled "bidiedges") $e.Ref $e.Ref.Unique }}
func(n *{{ $.Name }}, e *{{ $e.Type.Name }}){
{{ printf "%s = %s" $lhs $rhs }}
{{- $idx := $e.Ref.Index }}
{{- /* Set only in case this type was not loaded explicitly (without custom options). */}}
if !e.Edges.loadedTypes[{{ $idx }}] {
e.Edges.{{ $e.Ref.StructField }} = n
}
}); err != nil {
{{- else }}
{{- /* Keep it one-liner if there is not inverse-condition. */}}
func(n *{{ $.Name }}, e *{{ $e.Type.Name }}){ {{ printf "%s = %s" $lhs $rhs }} }); err != nil {
{{- end }}
return nil, err
}
}
Expand Down
26 changes: 15 additions & 11 deletions entc/gen/template/ent.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ type {{ $.Name }} struct {
}

{{- with $.Edges }}
{{- $edgesType := print $.Name "Edges"}}
// {{ $.Name }}Edges holds the relations/edges for other nodes in the graph.
type {{ $.Name }}Edges struct {
type {{ $edgesType }} struct {
{{- range $e := . }}
{{- template "model/edgecomment" $e }}
{{ $e.StructField }} {{ if not $e.Unique }}[]{{ end }}*{{ $e.Type.Name }} {{ with $e.StructTag }}`{{ . }}`{{ end }}
Expand All @@ -68,16 +69,19 @@ type {{ $.Name }}Edges struct {
{{- range $i, $e := . }}
// {{ $e.StructField }}OrErr returns the {{ $e.StructField }} value or an error if the edge
// was not loaded in eager-loading{{ if $e.Unique }}, or loaded but was not found{{ end }}.
func (e {{ $.Name }}Edges) {{ $e.StructField }}OrErr() ({{ if not $e.Unique }}[]{{ end }}*{{ $e.Type.Name }}, error) {
if e.loadedTypes[{{ $i }}] {
{{- if $e.Unique }}
if e.{{ $e.StructField }} == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: {{ $e.Type.Package }}.Label}
}
{{- end }}
return e.{{ $e.StructField }}, nil
}
func (e {{ $edgesType }}) {{ $e.StructField }}OrErr() ({{ if not $e.Unique }}[]{{ end }}*{{ $e.Type.Name }}, error) {
{{- if $e.Unique }}
if e.{{ $e.StructField }} != nil {
return e.{{ $e.StructField }}, nil
} else if e.loadedTypes[{{ $i }}] {
{{- /* Edge was loaded but was not found. */}}
return nil, &NotFoundError{label: {{ $e.Type.Package }}.Label}
}
{{- else }}
if e.loadedTypes[{{ $i }}] {
return e.{{ $e.StructField }}, nil
}
{{- end }}
return nil, &NotLoadedError{edge: "{{ $e.Name }}"}
}
{{- end }}
Expand Down
16 changes: 16 additions & 0 deletions entc/gen/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -2122,6 +2122,22 @@ func (e Edge) EntSQL() *entsql.Annotation {
return sqlAnnotate(e.Annotations)
}

// Index returns the index of the edge in the schema.
// Used mainly to extract its position in the "loadedTypes" array.
func (e Edge) Index() (int, error) {
// "owner" is the type that holds the edge.
owner := e.Owner
if e.IsInverse() {
owner = e.Ref.Type
}
for i, e1 := range owner.Edges {
if e1.Name == e.Name {
return i, nil
}
}
return 0, fmt.Errorf("edge %q was not found in its owner schema %q", e.Name, e.Owner.Name)
}

// Column returns the first element from the columns slice.
func (r Relation) Column() string {
if len(r.Columns) == 0 {
Expand Down
8 changes: 3 additions & 5 deletions entc/integration/cascadelete/ent/comment.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/cascadelete/ent/post.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/blob.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 6 additions & 10 deletions entc/integration/customid/ent/bloblink.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/car.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/device.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/doc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/intsid.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/note.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 6 additions & 10 deletions entc/integration/customid/ent/pet.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/session.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/token.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/customid/ent/user.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/edgefield/ent/card.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions entc/integration/edgefield/ent/info.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7284481

Please sign in to comment.