snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Atlas CI Public / lint (push) Failing after 13s
Atlas CI Public / sync (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 22:12:36 +05:00
commit 4b3ad0b611
2680 changed files with 719472 additions and 0 deletions

429
entc/gen/template/base.tmpl Normal file
View File

@@ -0,0 +1,429 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "base" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
{{ template "import" $ }}
import (
"reflect"
{{- range $n := $.Nodes }}
{{ $n.PackageAlias }} "{{ $n.Config.Package }}/{{ $n.PackageDir }}"
{{- end }}
)
// ent aliases to avoid import conflicts in user's code.
type (
Op = ent.Op
Hook = ent.Hook
Value = ent.Value
Query = ent.Query
QueryContext = ent.QueryContext
Querier = ent.Querier
QuerierFunc = ent.QuerierFunc
Interceptor = ent.Interceptor
InterceptFunc = ent.InterceptFunc
Traverser = ent.Traverser
TraverseFunc = ent.TraverseFunc
Policy = ent.Policy
Mutator = ent.Mutator
Mutation = ent.Mutation
MutateFunc = ent.MutateFunc
)
type clientCtxKey struct{}
// FromContext returns a Client stored inside a context, or nil if there isn't one.
func FromContext(ctx context.Context) *Client {
c, _ := ctx.Value(clientCtxKey{}).(*Client)
return c
}
// NewContext returns a new context with the given Client attached.
func NewContext(parent context.Context, c *Client) context.Context {
return context.WithValue(parent, clientCtxKey{}, c)
}
type txCtxKey struct {}
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
func TxFromContext(ctx context.Context) *Tx {
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
return tx
}
// NewTxContext returns a new context with the given Tx attached.
func NewTxContext(parent context.Context, tx *Tx) context.Context {
return context.WithValue(parent, txCtxKey{}, tx)
}
{{ $tmpl := printf "dialect/%s/order/signature" $.Storage }}
{{ xtemplate $tmpl . }}
{{ range $f, $order := order }}
{{ $f = pascal $f }}
// {{ $f }} applies the given fields in {{ upper $f }} order.
func {{ $f }}(fields ...string) func({{ $.Storage.Builder }}) {
{{- with extend (index $.Nodes 0) "Func" $f "Order" $order -}}
{{ $tmpl := printf "dialect/%s/order/func" $.Storage }}
return {{ xtemplate $tmpl . }}
{{ end -}}
}
{{ end }}
{{ $tmpl = printf "dialect/%s/group/signature" $.Storage }}
// AggregateFunc applies an aggregation step on the group-by traversal/selector.
{{ xtemplate $tmpl . }}
// As is a pseudo aggregation function for renaming another other functions with custom names. For example:
//
// GroupBy(field1, field2).
// Aggregate({{ $pkg }}.As({{ $pkg }}.Sum(field1), "sum_field1"), ({{ $pkg }}.As({{ $pkg }}.Sum(field2), "sum_field2")).
// Scan(ctx, &v)
//
func As(fn AggregateFunc, end string) AggregateFunc {
{{- $tmpl = printf "dialect/%s/group/as" $.Storage }}
return {{ xtemplate $tmpl . }}
}
{{ range $name, $withField := aggregate }}
{{ $fn := pascal $name }}
{{ $tmpl := printf "dialect/%s/group/const" $.Storage }}
{{ if hasTemplate $tmpl }}
{{ with extend (index $.Nodes 0) "Name" $name "Func" $fn -}}
{{ xtemplate $tmpl . }}
{{ end }}
{{ end }}
// {{ $fn }} applies the {{ quote $name }} aggregation function on {{ if $withField }}the given field of {{ end }}each group.
func {{ $fn }}({{ if $withField }}field string{{ end }}) AggregateFunc {
{{- with extend (index $.Nodes 0) "Func" $fn "WithField" $withField -}}
{{ $tmpl := printf "dialect/%s/group/func" $.Storage }}
return {{ xtemplate $tmpl . }}
{{ end -}}
}
{{ end }}
// ValidationError returns when validating a field or edge fails.
type ValidationError struct {
Name string // Field or edge name.
err error
}
// Error implements the error interface.
func (e *ValidationError) Error() string {
return e.err.Error()
}
// Unwrap implements the errors.Wrapper interface.
func (e *ValidationError) Unwrap() error {
return e.err
}
// IsValidationError returns a boolean indicating whether the error is a validation error.
func IsValidationError(err error) bool {
if err == nil {
return false
}
var e *ValidationError
return errors.As(err, &e)
}
// NotFoundError returns when trying to fetch a specific entity and it was not found in the database.
type NotFoundError struct {
label string
}
// Error implements the error interface.
func (e *NotFoundError) Error() string {
return "{{ $pkg }}: " + e.label + " not found"
}
// IsNotFound returns a boolean indicating whether the error is a not found error.
func IsNotFound(err error) bool {
if err == nil {
return false
}
var e *NotFoundError
return errors.As(err, &e)
}
// MaskNotFound masks not found error.
func MaskNotFound(err error) error {
if IsNotFound(err) {
return nil
}
return err
}
// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database.
type NotSingularError struct {
label string
}
// Error implements the error interface.
func (e *NotSingularError) Error() string {
return "{{ $pkg }}: " + e.label + " not singular"
}
// IsNotSingular returns a boolean indicating whether the error is a not singular error.
func IsNotSingular(err error) bool {
if err == nil {
return false
}
var e *NotSingularError
return errors.As(err, &e)
}
// NotLoadedError returns when trying to get a node that was not loaded by the query.
type NotLoadedError struct {
edge string
}
// Error implements the error interface.
func (e *NotLoadedError) Error() string {
return "{{ $pkg }}: " + e.edge + " edge was not loaded"
}
// IsNotLoaded returns a boolean indicating whether the error is a not loaded error.
func IsNotLoaded(err error) bool {
if err == nil {
return false
}
var e *NotLoadedError
return errors.As(err, &e)
}
// ConstraintError returns when trying to create/update one or more entities and
// one or more of their constraints failed. For example, violation of edge or
// field uniqueness.
type ConstraintError struct {
msg string
wrap error
}
// Error implements the error interface.
func (e ConstraintError) Error() string {
return "{{ $pkg }}: constraint failed: " + e.msg
}
// Unwrap implements the errors.Wrapper interface.
func (e *ConstraintError) Unwrap() error {
return e.wrap
}
// IsConstraintError returns a boolean indicating whether the error is a constraint failure.
func IsConstraintError(err error) bool {
if err == nil {
return false
}
var e *ConstraintError
return errors.As(err, &e)
}
// selector embedded by the different Select/GroupBy builders.
type selector struct {
label string
flds *[]string
fns []AggregateFunc
scan func (context.Context, any) error
}
// ScanX is like Scan, but panics if an error occurs.
func (s *selector) ScanX(ctx context.Context, v any) {
if err := s.scan(ctx, v); err != nil {
panic(err)
}
}
{{ range $t := primitives }}
{{ $plural := pascal $t | plural }}
// {{ $plural }} returns list of {{ plural $t }} from a selector. It is only allowed when selecting one field.
func (s *selector) {{ $plural }}(ctx context.Context) ([]{{ $t }}, error) {
if len(*s.flds) > 1 {
return nil, errors.New("{{ $pkg }}: {{ $plural }} is not achievable when selecting more than 1 field")
}
var v []{{ $t }}
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// {{ $plural }}X is like {{ $plural }}, but panics if an error occurs.
func (s *selector) {{ $plural }}X(ctx context.Context) []{{ $t }} {
v, err := s.{{ $plural }}(ctx)
if err != nil {
panic(err)
}
return v
}
{{ $singular := pascal $t -}}
// {{ $singular }} returns a single {{ $t }} from a selector. It is only allowed when selecting one field.
func (s *selector) {{ $singular }}(ctx context.Context) (_ {{ $t }}, err error) {
var v []{{ $t }}
if v, err = s.{{ $plural }}(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("{{ $pkg }}: {{ $plural }} returned %d results when one was expected", len(v))
}
return
}
// {{ $singular }}X is like {{ $singular }}, but panics if an error occurs.
func (s *selector) {{ $singular }}X(ctx context.Context) {{ $t }} {
v, err := s.{{ $singular }}(ctx)
if err != nil {
panic(err)
}
return v
}
{{ end }}
// withHooks invokes the builder operation with the given hooks, if any.
func withHooks[V Value, M any, PM interface {
*M
Mutation
}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) {
if len(hooks) == 0 {
return exec(ctx)
}
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
{{- /* redundant any() conversion is a workaround for golang/go#58633 */}}
mutationT, ok := any(m).(PM)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
// Set the mutation to the builder.
*mutation = *mutationT
return exec(ctx)
})
for i := len(hooks) - 1; i >= 0; i-- {
if hooks[i] == nil {
return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = hooks[i](mut)
}
v, err := mut.Mutate(ctx, mutation)
if err != nil {
return value, err
}
nv, ok := v.(V)
if !ok {
return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation)
}
return nv, nil
}
// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist.
func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context {
if ent.QueryFromContext(ctx) == nil {
qc.Op = op
ctx = ent.NewQueryContext(ctx, qc)
}
return ctx
}
func querierAll[V Value, Q interface {
{{ $.Storage }}All(context.Context, ...queryHook) (V, error)
}]() Querier {
return QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
query, ok := q.(Q)
if !ok {
return nil, fmt.Errorf("unexpected query type %T", q)
}
return query.{{ $.Storage }}All(ctx)
})
}
func querierCount[Q interface {
{{ $.Storage }}Count(context.Context) (int, error)
}]() Querier {
return QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
query, ok := q.(Q)
if !ok {
return nil, fmt.Errorf("unexpected query type %T", q)
}
return query.{{ $.Storage }}Count(ctx)
})
}
func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) {
for i := len(inters) - 1; i >= 0; i-- {
qr = inters[i].Intercept(qr)
}
rv, err := qr.Query(ctx, q)
if err != nil {
return v, err
}
vt, ok := rv.(V)
if !ok {
return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v)
}
return vt, nil
}
func scanWithInterceptors[Q1 ent.Query, Q2 interface {
{{ $.Storage }}Scan(context.Context, Q1, any) error
}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error {
rv := reflect.ValueOf(v)
var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
query, ok := q.(Q1)
if !ok {
return nil, fmt.Errorf("unexpected query type %T", q)
}
if err := selectOrGroup.{{ $.Storage }}Scan(ctx, query, v); err != nil {
return nil, err
}
if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() {
return rv.Elem().Interface(), nil
}
return v, nil
})
for i := len(inters) - 1; i >= 0; i-- {
qr = inters[i].Intercept(qr)
}
vv, err := qr.Query(ctx, rootQuery)
if err != nil {
return err
}
switch rv2 := reflect.ValueOf(vv); {
case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer:
case rv.Type() == rv2.Type():
rv.Elem().Set(rv2.Elem())
case rv.Elem().Type() == rv2.Type():
rv.Elem().Set(rv2)
}
return nil
}
{{/* expand error types and global helpers. */}}
{{ $tmpl = printf "dialect/%s/errors" $.Storage }}
{{ if hasTemplate $tmpl }}
{{ xtemplate $tmpl $ }}
{{ end }}
{{ $tmpl = printf "dialect/%s/globals" $.Storage }}
{{ if hasTemplate $tmpl }}
{{ xtemplate $tmpl $ }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,173 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "create" }}
{{ $pkg := base $.Config.Package }}
{{ $runtimeRequired := or $.NumHooks $.NumPolicy }}
{{ template "header" $ }}
{{ with extend $ "Imports" $.SiblingImports }}
{{ template "import" . }}
{{ end }}
{{ $builder := $.CreateName }}
{{ $receiver := $.CreateReceiver }}
{{ $mutation := print $receiver ".mutation" }}
// {{ $builder }} is the builder for creating a {{ $.Name }} entity.
type {{ $builder }} struct {
config
mutation *{{ $.MutationName }}
hooks []Hook
{{- /* Additional fields to add to the builder. */}}
{{- $tmpl := printf "dialect/%s/create/fields" $.Storage }}
{{- if hasTemplate $tmpl }}
{{- xtemplate $tmpl . }}
{{- end }}
}
{{ with extend $ "Receiver" $receiver "Builder" $builder }}
{{ template "setter" . }}
{{ end }}
// Save creates the {{ $.Name }} in the database.
func ({{ $receiver }} *{{ $builder }}) Save(ctx context.Context) (*{{ $.Name }}, error) {
{{- if $.HasDefault }}
{{- if $runtimeRequired }}
if err := {{ $receiver }}.defaults(); err != nil {
return nil, err
}
{{- else }}
{{ $receiver }}.defaults()
{{- end }}
{{- end }}
return withHooks(ctx, {{ $receiver }}.{{ $.Storage }}Save, {{ $mutation }}, {{ $receiver }}.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func ({{ $receiver }} *{{ $builder }}) SaveX(ctx context.Context) *{{ $.Name }} {
v, err := {{ $receiver }}.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func ({{ $receiver }} *{{ $builder }}) Exec(ctx context.Context) error {
_, err := {{ $receiver }}.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) ExecX(ctx context.Context) {
if err := {{ $receiver }}.Exec(ctx); err != nil {
panic(err)
}
}
{{- $fields := $.Fields }}{{ if $.HasOneFieldID }}{{ if $.ID.UserDefined }}{{ $fields = append $fields $.ID }}{{ end }}{{ end }}
{{ if $.HasDefault }}
// defaults sets the default values of the builder before save.
func ({{ $receiver }} *{{ $builder }}) defaults() {{ if $runtimeRequired }}error{{ end }}{
{{- range $f := $fields }}
{{- if $f.Default }}
if _, ok := {{ $mutation }}.{{ $f.MutationGet }}(); !ok {
{{- if and $runtimeRequired $f.DefaultFunc }}
if {{ $.Package }}.{{ $f.DefaultName }} == nil {
return fmt.Errorf("{{ $pkg }}: uninitialized {{ $.Package }}.{{ $f.DefaultName }} (forgotten import {{ $pkg }}/runtime?)")
}
{{- end }}
v := {{ $.Package }}.{{ $f.DefaultName }}{{ if $f.DefaultFunc }}(){{ end }}
{{ $mutation }}.{{ $f.MutationSet }}(v)
}
{{- end }}
{{- end }}
{{- if $runtimeRequired }}
return nil
{{- end }}
}
{{ end }}
// check runs all checks and user-defined validators on the builder.
func ({{ $receiver }} *{{ $builder }}) check() error {
{{- range $f := $fields }}
{{- if and $f.IsBlobLazy (not (hasTemplate (printf "dialect/%s/model/fields" $.Storage))) }}{{ continue }}{{ end }}
{{- $skip := false }}{{ if $.HasOneFieldID }}{{ if eq $f.Name $.ID.Name }}{{ $skip = true }}{{ end }}{{ end }}
{{- if and (not $f.Optional) (not $skip) }}
{{- $dialects := $f.RequiredFor }}
{{- $n := len $dialects }}
{{- if $n }}
{{- $partially := ne $n (len $.Config.Storage.Dialects) }}
{{- if $partially }}
switch {{ $receiver }}.driver.Dialect() {
case {{ join $dialects ", " }}:
{{- end }}
if _, ok := {{ $mutation }}.{{ $f.MutationGet }}(); !ok {
return &ValidationError{Name: "{{ $f.Name }}", err: errors.New(`{{ $pkg }}: missing required field "{{ $.Name }}.{{ $f.Name }}"`)}
}
{{- if $partially }}
}
{{- end }}
{{- end }}
{{- end }}
{{- $isValidator := and ($f.HasGoType) ($f.Type.Validator) }}
{{- with or $f.Validators $f.IsEnum $isValidator }}
if v, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
if err := {{ if or $f.Validators $f.IsEnum }}{{ $.Package }}.{{ $f.Validator }}({{ $f.BasicType "v" }}){{ else }}v.Validate(){{ end }}; err != nil {
return &ValidationError{Name: "{{ $f.Name }}", err: fmt.Errorf(`{{ $pkg }}: validator failed for field "{{ $.Name }}.{{ $f.Name }}": %w`, err)}
}
}
{{- end }}
{{- end }}
{{- range $e := $.EdgesWithID }}
{{- if not $e.Optional }}
if len({{ $mutation }}.{{ $e.StructField }}IDs()) == 0 {
return &ValidationError{Name: "{{ $e.Name }}", err: errors.New(`{{ $pkg }}: missing required edge "{{ $.Name }}.{{ $e.Name }}"`)}
}
{{- end }}
{{- end }}
return nil
}
{{ with extend $ "Receiver" $receiver "Builder" $builder }}
{{ $tmpl := printf "dialect/%s/create" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{- /* Support adding create methods by global templates. */}}
{{- with $tmpls := matchTemplate "create/additional/*" }}
{{- range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ $bulk := $.CreateBulkName }}
{{ $receiver = $.CreateBulReceiver }}
// {{ $bulk }} is the builder for creating many {{ $.Name }} entities in bulk.
type {{ $bulk }} struct {
config
err error
builders []*{{ $builder }}
{{- /* Additional fields to add to the builder. */}}
{{- $tmpl = printf "dialect/%s/create_bulk/fields" $.Storage }}
{{- if hasTemplate $tmpl }}
{{- xtemplate $tmpl . }}
{{- end }}
}
{{/* If the storage driver supports bulk creation */}}
{{ $tmpl = printf "dialect/%s/create_bulk" $.Storage }}
{{ if hasTemplate $tmpl }}
{{ with extend $ "Receiver" $receiver "Builder" $bulk }}
{{ xtemplate $tmpl . }}
{{ end }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,97 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "delete" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
{{ template "import" $ }}
import (
{{ $.PackageAlias }} "{{ $.Config.Package }}/{{ $.PackageDir }}"
)
{{ $builder := $.DeleteName }}
{{ $receiver := $.DeleteReceiver }}
{{ $mutation := print $receiver ".mutation" }}
// {{ $builder }} is the builder for deleting a {{ $.Name }} entity.
type {{ $builder }} struct {
config
hooks []Hook
mutation *{{ $.MutationName }}
}
// Where appends a list predicates to the {{ $builder }} builder.
func ({{ $receiver }} *{{ $builder }}) Where(ps ...predicate.{{ $.Name }}) *{{ $builder }} {
{{ $mutation }}.Where(ps...)
return {{ $receiver }}
}
// Exec executes the deletion query and returns how many vertices were deleted.
func ({{ $receiver }} *{{ $builder }}) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, {{ $receiver }}.{{ $.Storage }}Exec, {{ $mutation }}, {{ $receiver }}.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) ExecX(ctx context.Context) int {
n, err := {{ $receiver }}.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
{{ with extend $ "Receiver" $receiver "Builder" $builder }}
{{ $tmpl := printf "dialect/%s/delete" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{- /* Support adding delete methods by global templates. */}}
{{- with $tmpls := matchTemplate "delete/additional/*" }}
{{- range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ $onebuilder := $.DeleteOneName }}
{{ $oneReceiver := $.DeleteOneReceiver }}
// {{ $onebuilder }} is the builder for deleting a single {{ $.Name }} entity.
type {{ $onebuilder }} struct {
{{ $receiver }} *{{ $builder }}
}
// Where appends a list predicates to the {{ $builder }} builder.
func ({{ $oneReceiver }} *{{ $onebuilder }}) Where(ps ...predicate.{{ $.Name }}) *{{ $onebuilder }} {
{{ $oneReceiver }}.{{ $mutation }}.Where(ps...)
return {{ $oneReceiver }}
}
// Exec executes the deletion query.
func ({{ $oneReceiver }} *{{ $onebuilder }}) Exec(ctx context.Context) error {
n, err := {{ $oneReceiver }}.{{ $receiver }}.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{ {{ $.Package }}.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func ({{ $oneReceiver }} *{{ $onebuilder }}) ExecX(ctx context.Context) {
if err := {{ $oneReceiver }}.Exec(ctx); err != nil {
panic(err)
}
}
{{ end }}

View File

@@ -0,0 +1,14 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "entql" }}
{{ $tmpl := printf "dialect/%s/entql" $.Storage }}
{{ if hasTemplate $tmpl }}
{{ xtemplate $tmpl . }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,213 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "mutation_graph" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
import (
"context"
"errors"
"fmt"
"sync"
{{- range $n := $.Nodes }}
{{ $n.PackageAlias }} "{{ $.Config.Package }}/{{ $n.PackageDir }}"
{{- template "import/types" $n }}
{{- end }}
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
const (
// Operation types.
OpCreate = ent.OpCreate
OpDelete = ent.OpDelete
OpDeleteOne = ent.OpDeleteOne
OpUpdate = ent.OpUpdate
OpUpdateOne = ent.OpUpdateOne
// Node types.
{{- range $n := $.Nodes }}
{{ $n.TypeName }} = "{{ $n.Name }}"
{{- end }}
)
{{ range $n := $.MutableNodes }}
{{ $mutation := $n.MutationName }}
{{ $baseMutation := print $n.Package ".Mutation" }}
// {{ $mutation }} represents an operation that mutates the {{ $n.Name }} nodes in the graph.
type {{ $mutation }} struct {
{{ $baseMutation }}
config
{{- with $n.HasOneFieldID }}
{{ $n.ID.BuilderField }} *{{ $n.ID.Type }}
{{- end }}
done bool
oldValue func(context.Context) (*{{ $n.Name }}, error)
}
var _ ent.Mutation = (*{{ $mutation }})(nil)
{{ $mutationOption := print (camel $n.Name) "Option" }}
// {{ $mutationOption }} allows management of the mutation configuration using functional options.
type {{ $mutationOption }} func(*{{ $mutation }})
// new{{ $mutation }} creates new mutation for the {{ $n.Name }} entity.
func new{{ $mutation }}(c config, op Op, opts ...{{ $mutationOption }}) *{{ $mutation }} {
m := &{{ $mutation }}{
Mutation: *{{ $n.Package }}.NewMutation(op),
config: c,
}
for _, opt := range opts {
opt(m)
}
return m
}
{{ with $n.HasOneFieldID }}
{{ if $n.ID.UserDefined }}
// SetID sets the value of the id field. Note that this
// operation is only accepted on creation of {{ $n.Name }} entities.
func (m *{{ $mutation }}) SetID(id {{ $n.ID.Type }}) {
m.{{ $n.ID.BuilderField }} = &id
}
{{ end }}
// ID returns the ID value in the mutation. Note that the ID is only available
// if it was provided to the builder or after it was returned from the database.
func (m *{{ $mutation }}) ID() (id {{ $n.ID.Type }}, exists bool) {
if m.{{ $n.ID.BuilderField }} == nil {
return
}
return *m.{{ $n.ID.BuilderField }}, true
}
{{ $opt := print "with" $n.Name "ID" }}
// {{ $opt }} sets the ID field of the mutation.
func {{ $opt }}(id {{ $n.ID.Type }}) {{ $mutationOption }} {
return func(m *{{ $mutation }}) {
var (
err error
once sync.Once
value *{{ $n.Name }}
)
m.oldValue = func(ctx context.Context) (*{{ $n.Name }}, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
value, err = m.Client().{{ $n.Name }}.Get(ctx, id)
}
})
return value, err
}
m.{{ $n.ID.BuilderField }} = &id
}
}
{{ $opt = print "with" $n.Name }}
// {{ $opt }} sets the old {{ $n.Name }} of the mutation.
func {{ $opt }}(node *{{ $n.Name }}) {{ $mutationOption }} {
return func(m *{{ $mutation }}) {
m.oldValue = func(context.Context) (*{{ $n.Name }}, error) {
return node, nil
}
m.{{ $n.ID.BuilderField }} = &node.ID
}
}
{{ end }}
// Client returns a new `ent.Client` from the mutation. If the mutation was
// executed in a transaction (ent.Tx), a transactional client is returned.
func (m {{ $mutation }}) Client() *Client {
client := &Client{config: m.config}
client.init()
return client
}
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
// it returns an error otherwise.
func (m {{ $mutation }}) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("{{ $pkg }}: mutation is not running in a transaction")
}
tx := &Tx{config: m.config}
tx.init()
return tx, nil
}
{{ with $n.HasOneFieldID }}
// IDs queries the database and returns the entity ids that match the mutation's predicate.
// That means, if the mutation is applied within a transaction with an isolation level such
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
// or updated by the mutation.
func (m *{{ $mutation }}) IDs(ctx context.Context) ([]{{ $n.ID.Type }}, error) {
switch {
case m.Op().Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
if exists {
return []{{ $n.ID.Type }}{id}, nil
}
fallthrough
case m.Op().Is(OpUpdate | OpDelete):
return m.Client().{{ $n.Name }}.Query().Where(m.Predicates()...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.Op())
}
}
{{ end }}
{{ range $f := $n.Fields }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ if $n.HasOneFieldID }}
{{ $const := print $n.Package "." $f.Constant }}
// {{ $f.MutationGetOld }} returns the old "{{ $f.Name }}" field's value of the {{ $n.Name }} entity.
// If the {{ $n.Name }} object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *{{ $mutation }}) {{ $f.MutationGetOld }}(ctx context.Context) (v {{ if $f.NillableValue }}*{{ end }}{{ $f.Type }}, err error) {
if !m.Op().Is(OpUpdateOne) {
return v, errors.New("{{ $f.MutationGetOld }} is only allowed on UpdateOne operations")
}
if _, exists := m.ID(); !exists || m.oldValue == nil {
return v, errors.New("{{ $f.MutationGetOld }} requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for {{ $f.MutationGetOld }}: %w", err)
}
return oldValue.{{ $f.StructField }}, nil
}
{{ end }}
{{ end }}
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
func (m *{{ $mutation }}) OldField(ctx context.Context, name string) (ent.Value, error) {
{{- if $n.HasCompositeID }}
return nil, errors.New("edge schema {{ $n.Name }} does not support getting old values")
{{- else }}
{{- with $n.Fields }}
switch name {
{{- range $f := . }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{- $const := print $n.Package "." $f.Constant }}
case {{ $const }}:
return m.{{ $f.MutationGetOld }}(ctx)
{{- end }}
}
{{- end }}
return nil, fmt.Errorf("unknown {{ $n.Name }} field %s", name)
{{- end }}
}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,500 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "query" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
{{ with extend $ "Imports" $.SiblingImports }}
{{ template "import" . }}
{{ end }}
{{ $builder := $.QueryName }}
{{ $receiver := $.QueryReceiver }}
// {{ $builder }} is the builder for querying {{ $.Name }} entities.
type {{ $builder }} struct {
config
ctx *QueryContext
order []{{ $.Package }}.OrderOption
inters []Interceptor
predicates []predicate.{{ $.Name }}
{{- /* Eager loading fields. */}}
{{- range $e := $.Edges }}
{{ $e.EagerLoadField }} *{{ $e.Type.QueryName }}
{{- end }}
{{- /* Additional fields to add to the builder. */}}
{{- $tmpl := printf "dialect/%s/query/fields" $.Storage }}
{{- if hasTemplate $tmpl }}
{{- xtemplate $tmpl . }}
{{- end }}
// intermediate query (i.e. traversal path).
{{ $.Storage }} {{ $.Storage.Builder }}
path func(context.Context) ({{ $.Storage.Builder }}, error)
}
// Where adds a new predicate for the {{ $builder }} builder.
func ({{ $receiver }} *{{ $builder }}) Where(ps ...predicate.{{ $.Name }}) *{{ $builder }} {
{{ $receiver}}.predicates = append({{ $receiver }}.predicates, ps...)
return {{ $receiver }}
}
// Limit the number of records to be returned by this query.
func ({{ $receiver }} *{{ $builder }}) Limit(limit int) *{{ $builder }} {
{{ $receiver }}.ctx.Limit = &limit
return {{ $receiver }}
}
// Offset to start from.
func ({{ $receiver }} *{{ $builder }}) Offset(offset int) *{{ $builder }} {
{{ $receiver }}.ctx.Offset = &offset
return {{ $receiver }}
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func ({{ $receiver }} *{{ $builder }}) Unique(unique bool) *{{ $builder }} {
{{ $receiver }}.ctx.Unique = &unique
return {{ $receiver }}
}
// Order specifies how the records should be ordered.
func ({{ $receiver }} *{{ $builder }}) Order(o ...{{ $.Package }}.OrderOption) *{{ $builder }} {
{{ $receiver }}.order = append({{ $receiver }}.order, o...)
return {{ $receiver }}
}
{{/* this code has similarity with edge queries in client.tmpl */}}
{{ range $e := $.Edges }}
{{ $edge_builder := print $e.Type.QueryName }}
// Query{{ pascal $e.Name }} chains the current query on the "{{ $e.Name }}" edge.
func ({{ $receiver }} *{{ $builder }}) Query{{ pascal $e.Name }}() *{{ $edge_builder }} {
query := (&{{ $e.Type.ClientName }}{config: {{ $receiver }}.config}).Query()
query.path = func(ctx context.Context) (fromU {{ $.Storage.Builder }}, err error) {
if err := {{ $receiver }}.prepareQuery(ctx); err != nil {
return nil, err
}
{{- with extend $ "Receiver" $receiver "Edge" $e "Ident" "fromU" -}}
{{ $tmpl := printf "dialect/%s/query/path" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end -}}
return fromU, nil
}
return query
}
{{ end }}
// First returns the first {{ $.Name }} entity from the query.
// Returns a *NotFoundError when no {{ $.Name }} was found.
func ({{ $receiver }} *{{ $builder }}) First(ctx context.Context) (*{{ $.Name }}, error) {
nodes, err := {{ $receiver }}.Limit(1).All(setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{ {{ $.Package }}.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) FirstX(ctx context.Context) *{{ $.Name }} {
node, err := {{ $receiver }}.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
{{ if $.HasOneFieldID }}
// FirstID returns the first {{ $.Name }} ID from the query.
// Returns a *NotFoundError when no {{ $.Name }} ID was found.
func ({{ $receiver }} *{{ $builder }}) FirstID(ctx context.Context) (id {{ $.ID.Type }}, err error) {
var ids []{{ $.ID.Type }}
if ids, err = {{ $receiver }}.Limit(1).IDs(setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{ {{ $.Package }}.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) FirstIDX(ctx context.Context) {{ $.ID.Type }} {
id, err := {{ $receiver }}.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
{{ end }}
// Only returns a single {{ $.Name }} entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one {{ $.Name }} entity is found.
// Returns a *NotFoundError when no {{ $.Name }} entities are found.
func ({{ $receiver }} *{{ $builder }}) Only(ctx context.Context) (*{{ $.Name }}, error) {
nodes, err := {{ $receiver }}.Limit(2).All(setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{ {{ $.Package }}.Label}
default:
return nil, &NotSingularError{ {{ $.Package }}.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) OnlyX(ctx context.Context) *{{ $.Name }} {
node, err := {{ $receiver }}.Only(ctx)
if err != nil {
panic(err)
}
return node
}
{{ if $.HasOneFieldID }}
// OnlyID is like Only, but returns the only {{ $.Name }} ID in the query.
// Returns a *NotSingularError when more than one {{ $.Name }} ID is found.
// Returns a *NotFoundError when no entities are found.
func ({{ $receiver }} *{{ $builder }}) OnlyID(ctx context.Context) (id {{ $.ID.Type }}, err error) {
var ids []{{ $.ID.Type }}
if ids, err = {{ $receiver }}.Limit(2).IDs(setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{ {{ $.Package }}.Label}
default:
err = &NotSingularError{ {{ $.Package }}.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) OnlyIDX(ctx context.Context) {{ $.ID.Type }} {
id, err := {{ $receiver }}.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
{{ end }}
// All executes the query and returns a list of {{ plural $.Name }}.
func ({{ $receiver }} *{{ $builder }}) All(ctx context.Context) ([]*{{ $.Name }}, error) {
ctx = setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryAll)
if err := {{ $receiver }}.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*{{ $.Name }}, *{{ $builder }}]()
return withInterceptors[[]*{{ $.Name }}](ctx, {{ $receiver }}, qr, {{ $receiver }}.inters)
}
// AllX is like All, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) AllX(ctx context.Context) []*{{ $.Name }} {
nodes, err := {{ $receiver }}.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
{{ if $.HasOneFieldID }}
// IDs executes the query and returns a list of {{ $.Name }} IDs.
func ({{ $receiver }} *{{ $builder }}) IDs(ctx context.Context) (ids []{{ $.ID.Type }}, err error) {
{{- /* Since a graph traversal such as JOINs can return duplicate IDs, set the Unique modifier unless specified otherwise. */}}
if {{ $receiver }}.ctx.Unique == nil && {{ $receiver }}.path != nil {
{{ $receiver }}.Unique(true)
}
ctx = setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryIDs)
{{- if and $.ID.HasValueScanner (eq $.Storage.Name "sql") }}
var nodes []*{{ $.Name }}
if nodes, err = {{ $receiver }}.Select({{ $.Package }}.FieldID).All(ctx); err != nil {
return nil, err
}
ids = make([]{{ $.ID.Type }}, len(nodes))
for i := range nodes {
ids[i] = nodes[i].ID
}
{{- else }}
if err = {{ $receiver }}.Select({{ $.Package }}.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
{{- end }}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) IDsX(ctx context.Context) []{{ $.ID.Type }} {
ids, err := {{ $receiver }}.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
{{ end }}
// Count returns the count of the given query.
func ({{ $receiver }} *{{ $builder }}) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryCount)
if err := {{ $receiver }}.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, {{ $receiver }}, querierCount[*{{ $builder }}](), {{ $receiver }}.inters)
}
// CountX is like Count, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) CountX(ctx context.Context) int {
count, err := {{ $receiver }}.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func ({{ $receiver }} *{{ $builder }}) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, {{ $receiver }}.ctx, ent.OpQueryExist)
switch _, err := {{ $receiver }}.First{{ if $.HasOneFieldID }}ID{{ end }}(ctx);{
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("{{ $pkg }}: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) ExistX(ctx context.Context) bool {
exist, err := {{ $receiver }}.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the {{ $builder }} builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func ({{ $receiver }} *{{ $builder }}) Clone() *{{ $builder }} {
if {{ $receiver }} == nil {
return nil
}
return &{{ $builder }}{
config: {{ $receiver }}.config,
ctx: {{ $receiver }}.ctx.Clone(),
order: append([]{{ $.Package }}.OrderOption{}, {{ $receiver }}.order...),
inters: append([]Interceptor{}, {{ $receiver }}.inters...),
predicates: append([]predicate.{{ $.Name }}{}, {{ $receiver }}.predicates...),
{{- range $e := $.Edges }}
{{ $e.EagerLoadField }}: {{ $receiver }}.{{ $e.EagerLoadField }}.Clone(),
{{- end }}
// clone intermediate query.
{{ $.Storage }}: {{ $receiver }}.{{ $.Storage }}.Clone(),
path: {{ $receiver }}.path,
{{- if $.FeatureEnabled "sql/modifier" }}
modifiers: append([]func(*sql.Selector){}, {{ $receiver }}.modifiers...),
{{- end }}
}
}
{{- range $e := $.Edges }}
{{ $ebuilder := $e.Type.QueryName }}
{{ $func := print "With" $e.StructField }}
// {{ $func }} tells the query-builder to eager-load the nodes that are connected to
// the "{{ $e.Name }}" edge. The optional arguments are used to configure the query builder of the edge.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}(opts ...func(*{{ $ebuilder }})) *{{ $builder }} {
query := (&{{ $e.Type.ClientName }}{config: {{ $receiver }}.config}).Query()
for _, opt := range opts {
opt(query)
}
{{ $receiver }}.{{ $e.EagerLoadField }} = query
return {{ $receiver }}
}
{{- end }}
{{ $groupBuilder := pascal $.Name | printf "%sGroupBy" }}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: {{ join (keys aggregate) ", " }}.
{{- with len $.Fields }}
{{- $f := index $.Fields 0 }}
//
// Example:
//
// var v []struct {
// {{ $f.StructField }} {{ $f.Type }} `{{ $f.StructTag }}`
// Count int `json:"count,omitempty"`
// }
//
// client.{{ pascal $.Name }}.Query().
// GroupBy({{ $.Package }}.{{ $f.Constant }}).
// Aggregate({{ $pkg }}.Count()).
// Scan(ctx, &v)
//
{{- end }}
func ({{ $receiver }} *{{ $builder }}) GroupBy(field string, fields ...string) *{{ $groupBuilder }} {
{{ $receiver }}.ctx.Fields = append([]string{field}, fields...)
grbuild := &{{ $groupBuilder }}{build: {{ $receiver }}}
grbuild.flds = &{{ $receiver }}.ctx.Fields
grbuild.label = {{ $.Package }}.Label
grbuild.scan = grbuild.Scan
return grbuild
}
{{ $selectBuilder := pascal $.Name | printf "%sSelect" }}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
{{- with len $.Fields }}
{{- $f := index $.Fields 0 }}
//
// Example:
//
// var v []struct {
// {{ $f.StructField }} {{ $f.Type }} `{{ $f.StructTag }}`
// }
//
// client.{{ pascal $.Name }}.Query().
// Select({{ $.Package }}.{{ $f.Constant }}).
// Scan(ctx, &v)
//
{{- end }}
func ({{ $receiver }} *{{ $builder }}) Select(fields ...string) *{{ $selectBuilder }} {
{{ $receiver }}.ctx.Fields = append({{ $receiver }}.ctx.Fields, fields...)
sbuild := &{{ $selectBuilder }}{ {{ $builder }}: {{ $receiver }} }
sbuild.label = {{ $.Package }}.Label
sbuild.flds, sbuild.scan = &{{ $receiver }}.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a {{ $selectBuilder }} configured with the given aggregations.
func ({{ $receiver }} *{{ $builder }}) Aggregate(fns ...AggregateFunc) *{{ $selectBuilder }} {
return {{ $receiver }}.Select().Aggregate(fns...)
}
func ({{ $receiver }} *{{ $builder }}) prepareQuery(ctx context.Context) error {
for _, inter := range {{ $receiver }}.inters {
if inter == nil {
return fmt.Errorf("{{ $pkg }}: uninitialized interceptor (forgotten import {{ $pkg }}/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, {{ $receiver }}); err != nil {
return err
}
}
}
{{- /* Optional prepare checks per dialect. */}}
{{- $tmpl = printf "dialect/%s/query/preparecheck" $.Storage }}
{{- if hasTemplate $tmpl }}
{{- with extend $ "Receiver" $receiver "Package" $pkg }}
{{- xtemplate $tmpl . }}
{{- end }}
{{- end }}
if {{ $receiver }}.path != nil {
prev, err := {{ $receiver }}.path(ctx)
if err != nil {
return err
}
{{ $receiver }}.{{ $.Storage }} = prev
}
{{- if $.NumPolicy }}
if {{ $.Package }}.Policy == nil {
return errors.New("{{ $pkg }}: uninitialized {{ $.Package }}.Policy (forgotten import {{ $pkg }}/runtime?)")
}
if err := {{ $.Package }}.Policy.EvalQuery(ctx, {{ $receiver }}); err != nil {
return err
}
{{- end }}
return nil
}
{{ with extend $ "Receiver" $receiver "Builder" $builder "Package" $pkg }}
{{ $tmpl := printf "dialect/%s/query" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{- /* Support adding query methods by global templates. In order to generate dialect-specific methods,
prefix this template with "dialect/{{ .Storage }}". For example: "dialect/sql/query/additional/*". */}}
{{- with $tmpls := matchTemplate "query/additional/*" }}
{{- range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{/* groupby builder */}}
{{ $groupReceiver := $.GroupReceiver }}
// {{ $groupBuilder }} is the group-by builder for {{ $.Name }} entities.
type {{ $groupBuilder }} struct {
selector
build *{{ $builder }}
}
// Aggregate adds the given aggregation functions to the group-by query.
func ({{ $groupReceiver }} *{{ $groupBuilder }}) Aggregate(fns ...AggregateFunc) *{{ $groupBuilder }} {
{{ $groupReceiver }}.fns = append({{ $groupReceiver }}.fns, fns...)
return {{ $groupReceiver }}
}
// Scan applies the selector query and scans the result into the given value.
func ({{ $groupReceiver }} *{{ $groupBuilder }}) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, {{ $groupReceiver }}.build.ctx, ent.OpQueryGroupBy)
if err := {{ $groupReceiver }}.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*{{ $builder }}, *{{ $groupBuilder }}](ctx, {{ $groupReceiver }}.build, {{ $groupReceiver }}, {{ $groupReceiver }}.build.inters, v)
}
{{ with extend $ "Receiver" $groupReceiver "Builder" $groupBuilder }}
{{ $tmpl := printf "dialect/%s/group" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{/* select builder */}}
{{ $selectReceiver := $.SelectReceiver }}
// {{ $selectBuilder }} is the builder for selecting fields of {{ pascal $.Name }} entities.
type {{ $selectBuilder }} struct {
*{{ $builder }}
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func ({{ $selectReceiver }} *{{ $selectBuilder }}) Aggregate(fns ...AggregateFunc) *{{ $selectBuilder }} {
{{ $selectReceiver }}.fns = append({{ $selectReceiver }}.fns, fns...)
return {{ $selectReceiver }}
}
// Scan applies the selector query and scans the result into the given value.
func ({{ $selectReceiver }} *{{ $selectBuilder }}) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, {{ $selectReceiver }}.ctx, ent.OpQuerySelect)
if err := {{ $selectReceiver }}.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*{{ $builder }}, *{{ $selectBuilder }}](ctx, {{ $selectReceiver }}.{{ $.QueryName }}, {{ $selectReceiver }}, {{ $selectReceiver }}.inters, v)
}
{{ with extend $ "Receiver" $selectReceiver "Builder" $selectBuilder }}
{{ $tmpl := printf "dialect/%s/select" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,150 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "setter" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $fields := $.Fields }}
{{ $updater := false }}
{{ $creator := true }}
{{- if or (hasSuffix $builder "Update") (hasSuffix $builder "UpdateOne") }}
{{ $updater = true }}
{{ $creator = false }}
{{ $fields = $.MutableFields }}
{{- else if $.HasCompositeID }}
{{- else if $.ID.UserDefined }}
{{ $fields = append $fields $.ID }}
{{- end }}
{{ range $f := $fields }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{- /* Skip all blob fields on multi-row Update (blob key updates require UpdateOne). */}}
{{- if and $f.IsBlob $updater (not (hasSuffix $builder "UpdateOne")) }}{{ continue }}{{ end }}
{{ $func := print "Set" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}(v {{ $f.Type }}) *{{ $builder }} {
{{- /* setting numeric type override previous calls to Add. */}}
{{- if and $updater $f.SupportsMutationAdd }}
{{ $receiver }}.mutation.{{ $f.MutationReset }}()
{{- end }}
{{ $receiver }}.mutation.{{ $f.MutationSet }}(v)
return {{ $receiver }}
}
{{/* Avoid generating nillable setters for nillable types. */}}
{{ $nillableC := and $creator (or $f.Optional $f.Default) }}
{{ $nillableU := and $updater (not $f.UpdateDefault) }}
{{ $nillableFunc := print "SetNillable" $f.StructField }}
{{ $skipNillable := false }}{{ range $fields }}{{ if and (ne .Name $f.Name) (eq .MutationSet $nillableFunc) }}{{ $skipNillable = true }}{{ break }}{{ end }}{{ end }}
{{ if and (not $f.Type.Nillable) (not $skipNillable) (or $nillableC $nillableU) }}
// {{ $nillableFunc }} sets the "{{ $f.Name }}" field if the given value is not nil.
func ({{ $receiver }} *{{ $builder }}) {{ $nillableFunc }}(v *{{ $f.Type }}) *{{ $builder }} {
if v != nil {
{{ $receiver }}.{{ $func }}(*v)
}
return {{ $receiver }}
}
{{ end }}
{{ if and $updater $f.SupportsMutationAdd }}
// {{ $f.MutationAdd }} adds value to the "{{ $f.Name }}" field.
func ({{ $receiver }} *{{ $builder }}) {{ $f.MutationAdd }}(v {{ $f.SignedType }}) *{{ $builder }} {
{{ $receiver }}.mutation.{{ $f.MutationAdd }}(v)
return {{ $receiver }}
}
{{ end }}
{{ if and $updater $f.SupportsMutationAppend }}
// {{ $f.MutationAppend }} appends value to the "{{ $f.Name }}" field.
func ({{ $receiver }} *{{ $builder }}) {{ $f.MutationAppend }}(v {{ $f.Type }}) *{{ $builder }} {
{{ $receiver }}.mutation.{{ $f.MutationAppend }}(v)
return {{ $receiver }}
}
{{ end }}
{{ if and $f.Optional $updater }}
{{ $func := print "Clear" $f.StructField }}
// {{ $func }} clears the value of the "{{ $f.Name }}" field.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}() *{{ $builder }} {
{{ $receiver }}.mutation.{{ $func }}()
return {{ $receiver }}
}
{{ end }}
{{ end }}
{{- /* Lazy blob setters only on Create and UpdateOne (blob writes require a single-row context). */}}
{{- $updateOne := hasSuffix $builder "UpdateOne" }}
{{- if or $creator $updateOne }}
{{- if hasTemplate (printf "dialect/%s/model/fields" $.Storage) }}
{{- range $f := $.BlobFields }}
{{- if not $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ $func := print "Set" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}(v io.Reader) *{{ $builder }} {
{{ $receiver }}.mutation.Set{{ $f.StructField }}(v)
return {{ $receiver }}
}
{{- if and $f.Optional $updateOne }}
{{ $clearFunc := print "Clear" $f.StructField }}
// {{ $clearFunc }} clears the value of the "{{ $f.Name }}" field.
func ({{ $receiver }} *{{ $builder }}) {{ $clearFunc }}() *{{ $builder }} {
{{ $receiver }}.mutation.{{ $clearFunc }}()
return {{ $receiver }}
}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{ range $e := $.EdgesWithID }}
{{ if and $updater $e.Immutable }}
{{/* Skip to the next one as immutable edges cannot be updated. */}}
{{continue}}
{{ end }}
{{ $op := "add" }}{{ $idsFunc := $e.MutationAdd }}{{ if $e.Unique }}{{ $op = "set" }}{{ $idsFunc = $e.MutationSet }}{{ end }}
{{/* Check if this setter was already defined by the field-setters (e.g. edge-field with the same name). */}}
{{ $withSetter := not $e.HasFieldSetter }}
{{ if $withSetter }}
// {{ $idsFunc }} {{ $op }}s the "{{ $e.Name }}" edge to the {{ $e.Type.Name }} entity by ID{{ if not $e.Unique }}s{{ end }}.
func ({{ $receiver }} *{{ $builder }}) {{ $idsFunc }}({{ if $e.Unique }}id{{ else }}ids ...{{ end }} {{ $e.Type.ID.Type }}) *{{ $builder }} {
{{ $receiver }}.mutation.{{ $idsFunc }}({{ if $e.Unique }}id{{ else }}ids ...{{ end }})
return {{ $receiver }}
}
{{ end }}
{{ if and $e.Unique $e.Optional $withSetter }}
{{ $nillableIDsFunc := print "SetNillable" $e.StructField "ID" }}
// {{ $nillableIDsFunc }} sets the "{{ $e.Name }}" edge to the {{ $e.Type.Name }} entity by ID if the given value is not nil.
func ({{ $receiver }} *{{ $builder }}) {{ $nillableIDsFunc }}(id *{{ $e.Type.ID.Type }}) *{{ $builder }} {
if id != nil {
{{ $receiver}} = {{ $receiver }}.{{ $idsFunc }}(*id)
}
return {{ $receiver }}
}
{{ end }}
{{ $func := print (pascal $op) $e.StructField }}
// {{ $func }} {{ $op }}s the "{{ $e.Name }}" edge{{if not $e.Unique}}s{{ end }} to the {{ $e.Type.Name }} entity.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}(v {{ if not $e.Unique }}...{{ end }}*{{ $e.Type.Name}}) *{{ $builder }} {
{{ if $e.Unique -}}
return {{ $receiver }}.{{ $idsFunc }}(v.ID)
{{- else -}}
ids := make([]{{ $e.Type.ID.Type }}, len(v))
for i := range v {
ids[i] = v[i].ID
}
return {{ $receiver }}.{{ $idsFunc }}(ids...)
{{- end }}
}
{{ end }}
// Mutation returns the {{ $.MutationName }} object of the builder.
func ({{ $receiver }} *{{ $builder }}) Mutation() *{{ $.MutationName }} {
return {{ $receiver }}.mutation
}
{{ end }}

View File

@@ -0,0 +1,277 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "update" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
{{ with extend $ "Imports" $.SiblingImports }}
{{ template "import" . }}
{{ end }}
{{ $builder := $.UpdateName }}
{{ $receiver := $.UpdateReceiver }}
{{ $mutation := print $receiver ".mutation" }}
{{ $runtimeRequired := or $.NumHooks $.NumPolicy }}
// {{ $builder }} is the builder for updating {{ $.Name }} entities.
type {{ $builder }} struct {
config
{{- template "update/fields" $ -}}
}
// Where appends a list predicates to the {{ $builder }} builder.
func ({{ $receiver}} *{{ $builder }}) Where(ps ...predicate.{{ $.Name }}) *{{ $builder }} {
{{ $mutation }}.Where(ps...)
return {{ $receiver }}
}
{{ with extend $ "Receiver" $receiver "Builder" $builder }}
{{ template "setter" . }}
{{ end }}
{{ with extend $ "Receiver" $receiver "Builder" $builder }}
{{ template "update/edges" . }}
{{ end }}
// Save executes the query and returns the number of nodes affected by the update operation.
func ({{ $receiver }} *{{ $builder }}) Save(ctx context.Context) (int, error) {
{{- if $.HasUpdateDefault }}
{{- if $runtimeRequired }}
if err := {{ $receiver }}.defaults(); err != nil {
return 0, err
}
{{- else }}
{{ $receiver }}.defaults()
{{- end }}
{{- end }}
return withHooks(ctx, {{ $receiver }}.{{ $.Storage }}Save, {{ $mutation }}, {{ $receiver }}.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) SaveX(ctx context.Context) int {
affected, err := {{ $receiver }}.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func ({{ $receiver }} *{{ $builder }}) Exec(ctx context.Context) error {
_, err := {{ $receiver }}.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) ExecX(ctx context.Context) {
if err := {{ $receiver }}.Exec(ctx); err != nil {
panic(err)
}
}
{{ with extend $ "Receiver" $receiver "Builder" $builder "Package" $pkg }}
{{ template "update/checks" . }}
{{ end }}
{{ with extend $ "Receiver" $receiver "Builder" $builder "Package" $pkg }}
{{ $tmpl := printf "dialect/%s/update" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{ $onebuilder := $.UpdateOneName }}
{{ $receiver = $.UpdateOneReceiver }}
{{ $mutation = print $receiver ".mutation" }}
// {{ $onebuilder }} is the builder for updating a single {{ $.Name }} entity.
type {{ $onebuilder }} struct {
config
fields []string
{{- template "update/fields" $ }}
}
{{ with extend $ "Receiver" $receiver "Builder" $onebuilder }}
{{ template "setter" . }}
{{ end }}
{{ with extend $ "Receiver" $receiver "Builder" $onebuilder }}
{{ template "update/edges" . }}
{{ end }}
// Where appends a list predicates to the {{ $builder }} builder.
func ({{ $receiver }} *{{ $onebuilder }}) Where(ps ...predicate.{{ $.Name }}) *{{ $onebuilder }} {
{{ $mutation }}.Where(ps...)
return {{ $receiver }}
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func ({{ $receiver }} *{{ $onebuilder }}) Select(field string, fields ...string) *{{ $onebuilder }} {
{{ $receiver }}.fields = append([]string{field}, fields...)
return {{ $receiver }}
}
// Save executes the query and returns the updated {{ $.Name }} entity.
func ({{ $receiver }} *{{ $onebuilder }} ) Save(ctx context.Context) (*{{ $.Name }}, error) {
{{- if $.HasUpdateDefault }}
{{- if $runtimeRequired }}
if err := {{ $receiver }}.defaults(); err != nil {
return nil, err
}
{{- else }}
{{ $receiver }}.defaults()
{{- end }}
{{- end }}
return withHooks(ctx, {{ $receiver }}.{{ $.Storage }}Save, {{ $mutation }}, {{ $receiver }}.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func ({{ $receiver }} *{{ $onebuilder }}) SaveX(ctx context.Context) *{{ $.Name }} {
node, err := {{ $receiver }}.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func ({{ $receiver }} *{{ $onebuilder }}) Exec(ctx context.Context) error {
_, err := {{ $receiver }}.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func ({{ $receiver }} *{{ $onebuilder }}) ExecX(ctx context.Context) {
if err := {{ $receiver }}.Exec(ctx); err != nil {
panic(err)
}
}
{{ with extend $ "Receiver" $receiver "Builder" $onebuilder "Package" $pkg }}
{{ template "update/checks" . }}
{{ end }}
{{ with extend $ "Receiver" $receiver "Builder" $onebuilder "Package" $pkg }}
{{ $tmpl := printf "dialect/%s/update" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{- /* Support adding update methods by global templates. */}}
{{- with $tmpls := matchTemplate "update/additional/*" }}
{{- range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}
{{/* shared struct fields between the two updaters */}}
{{ define "update/fields"}}
hooks []Hook
mutation *{{ $.MutationName }}
{{- /* Additional fields to add to the builder. */}}
{{- $tmpl := printf "dialect/%s/update/fields" $.Storage }}
{{- if hasTemplate $tmpl }}
{{- xtemplate $tmpl . }}
{{- end }}
{{ end }}
{{/* shared edges removal between the two updaters */}}
{{ define "update/edges" }}
{{ $builder := pascal .Scope.Builder }}
{{ $receiver := .Scope.Receiver }}
{{ $mutation := print $receiver ".mutation" }}
{{ range $e := $.EdgesWithID }}
{{ if $e.Immutable }}
{{/* Skip to the next one as immutable edges cannot be updated. */}}
{{continue}}
{{ end }}
{{ $func := $e.MutationClear }}
// {{ $func }} clears {{ if $e.Unique }}the "{{ $e.Name }}" edge{{ else }}all "{{ $e.Name }}" edges{{ end }} to the {{ $e.Type.Name }} entity.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}() *{{ $builder }} {
{{ $mutation }}.{{ $func }}()
return {{ $receiver }}
}
{{ if not $e.Unique }}
{{ $idsFunc := print "Remove" (singular $e.Name | pascal) "IDs" }}
// {{ $idsFunc }} removes the "{{ $e.Name }}" edge to {{ $e.Type.Name }} entities by IDs.
func ({{ $receiver }} *{{ $builder }}) {{ $idsFunc }}(ids ...{{ $e.Type.ID.Type }}) *{{ $builder }} {
{{ $mutation }}.{{ $idsFunc }}(ids...)
return {{ $receiver }}
}
{{ $func := print "Remove" $e.StructField }}
// {{ $func }} removes "{{ $e.Name }}" edges to {{ $e.Type.Name }} entities.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}(v ...*{{ $e.Type.Name }}) *{{ $builder }} {
ids := make([]{{ $e.Type.ID.Type }}, len(v))
for i := range v {
ids[i] = v[i].ID
}
return {{ $receiver }}.{{ $idsFunc }}(ids...)
}
{{ end }}
{{ end }}
{{ end }}
{{/* shared template for the 2 update builders */}}
{{ define "update/checks" }}
{{ $pkg := .Scope.Package }}
{{ $receiver := .Scope.Receiver }}
{{ $builder := pascal .Scope.Builder }}
{{ $mutation := print $receiver ".mutation" }}
{{ $runtimeRequired := or $.NumHooks $.NumPolicy }}
{{ if $.HasUpdateDefault }}
// defaults sets the default values of the builder before save.
func ({{ $receiver }} *{{ $builder }}) defaults() {{ if $runtimeRequired }}error{{ end }}{
{{- range $f := $.Fields }}
{{- if $f.UpdateDefault }}
if _, ok := {{ $mutation }}.{{ $f.MutationGet }}(); !ok {{ if $f.Optional }} && !{{ $mutation }}.{{ $f.StructField }}Cleared() {{ end }} {
{{- if $runtimeRequired }}
if {{ $.Package }}.{{ $f.UpdateDefaultName }} == nil {
return fmt.Errorf("{{ $pkg }}: uninitialized {{ $.Package }}.{{ $f.UpdateDefaultName }} (forgotten import {{ $pkg }}/runtime?)")
}
{{- end }}
v := {{ $.Package }}.{{ $f.UpdateDefaultName }}()
{{ $mutation }}.{{ $f.MutationSet }}(v)
}
{{- end }}
{{- end }}
{{- if $runtimeRequired }}
return nil
{{- end }}
}
{{ end }}
{{ if $.HasUpdateCheckers }}
// check runs all checks and user-defined validators on the builder.
func ({{ $receiver }} *{{ $builder }}) check() error {
{{- range $f := $.Fields }}
{{- $isValidator := and ($f.HasGoType) ($f.Type.Validator) }}
{{- with and (not $f.Immutable) (or $f.Validators $f.IsEnum $isValidator) }}
if v, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
if err := {{ if or $f.Validators $f.IsEnum }}{{ $.Package }}.{{ $f.Validator }}({{ $f.BasicType "v" }}){{ else }}v.Validate(){{ end }}; err != nil {
return &ValidationError{Name: "{{ $f.Name }}", err: fmt.Errorf(`{{ $pkg }}: validator failed for field "{{ $.Name }}.{{ $f.Name }}": %w`, err)}
}
}
{{- end }}
{{- end }}
{{- range $e := $.Edges }}
{{- if and $e.Unique (not $e.Optional) }}
if {{ $mutation }}.{{ $e.StructField }}Cleared() && len({{ $mutation }}.{{ $e.StructField }}IDs()) > 0 {
return errors.New(`{{ $pkg }}: clearing a required unique edge "{{ $.Name }}.{{ $e.Name }}"`)
}
{{- end }}
{{- end }}
return nil
}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,561 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "client/init" }}
// Client is the client that holds all ent builders.
type Client struct {
config
{{- if $.SupportMigrate }}
// Schema is the client for creating, migrating and dropping schema.
Schema *migrate.Schema
{{- end }}
{{- range $n := $.Nodes }}
// {{ $n.Name }} is the client for interacting with the {{ $n.Name }} builders.
{{ $n.Name }} *{{ $n.ClientName }}
{{- end }}
{{- template "client/fields/additional" $ }}
{{- with $tmpls := matchTemplate "client/fields/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
}
// NewClient creates a new client configured with the given options.
func NewClient(opts ...Option) *Client {
client := &Client{config: newConfig(opts...)}
client.init()
return client
}
func (c *Client) init() {
{{- if $.SupportMigrate }}
c.Schema = migrate.NewSchema(c.driver)
{{- end }}
{{- range $n := $.Nodes }}
c.{{ $n.Name }} = New{{ $n.ClientName }}(c.config)
{{- end }}
}
{{ end }}
{{ define "client" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
{{/* Additional dependencies injected to config. */}}
{{ $deps := list }}{{ with $.Config.Annotations }}{{ $deps = $.Config.Annotations.Dependencies }}{{ end }}
import (
"context"
"log"
"{{ $.Config.Package }}/migrate"
{{ range $n := $.Nodes }}
{{ $n.PackageAlias }} "{{ $n.Config.Package }}/{{ $n.PackageDir }}"
{{- end }}
{{- range $dep := $deps }}
{{ $dep.Type.PkgName }} "{{ $dep.Type.PkgPath }}"
{{- end }}
"entgo.io/ent/dialect"
{{- $hasBlobNodes := false }}{{ $blobSupported := hasTemplate (printf "dialect/%s/model/fields" $.Storage) }}{{ if $blobSupported }}{{ range $n := $.Nodes }}{{ if $n.HasBlobFields }}{{ $hasBlobNodes = true }}{{ end }}{{ end }}{{ end }}
{{ range $import := $.Storage.Imports -}}
"{{ $import }}"
{{ end -}}
{{- template "import/additional" $ }}
)
{{ template "client/init" $ }}
type (
// config is the configuration for the client and its builder.
config struct {
// driver used for executing database requests.
driver dialect.Driver
// debug enable a debug logging.
debug bool
// log used for logging on debug mode.
log func(...any)
// hooks to execute on mutations.
hooks *hooks
// interceptors to execute on queries.
inters *inters
{{- if $hasBlobNodes }}
// blobOpeners configures how blob buckets are opened for each entity type.
blobOpeners BlobOpeners
{{- end }}
{{- /* Additional dependency fields. */}}
{{- range $dep := $deps }}
{{ $dep.Field }} {{ $dep.Type }}
{{- end }}
{{- /* Support adding config fields from both global or dialect-specific templates. */}}
{{- range $prefix := list "" (printf "dialect/%s/" $.Storage) }}
{{- with $tmpls := matchTemplate (print $prefix "config/fields/*") }}
{{- range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- end }}
}
// Option function to configure the client.
Option func(*config)
)
// newConfig creates a new config for the client.
func newConfig(opts ...Option) config {
cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}}
{{- with $tmpls := matchTemplate "config/init/fields/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
cfg.options(opts...)
return cfg
}
// options applies the options on the config object.
func (c *config) options(opts ...Option) {
for _, opt := range opts {
opt(c)
}
if c.debug {
c.driver = dialect.Debug(c.driver, c.log)
}
}
// Debug enables debug logging on the ent.Driver.
func Debug() Option {
return func(c *config) {
c.debug = true
}
}
// Log sets the logging function for debug mode.
func Log(fn func(...any)) Option {
return func(c *config) {
c.log = fn
}
}
// Driver configures the client driver.
func Driver(driver dialect.Driver) Option {
return func(c *config) {
c.driver = driver
}
}
{{- /* Additional dependency options reside on the config object. */}}
{{- range $dep := $deps }}
// {{ $dep.Option }} configures the {{ $dep.Field }}.
func {{ $dep.Option }}(v {{ $dep.Type }}) Option {
return func(c *config) {
c.{{ $dep.Field }} = v
}
}
{{- end }}
{{- if $hasBlobNodes }}
// Blob is an alias for the [ent.Blob] interface defined in the entgo.io/ent package.
type Blob = ent.Blob
// BlobOpeners configures how blob buckets are opened for each entity type.
// Each field is a function that opens a blob bucket for the given field name.
type BlobOpeners struct {
{{- range $n := $.Nodes }}
{{- if $n.HasBlobFields }}
{{ $n.Name }} ent.BlobOpener
{{- end }}
{{- end }}
}
// WithBlobOpeners configures the blob bucket openers.
func WithBlobOpeners(openers BlobOpeners) Option {
return func(c *config) {
c.blobOpeners = openers
}
}
{{- $needsDefaultBlobKey := false }}
{{- range $n := $.Nodes }}
{{- range $f := $n.BlobFields }}
{{- if not $f.HasBlobKey }}{{ $needsDefaultBlobKey = true }}{{ end }}
{{- end }}
{{- end }}
{{- if $needsDefaultBlobKey }}
func defaultBlobKey(_ context.Context, data []byte) (string, error) {
h := sha256.Sum256(data)
return hex.EncodeToString(h[:]), nil
}
{{- end }}
{{- end }}
// Open opens a database/sql.DB specified by the driver name and
// the data source name, and returns a new client attached to it.
// Optional parameters can be added for configuring the client.
func Open(driverName, dataSourceName string, options ...Option) (*Client, error) {
switch driverName {
case {{ join $.Storage.Dialects ", " }}:
{{- $tmpl := printf "dialect/%s/client/open" $.Storage -}}
{{- xtemplate $tmpl . -}}
default:
return nil, fmt.Errorf("unsupported driver: %q", driverName)
}
}
// ErrTxStarted is returned when trying to start a new transaction from a transactional client.
var ErrTxStarted = errors.New("{{ $pkg }}: cannot start a transaction within a transaction")
// Tx returns a new transactional client. The provided context
// is used until the transaction is committed or rolled back.
func (c *Client) Tx(ctx context.Context) (*Tx, error) {
if _, ok := c.driver.(*txDriver); ok {
return nil, ErrTxStarted
}
tx, err := newTx(ctx, c.driver)
if err != nil {
return nil, fmt.Errorf("{{ $pkg }}: starting a transaction: %w", err)
}
cfg := c.config
cfg.driver = tx
return &Tx{
ctx: ctx,
config: cfg,
{{- range $n := $.Nodes }}
{{ $n.Name }}: New{{ $n.ClientName }}(cfg),
{{- end }}
}, nil
}
{{- /* If the storage driver supports TxOptions (like SQL) */}}
{{- $tmpl = printf "dialect/%s/txoptions" $.Storage }}
{{- if hasTemplate $tmpl }}
{{- xtemplate $tmpl . }}
{{- end }}
// Debug returns a new debug-client. It's used to get verbose logging on specific operations.
//
// client.Debug().
// {{ (index $.Nodes 0).Name }}.
// Query().
// Count(ctx)
//
func (c *Client) Debug() *Client {
if c.debug {
return c
}
cfg := c.config
cfg.driver = dialect.Debug(c.driver, c.log)
client := &Client{config: cfg}
client.init()
return client
}
// Close closes the database connection and prevents new queries from starting.
func (c *Client) Close() error {
return c.driver.Close()
}
// Use adds the mutation hooks to all the entity clients.
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) {
{{- if lt (len $.Nodes) 6 }}
{{- range $n := $.MutableNodes }}
c.{{ $n.Name }}.Use(hooks...)
{{- end }}
{{- /* Use a for-loop for setting hooks in case there are too many nodes. */}}
{{- else }}
{{- $hooks := slist }}
{{- range $n := $.MutableNodes }}{{ $hooks = append $hooks (printf "c.%s," $n.Name) }}{{ end }}
for _, n := range []interface{ Use(...Hook) }{
{{ joinWords $hooks 80 }}
}{
n.Use(hooks...)
}
{{- end }}
}
// Intercept adds the query interceptors to all the entity clients.
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) {
{{- if lt (len $.Nodes) 6 }}
{{- range $n := $.Nodes }}
c.{{ $n.Name }}.Intercept(interceptors...)
{{- end }}
{{- /* Use a for-loop for setting interceptors in case there are too many nodes. */}}
{{- else }}
{{- $inters := slist }}
{{- range $n := $.Nodes }}{{ $inters = append $inters (printf "c.%s," $n.Name) }}{{ end }}
for _, n := range []interface{ Intercept(...Interceptor) }{
{{ joinWords $inters 80 }}
}{
n.Intercept(interceptors...)
}
{{- end }}
}
{{- with $tmpls := matchTemplate "client/additional/*" "client/additional/*/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
// Mutate implements the ent.Mutator interface.
func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
switch m := m.(type) {
{{- range $n := $.MutableNodes }}
case *{{ $n.MutationName }}:
return c.{{ $n.Name }}.mutate(ctx, m)
{{- end }}
default:
return nil, fmt.Errorf("{{ $pkg }}: unknown mutation type %T", m)
}
}
{{ range $n := $.Nodes }}
{{ $client := $n.ClientName }}
// {{ $client }} is a client for the {{ $n.Name }} schema.
type {{ $client }} struct {
config
}
{{ $rec := $n.Receiver }}{{ if eq $rec "c" }}{{ $rec = printf "%.2s" $n.Name | lower }}{{ end }}
// New{{ $client }} returns a client for the {{ $n.Name }} from the given config.
func New{{ $client }}(c config) *{{ $client }} {
return &{{ $client }}{config: c}
}
{{- if not $n.IsView }}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `{{ $n.Package }}.Hooks(f(g(h())))`.
func (c *{{ $client }}) Use(hooks ...Hook) {
c.hooks.{{ $n.Name }} = append(c.hooks.{{ $n.Name }}, hooks...)
}
{{- end }}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `{{ $n.Package }}.Intercept(f(g(h())))`.
func (c *{{ $client }}) Intercept(interceptors ...Interceptor) {
c.inters.{{ $n.Name }} = append(c.inters.{{ $n.Name }}, interceptors...)
}
{{- if not $n.IsView }}
// Create returns a builder for creating a {{ $n.Name }} entity.
func (c *{{ $client }}) Create() *{{ $n.CreateName }} {
mutation := new{{ $n.MutationName }}(c.config, OpCreate)
return &{{ $n.CreateName }}{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of {{ $n.Name }} entities.
func (c *{{ $client }}) CreateBulk(builders ...*{{ $n.CreateName }}) *{{ $n.CreateBulkName }} {
return &{{ $n.CreateBulkName }}{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *{{ $client }}) MapCreateBulk(slice any, setFunc func(*{{ $n.CreateName }}, int)) *{{ $n.CreateBulkName }} {
{{- /* This check should be replaced with type-parameter method, if it will be supported by Go. */}}
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &{{ $n.CreateBulkName }}{err: fmt.Errorf("calling to {{ $client }}.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*{{ $n.CreateName }}, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &{{ $n.CreateBulkName }}{config: c.config, builders: builders}
}
// Update returns an update builder for {{ $n.Name }}.
func (c *{{ $client }}) Update() *{{ $n.UpdateName }} {
mutation := new{{ $n.MutationName }}(c.config, OpUpdate)
return &{{ $n.UpdateName }}{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *{{ $client }}) UpdateOne({{ $rec }} *{{ $n.Name }}) *{{ $n.UpdateOneName }} {
{{- if $n.HasOneFieldID }}
mutation := new{{ $n.MutationName }}(c.config, OpUpdateOne, {{ print "with" $n.Name }}({{ $rec }}))
{{- else }}
mutation := new{{ $n.MutationName }}(c.config, OpUpdateOne)
{{- range $id := $n.EdgeSchema.ID }}
mutation.{{ $id.MutationSet }}({{ $rec }}.{{ $id.StructField }})
{{- end }}
{{- end }}
return &{{ $n.UpdateOneName }}{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
{{ with $n.HasOneFieldID }}
// UpdateOneID returns an update builder for the given id.
func (c *{{ $client }}) UpdateOneID(id {{ $n.ID.Type }}) *{{ $n.UpdateOneName }} {
mutation := new{{ $n.MutationName }}(c.config, OpUpdateOne, {{ print "with" $n.Name "ID" }}(id))
return &{{ $n.UpdateOneName }}{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
{{ end }}
// Delete returns a delete builder for {{ $n.Name }}.
func (c *{{ $client }}) Delete() *{{ $n.DeleteName }} {
mutation := new{{ $n.MutationName }}(c.config, OpDelete)
return &{{ $n.DeleteName }}{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
{{ with $n.HasOneFieldID }}
// DeleteOne returns a builder for deleting the given entity.
func (c *{{ $client }}) DeleteOne({{ $rec }} *{{ $n.Name }}) *{{ $n.DeleteOneName }} {
return c.DeleteOneID({{ $rec }}.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *{{ $client }}) DeleteOneID(id {{ $n.ID.Type }}) *{{ $n.DeleteOneName }} {
{{- $builder := "builder" }}{{ if eq $n.Package $builder }}{{ $builder = "builderC" }}{{ end }}
{{ $builder }} := c.Delete().Where({{ $n.Package }}.ID(id))
{{ $builder }}.mutation.{{ $n.ID.BuilderField }} = &id
{{ $builder }}.mutation.SetOp(OpDeleteOne)
return &{{ $n.DeleteOneName }}{ {{ $builder }} }
}
{{ end }}
{{- end }} {{/* End of if not IsView. */}}
// Query returns a query builder for {{ $n.Name }}.
func (c *{{ $client }}) Query() *{{ $n.QueryName }} {
return &{{ $n.QueryName }}{
config: c.config,
ctx: &QueryContext{Type: {{ $n.TypeName }} },
inters: c.Interceptors(),
{{- with $tmpls := matchTemplate (printf "dialect/%s/query/fields/init/*" $.Storage) }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $n }}
{{- end }}
{{- end }}
}
}
{{ with $n.HasOneFieldID }}
// Get returns a {{ $n.Name }} entity by its id.
func (c *{{ $client }}) Get(ctx context.Context, id {{ $n.ID.Type }}) (*{{ $n.Name }}, error) {
return c.Query().Where({{ $n.Package }}.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *{{ $client }}) GetX(ctx context.Context, id {{ $n.ID.Type }}) *{{ $n.Name }} {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
{{ end }}
{{ range $e := $n.Edges }}
{{ $builder := $e.Type.QueryName }}
{{ $arg := $rec }}{{ if eq $arg "id" }}{{ $arg = "node" }}{{ end }}
{{ $func := print "Query" (pascal $e.Name) }}
// Query{{ pascal $e.Name }} queries the {{ $e.Name }} edge of a {{ $n.Name }}.
func (c *{{ $client }}) {{ $func }}({{ $arg }} *{{ $n.Name }}) *{{ $builder }} {
{{- if $n.HasOneFieldID }}
query := (&{{ $e.Type.ClientName }}{config: c.config}).Query()
query.path = func(context.Context) (fromV {{ $.Storage.Builder }}, _ error) {
{{- with extend $n "Receiver" $arg "Edge" $e "Ident" "fromV" }}
{{ $tmpl := printf "dialect/%s/query/from" $.Storage }}
{{- xtemplate $tmpl . -}}
{{- end -}}
return fromV, nil
}
return query
{{- else }}
{{- /* For edge schema, we use the predicate-based approach. */}}
return c.Query().
Where({{ range $id := $n.EdgeSchema.ID }}{{ $n.Package }}.{{ $id.StructField }}({{ $arg }}.{{ $id.StructField }}),{{ end }}).
{{ $func }}()
{{- end }}
}
{{ end }}
{{- if not $n.IsView }}
// Hooks returns the client hooks.
func (c *{{ $client }}) Hooks() []Hook {
{{- if or $n.NumHooks $n.NumPolicy }}
hooks := c.hooks.{{ $n.Name }}
return append(hooks[:len(hooks):len(hooks)], {{ $n.Package }}.Hooks[:]...)
{{- else }}
return c.hooks.{{ $n.Name }}
{{- end }}
}
{{- end }}
// Interceptors returns the client interceptors.
func (c *{{ $client }}) Interceptors() []Interceptor {
{{- if $n.NumInterceptors }}
inters := c.inters.{{ $n.Name }}
return append(inters[:len(inters):len(inters)], {{ $n.Package }}.Interceptors[:]...)
{{- else }}
return c.inters.{{ $n.Name }}
{{- end }}
}
{{ if not $n.IsView }}
func (c *{{ $client }}) mutate(ctx context.Context, m *{{ $n.MutationName }}) (Value, error) {
switch m.Op() {
case OpCreate:
return (&{{ $n.CreateName }}{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&{{ $n.UpdateName }}{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&{{ $n.UpdateOneName }}{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&{{ $n.DeleteName }}{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("{{ $pkg }}: unknown {{ $n.Name }} mutation op: %q", m.Op())
}
}
{{ end }}
{{- end }}
// hooks and interceptors per client, for fast access.
type (
{{- $leni := len $.Nodes | add -1 }}
{{- $hooks := slist }}
{{- $inters := slist }}
{{- range $i, $n := $.Nodes }}
{{- if eq $i $leni }}
{{ if not $n.IsView }}{{- $hooks = append $hooks (printf "%s []ent.Hook" $n.Name) }}{{ end }}
{{- $inters = append $inters (printf "%s []ent.Interceptor" $n.Name) }}
{{- else }}
{{ if not $n.IsView }}{{- $hooks = append $hooks (print $n.Name ",") }}{{ end }}
{{- $inters = append $inters (print $n.Name ",") }}
{{- end }}
{{- end }}
hooks struct {
{{ joinWords $hooks 80 }}
}
inters struct {
{{ joinWords $inters 80 }}
}
)
{{- /* Support adding config options from both global or dialect-specific templates. */}}
{{- range $prefix := list "" (printf "dialect/%s/" $.Storage) }}
{{- with $tmpls := matchTemplate (print $prefix "config/options/*") }}
{{- range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- end }}
{{- with $tmpls := matchTemplate "config/additional/*" "config/additional/*/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}
{{/* A template that can be overridden in order to add additional fields to the client.*/}}
{{ define "client/fields/additional" }}{{ end }}

View File

@@ -0,0 +1,57 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "dialect/gremlin/order/signature" -}}
// OrderFunc applies an ordering on the graph traversal.
type OrderFunc func(*dsl.Traversal)
{{- end }}
{{ define "dialect/gremlin/order/func" -}}
{{- $order := $.Scope.Order -}}
func(tr *dsl.Traversal) {
for _, f := range fields {
tr.By(f, dsl.{{ pascal $order }})
}
}
{{- end }}
{{/* custom signature for group-by function */}}
{{ define "dialect/gremlin/group/signature" -}}
// It gets two labels as parameters. The first used in the `As` step for the predicate,
// and the second is an optional name for the next predicates (or for later usage).
type AggregateFunc func(string, string) (string, *dsl.Traversal)
{{- end }}
{{ define "dialect/gremlin/group/as" -}}
func(start, _ string) (string, *dsl.Traversal) {
return fn(start, end)
}
{{- end }}
{{ define "dialect/gremlin/group/func" -}}
{{- $fn := $.Scope.Func -}}
{{- $withField := $.Scope.WithField -}}
func(start, end string) (string, *dsl.Traversal) {
if end == "" {
end = Default{{ $fn }}Label
}
return end, __.As(start).{{ if $withField }}Unfold().Values(field).{{ $fn }}(){{ else }}{{ $fn }}(dsl.Local){{ end }}.As(end)
}
{{- end }}
{{/* optional constants for group-by default values. */}}
{{ define "dialect/gremlin/group/const" -}}
{{- $fn := $.Scope.Func }}
{{- $name := $.Scope.Name }}
{{- $pkg := base $.Config.Package }}
// Default{{ $fn }}Label is the default label name for the {{ $fn }} aggregation function.
// It should be used as the struct-tag for decoding, or a map key for interaction with the returned response.
// In order to {{ quote $name }} 2 or more fields and avoid conflicting, use the `{{ $pkg }}.As({{ $pkg }}.{{ $fn }}(field), "custom_name")`
// function with custom name in order to override it.
const Default{{ $fn }}Label = {{ quote $name }}
{{- end }}

View File

@@ -0,0 +1,95 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/gremlin/create" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $mutation := print $receiver ".mutation" }}
func ({{ $receiver }} *{{ $builder }}) gremlinSave(ctx context.Context) (*{{ $.Name }}, error) {
if err := {{ $receiver }}.check(); err != nil {
return nil, err
}
res := &gremlin.Response{}
query, bindings := {{ $receiver }}.gremlin().Query()
if err := {{ $receiver }}.driver.Exec(ctx, query, bindings, res); err != nil {
return nil, err
}
if err, ok := isConstantError(res); ok {
return nil, err
}
rnode := &{{ $.Name }}{config: {{ $receiver }}.config}
if err := rnode.FromResponse(res); err != nil {
return nil, err
}
{{- if $.HasOneFieldID }}
{{ $mutation }}.{{ $.ID.BuilderField }} = &rnode.{{ $.ID.StructField }}
{{ $mutation }}.done = true
{{- end }}
return rnode, nil
}
func ({{ $receiver }} *{{ $builder }}) gremlin() *dsl.Traversal {
{{- with .NumConstraint }}
type constraint struct {
pred *dsl.Traversal // constraint predicate.
test *dsl.Traversal // test matches and its constant.
}
constraints := make([]*constraint, 0, {{ . }})
{{- end }}
v := g.AddV({{ $.Package }}.Label)
{{- if $.ID.UserDefined }}
if id, ok := {{ $mutation }}.{{ $.ID.MutationGet }}(); ok {
v.Property(dsl.ID, id)
}
{{- end }}
{{- range $f := $.MutationFields }}
{{- if $f.IsBlob }}{{ continue }}{{ end }}
if value, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
{{- if $f.Unique }}
constraints = append(constraints, &constraint{
pred: g.V().Has({{ $.Package }}.Label, {{ $.Package }}.{{ $f.Constant }}, value).Count(),
test: __.Is(p.NEQ(0)).Constant(NewErrUniqueField({{ $.Package }}.Label, {{ $.Package }}.{{ $f.Constant }}, value)),
})
{{- end }}
v.Property(dsl.Single, {{ $.Package }}.{{ $f.Constant }}, value)
}
{{- end }}
{{- range $e := $.Edges }}
{{- $direction := "In" }}
{{- $name := printf "%s.%s" $.Package $e.LabelConstant }}
for _, id := range {{ $mutation }}.{{ $e.StructField }}IDs() {
{{- if $e.IsInverse }}
{{- $direction = "Out" }}
{{- $name = printf "%s.%s" $e.Type.Package $e.LabelConstant }}
v.AddE({{ $name }}).From(g.V(id)).InV()
{{- else }}
v.AddE({{ $name }}).To(g.V(id)).OutV()
{{- end }}
{{- if $e.HasConstraint }}
constraints = append(constraints, &constraint{
pred: g.E().HasLabel({{ $name }}).{{ $direction }}V().HasID(id).Count(),
test: __.Is(p.NEQ(0)).Constant(NewErrUniqueEdge({{ $.Package }}.Label, {{ $name }}, id)),
})
{{- end }}
}
{{- end }}
{{- with .NumConstraint }}
if len(constraints) == 0 {
return v.ValueMap(true)
}
tr := constraints[0].pred.Coalesce(constraints[0].test, v.ValueMap(true))
for _, cr := range constraints[1:] {
tr = cr.pred.Coalesce(cr.test, tr)
}
return tr
{{- else }}
return v.ValueMap(true)
{{- end }}
}
{{ end }}

View File

@@ -0,0 +1,87 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "dialect/gremlin/decode/one" }}
{{ $receiver := $.Receiver }}
// FromResponse scans the gremlin response data into {{ $.Name }}.
func ({{ $receiver }} *{{ $.Name }}) FromResponse(res *gremlin.Response) error {
vmap, err := res.ReadValueMap()
if err != nil {
return err
}
{{- $scan := print "scan" $receiver }}
var {{ $scan }} struct {
ID {{ $.ID.Type }} `json:"id,omitempty"`
{{ range $f := $.Fields }}
{{- if $f.IsBlob }}{{ continue }}{{ end }}
{{- $f.StructField }} {{ if and $f.IsTime (not $f.HasGoType) }}int64{{ else }}{{ if $f.NillableValue }}*{{ end }}{{ $f.Type }}{{ end }} `json:"{{ $f.StorageKey }},omitempty"`
{{ end }}
}
if err := vmap.Decode(&{{ $scan }}); err != nil {
return err
}
{{ $receiver }}.ID = {{ $scan }}.ID
{{- range $i, $f := $.Fields }}
{{- if $f.IsBlob }}{{ continue }}{{ end }}
{{- if and $f.IsTime (not $f.HasGoType) }}
{{- if $f.Nillable }}
v{{ $i }} := time.Unix(0, {{ $scan }}.{{ $f.StructField }})
{{ $receiver }}.{{ $f.StructField }} = &v{{ $i }}
{{- else }}
{{ $receiver }}.{{ $f.StructField }} = time.Unix(0, {{ $scan }}.{{ $f.StructField }})
{{- end }}
{{- else }}
{{ $receiver }}.{{ $f.StructField }} = {{ $scan }}.{{ $f.StructField }}
{{- end }}
{{- end }}
return nil
}
{{ end }}
{{ define "dialect/gremlin/decode/many" }}
{{ $receiver := $.Receiver }}
{{ $slice := $.Scope.Slice }}
// FromResponse scans the gremlin response data into {{ $slice }}.
func ({{ $receiver }} *{{ $slice }}) FromResponse(res *gremlin.Response) error {
vmap, err := res.ReadValueMap()
if err != nil {
return err
}
{{- $scan := print "scan" $receiver }}
var {{ $scan }} []struct {
ID {{ $.ID.Type }} `json:"id,omitempty"`
{{ range $f := $.Fields }}
{{- if $f.IsBlob }}{{ continue }}{{ end }}
{{- $f.StructField }} {{ if and $f.IsTime (not $f.HasGoType) }}int64{{ else }}{{ if $f.NillableValue }}*{{ end }}{{ $f.Type }}{{ end }} `json:"{{ $f.StorageKey }},omitempty"`
{{ end }}
}
if err := vmap.Decode(&{{ $scan }}); err != nil {
return err
}
for _, v := range {{ $scan }} {
node := &{{ $.Name }}{ID: v.ID}
{{- range $i, $f := $.Fields }}
{{- if $f.IsBlob }}{{ continue }}{{ end }}
{{- if and $f.IsTime (not $f.HasGoType) }}
{{- if $f.Nillable }}
v{{ $i }} := time.Unix(0, v.{{ $f.StructField }})
node.{{ $f.StructField }} = &v{{ $i }}
{{- else }}
node.{{ $f.StructField }} = time.Unix(0, v.{{ $f.StructField }})
{{- end }}
{{- else }}
node.{{ $f.StructField }} = v.{{ $f.StructField }}
{{- end }}
{{- end }}
*{{ $receiver }} = append(*{{ $receiver }}, node)
}
return nil
}
{{ end }}

View File

@@ -0,0 +1,32 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/gremlin/delete" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $mutation := print $receiver ".mutation" }}
func ({{ $receiver}} *{{ $builder }}) gremlinExec(ctx context.Context) (int, error) {
res := &gremlin.Response{}
query, bindings := {{ $receiver }}.gremlin().Query()
if err := {{ $receiver }}.driver.Exec(ctx, query, bindings, res); err != nil {
return 0, err
}
{{ $mutation }}.done = true
return res.ReadInt()
}
func ({{ $receiver }} *{{ $builder }}) gremlin() *dsl.Traversal {
t := g.V().HasLabel({{ $.Package }}.Label)
for _, p := range {{ $mutation }}.Predicates() {
p(t)
}
return t.SideEffect(__.Drop()).Count()
}
{{ end }}

View File

@@ -0,0 +1,54 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{/* custom errors and errors handlers from sql dialects */}}
{{ define "dialect/gremlin/errors" }}
{{ $pkg := base $.Config.Package }}
// Code implements the dsl.Node interface.
func (e ConstraintError) Code() (string, []any) {
return strconv.Quote(e.prefix() + e.msg), nil
}
func (e *ConstraintError) UnmarshalGraphson(b []byte) error {
var v [1]*string
if err := graphson.Unmarshal(b, &v); err != nil {
return err
}
if v[0] == nil {
return fmt.Errorf("{{ $pkg }}: missing string value")
}
if !strings.HasPrefix(*v[0], e.prefix()) {
return fmt.Errorf("{{ $pkg }}: invalid string for error: %s", *v[0])
}
e.msg = strings.TrimPrefix(*v[0], e.prefix())
return nil
}
// prefix returns the prefix used for gremlin constants.
func (ConstraintError) prefix() string { return "Error: " }
// NewErrUniqueField creates a constraint error for unique fields.
func NewErrUniqueField(label, field string, v any) *ConstraintError {
return &ConstraintError{msg: fmt.Sprintf("field %s.%s with value: %#v", label, field, v)}
}
// NewErrUniqueEdge creates a constraint error for unique edges.
func NewErrUniqueEdge(label, edge, id string) *ConstraintError {
return &ConstraintError{msg: fmt.Sprintf("edge %s.%s with id: %#v", label, edge, id)}
}
// isConstantError indicates if the given response holds a gremlin constant containing an error.
func isConstantError(r *gremlin.Response) (*ConstraintError, bool) {
e := &ConstraintError{}
if err := graphson.Unmarshal(r.Result.Data, e); err != nil {
return nil, false
}
return e, true
}
{{ end }}

View File

@@ -0,0 +1,14 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{/* custom globals and helpers for gremlin dialects */}}
{{ define "dialect/gremlin/globals" }}
{{/* Align API with SQL driver. */}}
// queryHook describes an internal hook for the different gremlinAll methods.
type queryHook func(context.Context)
{{ end }}

View File

@@ -0,0 +1,46 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/gremlin/group" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
func ({{ $receiver }} *{{ $builder }}) gremlinScan(ctx context.Context, root *{{ $.QueryName }}, v any) error {
var (
trs []any
names []any
)
for _, fn := range {{ $receiver }}.fns {
name, tr := fn("p", "")
trs = append(trs, tr)
names = append(names, name)
}
for _, f := range *{{ $receiver }}.flds {
names = append(names, f)
trs = append(trs, __.As("p").Unfold().Values(f).As(f))
}
query, bindings := root.gremlinQuery(ctx).Group().
By(__.Values(*{{ $receiver }}.flds...).Fold()).
By(__.Fold().Match(trs...).Select(names...)).
Select(dsl.Values).
Next().
Query()
res := &gremlin.Response{}
if err := {{ $receiver }}.build.driver.Exec(ctx, query, bindings, res); err != nil {
return err
}
if len(*{{ $receiver }}.flds)+len({{ $receiver }}.fns) == 1 {
return res.ReadVal(v)
}
vm, err := res.ReadValueMap()
if err != nil {
return err
}
return vm.Decode(v)
}
{{ end }}

View File

@@ -0,0 +1,19 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type*/}}
{{/* constants needed for gremlin dialects. */}}
{{ define "dialect/gremlin/meta/constants" }}
{{ range $e := $.Edges }}{{ $label := $e.LabelConstant -}}
{{ if $e.IsInverse }}{{- $label = $e.InverseLabelConstant -}}
// {{ $label }} holds the string label denoting the {{ lower $e.Name }} inverse edge type in the database.
{{ else -}}
// {{ $label }} holds the string label denoting the {{ lower $e.Name }} edge type in the database.
{{ end -}}
{{ $label }} = "{{ $e.Label }}"
{{ end -}}
{{ end }}

View File

@@ -0,0 +1,24 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "dialect/gremlin/client/open" }}
u, err := url.Parse(dataSourceName)
if err != nil {
return nil, err
}
c, err := gremlin.NewClient(gremlin.Config{
Endpoint: gremlin.Endpoint{
URL: u,
},
})
if err != nil {
return nil, err
}
drv := gremlin.NewDriver(c)
return NewClient(append(options, Driver(drv))...), nil
{{ end }}

View File

@@ -0,0 +1,135 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/gremlin/predicate/id" -}}
func(t *dsl.Traversal) {
t.HasID(id)
}
{{- end }}
{{ define "dialect/gremlin/predicate/id/ops" -}}
{{- $op := $.Scope.Op -}}
{{- $arg := $.Scope.Arg -}}
{{- $storage := $.Scope.Storage -}}
func(t *dsl.Traversal) {
{{- if $op.Variadic }}
v := make([]any, len({{ $arg }}))
for i := range v {
v[i] = {{ $arg }}[i]
}
{{- end }}
t.HasID(p.{{ call $storage.OpCode $op }}({{ if $op.Variadic }}v...{{ else }}{{ $arg }}{{ end }}))
}
{{- end }}
{{ define "dialect/gremlin/predicate/field" -}}
{{- $f := $.Scope.Field -}}
{{- $arg := $.Scope.Arg -}}
func(t *dsl.Traversal) {
t.Has(Label, {{ $f.Constant }}, p.EQ({{ $arg }}))
}
{{- end }}
{{ define "dialect/gremlin/predicate/field/ops" -}}
{{- $f := $.Scope.Field -}}
{{- $op := $.Scope.Op -}}
{{- $arg := $.Scope.Arg -}}
{{- $storage := $.Scope.Storage -}}
func(t *dsl.Traversal) {
{{- if not $op.Niladic }}
t.Has(Label, {{ $f.Constant }}, p.{{ call $storage.OpCode $op }}({{ $arg }}{{ if $op.Variadic }}...{{ end }}))
{{- else }}
t.HasLabel(Label).{{ call $storage.OpCode $op }}({{ $f.Constant }})
{{- end }}
}
{{- end }}
{{ define "dialect/gremlin/predicate/edge/has" -}}
{{- $e := $.Scope.Edge -}}
{{- $label := $e.LabelConstant -}}
{{- $direction := "Out" -}}
{{- if $e.IsInverse -}}
{{- $direction = "In" -}}
{{/* avoid circular dependencies */}}
{{- $label = $e.InverseLabelConstant -}}
{{- end -}}
func(t *dsl.Traversal) {
{{- /* if it's an edge with self-reference, take the two vertices */}}
{{- if $e.Bidi }}
t.Both({{ $label }})
{{- else }}
t.{{ $direction }}E({{ $label }}).{{ $direction }}V()
{{- end }}
}
{{- end }}
{{ define "dialect/gremlin/predicate/edge/haswith" -}}
{{- $e := $.Scope.Edge -}}
{{- $label := $e.LabelConstant -}}
{{- $direction := "Out" -}}
{{- $inverse_direction := "In" -}}
{{- if $e.IsInverse -}}
{{- $direction = "In" -}}
{{- $inverse_direction = "Out" -}}
{{/* avoid circular dependencies */}}
{{- $label = $e.InverseLabelConstant -}}
{{- end -}}
func(t *dsl.Traversal) {
{{- if $e.Bidi }}{{/* selfref means it should be true in one of the directions */}}
in, out := __.InV(), __.OutV()
for _, p := range preds {
p(in)
p(out)
}
t.Where(
__.Or(
__.OutE({{ $label }}).Where(in),
__.InE({{ $label }}).Where(out),
),
)
{{- else }}
tr := __.{{ $inverse_direction }}V()
for _, p := range preds {
p(tr)
}
t.{{ $direction }}E({{ $label }}).Where(tr).{{ $direction }}V()
{{- end }}
}
{{- end }}
{{ define "dialect/gremlin/predicate/and" -}}
func(tr *dsl.Traversal) {
trs := make([]any, 0, len(predicates))
for _, p := range predicates {
t := __.New()
p(t)
trs = append(trs, t)
}
tr.Where(__.And(trs...))
}
{{- end }}
{{ define "dialect/gremlin/predicate/or" -}}
func(tr *dsl.Traversal) {
trs := make([]any, 0, len(predicates))
for _, p := range predicates {
t := __.New()
p(t)
trs = append(trs, t)
}
tr.Where(__.Or(trs...))
}
{{- end }}
{{ define "dialect/gremlin/predicate/not" -}}
func(tr *dsl.Traversal) {
t := __.New()
p(t)
tr.Where(__.Not(t))
}
{{- end }}

View File

@@ -0,0 +1,107 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/gremlin/query" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
func ({{ $receiver }} *{{ $builder }}) gremlinAll(ctx context.Context, hooks ...queryHook) ([]*{{ $.Name }}, error) {
res := &gremlin.Response{}
traversal := {{ $receiver }}.gremlinQuery(ctx)
if len({{ $receiver }}.ctx.Fields) > 0 {
fields := make([]any, len({{ $receiver }}.ctx.Fields))
for i, f := range {{ $receiver }}.ctx.Fields {
fields[i] = f
}
traversal.ValueMap(fields...)
} else {
traversal.ValueMap(true)
}
query, bindings := traversal.Query()
if err := {{ $receiver }}.driver.Exec(ctx, query, bindings, res); err != nil {
return nil, err
}
var {{ plural $.Receiver }} {{ plural $.Name }}
if err := {{ plural $.Receiver }}.FromResponse(res); err != nil {
return nil, err
}
for i := range {{ plural $.Receiver }} {
{{ plural $.Receiver }}[i].config = {{ $receiver }}.config
}
return {{ plural $.Receiver }}, nil
}
func ({{ $receiver }} *{{ $builder }}) gremlinCount(ctx context.Context) (int, error) {
res := &gremlin.Response{}
query, bindings := {{ $receiver }}.gremlinQuery(ctx).Count().Query()
if err := {{ $receiver }}.driver.Exec(ctx, query, bindings, res); err != nil {
return 0, err
}
return res.ReadInt()
}
func ({{ $receiver }} *{{ $builder }}) gremlinQuery(context.Context) *dsl.Traversal {
v := g.V().HasLabel({{ $.Package }}.Label)
if {{ $receiver }}.gremlin != nil {
v = {{ $receiver }}.gremlin.Clone()
}
for _, p := range {{ $receiver }}.predicates {
p(v)
}
if len({{ $receiver }}.order) > 0 {
v.Order()
for _, p := range {{ $receiver }}.order {
p(v)
}
}
switch limit, offset := {{ $receiver }}.ctx.Limit, {{ $receiver }}.ctx.Offset; {
case limit != nil && offset != nil:
v.Range(*offset, *offset + *limit)
case offset != nil:
v.Range(*offset, math.MaxInt32)
case limit != nil:
v.Limit(*limit)
}
if unique := {{ $receiver }}.ctx.Unique; unique == nil || *unique {
v.Dedup()
}
return v
}
{{ end }}
{{/* query/path defines the query generation for path of a given edge. */}}
{{ define "dialect/gremlin/query/path" }}
{{- $e := $.Scope.Edge }} {{/* the edge we need to genegrate the path to. */}}
{{- $receiver := $.Scope.Receiver }}
{{- $ident := $.Scope.Ident }}
gremlin := {{ $receiver }}.gremlinQuery(ctx)
{{- if $e.Bidi }}
{{ $ident }} = gremlin.Both({{ $.Package }}.{{ $e.LabelConstant }})
{{- else if $e.IsInverse }}
{{ $ident }} = gremlin.InE({{ $e.Type.Package }}.{{ $e.LabelConstant }}).OutV()
{{- else }}
{{ $ident }} = gremlin.OutE({{ $.Package }}.{{ $e.LabelConstant }}).InV()
{{- end }}
{{ end }}
{{/* query/from defines the query generation for an edge query from a given node. */}}
{{ define "dialect/gremlin/query/from" }}
{{- $n := $ }} {{/* the node we start the query from. */}}
{{- $e := $.Scope.Edge }} {{/* the edge we need to genegrate the path to. */}}
{{- $receiver := $.Scope.Receiver }}
{{- $ident := $.Scope.Ident -}}
{{- if $e.Bidi }}
{{ $ident }} = g.V({{ $receiver }}.ID).Both({{ $n.Package }}.{{ $e.LabelConstant }})
{{- else if $e.IsInverse }}
{{ $ident }} = g.V({{ $receiver }}.ID).InE({{ $e.Type.Package }}.{{ $e.LabelConstant }}).OutV()
{{- else }}
{{ $ident }} = g.V({{ $receiver }}.ID).OutE({{ $n.Package }}.{{ $e.LabelConstant }}).InV()
{{- end }}
{{ end }}

View File

@@ -0,0 +1,44 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/gremlin/select" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
func ({{ $receiver }} *{{ $builder }}) gremlinScan(ctx context.Context, root *{{ $.QueryName }}, v any) error {
var (
res = &gremlin.Response{}
traversal = root.gremlinQuery(ctx)
)
if fields := {{ $receiver }}.ctx.Fields; len(fields) == 1 {
if fields[0] != {{ $.Package }}.FieldID {
traversal = traversal.Values(fields...)
} else {
traversal = traversal.ID()
}
} else {
fields := make([]any, len({{ $receiver }}.ctx.Fields))
for i, f := range {{ $receiver }}.ctx.Fields {
fields[i] = f
}
traversal = traversal.ValueMap(fields...)
}
query, bindings := traversal.Query()
if err := {{ $receiver }}.driver.Exec(ctx, query, bindings, res); err != nil {
return err
}
if len(root.ctx.Fields) == 1 {
return res.ReadVal(v)
}
vm, err := res.ReadValueMap()
if err != nil {
return err
}
return vm.Decode(v)
}
{{ end }}

View File

@@ -0,0 +1,198 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/gremlin/update" }}
{{ $pkg := $.Scope.Package }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $mutation := print $receiver ".mutation" }}
{{ $one := hasSuffix $builder "One" }}
{{ $zero := 0 }}{{ if $one }}{{ $zero = "nil" }}{{ end }}
func ({{ $receiver }} *{{ $builder }}) gremlinSave(ctx context.Context) ({{- if $one }}*{{ $.Name }}{{ else }}int{{ end }}, error) {
{{- if $.HasUpdateCheckers }}
if err := {{ $receiver }}.check(); err != nil {
return {{ $zero }}, err
}
{{- end }}
res := &gremlin.Response{}
{{- if $one }}
id, ok := {{ $mutation }}.{{ $.ID.MutationGet }}()
if !ok {
return {{ $zero }}, &ValidationError{Name: "{{ $.ID.Name }}", err: errors.New(`{{ $pkg }}: missing "{{ $.Name }}.{{ $.ID.Name }}" for update`)}
}
query, bindings := {{ $receiver }}.gremlin(id).Query()
{{- else }}
query, bindings := {{ $receiver }}.gremlin().Query()
{{- end }}
if err := {{ $receiver }}.driver.Exec(ctx, query, bindings, res); err != nil {
return {{ $zero }}, err
}
if err, ok := isConstantError(res); ok {
return {{ $zero }}, err
}
{{ $mutation }}.done = true
{{- if $one }}
{{- $r := $.Receiver }}
{{ $r }} := &{{ $.Name }}{config: {{ $receiver }}.config}
if err := {{ $r }}.FromResponse(res); err != nil {
return nil, err
}
return {{ $r }}, nil
{{- else }}
return res.ReadInt()
{{- end }}
}
func ({{ $receiver }} *{{ $builder }}) gremlin({{ if $one }}id {{ $.ID.Type }}{{ end }}) *dsl.Traversal {
{{- with .NumConstraint }}
type constraint struct {
pred *dsl.Traversal // constraint predicate.
test *dsl.Traversal // test matches and its constant.
}
constraints := make([]*constraint, 0, {{ . }})
{{- end }}
{{- /* case of update specific vertex */}}
{{- if $one }}
v := g.V(id)
{{- /* general update for N vertices */}}
{{- else }}
v := g.V().HasLabel({{ $.Package }}.Label)
for _, p := range {{ $mutation }}.Predicates() {
p(v)
}
{{- end }}
var (
{{ if or .NumConstraint (len $.Edges) }}
rv = v.Clone()
_ = rv
{{ end }}
trs []*dsl.Traversal
)
{{- range $f := $.MutationFields }}
{{- if $f.IsBlob }}{{ continue }}{{ end }}
{{- if or (not $f.Immutable) $f.UpdateDefault }}
if value, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
{{- if $f.Unique }}
constraints = append(constraints, &constraint{
pred: g.V().Has({{ $.Package }}.Label, {{ $.Package }}.{{ $f.Constant }}, value).Count(),
test: __.Is(p.NEQ(0)).Constant(NewErrUniqueField({{ $.Package }}.Label, {{ $.Package }}.{{ $f.Constant }}, value)),
})
{{- end }}
v.Property(dsl.Single, {{ $.Package }}.{{ $f.Constant }}, value)
}
{{- if $f.SupportsMutationAdd }}
if value, ok := {{ $mutation }}.Added{{ $f.StructField }}(); ok {
{{- if $f.Unique }}
addValue := rv.Clone().Union(__.Values({{ $.Package }}.{{ $f.Constant }}), __.Constant(value)).Sum().Next()
constraints = append(constraints, &constraint{
pred: g.V().Has({{ $.Package }}.Label, {{ $.Package }}.{{ $f.Constant }}, addValue).Count(),
test: __.Is(p.NEQ(0)).Constant(NewErrUniqueField({{ $.Package }}.Label, {{ $.Package }}.{{ $f.Constant }}, fmt.Sprintf("+= %v", value))),
})
{{- end }}
v.Property(dsl.Single, {{ $.Package }}.{{ $f.Constant }}, __.Union(__.Values({{ $.Package }}.{{ $f.Constant }}), __.Constant(value)).Sum())
}
{{- end }}
{{- end }}
{{- end }}
{{- /* clear optional fields. */}}
{{- with $.HasOptional }}
var properties []any
{{- range $f := $.MutationFields }}
{{- if $f.IsBlob }}{{ continue }}{{ end }}
{{- if $f.Optional }}
if {{ $mutation }}.{{ $f.StructField }}Cleared() {
properties = append(properties, {{ $.Package }}.{{ $f.Constant }})
}
{{- end }}
{{- end }}
if len(properties) > 0 {
v.SideEffect(__.Properties(properties...).Drop())
}
{{- end }}
{{- range $e := $.Edges }}
{{- $direction := "In" }}
{{- $name := printf "%s.%s" $.Package $e.LabelConstant }}
{{- if $e.IsInverse }}
{{- $direction = "Out" }}
{{- $name = printf "%s.%s" $e.Type.Package $e.LabelConstant }}
{{- end }}
{{- /* remove edges */}}
{{- if $e.Unique }}
if {{ $mutation }}.{{ $e.StructField }}Cleared() {
{{- else }}
for _, id := range {{ $mutation }}.Removed{{ $e.StructField }}IDs() {
{{- end }}
{{- if $e.Bidi }}
tr := rv.Clone().BothE({{ $name }}){{ if not $e.Unique }}.Where(__.Or(__.InV().HasID(id), __.OutV().HasID(id))){{ end }}.Drop().Iterate()
{{- else if $e.IsInverse }}
tr := rv.Clone().InE({{ $name }}){{ if not $e.Unique }}.Where(__.OtherV().HasID(id)){{ end }}.Drop().Iterate()
{{- else }}
tr := rv.Clone().OutE({{ $name }}){{ if not $e.Unique }}.Where(__.OtherV().HasID(id)){{ end }}.Drop().Iterate()
{{- end }}
trs = append(trs, tr)
}
{{- /* update edges */}}
for _, id := range {{ $mutation }}.{{ $e.StructField }}IDs() {
{{- if $e.IsInverse }}
v.AddE({{ $name }}).From(g.V(id)).InV()
{{- else }}
v.AddE({{ $name }}).To(g.V(id)).OutV()
{{- end }}
{{- if $e.HasConstraint }}
{{- if $e.Bidi }}
constraints = append(constraints, &constraint{
pred: rv.Clone().Both({{ $name }}).Count(),
test: __.Is(p.NEQ(0)).Constant(NewErrUniqueEdge({{ $.Package }}.Label, {{ $name }}, id)),
})
constraints = append(constraints, &constraint{
pred: g.E().HasLabel({{ $name }}).Where(__.Or(__.InV().HasID(id), __.OutV().HasID(id))).Count(),
test: __.Is(p.NEQ(0)).Constant(NewErrUniqueEdge({{ $.Package }}.Label, {{ $name }}, id)),
})
{{- else }}
constraints = append(constraints, &constraint{
pred: g.E().HasLabel({{ $name }}).{{ $direction }}V().HasID(id).Count(),
test: __.Is(p.NEQ(0)).Constant(NewErrUniqueEdge({{ $.Package }}.Label, {{ $name }}, id)),
})
{{- end }}
{{- end }}
}
{{- end }}
{{- if $one }}
if len({{ $receiver }}.fields) > 0 {
fields := make([]any, 0, len({{ $receiver }}.fields)+1)
fields = append(fields, true)
for _, f := range {{ $receiver }}.fields {
fields = append(fields, f)
}
v.ValueMap(fields...)
} else {
v.ValueMap(true)
}
{{- else }}
v.Count()
{{- end }}
{{- with .NumConstraint }}
if len(constraints) > 0 {
{{- /* make sure the traversal does not contain more than one vertex if we have constraint */}}
{{- if not $one }}
constraints = append(constraints, &constraint{
pred: rv.Count(),
test: __.Is(p.GT(1)).Constant(&ConstraintError{msg: "update traversal contains more than one vertex"}),
})
{{- end }}
v = constraints[0].pred.Coalesce(constraints[0].test, v)
for _, cr := range constraints[1:] {
v = cr.pred.Coalesce(cr.test, v)
}
}
{{- end }}
trs = append(trs, v)
return dsl.Join(trs...)
}
{{ end }}

View File

@@ -0,0 +1,67 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "dialect/sql/order/signature" -}}
// OrderFunc applies an ordering on the sql selector.
// Deprecated: Use Asc/Desc functions or the package builders instead.
type OrderFunc func(*sql.Selector)
var (
initCheck sync.Once
columnCheck sql.ColumnCheck
)
// checkColumn checks if the column exists in the given table.
func checkColumn(t, c string) error {
initCheck.Do(func() {
columnCheck = sql.NewColumnCheck(map[string]func(string) bool{
{{- range $n := $.Nodes }}
{{ $n.Package }}.Table: {{ $n.Package }}.ValidColumn,
{{- end }}
})
})
return columnCheck(t, c)
}
{{- end }}
{{ define "dialect/sql/order/func" -}}
{{- $f := $.Scope.Func -}}
func(s *sql.Selector) {
for _, f := range fields {
if err := checkColumn(s.TableName(), f); err != nil {
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("{{ base $.Config.Package }}: %w", err)})
}
s.OrderBy(sql.{{ $f }}(s.C(f)))
}
}
{{- end }}
{{/* custom signature for group-by function */}}
{{ define "dialect/sql/group/signature" -}}
type AggregateFunc func(*sql.Selector) string
{{- end }}
{{ define "dialect/sql/group/as" -}}
func(s *sql.Selector) string {
return sql.As(fn(s), end)
}
{{- end }}
{{ define "dialect/sql/group/func" -}}
{{- $fn := $.Scope.Func -}}
{{- $withField := $.Scope.WithField -}}
func(s *sql.Selector) string {
{{- if $withField }}
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("{{ base $.Config.Package }}: %w", err)})
return ""
}
{{- end }}
return sql.{{ if eq $fn "Mean" }}Avg{{ else }}{{ $fn }}{{ end }}({{ if $withField }}s.C(field){{ else }}"*"{{ end }})
}
{{- end }}

View File

@@ -0,0 +1,492 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "dialect/sql/create" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $mutation := print $receiver ".mutation" }}
{{ $pkg := base $.Config.Package }}
func ({{ $receiver }} *{{ $builder }}) sqlSave(ctx context.Context) (*{{ $.Name }}, error) {
if err := {{ $receiver }}.check(); err != nil {
return nil, err
}
_node, _spec {{ if $.HasValueScanner }}, err {{ end }} := {{ $receiver }}.createSpec()
{{- if $.HasValueScanner }}
if err != nil {
return nil, err
}
{{- end }}
{{- if $.HasBlobFields }}
_blobs := ent.NewBlobs({{ $mutation }}.blobOpeners.{{ $.Name }})
{{- range $f := $.BlobFields }}
{{- if not $f.IsBlobLazy }}
if value, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
{{- if $f.HasValueScanner }}
_blobDV, err := {{ $f.ValueFunc }}(value)
if err != nil {
return nil, fmt.Errorf("{{ $pkg }}: encoding {{ $f.Name }}: %w", err)
}
var _blobData []byte
switch v := _blobDV.(type) {
case []byte:
_blobData = v
case string:
_blobData = []byte(v)
default:
return nil, fmt.Errorf("{{ $pkg }}: encoding {{ $f.Name }}: expected []byte or string, got %T", _blobDV)
}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, _blobData,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_node.{{ $f.BlobKeyColumn }} = &k
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- else if $f.IsBlobGoString }}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, []byte(value),
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_node.{{ $f.BlobKeyColumn }} = &k
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- else }}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, value,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_node.{{ $f.BlobKeyColumn }} = &k
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- end }}
_node.{{ $f.StructField }} = value
}
{{- else }}
if r, ok := {{ $mutation }}.{{ $f.StructField }}(); ok {
_blobData, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("{{ $pkg }}: reading {{ $f.Name }}: %w", err)
}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, _blobData,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_node.{{ $f.BlobKeyColumn }} = &k
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
}
{{- end }}
{{- end }}
_blobCleanup, err := _blobs.Create(ctx, {{ if $.HasBlobRefCheckedFields }}&sqlgraph.BlobSpec{
Driver: {{ $receiver }}.driver,
Table: {{ $.Package }}.Table,
Columns: map[string]string{
{{- range $f := $.BlobRefCheckedFields }}
{{ $.Package }}.{{ $f.Constant }}: "{{ $f.BlobKeyColumn }}",
{{- end }}
},
CheckRefs: map[string]bool{
{{- range $f := $.BlobRefCheckedFields }}
{{ $.Package }}.{{ $f.Constant }}: true,
{{- end }}
},
}{{ else }}nil{{ end }})
if err != nil {
return nil, err
}
{{- end }}
if err := sqlgraph.CreateNode(ctx, {{ $receiver }}.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
{{- if $.HasBlobFields }}
return nil, errors.Join(err, _blobCleanup(ctx))
{{- else }}
return nil, err
{{- end }}
}
{{- if $.HasBlobFields }}
if txd, ok := {{ $receiver }}.driver.(*txDriver); ok {
txd.mu.Lock()
txd.onRollback = append(txd.onRollback, func(next Rollbacker) Rollbacker {
return RollbackFunc(func(ctx context.Context, tx *Tx) error {
err := next.Rollback(ctx, tx)
return errors.Join(err, _blobCleanup(ctx))
})
})
txd.mu.Unlock()
}
{{- end }}
{{- if $.HasCompositeID }}
{{- else if or $.ID.HasValueScanner $.ID.Type.ValueScanner (not $.ID.Type.Numeric) }}
if _spec.ID.Value != nil {
{{- if $.ID.HasValueScanner }}
sv, ok := _spec.ID.Value.(field.ValueScanner)
if !ok {
sv = {{ $.ID.ScanValueFunc }}()
if err := sv.Scan(_spec.ID.Value); err != nil {
return nil, err
}
}
if value, err := {{ $.ID.FromValueFunc }}(sv); err != nil {
return nil, err
} else {
_node.ID = value
}
{{- else }}
{{- /* If the ID type is not a pointer, but implements the ValueScanner interface (e.g. UUID fields). */}}
{{- if and $.ID.Type.ValueScanner (not $.ID.Type.RType.IsPtr) }}
if id, ok := _spec.ID.Value.(*{{ $.ID.Type }}); ok {
_node.ID = *id
{{- else }}
if id, ok := _spec.ID.Value.({{ $.ID.Type }}); ok {
_node.ID = id
{{- end }}
{{- if $.ID.Type.ValueScanner }}
} else if err := _node.ID.Scan(_spec.ID.Value); err != nil {
return nil, err
{{- else }}
} else {
return nil, fmt.Errorf("unexpected {{ $.Name }}.ID type: %T", _spec.ID.Value)
{{- end }}
}
{{- end }}
}
{{- else }}
{{- if $.ID.UserDefined }}
if _spec.ID.Value != _node.ID {
{{- end }}
id := _spec.ID.Value.(int64)
_node.ID = {{ $.ID.Type }}(id)
{{- if $.ID.UserDefined }}
}
{{- end }}
{{- end }}
{{- if $.HasOneFieldID }}
{{ $mutation }}.{{ $.ID.BuilderField }} = &_node.{{ $.ID.StructField }}
{{ $mutation }}.done = true
{{- end }}
return _node, nil
}
func ({{ $receiver }} *{{ $builder }}) createSpec() (*{{ $.Name }}, *sqlgraph.CreateSpec{{ if $.HasValueScanner }}, error{{ end }}) {
var (
_node = &{{ $.Name }}{config: {{ $receiver }}.config}
_spec = sqlgraph.NewCreateSpec({{ $.Package }}.Table, {{ if $.HasOneFieldID }}sqlgraph.NewFieldSpec({{ $.Package }}.{{ $.ID.Constant }}, field.{{ $.ID.Type.ConstName }}){{ else }}nil{{ end }})
)
{{- /* Allow mutating the sqlgraph.CreateSpec by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/create/spec/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- if and (not $.HasCompositeID) $.ID.UserDefined }}
if id, ok := {{ $mutation }}.{{ $.ID.MutationGet }}(); ok {
_node.ID = id
{{- if $.ID.HasValueScanner }}
vv, err := {{ $.ID.ValueFunc }}(id)
if err != nil {
return nil, nil, err
}
_spec.ID.Value = vv
{{- else }}
_spec.ID.Value = {{ if and $.ID.Type.ValueScanner (not $.ID.Type.RType.IsPtr) }}&{{ end }}id
{{- end }}
}
{{- end }}
{{- range $f := $.MutationFields }}
{{- if $f.IsBlobNoColumn }}{{ continue }}{{ end }}
if value, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
{{- if $f.HasValueScanner }}
vv, err := {{ $f.ValueFunc }}(value)
if err != nil {
return nil, nil, err
}
_spec.SetField({{ $.Package }}.{{ $f.Constant }}, field.{{ $f.Type.ConstName }}, vv)
{{- else }}
_spec.SetField({{ $.Package }}.{{ $f.Constant }}, field.{{ $f.Type.ConstName }}, value)
{{- end }}
_node.{{ $f.StructField }} = {{ if $f.NillableValue }}&{{ end }}value
}
{{- end }}
{{- range $e := $.EdgesWithID }}
if nodes := {{ $mutation }}.{{ $e.StructField }}IDs(); len(nodes) > 0 {
{{- with extend $ "Edge" $e "Nodes" true "Zero" "nil" }}
{{ template "dialect/sql/defedge" . }}{{/* defined in sql/update.tmpl */}}
{{- end }}
{{- if $e.OwnFK }}
{{- $fk := $e.ForeignKey }}
_node.{{ $fk.StructField }} = {{ if $fk.Field.NillableValue }}&{{ end }}nodes[0]
{{- end }}
_spec.Edges = append(_spec.Edges, edge)
}
{{- end }}
return _node, _spec{{ if $.HasValueScanner }}, nil{{ end }}
}
{{- /* Allow adding methods to the create-builder by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/create/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}
{{/* Additional fields for the create builder. */}}
{{ define "dialect/sql/create/fields" }}
{{- with $tmpls := matchTemplate "dialect/sql/create/fields/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- end }}
{{/* Additional fields for the create_bulk builder. */}}
{{ define "dialect/sql/create_bulk/fields" }}
{{- with $tmpls := matchTemplate "dialect/sql/create_bulk/fields/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- end }}
{{ define "dialect/sql/create_bulk" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $pkg := base $.Config.Package }}
// Save creates the {{ $.Name }} entities in the database.
func ({{ $receiver }} *{{ $builder }}) Save(ctx context.Context) ([]*{{ $.Name }}, error) {
{{- /* Initialization error was set by MapCreateBulk. */}}
if {{ $receiver }}.err != nil {
return nil, {{ $receiver }}.err
}
specs := make([]*sqlgraph.CreateSpec, len({{ $receiver }}.builders))
nodes := make([]*{{ $.Name }}, len({{ $receiver }}.builders))
mutators := make([]Mutator, len({{ $receiver }}.builders))
{{- if $.HasBlobFields }}
_blobs := ent.NewBlobs({{ $receiver }}.blobOpeners.{{ $.Name }})
{{- end }}
for i := range {{ $receiver }}.builders {
func(i int, root context.Context) {
builder := {{ $receiver }}.builders[i]
{{- if $.HasDefault }}
builder.defaults()
{{- end }}
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*{{ $.MutationName }})
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] {{ if $.HasValueScanner }}, err {{ end }}= builder.createSpec()
{{- if $.HasValueScanner }}
if err != nil {
return nil, err
}
{{- end }}
{{- if $.HasBlobFields }}
{{- range $f := $.BlobFields }}
{{- if not $f.IsBlobLazy }}
if value, ok := mutation.{{ $f.MutationGet }}(); ok {
{{- if $f.HasValueScanner }}
_blobDV, err := {{ $f.ValueFunc }}(value)
if err != nil {
return nil, fmt.Errorf("{{ $pkg }}: encoding {{ $f.Name }}: %w", err)
}
var _blobData []byte
switch v := _blobDV.(type) {
case []byte:
_blobData = v
case string:
_blobData = []byte(v)
default:
return nil, fmt.Errorf("{{ $pkg }}: encoding {{ $f.Name }}: expected []byte or string, got %T", _blobDV)
}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, _blobData,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
nodes[i].{{ $f.BlobKeyColumn }} = &k
specs[i].SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- else if $f.IsBlobGoString }}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, []byte(value),
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
nodes[i].{{ $f.BlobKeyColumn }} = &k
specs[i].SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- else }}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, value,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
nodes[i].{{ $f.BlobKeyColumn }} = &k
specs[i].SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- end }}
nodes[i].{{ $f.StructField }} = value
}
{{- else }}
if r, ok := mutation.{{ $f.StructField }}(); ok {
_blobData, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("{{ $pkg }}: reading {{ $f.Name }}: %w", err)
}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, _blobData,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
nodes[i].{{ $f.BlobKeyColumn }} = &k
specs[i].SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
}
{{- end }}
{{- end }}
{{- end }}
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, {{ $receiver }}.builders[i+1].mutation)
} else {
{{- if $.HasBlobFields }}
// Write blobs before creating SQL rows so insert failures can clean up written objects.
_blobCleanup, err := _blobs.Create(ctx, {{ if $.HasBlobRefCheckedFields }}&sqlgraph.BlobSpec{
Driver: {{ $receiver }}.driver,
Table: {{ $.Package }}.Table,
Columns: map[string]string{
{{- range $f := $.BlobRefCheckedFields }}
{{ $.Package }}.{{ $f.Constant }}: "{{ $f.BlobKeyColumn }}",
{{- end }}
},
CheckRefs: map[string]bool{
{{- range $f := $.BlobRefCheckedFields }}
{{ $.Package }}.{{ $f.Constant }}: true,
{{- end }}
},
}{{ else }}nil{{ end }})
if err != nil {
return nil, err
}
{{- end }}
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
{{- /* Allow mutating the sqlgraph.BatchCreateSpec by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/create_bulk/spec/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, {{ $receiver }}.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
{{- if $.HasBlobFields }}
return nil, errors.Join(err, _blobCleanup(ctx))
{{- end }}
}
{{- if $.HasBlobFields }}
if txd, ok := {{ $receiver }}.driver.(*txDriver); ok {
txd.mu.Lock()
txd.onRollback = append(txd.onRollback, func(next Rollbacker) Rollbacker {
return RollbackFunc(func(ctx context.Context, tx *Tx) error {
err := next.Rollback(ctx, tx)
return errors.Join(err, _blobCleanup(ctx))
})
})
txd.mu.Unlock()
}
{{- end }}
}
if err != nil {
return nil, err
}
{{- if $.HasOneFieldID }}
mutation.{{ $.ID.BuilderField }} = &nodes[i].{{ $.ID.StructField }}
{{- if and (or $.ID.IsString $.ID.IsUUID $.ID.IsBytes $.ID.IsOther) (not $.ID.HasValueScanner) }}
{{- /* Do nothing, because these 4 types must be supplied by the user. */ -}}
{{- else if $.ID.HasValueScanner }}
if specs[i].ID.Value != nil {
sv, ok := specs[i].ID.Value.(field.ValueScanner)
if !ok {
sv = {{ $.ID.ScanValueFunc }}()
if err := sv.Scan(specs[i].ID.Value); err != nil {
return nil, err
}
}
if id, err := {{ $.ID.FromValueFunc }}(sv); err != nil {
return nil, err
} else {
nodes[i].ID = id
}
}
{{- else if or $.ID.Type.ValueScanner }}
if specs[i].ID.Value != nil {
if err := nodes[i].ID.Scan(specs[i].ID.Value); err != nil {
return nil, err
}
}
{{- else }}
if specs[i].ID.Value != nil {{ if $.ID.UserDefined }}&& nodes[i].ID == 0{{ end }} {
id := specs[i].ID.Value.(int64)
nodes[i].ID = {{ $.ID.Type }}(id)
}
{{- end }}
{{- end }}
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, {{ $receiver }}.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) SaveX(ctx context.Context) []*{{ $.Name }} {
v, err := {{ $receiver }}.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func ({{ $receiver }} *{{ $builder }}) Exec(ctx context.Context) error {
_, err := {{ $receiver }}.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func ({{ $receiver }} *{{ $builder }}) ExecX(ctx context.Context) {
if err := {{ $receiver }}.Exec(ctx); err != nil {
panic(err)
}
}
{{- /* Allow adding methods to the create_bulk builder by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/create_bulk/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,181 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/sql/decode/one" }}
{{ $receiver := $.Receiver }}
{{ $ctypes := dict }}
{{ if $.HasOneFieldID }}
{{- if not $.ID.HasValueScanner }}
{{ $idscantype := $.ID.NewScanType }}
{{ $ctypes = set $ctypes $idscantype (list $.ID.Constant) }}
{{- end }}
{{ end }}
{{ range $f := $.Fields }}
{{- if $f.HasValueScanner }}
{{- continue }}
{{- end }}
{{- if or $f.IsBlobNoColumn $f.IsBlobLazy }}
{{- continue }}
{{- end }}
{{ $names := list }}
{{ if hasKey $ctypes $f.NewScanType }}
{{ $names = get $ctypes $f.NewScanType }}
{{ end }}
{{ $names = append $names $f.Constant }}
{{ $ctypes = set $ctypes $f.NewScanType $names }}
{{ end }}
// scanValues returns the types for scanning values from sql.Rows.
func (*{{ $.Name }}) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
{{- if and $.HasOneFieldID $.ID.HasValueScanner }}
case {{ $.Package }}.{{ $.ID.Constant }}:
values[i] = {{ $.ID.ScanValueFunc }}()
{{- end }}
{{- range $type, $columns := $ctypes }}
case {{ range $i, $c := $columns }}{{ if ne $i 0 }},{{ end }}{{ $.Package }}.{{ $c }}{{ end }}:
values[i] = {{ $type }}
{{- end }}
{{- range $f := $.Fields }}
{{- if $f.IsBlobNoColumn }}{{ continue }}{{ end }}
{{- if $f.HasValueScanner }}
case {{ $.Package }}.{{ $f.Constant }}:
values[i] = {{ $f.ScanValueFunc }}()
{{- end }}
{{- end }}
{{- range $i, $fk := $.UnexportedForeignKeys }}
{{- $f := $fk.Field }}
case {{ $.Package }}.ForeignKeys[{{ $i }}]: // {{ $f.Name }}
values[i] = {{ if not $f.UserDefined }}new(sql.NullInt64){{ else }}{{ $f.NewScanType }}{{ end }}
{{- end }}
{{- range $i, $bk := $.BlobKeys }}
case {{ $.Package }}.BlobKeys[{{ $i }}]: // {{ $bk.Field.Name }}
values[i] = new(sql.NullString)
{{- end }}
default:
{{- /* In case of unknown column that was added by a modifier, predicate, etc., fallback to any. */}}
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the {{ $.Name }} fields.
func ({{ $receiver }} *{{ $.Name }}) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
{{- if $.HasOneFieldID }}
case {{ $.Package }}.{{ $.ID.Constant }}:
{{- if or $.ID.IsString $.ID.IsBytes $.ID.HasGoType $.ID.HasValueScanner }}
{{- with extend $ "Idx" "i" "Field" $.ID "Rec" $receiver }}
{{ template "dialect/sql/decode/field" . }}
{{- end }}
{{- else }}
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
{{ $receiver }}.ID = {{ $.ID.Type }}(value.Int64)
{{- end }}
{{- end }}
{{- range $f := $.Fields }}
{{- if or $f.IsBlobNoColumn $f.IsBlobLazy }}
{{- continue }}
{{- end }}
case {{ $.Package }}.{{ $f.Constant }}:
{{- with extend $ "Idx" "i" "Field" $f "Rec" $receiver }}
{{ template "dialect/sql/decode/field" . }}
{{- end }}
{{- end }}
{{- range $i, $fk := $.UnexportedForeignKeys }}
{{- $f := $fk.Field }}
case {{ if $fk.UserDefined }}{{ $.Package }}.{{ $.ID.Constant }}{{ else }}{{ $.Package }}.ForeignKeys[{{ $i }}]{{ end }}:
{{- if or $fk.UserDefined (and $f.UserDefined (or $f.IsString $f.IsBytes $f.HasGoType)) }}
{{- with extend $ "Idx" "i" "Field" $f "Rec" $receiver "StructField" $fk.StructField }}
{{ template "dialect/sql/decode/field" . }}
{{- end }}
{{- else }}
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for edge-field {{ $f.Name}}", value)
} else if value.Valid {
{{ $receiver }}.{{ $fk.StructField }} = new({{ $f.Type }})
{{ if and $f.Nillable (not $f.Type.Nillable) }}*{{ end }}{{ $receiver }}.{{ $fk.StructField }} = {{ $f.Type }}(value.Int64)
}
{{- end }}
{{- end }}
{{- range $i, $bk := $.BlobKeys }}
case {{ $.Package }}.BlobKeys[{{ $i }}]:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field {{ $bk.Field.Name }}", values[i])
} else if value.Valid {
{{ $receiver }}.{{ $bk.StructField }} = &value.String
}
{{- end }}
default:
{{- /* In case of no match, allow getting this value by its name. */}}
{{ $receiver }}.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// {{ $.ValueName }} returns the ent.Value that was dynamically selected and assigned to the {{ $.Name }}.
// This includes values selected through modifiers, order, etc.
func ({{ $receiver }} *{{ $.Name }}) {{ $.ValueName }}(name string) (ent.Value, error) {
return {{ $receiver }}.selectValues.Get(name)
}
{{ end }}
{{ define "dialect/sql/decode/field" }}
{{- $i := $.Scope.Idx -}}
{{- $f := $.Scope.Field -}}
{{- $ret := $.Scope.Rec -}}
{{- $field := $f.StructField }}{{ with $.Scope.StructField }}{{ $field = . }}{{ end -}}
{{- if $f.HasValueScanner -}}
if value, err := {{ $f.FromValueFunc }}(values[{{ $i }}]); err != nil {
return err
} else {
{{ $ret }}.{{ $field }} = value
}
{{- else if $f.IsJSON -}}
if value, ok := values[{{ $i }}].(*{{ $f.ScanType }}); !ok {
return fmt.Errorf("unexpected type %T for field {{ $f.Name }}", values[{{ $i }}])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &{{ $ret }}.{{ $field }}); err != nil {
return fmt.Errorf("unmarshal field {{ $f.Name }}: %w", err)
}
}
{{- else }}
{{- $scantype := $f.ScanType -}}
if value, ok := values[{{ $i }}].(*{{ $scantype }}); !ok {
return fmt.Errorf("unexpected type %T for field {{ $f.Name }}", values[{{ $i }}])
{{- if hasPrefix $scantype "sql.Null" }}
} else if value.Valid {
{{- if $f.NillableValue }}
{{ $ret }}.{{ $field }} = new({{ $f.Type }})
*{{ $ret }}.{{ $field }} = {{ $f.ScanTypeField "value" }}
{{- else }}
{{ $ret }}.{{ $field }} = {{ $f.ScanTypeField "value" }}
{{- end }}
{{- else }}
} else if value != nil {
{{ $ret }}.{{ $field }} = {{ if and (not $f.Nillable) (not $f.Type.RType.IsPtr) }}*{{ end }}value
{{- end }}
}
{{- end }}
{{- end }}
{{ define "dialect/sql/decode/many" }}
{{ end }}

View File

@@ -0,0 +1,84 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/sql/delete" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $mutation := print $receiver ".mutation" }}
func ({{ $receiver}} *{{ $builder }}) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec({{ $.Package }}.Table, {{ if $.HasOneFieldID }}sqlgraph.NewFieldSpec({{ $.Package }}.{{ $.ID.Constant }}, field.{{ $.ID.Type.ConstName }}){{ else }}nil{{ end }})
{{- /* Allow mutating the sqlgraph.DeleteSpec by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/delete/spec/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
if ps := {{ $mutation }}.Predicates(); len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
{{- if $.HasBlobFields }}
// Collect blob keys before deleting rows so we can remove blobs from storage afterward.
_blobKeys, _blobErr := ent.NewBlobs({{ $mutation }}.blobOpeners.{{ $.Name }}).Delete(ctx, &sqlgraph.BlobSpec{
Driver: {{ $receiver }}.driver,
Predicate: _spec.Predicate,
Table: {{ $.Package }}.Table,
Columns: map[string]string{
{{- range $f := $.BlobFields }}
{{ $.Package }}.{{ $f.Constant }}: "{{ $f.BlobKeyColumn }}",
{{- end }}
},
{{- if $.HasBlobRefCheckedFields }}
CheckRefs: map[string]bool{
{{- range $f := $.BlobRefCheckedFields }}
{{ $.Package }}.{{ $f.Constant }}: true,
{{- end }}
},
{{- end }}
})
if _blobErr != nil {
return 0, _blobErr
}
{{- end }}
affected, err := sqlgraph.DeleteNodes(ctx, {{ $receiver}}.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
{{- if $.HasBlobFields }}
if err == nil {
// Resolve which blobs are unreferenced now that the rows are gone, while the
// transaction is still open, and remove them from storage once it commits.
_blobCleanup, _blobErr := _blobKeys.Cleanup(ctx)
if _blobErr != nil {
return 0, _blobErr
}
if txd, ok := {{ $receiver }}.driver.(*txDriver); ok {
txd.mu.Lock()
txd.onCommit = append(txd.onCommit, func(next Committer) Committer {
return CommitFunc(func(ctx context.Context, tx *Tx) error {
if err := next.Commit(ctx, tx); err != nil {
return err
}
return _blobCleanup(ctx)
})
})
txd.mu.Unlock()
} else {
err = _blobCleanup(ctx)
}
}
{{- end }}
{{ $mutation }}.done = true
return affected, err
}
{{ end }}

View File

@@ -0,0 +1,25 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{/* Additional fields for the generated model for holding the foreign-keys */}}
{{ define "dialect/sql/model/fields" }}
{{- range $fk := $.UnexportedForeignKeys }}
{{- $f := $fk.Field }}
{{ $fk.StructField }} {{ if $f.Nillable }}*{{ end }}{{ $f.Type }}
{{- end }}
{{- range $bk := $.BlobKeys }}
{{ $bk.StructField }} *string
{{- end }}
selectValues sql.SelectValues
{{- /* Allow adding struct fields by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/model/fields/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,177 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "dialect/sql/entql" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
import (
"context"
"time"
"{{ $.Config.Package }}/predicate"
{{- range $n := $.Nodes }}
{{ $n.PackageAlias }} "{{ $.Config.Package }}/{{ $n.PackageDir }}"
{{- end }}
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entql"
"entgo.io/ent/schema/field"
)
// schemaGraph holds a representation of ent/schema at runtime.
var schemaGraph = func() *sqlgraph.Schema {
graph := &sqlgraph.Schema{Nodes: make([]*sqlgraph.Node, {{ len $.Nodes }})}
{{- range $i, $n := $.Nodes }}
graph.Nodes[{{ $i }}] = &sqlgraph.Node{
NodeSpec: sqlgraph.NodeSpec{
Table: {{ $n.Package }}.Table,
Columns: {{ $n.Package }}.Columns,
{{- if $n.HasOneFieldID }}
ID: &sqlgraph.FieldSpec{
Type: field.{{ $n.ID.Type.ConstName }},
Column: {{ $n.Package }}.{{ $n.ID.Constant }},
},
{{- else }}
CompositeID: []*sqlgraph.FieldSpec{
{{- range $id := $n.EdgeSchema.ID }}
{
Type: field.{{ $id.Type.ConstName }},
Column: {{ $n.Package }}.{{ $id.Constant }},
},
{{- end }}
},
{{- end }}
},
Type: "{{ $n.Name }}",
Fields: map[string]*sqlgraph.FieldSpec{
{{- range $f := $n.Fields }}
{{- if not (or $f.IsBlobNoColumn $f.IsBlobLazy) }}
{{ $n.Package }}.{{ $f.Constant }}: {Type: field.{{ $f.Type.ConstName }}, Column: {{ $n.Package }}.{{ $f.Constant }}},
{{- end }}
{{- end }}
},
}
{{- end }}
{{- range $n := $.Nodes }}
{{- range $e := $n.Edges }}
graph.MustAddE(
"{{ $e.Name }}",
&sqlgraph.EdgeSpec{
Rel: sqlgraph.{{ $e.Rel.Type }},
Inverse: {{ $e.IsInverse }},
Table: {{ $n.Package }}.{{ $e.TableConstant }},
Columns: {{ if $e.M2M }}{{ $n.Package }}.{{ $e.PKConstant }}{{ else }}[]string{ {{ $n.Package }}.{{ $e.ColumnConstant }} }{{ end }},
Bidi: {{ $e.Bidi }},
},
"{{ $n.Name }}",
"{{ $e.Type.Name }}",
)
{{- end }}
{{- end }}
return graph
}()
// predicateAdder wraps the addPredicate method.
// All update, update-one and query builders implement this interface.
type predicateAdder interface {
addPredicate(func(s *sql.Selector))
}
{{ range $i, $n := $.Nodes }}
{{ $builder := $n.QueryName }}
{{ $receiver := $n.QueryReceiver }}
{{ $mutation := $n.MutationName }}
{{ $filter := print $n.FilterName }}
// addPredicate implements the predicateAdder interface.
func ({{ $receiver }} *{{ $builder }}) addPredicate(pred func(s *sql.Selector)) {
{{ $receiver }}.predicates = append({{ $receiver }}.predicates, pred)
}
// Filter returns a Filter implementation to apply filters on the {{ $builder }} builder.
func ({{ $receiver }} *{{ $builder }}) Filter() *{{ $filter }} {
return &{{ $filter }}{config: {{ $receiver }}.config, predicateAdder: {{ $receiver}} }
}
{{- if not $n.IsView }}
// addPredicate implements the predicateAdder interface.
func (m *{{ $mutation }}) addPredicate(pred func(s *sql.Selector)) {
m.Where(pred)
}
// Filter returns an entql.Where implementation to apply filters on the {{ $mutation }} builder.
func (m *{{ $mutation }}) Filter() *{{ $filter }} {
return &{{ $filter }}{config: m.config, predicateAdder: m}
}
{{- end }}
// {{ $filter }} provides a generic filtering capability at runtime for {{ $builder }}.
type {{ $filter }} struct {
predicateAdder
config
}
// Where applies the entql predicate on the query filter.
func (f *{{ $filter }}) Where(p entql.P) {
f.addPredicate(func(s *sql.Selector) {
if err := schemaGraph.EvalP(schemaGraph.Nodes[{{ $i }}].Type, p, s); err != nil {
s.AddError(err)
}
})
}
{{- if $n.HasOneFieldID }}
{{ $type := $n.ID.Type.Type.String }}
{{ $iface := print (pascal $type) "P" }}
{{- if $n.ID.IsTime }}{{ $iface = "TimeP" }}
{{- else if or $n.ID.IsBytes $n.ID.IsJSON }}{{ $iface = "BytesP" }}
{{- else if $n.ID.IsUUID }}{{ $iface = "ValueP" }}
{{- end }}
// WhereID applies the entql {{ $type }} predicate on the id field.
func (f *{{ $filter }}) WhereID(p entql.{{ $iface }}) {
f.Where(p.Field({{ $n.Package }}.{{ $n.ID.Constant }}))
}
{{- end }}
{{ range $f := $n.Fields }}
{{- if or $f.IsBlobNoColumn $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ $type := $f.Type.Type.String }}
{{ $iface := print (pascal $type) "P" }}
{{- if $f.IsTime }}{{ $iface = "TimeP" }}
{{- else if or $f.IsBytes $f.IsJSON }}{{ $iface = "BytesP" }}
{{- else if $f.IsUUID }}{{ $iface = "ValueP" }}
{{- end }}
// Where{{ $f.StructField }} applies the entql {{ $type }} predicate on the {{ $f.Name }} field.
func (f *{{ $filter }}) Where{{ $f.StructField }}(p entql.{{ $iface }}) {
f.Where(p.Field({{ $n.Package }}.{{ $f.Constant }}))
}
{{ end }}
{{ range $e := $n.Edges }}
{{ $func := print "WhereHas" $e.StructField }}
// {{ $func }} applies a predicate to check if query has an edge {{ $e.Name }}.
func (f *{{ $filter }}) {{ $func }}() {
f.Where(entql.HasEdge("{{ $e.Name }}"))
}
{{ $func = print "WhereHas" $e.StructField "With" }}
// {{ $func }} applies a predicate to check if query has an edge {{ $e.Name }} with a given conditions (other predicates).
func (f *{{ $filter }}) {{ $func }}(preds ...predicate.{{ $e.Type.Name }}) {
f.Where(entql.HasEdgeWith("{{ $e.Name }}", sqlgraph.WrapFunc(func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})))
}
{{ end }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,11 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{/* custom errors and errors handlers for sql dialects */}}
{{ define "dialect/sql/errors" }}
{{ end }}

View File

@@ -0,0 +1,71 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph*/}}
{{- define "import/additional/stdsql" -}}
{{- if $.FeatureEnabled "sql/execquery" }}
stdsql "database/sql"
{{- end }}
{{- end -}}
{{/* Template for adding "ExecContext"/"QueryContext" methods to the config. */}}
{{ define "config/additional/sql/execquery" }}
{{- if $.FeatureEnabled "sql/execquery" }}
// ExecContext allows calling the underlying ExecContext method of the driver if it is supported by it.
// See, database/sql#DB.ExecContext for more information.
func (c *config) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) {
ex, ok := c.driver.(interface {
ExecContext(context.Context, string, ...any) (stdsql.Result, error)
})
if !ok {
return nil, fmt.Errorf("Driver.ExecContext is not supported")
}
return ex.ExecContext(ctx, query, args...)
}
// QueryContext allows calling the underlying QueryContext method of the driver if it is supported by it.
// See, database/sql#DB.QueryContext for more information.
func (c *config) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) {
q, ok := c.driver.(interface {
QueryContext(context.Context, string, ...any) (*stdsql.Rows, error)
})
if !ok {
return nil, fmt.Errorf("Driver.QueryContext is not supported")
}
return q.QueryContext(ctx, query, args...)
}
{{- end }}
{{ end }}
{{/* Template for adding "ExecContext"/"QueryContext" methods to the client. */}}
{{ define "tx/additional/sql/execquery" }}
{{- if $.FeatureEnabled "sql/execquery" }}
// ExecContext allows calling the underlying ExecContext method of the transaction if it is supported by it.
// See, database/sql#Tx.ExecContext for more information.
func (tx *txDriver) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) {
ex, ok := tx.tx.(interface {
ExecContext(context.Context, string, ...any) (stdsql.Result, error)
})
if !ok {
return nil, fmt.Errorf("Tx.ExecContext is not supported")
}
return ex.ExecContext(ctx, query, args...)
}
// QueryContext allows calling the underlying QueryContext method of the transaction if it is supported by it.
// See, database/sql#Tx.QueryContext for more information.
func (tx *txDriver) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) {
q, ok := tx.tx.(interface {
QueryContext(context.Context, string, ...any) (*stdsql.Rows, error)
})
if !ok {
return nil, fmt.Errorf("Tx.QueryContext is not supported")
}
return q.QueryContext(ctx, query, args...)
}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,46 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{/* Templates used by the "sql/lock" feature-flag to add "SELECT ... FOR UPDATE/SHARE" capabilities. */}}
{{ define "dialect/sql/query/additional/locking" }}
{{ if $.FeatureEnabled "sql/lock" }}
{{ template "helper/sqlock" $ }}
{{ end }}
{{ end }}
{{ define "helper/sqlock" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func ({{ $receiver }} *{{ $builder }}) ForUpdate(opts ...sql.LockOption) *{{ $builder }} {
if {{ $receiver }}.driver.Dialect() == dialect.Postgres {
{{ $receiver }}.Unique(false)
}
{{ $receiver }}.modifiers = append({{ $receiver }}.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return {{ $receiver }}
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func ({{ $receiver }} *{{ $builder }}) ForShare(opts ...sql.LockOption) *{{ $builder }} {
if {{ $receiver }}.driver.Dialect() == dialect.Postgres {
{{ $receiver }}.Unique(false)
}
{{ $receiver }}.modifiers = append({{ $receiver }}.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return {{ $receiver }}
}
{{ end }}

View File

@@ -0,0 +1,40 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "migrate/diff" }}
// Diff compares the state read from a database connection or migration directory with
// the state defined by the Ent schema. Changes will be written to new migration files.
func Diff(ctx context.Context, url string, opts ...schema.MigrateOption) error {
return NamedDiff(ctx, url, "changes", opts...)
}
// NamedDiff compares the state read from a database connection or migration directory with
// the state defined by the Ent schema. Changes will be written to new named migration files.
func NamedDiff(ctx context.Context, url, name string, opts ...schema.MigrateOption) error {
return schema.Diff(ctx, url, name, Tables, opts...)
}
// Diff creates a migration file containing the statements to resolve the diff
// between the Ent schema and the connected database.
func (s *Schema) Diff(ctx context.Context, opts ...schema.MigrateOption) error {
migrate, err := schema.NewMigrate(s.drv, opts...)
if err != nil {
return fmt.Errorf("ent/migrate: %w", err)
}
return migrate.Diff(ctx, Tables...)
}
// NamedDiff creates a named migration file containing the statements to resolve the diff
// between the Ent schema and the connected database.
func (s *Schema) NamedDiff(ctx context.Context, name string, opts ...schema.MigrateOption) error {
migrate, err := schema.NewMigrate(s.drv, opts...)
if err != nil {
return fmt.Errorf("ent/migrate: %w", err)
}
return migrate.NamedDiff(ctx, name, Tables...)
}
{{ end }}

View File

@@ -0,0 +1,92 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{/* Templates used by the "sql/modifier" feature-flag to add custom modifiers to the builders. */}}
{{/* Template for adding the "modifiers" field to the query builder. */}}
{{ define "dialect/sql/query/fields/additional/modify" -}}
{{- if or ($.FeatureEnabled "sql/lock") ($.FeatureEnabled "sql/modifier") }}
modifiers []func(*sql.Selector)
{{- end }}
{{- end -}}
{{/* Template for adding the "executing" the list of modifiers on the sql.Selector. */}}
{{ define "dialect/sql/query/selector/modify" }}
{{- if or ($.FeatureEnabled "sql/lock") ($.FeatureEnabled "sql/modifier") }}
{{- $receiver := $.Scope.Receiver }}
for _, m := range {{ $receiver }}.modifiers {
m(selector)
}
{{- end }}
{{- end -}}
{{/* Template for passing the modifiers to the sqlgraph.QuerySpec. */}}
{{ define "dialect/sql/query/spec/modify" }}
{{- if or ($.FeatureEnabled "sql/lock") ($.FeatureEnabled "sql/modifier") }}
{{- $receiver := $.Scope.Receiver }}
if len({{ $receiver }}.modifiers) > 0 {
_spec.Modifiers = {{ $receiver }}.modifiers
}
{{- end }}
{{- end -}}
{{/* A template for adding the Modify method to the query-builder. */}}
{{ define "dialect/sql/query/additional/modify" }}
{{ if $.FeatureEnabled "sql/modifier" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $selectBuilder := pascal $.Name | printf "%sSelect" }}
// Modify adds a query modifier for attaching custom logic to queries.
func ({{ $receiver }} *{{ $builder }}) Modify(modifiers ...func(s *sql.Selector)) *{{ $selectBuilder }} {
{{ $receiver }}.modifiers = append({{ $receiver }}.modifiers, modifiers...)
return {{ $receiver }}.Select()
}
{{ end }}
{{ end }}
{{/* A template for adding the Modify method to the select-builder. */}}
{{ define "dialect/sql/select/additional/modify" }}
{{ if $.FeatureEnabled "sql/modifier" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
// Modify adds a query modifier for attaching custom logic to queries.
func ({{ $receiver }} *{{ $builder }}) Modify(modifiers ...func(s *sql.Selector)) *{{ $builder }} {
{{ $receiver }}.modifiers = append({{ $receiver }}.modifiers, modifiers...)
return {{ $receiver }}
}
{{ end }}
{{ end }}
{{/* Template for adding the "modifiers" field to the update builder. */}}
{{ define "dialect/sql/update/fields/additional/modify" -}}
{{- if $.FeatureEnabled "sql/modifier" }}
modifiers []func(*sql.UpdateBuilder)
{{- end }}
{{- end -}}
{{/* A template for adding the Modify method to the update and updateone builders. */}}
{{ define "dialect/sql/update/additional/modify" }}
{{ if $.FeatureEnabled "sql/modifier" }}
{{ $pkg := $.Scope.Package }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
// Modify adds a statement modifier for attaching custom logic to the UPDATE statement.
func ({{ $receiver }} *{{ $builder }}) Modify(modifiers ...func(u *sql.UpdateBuilder)) *{{ $builder }} {
{{ $receiver }}.modifiers = append({{ $receiver }}.modifiers, modifiers...)
return {{ $receiver }}
}
{{ end }}
{{ end }}
{{/* Template for passing the modifiers to the sqlgraph.UpdateSpec. */}}
{{ define "dialect/sql/update/spec/modify" }}
{{- if $.FeatureEnabled "sql/modifier" }}
{{- $receiver := $.Scope.Receiver }}
_spec.AddModifiers({{ $receiver }}.modifiers...)
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,121 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{/* Templates used by the "namedges" feature-flag to allow eager-loading edges with dynamic names. */}}
{{ define "dialect/sql/model/edges/fields/additional/namedges" }}
{{- if $.FeatureEnabled "namedges" }}
{{- range $e := $.Edges }}
{{- if not $e.Unique }}
named{{ $e.StructField }} map[string][]*{{ $e.Type.Name }}
{{- end }}
{{- end }}
{{- end }}
{{ end }}
{{ define "dialect/sql/model/additional/namedges" }}
{{- if $.FeatureEnabled "namedges" }}
{{ $receiver := $.Receiver }}
{{- range $e := $.Edges }}
{{- if not $e.Unique }}
{{ $func := print "Named" $e.StructField }}
// {{ $func }} returns the {{ $e.StructField }} named value or an error if the edge was not
// loaded in eager-loading with this name.
func ({{ $receiver }} *{{ $.Name }}) Named{{ $e.StructField }}(name string) ([]*{{ $e.Type.Name }}, error) {
if {{ $receiver }}.Edges.named{{ $e.StructField }} == nil {
return nil, &NotLoadedError{edge: name}
}
nodes, ok := {{ $receiver }}.Edges.named{{ $e.StructField }}[name]
if !ok {
return nil, &NotLoadedError{edge: name}
}
return nodes, nil
}
func ({{ $receiver }} *{{ $.Name }}) appendNamed{{ $e.StructField }}(name string, edges ...*{{ $e.Type.Name }}) {
if {{ $receiver }}.Edges.named{{ $e.StructField }} == nil {
{{ $receiver }}.Edges.named{{ $e.StructField }} = make(map[string][]*{{ $e.Type.Name }})
}
if len(edges) == 0 {
{{- /* Prefer empty array over nil to stay consistent with the standard eager-loading API. */}}
{{ $receiver }}.Edges.named{{ $e.StructField }}[name] = []*{{ $e.Type.Name }}{}
} else {
{{ $receiver }}.Edges.named{{ $e.StructField }}[name] = append({{ $receiver }}.Edges.named{{ $e.StructField }}[name], edges...)
}
}
{{- end }}
{{- end }}
{{- end }}
{{ end }}
{{- define "dialect/sql/query/fields/additional/namedges" }}
{{- if $.FeatureEnabled "namedges" }}
{{- range $e := $.Edges }}
{{- if not $e.Unique }}
{{ $e.EagerLoadNamedField }} map[string]*{{ $e.Type.QueryName }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{ define "dialect/sql/query/additional/namedges" }}
{{- if $.FeatureEnabled "namedges" }}
{{ $builder := $.QueryName }}
{{ $receiver := $.QueryReceiver }}
{{- range $e := $.Edges }}
{{- if not $e.Unique }}
{{ $ebuilder := $e.Type.QueryName }}
{{ $func := print "WithNamed" $e.StructField }}
// {{ $func }} tells the query-builder to eager-load the nodes that are connected to the "{{ $e.Name }}"
// edge with the given name. The optional arguments are used to configure the query builder of the edge.
func ({{ $receiver }} *{{ $builder }}) {{ $func }}(name string, opts ...func(*{{ $ebuilder }})) *{{ $builder }} {
query := (&{{ $e.Type.ClientName }}{config: {{ $receiver }}.config}).Query()
for _, opt := range opts {
opt(query)
}
if {{ $receiver }}.{{ $e.EagerLoadNamedField }} == nil {
{{ $receiver }}.{{ $e.EagerLoadNamedField }} = make(map[string]*{{ $e.Type.QueryName }})
}
{{ $receiver }}.{{ $e.EagerLoadNamedField }}[name] = query
return {{ $receiver }}
}
{{- end }}
{{- end }}
{{- end }}
{{ end }}
{{/* Process nodes before they are returned and resolve named-edges. */}}
{{- define "dialect/sql/query/all/nodes/namedges" }}
{{- if $.FeatureEnabled "namedges" }}
{{- $builder := pascal $.Scope.Builder }}
{{- $receiver := $.Scope.Receiver }}
{{- range $e := $.Edges }}
{{- if not $e.Unique }}
for name, query := range {{ $receiver }}.{{ $e.EagerLoadNamedField }} {
if err := {{ $receiver }}.load{{ $e.StructField }}(ctx, query, nodes,
func(n *{{ $.Name }}) { n.appendNamed{{ $e.StructField }}(name) },
{{- if and ($.FeatureEnabled "bidiedges") $e.Ref $e.Ref.Unique }}
func(n *{{ $.Name }}, e *{{ $e.Type.Name }}){
n.appendNamed{{ $e.StructField }}(name, e)
{{- $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 }}){ n.appendNamed{{ $e.StructField }}(name, e) }); err != nil {
{{- end }}
return nil, err
}
}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,220 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{- define "dialect/sql/internal/schemaconfig" -}}
{{ with extend $ "Package" "internal" -}}
{{ template "header" . }}
{{ end }}
import "context"
// SchemaConfig represents alternative schema names for all tables
// that can be passed at runtime.
type SchemaConfig struct {
{{- range $n := $.Nodes }}
{{ $n.Name }} string // {{ $n.Name }} table.
{{- range $e := $n.Edges }}
{{- /* Skip adding join-table in case the edge is inverse or already defined as an edge-schema. */}}
{{- if and $e.M2M (not $e.Inverse) }}
{{ $n.Name }}{{ $e.StructField }} string // {{ $n.Name }}-{{ $e.Name }}->{{ $e.Type.Name }} table.
{{- end }}
{{- end }}
{{- end }}
}
type schemaCtxKey struct{}
// SchemaConfigFromContext returns a SchemaConfig stored inside a context, or empty if there isn't one.
func SchemaConfigFromContext(ctx context.Context) SchemaConfig {
config, _ := ctx.Value(schemaCtxKey{}).(SchemaConfig)
return config
}
// NewSchemaConfigContext returns a new context with the given SchemaConfig attached.
func NewSchemaConfigContext(parent context.Context, config SchemaConfig) context.Context {
return context.WithValue(parent, schemaCtxKey{}, config)
}
{{- end }}
{{/* Additional imports by the schemaconfig feature. */}}
{{- define "dialect/sql/import/additional/schemaconfig" -}}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
"{{ $.Config.Package }}/internal"
{{- end }}
{{- end }}
{{/* Additional fields to the config struct. */}}
{{- define "dialect/sql/config/fields/schemaconfig" -}}
{{- if $.FeatureEnabled "sql/schemaconfig" -}}
// schemaConfig contains alternative names for all tables.
schemaConfig SchemaConfig
{{- end }}
{{- end -}}
{{/* Additional top-level code for the generated config.go file. */}}
{{- define "dialect/sql/config/options/schemaconfig" }}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
// SchemaConfig represents alternative schema names for all tables
// that can be passed at runtime.
type SchemaConfig = internal.SchemaConfig
// AlternateSchemas allows alternate schema names to be
// passed into ent operations.
func AlternateSchema(schemaConfig SchemaConfig) Option {
return func(c *config) {
c.schemaConfig = schemaConfig
}
}
{{- end }}
{{- end }}
{{- define "dialect/sql/delete/spec/ctxschemaconfig" }}
{{- template "dialect/sql/spec/ctxschemaconfig" $ }}
{{- end }}
{{- define "dialect/sql/update/spec/ctxschemaconfig" }}
{{- template "dialect/sql/spec/ctxschemaconfig" $ }}
{{- end }}
{{- define "dialect/sql/create/spec/ctxschemaconfig" }}
{{- with extend $ "Ident" "_spec" "SkipContext" true }}
{{- template "dialect/sql/spec/ctxschemaconfig" . }}
{{- end }}
{{- end }}
{{- define "dialect/sql/query/spec/ctxschemaconfig" }}
{{- template "dialect/sql/spec/ctxschemaconfig" . }}
{{- end }}
{{- define "dialect/sql/query/eagerloading/join/schemaconfig" }}
{{- with extend $ "Ident" "joinT" "CallSet" true }}
{{- template "dialect/sql/defedge/spec/schemaconfig" . }}
{{- end }}
{{- end }}
{{- define "dialect/sql/defedge/spec/schemaconfig" }}
{{- $e := $.Scope.Edge }}
{{- $builder := pascal $.Scope.Builder }}
{{- $receiver := $.Scope.Receiver }}
{{- $ident := "edge" }}{{ with $.Scope.Ident }}{{ $ident = . }}{{ end }}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
{{- $schema := $e.Type.Name }}
{{- if $e.OwnFK }}
{{- $schema = $.Name }}
{{- else if $e.M2M }}
{{- if $e.Through }}
{{- $schema = $e.Through.Name }}
{{- else if $e.Inverse }}
{{- $schema = print $e.Type.Name (pascal $e.Inverse) }}
{{- else }}
{{- $schema = print $.Name $e.StructField }}
{{- end }}
{{- end }}
{{- if $.Scope.CallSet }}
{{ $ident }}.Schema({{ $receiver }}.schemaConfig.{{ $schema }})
{{- else }}
{{ $ident }}.Schema = {{ $receiver }}.schemaConfig.{{ $schema }}
{{- end }}
{{- end }}
{{- end }}
{{/* A template for injecting the SchemaConfig to the context. Should be executed before other templates. */}}
{{- define "dialect/sql/spec/ctxschemaconfig" -}}
{{- $builder := pascal $.Scope.Builder }}
{{- $receiver := $.Scope.Receiver }}
{{- $ident := "_spec.Node" }}{{ with $.Scope.Ident }}{{ $ident = . }}{{ end }}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
{{ $ident }}.Schema = {{ $receiver }}.schemaConfig.{{ $.Name }}
{{- if not $.Scope.SkipContext }}
ctx = internal.NewSchemaConfigContext(ctx, {{ $receiver }}.schemaConfig)
{{- end }}
{{- end }}
{{- end -}}
{{- define "dialect/sql/query/selector/ctxschemaconfig" -}}
{{- $builder := pascal $.Scope.Builder }}
{{- $receiver := $.Scope.Receiver }}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
t1.Schema({{ $receiver }}.schemaConfig.{{ $.Name }})
ctx = internal.NewSchemaConfigContext(ctx, {{ $receiver }}.schemaConfig)
selector.WithContext(ctx)
{{- end }}
{{- end -}}
{{- define "dialect/sql/query/path/ctxschemaconfig" }}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
schemaConfig := {{ $.Scope.Receiver }}.schemaConfig
{{- template "dialect/sql/query/step/ctxschemaconfig" . }}
{{- end -}}
{{- end -}}
{{- define "dialect/sql/query/from/ctxschemaconfig" }}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
schemaConfig := {{ $.Scope.Receiver }}.schemaConfig
{{- template "dialect/sql/query/step/ctxschemaconfig" . }}
{{- end -}}
{{- end -}}
{{- define "dialect/sql/predicate/edge/has/ctxschemaconfig" -}}
{{- template "dialect/sql/predicate/edge/ctxschemaconfig" . }}
{{- end -}}
{{- define "dialect/sql/predicate/edge/haswith/ctxschemaconfig" -}}
{{- template "dialect/sql/predicate/edge/ctxschemaconfig" . }}
{{- end -}}
{{- define "dialect/sql/predicate/edge/ctxschemaconfig" -}}
{{- if $.FeatureEnabled "sql/schemaconfig" }}
schemaConfig := internal.SchemaConfigFromContext(s.Context())
{{- template "dialect/sql/query/step/ctxschemaconfig" . }}
{{- end -}}
{{- end -}}
{{- define "dialect/sql/query/step/ctxschemaconfig" -}}
{{- $e := $.Scope.Edge }}
step.To.Schema = schemaConfig.{{ $e.Type.Name }}
{{- $schema := $e.Type.Name }}
{{- if $e.OwnFK }}
{{- $schema = $.Name }}
{{- else if $e.M2M }}
{{- if $e.Through }}
{{- $schema = $e.Through.Name }}
{{- else if $e.Inverse }}
{{- $schema = print $e.Type.Name (pascal $e.Inverse) }}
{{- else }}
{{- $schema = print $.Name $e.StructField }}
{{- end }}
{{- end }}
step.Edge.Schema = schemaConfig.{{ $schema }}
{{- end -}}
{{- define "config/init/fields/multischema" }}
{{- if $.FeatureEnabled "sql/multischema" }}
cfg.schemaConfig = DefaultSchemaConfig
{{- end }}
{{- end }}
{{- define "dialect/sql/config/options/multischema" }}
{{- if $.FeatureEnabled "sql/multischema" }}
{{- $all := $.TableSchemas }}
var (
// DefaultSchemaConfig represents the default schema names for all tables as defined in ent/schema.
DefaultSchemaConfig = SchemaConfig {
{{- range $n := $.Nodes }}
{{ $n.Name }}: tableSchemas[{{ indexOf $all $n.TableSchema }}],
{{- range $e := $n.Edges }}
{{- if and $e.M2M (not $e.Inverse) (not $e.Through) }}
{{ $n.Name }}{{ $e.StructField }}: tableSchemas[{{ indexOf $all $e.TableSchema }}],
{{- end }}
{{- end }}
{{- end }}
}
tableSchemas = [...]string{ {{- range $i, $s := $all }}{{ if $i }},{{ end }}"{{ $s }}"{{ end }} }
)
{{- end }}
{{- end }}

View File

@@ -0,0 +1,493 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{/* Templates used by the "sql/upsert" feature-flag to allows configuring the
`ON CONFLICT` / `ON DUPLICATE KEY` clause for `INSERT` statements */}}
{{/* Template for adding the "conflict" field to the create builder. */}}
{{ define "dialect/sql/create/fields/additional/upsert" -}}
{{- if $.FeatureEnabled "sql/upsert" }}
conflict []sql.ConflictOption
{{- end }}
{{- end -}}
{{/* Template for adding the "conflict" field to the create-bulk builder. */}}
{{ define "dialect/sql/create_bulk/fields/additional/upsert" -}}
{{- if $.FeatureEnabled "sql/upsert" }}
conflict []sql.ConflictOption
{{- end }}
{{- end -}}
{{/* Template for passing the "OnConflict" options to the sqlgraph.CreateSpec. */}}
{{- define "dialect/sql/create/spec/upsert" }}
{{- if $.FeatureEnabled "sql/upsert" }}
_spec.OnConflict = {{ $.Scope.Receiver }}.conflict
{{- end }}
{{- end }}
{{/* Template for passing the "OnConflict" options to the sqlgraph.BatchCreateSpec. */}}
{{- define "dialect/sql/create_bulk/spec/upsert" }}
{{- if $.FeatureEnabled "sql/upsert" }}
spec.OnConflict = {{ $.Scope.Receiver }}.conflict
{{- end }}
{{- end }}
{{/* Template for adding the "OnConflict" methods to the create builder. */}}
{{ define "dialect/sql/create/additional/upsert" }}
{{ if $.FeatureEnabled "sql/upsert" }}
{{ template "helper/upsertone" $ }}
{{ end }}
{{ end }}
{{ define "helper/upsertone" }}
{{ $pkg := base $.Config.Package }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $upsertOne := print $.Name "UpsertOne" }}
{{ $upsertSet := print $.Name "Upsert" }}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.{{ $.Name }}.Create().
{{- with $.Fields }}
{{- $f := index $.Fields 0 }}
// Set{{ index $f.StructField }}(v).
{{- end }}
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
{{- with $.Fields }}
{{- $f := index $.Fields 0 }}
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.{{ $.Name }}Upsert) {
// Set{{ index $f.StructField }}(v+v).
// }).
{{- end }}
// Exec(ctx)
//
func ({{ $receiver }} *{{ $builder }}) OnConflict(opts ...sql.ConflictOption) *{{ $upsertOne }} {
{{ $receiver }}.conflict = opts
return &{{ $upsertOne }}{
create: {{ $receiver }},
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.{{ $.Name }}.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
//
func ({{ $receiver }} *{{ $builder }}) OnConflictColumns(columns ...string) *{{ $upsertOne }} {
{{ $receiver }}.conflict = append({{ $receiver }}.conflict, sql.ConflictColumns(columns...))
return &{{ $upsertOne }}{
create: {{ $receiver }},
}
}
type (
// {{ $upsertOne }} is the builder for "upsert"-ing
// one {{ $.Name }} node.
{{ $upsertOne }} struct {
create *{{ $builder }}
}
// {{ $upsertSet }} is the "OnConflict" setter.
{{ $upsertSet }} struct {
*sql.UpdateSet
}
)
{{ range $f := $.UpsertFields }}
{{- if $f.IsBlob }}
{{ $func := print "Update" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field to the value that was provided on create.
func (u *{{ $upsertSet }}) {{ $func }}() *{{ $upsertSet }} {
{{- if not $f.IsBlobNoColumn }}
u.SetExcluded({{ $.Package }}.{{ $f.Constant }})
{{- end }}
u.SetExcluded("{{ $f.BlobKeyColumn }}")
return u
}
{{ if $f.Optional }}
{{ $func := print "Clear" $f.StructField }}
// {{ $func }} clears the value of the "{{ $f.Name }}" field.
func (u *{{ $upsertSet }}) {{ $func }}() *{{ $upsertSet }} {
{{- if not $f.IsBlobNoColumn }}
u.SetNull({{ $.Package }}.{{ $f.Constant }})
{{- end }}
u.SetNull("{{ $f.BlobKeyColumn }}")
return u
}
{{ end }}
{{- continue }}{{ end }}
{{ $func := print "Set" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field.
func (u *{{ $upsertSet }}) {{ $func }}(v {{ $f.Type }}) *{{ $upsertSet }} {
u.Set({{ $.Package }}.{{ $f.Constant }}, v)
return u
}
{{ $func = print "Update" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field to the value that was provided on create.
func (u *{{ $upsertSet }}) {{ $func }}() *{{ $upsertSet }} {
u.SetExcluded({{ $.Package }}.{{ $f.Constant }})
return u
}
{{ if $f.SupportsMutationAdd }}
{{ $func := print "Add" $f.StructField }}
// {{ $func }} adds v to the "{{ $f.Name }}" field.
func (u *{{ $upsertSet }}) {{ $func }}(v {{ $f.Type }}) *{{ $upsertSet }} {
u.Add({{ $.Package }}.{{ $f.Constant }}, v)
return u
}
{{ end }}
{{ if $f.Optional }}
{{ $func := print "Clear" $f.StructField }}
// {{ $func }} clears the value of the "{{ $f.Name }}" field.
func (u *{{ $upsertSet }}) {{ $func }}() *{{ $upsertSet }} {
u.SetNull({{ $.Package }}.{{ $f.Constant }})
return u
}
{{ end }}
{{ end }}
{{ $udfID := false }}{{ if $.HasOneFieldID }}{{ $udfID = $.ID.UserDefined }}{{ end }}
// UpdateNewValues updates the mutable fields using the new values that were set on create{{ if $udfID }} except the ID field{{ end }}.
// Using this option is equivalent to using:
//
// client.{{ $.Name }}.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
{{- if $udfID }}
// sql.ResolveWith(func(u *sql.UpdateSet) {
// u.SetIgnore({{ $.Package }}.{{ $.ID.Constant }})
// }),
{{- end }}
// ).
// Exec(ctx)
//
func (u *{{ $upsertOne }}) UpdateNewValues() *{{ $upsertOne }} {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
{{- if or $udfID $.ImmutableFields }}
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
{{- if $udfID }}
if _, exists := u.create.mutation.ID(); exists {
s.SetIgnore({{ $.Package }}.{{ $.ID.Constant }})
}
{{- end }}
{{- range $f := $.ImmutableFields }}
if _, exists := u.create.mutation.{{ $f.MutationGet }}(); exists {
s.SetIgnore({{ $.Package }}.{{ $f.Constant }})
}
{{- end }}
}))
{{- end }}
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.{{ $.Name }}.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
//
func (u *{{ $upsertOne }}) Ignore() *{{ $upsertOne }} {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *{{ $upsertOne }}) DoNothing() *{{ $upsertOne }} {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the {{ $builder }}.OnConflict
// documentation for more info.
func (u *{{ $upsertOne }}) Update(set func(*{{ $upsertSet }})) *{{ $upsertOne }} {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&{{ $upsertSet }}{UpdateSet: update})
}))
return u
}
{{ with extend $ "Upsert" $upsertOne "UpsertSet" $upsertSet }}
{{ template "helper/upsert/fields" . }}
{{ end }}
// Exec executes the query.
func (u *{{ $upsertOne }}) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("{{ $pkg }}: missing options for {{ $builder }}.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *{{ $upsertOne }}) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}
{{ if $.HasOneFieldID }}
// Exec executes the UPSERT query and returns the inserted/updated ID.
func (u *{{ $upsertOne }}) ID(ctx context.Context) (id {{ $.ID.Type }}, err error) {
{{- if and $udfID (not $.ID.Type.Numeric) }}
if u.create.driver.Dialect() == dialect.MySQL {
// In case of "ON CONFLICT", there is no way to get back non-numeric ID
// fields from the database since MySQL does not support the RETURNING clause.
return id, errors.New("{{ $pkg }}: {{ $upsertOne }}.ID is not supported by MySQL driver. Use {{ $upsertOne }}.Exec instead")
}
{{- end }}
node, err := u.create.Save(ctx)
if err != nil {
return id, err
}
return node.ID, nil
}
// IDX is like ID, but panics if an error occurs.
func (u *{{ $upsertOne }}) IDX(ctx context.Context) {{ $.ID.Type }} {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
{{ end }}
{{ end }}
{{ define "dialect/sql/create_bulk/additional/upsert" }}
{{ if $.FeatureEnabled "sql/upsert" }}
{{ template "helper/upsertbulk" $ }}
{{ end }}
{{ end }}
{{/* Template for adding the "OnConflict" methods to the create-bulk builder. */}}
{{ define "helper/upsertbulk" }}
{{ $pkg := base $.Config.Package }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $upsertBulk := print $.Name "UpsertBulk" }}
{{ $upsertSet := print $.Name "Upsert" }}
{{ $udfID := false }}{{ if $.HasOneFieldID }}{{ $udfID = $.ID.UserDefined }}{{ end }}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.{{ $.Name }}.CreateBulk(builders...).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
{{- with $.Fields }}
{{- $f := index $.Fields 0 }}
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.{{ $.Name }}Upsert) {
// Set{{ index $f.StructField }}(v+v).
// }).
{{- end }}
// Exec(ctx)
//
func ({{ $receiver }} *{{ $builder }}) OnConflict(opts ...sql.ConflictOption) *{{ $upsertBulk }} {
{{ $receiver }}.conflict = opts
return &{{ $upsertBulk }}{
create: {{ $receiver }},
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.{{ $.Name }}.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
//
func ({{ $receiver }} *{{ $builder }}) OnConflictColumns(columns ...string) *{{ $upsertBulk }} {
{{ $receiver }}.conflict = append({{ $receiver }}.conflict, sql.ConflictColumns(columns...))
return &{{ $upsertBulk }}{
create: {{ $receiver }},
}
}
// {{ $upsertBulk }} is the builder for "upsert"-ing
// a bulk of {{ $.Name }} nodes.
type {{ $upsertBulk }} struct {
create *{{ $builder }}
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.{{ $.Name }}.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
{{- if $udfID }}
// sql.ResolveWith(func(u *sql.UpdateSet) {
// u.SetIgnore({{ $.Package }}.{{ $.ID.Constant }})
// }),
{{- end }}
// ).
// Exec(ctx)
//
func (u *{{ $upsertBulk }}) UpdateNewValues() *{{ $upsertBulk }} {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
{{- if or $udfID $.ImmutableFields }}
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
for _, b := range u.create.builders {
{{- if $udfID }}
if _, exists := b.mutation.ID(); exists {
s.SetIgnore({{ $.Package }}.{{ $.ID.Constant }})
}
{{- end }}
{{- range $f := $.ImmutableFields }}
if _, exists := b.mutation.{{ $f.MutationGet }}(); exists {
s.SetIgnore({{ $.Package }}.{{ $f.Constant }})
}
{{- end }}
}
}))
{{- end }}
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.{{ $.Name }}.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
//
func (u *{{ $upsertBulk }}) Ignore() *{{ $upsertBulk }} {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *{{ $upsertBulk }}) DoNothing() *{{ $upsertBulk }} {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the {{ $builder }}.OnConflict
// documentation for more info.
func (u *{{ $upsertBulk }}) Update(set func(*{{ $upsertSet }})) *{{ $upsertBulk }} {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&{{ $upsertSet }}{UpdateSet: update})
}))
return u
}
{{ with extend $ "Upsert" $upsertBulk "UpsertSet" $upsertSet }}
{{ template "helper/upsert/fields" . }}
{{ end }}
// Exec executes the query.
func (u *{{ $upsertBulk }}) Exec(ctx context.Context) error {
if u.create.err != nil {
return u.create.err
}
for i, b := range u.create.builders {
if len(b.conflict) != 0 {
return fmt.Errorf("{{ $pkg }}: OnConflict was set for builder %d. Set it on the {{ $builder }} instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("{{ $pkg }}: missing options for {{ $builder }}.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *{{ $upsertBulk }}) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}
{{- end }}
{{ define "helper/upsert/fields" }}
{{ $upsert := $.Scope.Upsert }}
{{ $upsertSet := $.Scope.UpsertSet }}
{{ range $f := $.UpsertFields }}
{{- if $f.IsBlob }}
{{ $func := print "Update" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field to the value that was provided on create.
func (u *{{ $upsert }}) {{ $func }}() *{{ $upsert }} {
return u.Update(func(s *{{ $upsertSet }}) {
s.{{ $func }}()
})
}
{{ if $f.Optional }}
{{ $func := print "Clear" $f.StructField }}
// {{ $func }} clears the value of the "{{ $f.Name }}" field.
func (u *{{ $upsert }}) {{ $func }}() *{{ $upsert }} {
return u.Update(func(s *{{ $upsertSet }}) {
s.{{ $func }}()
})
}
{{ end }}
{{- continue }}{{ end }}
{{ $func := print "Set" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field.
func (u *{{ $upsert }}) {{ $func }}(v {{ $f.Type }}) *{{ $upsert }} {
return u.Update(func(s *{{ $upsertSet }}) {
s.{{ $func }}(v)
})
}
{{ if $f.SupportsMutationAdd }}
{{ $func := print "Add" $f.StructField }}
// {{ $func }} adds v to the "{{ $f.Name }}" field.
func (u *{{ $upsert }}) {{ $func }}(v {{ $f.Type }}) *{{ $upsert }} {
return u.Update(func(s *{{ $upsertSet }}) {
s.{{ $func }}(v)
})
}
{{ end }}
{{ $func = print "Update" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field to the value that was provided on create.
func (u *{{ $upsert }}) {{ $func }}() *{{ $upsert }} {
return u.Update(func(s *{{ $upsertSet }}) {
s.{{ $func }}()
})
}
{{ if $f.Optional }}
{{ $func := print "Clear" $f.StructField }}
// {{ $func }} clears the value of the "{{ $f.Name }}" field.
func (u *{{ $upsert }}) {{ $func }}() *{{ $upsert }} {
return u.Update(func(s *{{ $upsertSet }}) {
s.{{ $func }}()
})
}
{{ end }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,14 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{/* custom globals and helpers for sql dialects */}}
{{ define "dialect/sql/globals" }}
// queryHook describes an internal hook for the different sqlAll methods.
type queryHook func(context.Context, *sqlgraph.QuerySpec)
{{ end }}

View File

@@ -0,0 +1,40 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/sql/group" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
func ({{ $receiver }} *{{ $builder }}) sqlScan(ctx context.Context, root *{{ $.QueryName }}, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len({{ $receiver}}.fns))
for _, fn := range {{ $receiver }}.fns {
aggregation = append(aggregation, fn(selector))
}
{{- /* If no columns were selected, the default selection is the fields used for "group-by", and the aggregation functions.*/}}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*{{ $receiver }}.flds) + len({{ $receiver}}.fns))
for _, f := range *{{ $receiver }}.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*{{ $receiver }}.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := {{ $receiver }}.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
{{ end }}

View File

@@ -0,0 +1,194 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{/* Constants needed for sql dialects. */}}
{{ define "dialect/sql/meta/constants" }}
{{- range $t := $.RelatedTypes }}
{{- $withEID := $.HasCompositeID }}
{{- if and (not $withEID) $t.HasOneFieldID }}
{{- if ne $t.ID.StorageKey $.ID.StorageKey }}{{ $withEID = true }}{{ end }}
{{- end }}
{{- if $withEID }}
// {{ $t.Name }}FieldID holds the string denoting the ID field of the {{ $t.Name }}.
{{ $t.Name }}FieldID = "{{ $t.ID.StorageKey }}"
{{- end }}
{{- end }}
// Table holds the table name of the {{ lower $.Name }} in the database.
Table = "{{ $.Table }}"
{{- range $e := $.Edges }}
// {{ $e.TableConstant }} is the table that holds the {{ $e.Name }} relation/edge.
{{- if $e.M2M }} The primary key declared below.{{ end }}
{{ $e.TableConstant }} = "{{ $e.Rel.Table }}"
{{- if ne $.Table $e.Type.Table }}
// {{ $e.InverseTableConstant }} is the table name for the {{ $e.Type.Name }} entity.
// It exists in this package in order to avoid circular dependency with the "{{ $e.Type.Package }}" package.
{{ $e.InverseTableConstant }} = "{{ $e.Type.Table }}"
{{- end }}
{{- if not $e.M2M }}
// {{ $e.ColumnConstant }} is the table column denoting the {{ $e.Name }} relation/edge.
{{ $e.ColumnConstant }} = "{{ $e.Rel.Column }}"
{{- end }}
{{- end }}
{{- with $tmpls := matchTemplate "dialect/sql/meta/constants/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}
{{/* Variables needed for sql dialects. */}}
{{ define "dialect/sql/meta/variables" }}
// Columns holds all SQL columns for {{ lower $.Name }} fields.
var Columns = []string{
{{- if $.HasOneFieldID }}
{{ $.ID.Constant }},
{{- end }}
{{- range $f := $.Fields }}
{{- if and (not $f.IsDeprecated) (not $f.IsBlobNoColumn) (not $f.IsBlobLazy) }}
{{ $f.Constant }},
{{- end }}
{{- end }}
{{- range $k := $.BlobKeys }}
"{{ $k.Field.Name }}",
{{- end }}
}
{{/* If any of the edges owns a foreign-key */}}
{{ with $.UnexportedForeignKeys }}
// ForeignKeys holds the SQL foreign-keys that are owned by the "{{ $.Table }}"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
{{- range $fk := . }}
"{{ $fk.Edge.Rel.Column }}",
{{- end }}
}
{{ end }}
{{ with $.BlobKeys }}
// BlobKeys holds the SQL columns for blob storage keys.
var BlobKeys = []string{
{{- range $bk := . }}
"{{ $bk.Field.Name }}",
{{- end }}
}
{{ end }}
{{ with $.NumM2M }}
var (
{{- range $e := $.Edges }}
{{- if $e.M2M }}
// {{ $e.PKConstant }} and {{ $e.ColumnConstant }}2 are the table columns denoting the
// primary key for the {{ $e.Name }} relation (M2M).
{{ $e.PKConstant }} = []string{"{{ index $e.Rel.Columns 0 }}", "{{ index $e.Rel.Columns 1 }}"}
{{- end }}
{{- end }}
)
{{ end }}
{{ end }}
{{/* Functions needed for sql dialects. */}}
{{ define "dialect/sql/meta/functions" }}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
{{- with $.UnexportedForeignKeys }}
for i := range ForeignKeys {
if column == ForeignKeys[i] {
return true
}
}
{{- end }}
{{- with $.BlobKeys }}
for i := range BlobKeys {
if column == BlobKeys[i] {
return true
}
}
{{- end }}
{{- with $.DeprecatedFields }}
for _, f := range [...]string{ {{- range . }}{{ .Constant }},{{ end }} } {
if column == f {
return true
}
}
{{- end }}
return false
}
{{ end }}
{{/* Type-safe order-by builders. */}}
{{ define "dialect/sql/meta/order" }}
{{ if $.HasOneFieldID }}
// {{ $.ID.OrderName }} orders the results by the {{ $.ID.Name }} field.
func {{ $.ID.OrderName }}(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField({{ $.ID.Constant }}, opts...).ToFunc()
}
{{ end }}
{{- range $f := $.Fields }}
{{- if $f.Type.Comparable }}
// {{ $f.OrderName }} orders the results by the {{ $f.Name }} field.
func {{ $f.OrderName }}(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField({{ $f.Constant }}, opts...).ToFunc()
}
{{- end }}
{{- end }}
{{- range $e := $.Edges }}
{{- if $e.Unique }}
// {{ $e.OrderFieldName }} orders the results by {{ $e.Name }} field.
func {{ $e.OrderFieldName }}(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, new{{ pascal $e.Name }}Step(), sql.OrderByField(field, opts...))
}
}
{{- else }}
// {{ $e.OrderCountName }} orders the results by {{ $e.Name }} count.
func {{ $e.OrderCountName }}(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, new{{ pascal $e.Name }}Step(), opts...)
}
}
// {{ $e.OrderTermsName }} orders the results by {{ $e.Name }} terms.
func {{ $e.OrderTermsName }}(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, new{{ pascal $e.Name }}Step(), append([]sql.OrderTerm{term}, terms...)...)
}
}
{{- end }}
{{- end }}
{{- range $e := $.Edges }}
func new{{ pascal $e.Name }}Step() *sqlgraph.Step {
return sqlgraph.NewStep(
{{- if $.HasCompositeID }}
{{- /* Query that goes from the edge schema. */}}
sqlgraph.From(Table, {{ $e.ColumnConstant }}),
sqlgraph.To({{ $e.InverseTableConstant }}, {{ print $e.Type.Name "FieldID" }}),
{{- else }}
sqlgraph.From(Table, {{ $.ID.Constant }}),
{{- if $e.Type.HasOneFieldID }}
{{- $refid := $.ID.Constant }}{{ if ne $e.Type.ID.StorageKey $.ID.StorageKey }}{{ $refid = print $e.Type.Name "FieldID" }}{{ end }}
sqlgraph.To({{ if ne $.Table $e.Type.Table }}{{ $e.InverseTableConstant }}{{ else }}Table{{ end }}, {{ $refid }}),
{{- else }}
{{- /* Query that goes to the edge schema. */}}
sqlgraph.To({{ if ne $.Table $e.Type.Table }}{{ $e.InverseTableConstant }}{{ else }}Table{{ end }}, {{ $e.ColumnConstant }}),
{{- end }}
{{- end }}
sqlgraph.Edge(sqlgraph.{{ $e.Rel.Type }}, {{ $e.IsInverse }}, {{ $e.TableConstant }},
{{- if $e.M2M -}}
{{ $e.PKConstant }}...
{{- else -}}
{{ $e.ColumnConstant }}
{{- end -}}
),
)
}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,15 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "dialect/sql/client/open" }}
drv, err := sql.Open(driverName, dataSourceName)
if err != nil {
return nil, err
}
return NewClient(append(options, Driver(drv))...), nil
{{ end }}

View File

@@ -0,0 +1,99 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/sql/predicate/id" -}}
{{- $arg := "id" }}
{{- with $.Scope.Arg }}
{{- $arg = . }}
{{- end -}}
sql.FieldEQ({{ $.ID.Constant }}, {{ $arg }})
{{- end }}
{{ define "dialect/sql/predicate/id/ops" -}}
{{- $op := $.Scope.Op -}}
{{- $storage := $.Scope.Storage -}}
{{- $arg := "id" -}}
{{- if $op.Variadic }}{{ $arg = "ids" }}{{ end -}}
{{- with $.Scope.Arg -}}
{{- $arg = . -}}
{{- end -}}
sql.Field{{ call $storage.OpCode $op }}({{ $.ID.Constant }}{{ if not $op.Niladic }}, {{ $arg }}{{ if $op.Variadic }}...{{ end }}{{ end }})
{{- end }}
{{ define "dialect/sql/predicate/field" -}}
{{- $f := $.Scope.Field -}}
{{- $arg := $.Scope.Arg -}}
sql.FieldEQ({{ $f.Constant }}, {{ $arg }})
{{- end }}
{{ define "dialect/sql/predicate/field/ops" -}}
{{- $f := $.Scope.Field -}}
{{- $op := $.Scope.Op -}}
{{- $arg := $.Scope.Arg -}}
{{- $storage := $.Scope.Storage -}}
sql.Field{{ call $storage.OpCode $op }}({{ $f.Constant }}{{ if not $op.Niladic }}, {{ $arg }}{{ if $op.Variadic }}...{{ end }}{{ end }})
{{- end }}
{{ define "dialect/sql/predicate/edge/has" -}}
{{- $e := $.Scope.Edge -}}
func(s *sql.Selector) {
step := sqlgraph.NewStep(
{{- if $.HasCompositeID }}
{{- /* Query that goes from the edge schema. */}}
sqlgraph.From(Table, {{ $e.ColumnConstant }}),
{{- else }}
sqlgraph.From(Table, {{ $.ID.Constant }}),
{{- end }}
sqlgraph.Edge(sqlgraph.{{ $e.Rel.Type }}, {{ $e.IsInverse }}, {{ $e.TableConstant }},
{{- if $e.M2M -}}
{{ $e.PKConstant }}...
{{- else -}}
{{ $e.ColumnConstant }}
{{- end -}}
),
)
{{- /* Allow mutating the sqlgraph.Step by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/predicate/edge/has/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- /* sqlgraph.To is meaningless on Has<E> (without predicates), because unlike Has<E>With no predicates are applied on the "other" table. */}}
sqlgraph.HasNeighbors(s, step)
}
{{- end }}
{{ define "dialect/sql/predicate/edge/haswith" -}}
{{- $e := $.Scope.Edge -}}
func(s *sql.Selector) {
step := new{{ pascal $e.Name }}Step()
{{- /* Allow mutating the sqlgraph.Step by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/predicate/edge/haswith/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
}
{{- end }}
{{ define "dialect/sql/predicate/and" -}}
sql.AndPredicates(predicates...)
{{- end }}
{{ define "dialect/sql/predicate/or" -}}
sql.OrPredicates(predicates...)
{{- end }}
{{ define "dialect/sql/predicate/not" -}}
sql.NotPredicates(p)
{{- end }}

View File

@@ -0,0 +1,601 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{/* Additional fields for the builder. */}}
{{ define "dialect/sql/query/fields" }}
{{- with $.UnexportedForeignKeys }}
withFKs bool
{{- end }}
{{- with $tmpls := matchTemplate "dialect/sql/query/fields/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- end }}
{{ define "dialect/sql/query" }}
{{ $pkg := $.Scope.Package }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
func ({{ $receiver }} *{{ $builder }}) sqlAll(ctx context.Context, hooks ...queryHook) ([]*{{ $.Name }}, error) {
var (
nodes = []*{{ $.Name }}{}
{{- with $.UnexportedForeignKeys }}
withFKs = {{ $receiver }}.withFKs
{{- end }}
_spec = {{ $receiver }}.querySpec()
{{- with $.Edges }}
loadedTypes = [{{ len . }}]bool{
{{- range $e := . }}
{{ $receiver }}.{{ $e.EagerLoadField }} != nil,
{{- end }}
}
{{- end }}
)
{{- with $.UnexportedForeignKeys }}
{{- $edgesWithoutField := list }}
{{- range $.FKEdges }}{{ if not .Field }}{{ $edgesWithoutField = append $edgesWithoutField . }}{{ end }}{{ end }}
{{- if $edgesWithoutField }}
if {{ range $i, $e := $edgesWithoutField }}{{ if $i }} || {{ end }}{{ $receiver }}.{{ $e.EagerLoadField }} != nil{{ end }} {
withFKs = true
}
{{- end }}
if withFKs {
_spec.Node.Columns = append(_spec.Node.Columns, {{ $.Package }}.ForeignKeys...)
}
{{- end }}
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*{{ $.Name }}).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &{{ $.Name }}{config: {{ $receiver }}.config}
nodes = append(nodes, node)
{{- with $.Edges }}
node.Edges.loadedTypes = loadedTypes
{{- end }}
return node.assignValues(columns, values)
}
{{- /* Allow mutating the sqlgraph.QuerySpec by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/query/spec/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, {{ $receiver }}.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
{{- range $e := $.Edges }}
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 }},
{{- $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
}
}
{{- end }}
{{- /* Allow extensions to inject code using templates to process nodes before they are returned. */}}
{{- with $tmpls := matchTemplate "dialect/sql/query/all/nodes/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- if $.HasLoadOnScanFields }}
if err := {{ $receiver }}.loadBlobFields(ctx, nodes); err != nil {
return nil, err
}
{{- end }}
return nodes, nil
}
{{- if $.HasLoadOnScanFields }}
func ({{ $receiver }} *{{ $builder }}) loadBlobFields(ctx context.Context, nodes []*{{ $.Name }}) error {
_blobs := ent.NewBlobStore({{ $receiver }}.blobOpeners.{{ $.Name }})
for _, n := range nodes {
{{- range $f := $.LoadOnScanFields }}
if n.{{ $f.BlobKeyColumn }} != nil && *n.{{ $f.BlobKeyColumn }} != "" {
{{- if $f.IsBlobNoColumn }}
{{- /* Non-DualWrite: blob is the only source of data. */}}
data, err := _blobs.Read(ctx, {{ $.Package }}.{{ $f.Constant }}, *n.{{ $f.BlobKeyColumn }})
if err != nil {
return errors.Join(fmt.Errorf("loading {{ $f.Name }}: %w", err), _blobs.Close())
}
if data == nil {
return errors.Join(fmt.Errorf("loading {{ $f.Name }}: object %q not found in blob storage", *n.{{ $f.BlobKeyColumn }}), _blobs.Close())
}
{{- if $f.HasValueScanner }}
sv := {{ $f.ScanValueFunc }}()
if err := sv.Scan(data); err != nil {
return errors.Join(fmt.Errorf("scanning {{ $f.Name }}: %w", err), _blobs.Close())
}
v, err := {{ $f.FromValueFunc }}(sv)
if err != nil {
return errors.Join(fmt.Errorf("scanning {{ $f.Name }}: %w", err), _blobs.Close())
}
n.{{ $f.StructField }} = v
{{- else if $f.IsBlobGoString }}
n.{{ $f.StructField }} = {{ $f.Type }}(data)
{{- else }}
n.{{ $f.StructField }} = data
{{- end }}
{{- else }}
{{- /* DualWrite: prefer blob value, but preserve the column value when blob is missing. */}}
switch data, err := _blobs.Read(ctx, {{ $.Package }}.{{ $f.Constant }}, *n.{{ $f.BlobKeyColumn }}); {
case err != nil:
return errors.Join(fmt.Errorf("loading {{ $f.Name }}: %w", err), _blobs.Close())
case data != nil:
{{- if $f.HasValueScanner }}
sv := {{ $f.ScanValueFunc }}()
if err := sv.Scan(data); err != nil {
return errors.Join(fmt.Errorf("scanning {{ $f.Name }}: %w", err), _blobs.Close())
}
v, err := {{ $f.FromValueFunc }}(sv)
if err != nil {
return errors.Join(fmt.Errorf("scanning {{ $f.Name }}: %w", err), _blobs.Close())
}
n.{{ $f.StructField }} = v
{{- else if $f.IsBlobGoString }}
n.{{ $f.StructField }} = {{ $f.Type }}(data)
{{- else }}
n.{{ $f.StructField }} = data
{{- end }}
}
{{- end }}
}
{{- end }}
}
return _blobs.Close()
}
{{- end }}
{{/* Generate a method to eager-load each edge. */}}
{{- range $e := $.Edges }}
func ({{ $receiver }} *{{ $builder }}) load{{ $e.StructField }}(ctx context.Context, query *{{ $e.Type.QueryName }}, nodes []*{{ $.Name }}, init func(*{{ $.Name }}), assign func(*{{ $.Name }}, *{{ $e.Type.Name }})) error {
{{- if $e.M2M }}
edgeIDs := make([]driver.Value, len(nodes))
byID := make(map[{{ $.ID.Type }}]*{{ $.Name }})
nids := make(map[{{ $e.Type.ID.Type }}]map[*{{ $.Name }}]struct{})
for i, node := range nodes {
{{- if $.ID.HasValueScanner }}
vv, err := {{ $.ID.ValueFunc }}(node.ID)
if err != nil {
return err
}
edgeIDs[i] = vv
{{- else }}
edgeIDs[i] = node.ID
{{- end }}
byID[node.ID] = node
if init != nil {
init(node)
}
}
query.Where(func(s *sql.Selector) {
joinT := sql.Table({{ $.Package }}.{{ $e.TableConstant }})
{{- with $tmpls := matchTemplate "dialect/sql/query/eagerloading/join/*" }}
{{- range $tmpl := $tmpls }}
{{- with extend $ "Edge" $e }}
{{- xtemplate $tmpl . }}
{{- end }}
{{- end }}
{{- end }}
{{- $edgeid := print $e.Type.Package "." $e.Type.ID.Constant }}
{{- $fk1idx := 1 }}{{- $fk2idx := 0 }}{{ if $e.IsInverse }}{{ $fk1idx = 0 }}{{ $fk2idx = 1 }}{{ end }}
s.Join(joinT).On(s.C({{ $edgeid }}), joinT.C({{ $.Package }}.{{ $e.PKConstant }}[{{ $fk1idx }}]))
s.Where(sql.InValues(joinT.C({{ $.Package }}.{{ $e.PKConstant }}[{{ $fk2idx }}]), edgeIDs...))
columns := s.SelectedColumns()
s.Select(joinT.C({{ $.Package }}.{{ $e.PKConstant }}[{{ $fk2idx }}]))
s.AppendSelect(columns...)
s.SetDistinct(false)
})
if err := query.prepareQuery(ctx); err != nil {
return err
}
qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) {
assign := spec.Assign
values := spec.ScanValues
{{- $out := "sql.NullInt64" }}{{ if $.ID.UserDefined }}{{ $out = $.ID.ScanType }}{{ end }}
{{- $in := "sql.NullInt64" }}{{ if $e.Type.ID.UserDefined }}{{ $in = $e.Type.ID.ScanType }}{{ end }}
spec.ScanValues = func(columns []string) ([]any, error) {
values, err := values(columns[1:])
if err != nil {
return nil, err
}
{{- if $.ID.HasValueScanner }}
return append([]any{ {{ $.ID.ScanValueFunc }}() }, values...), nil
{{- else }}
return append([]any{new({{ $out }})}, values...), nil
{{- end }}
}
spec.Assign = func(columns []string, values []any) error {
{{- if $.ID.HasValueScanner }}
var outValue {{ $.ID.Type }}
{{- with extend $ "Arg" "values[0]" "Field" $.ID "ScanType" $out "Ident" "outValue" }}
{{ template "dialect/sql/query/eagerloading/m2massign" . }}
{{- end }}
{{- else }}
outValue := {{- with extend $ "Arg" "values[0]" "Field" $.ID "ScanType" $out }}{{ template "dialect/sql/query/eagerloading/m2massignexpr" . }}{{- end }}
{{- end }}
{{- if $e.Type.ID.HasValueScanner }}
var inValue {{ $e.Type.ID.Type }}
{{- with extend $ "Arg" "values[1]" "Field" $e.Type.ID "ScanType" $in "Ident" "inValue" }}
{{ template "dialect/sql/query/eagerloading/m2massign" . }}
{{- end }}
{{- else }}
inValue := {{- with extend $ "Arg" "values[1]" "Field" $e.Type.ID "ScanType" $in }}{{ template "dialect/sql/query/eagerloading/m2massignexpr" . }}{{- end }}
{{- end }}
if nids[inValue] == nil {
nids[inValue] = map[*{{ $.Name }}]struct{}{byID[outValue]: {}}
return assign(columns[1:], values[1:])
}
nids[inValue][byID[outValue]] = struct{}{}
return nil
}
})
})
neighbors, err := withInterceptors[[]*{{ $e.Type.Name }}](ctx, query, qr, query.inters)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nids[n.ID]
if !ok {
return fmt.Errorf(`unexpected "{{ $e.Name }}" node returned %v`, n.ID)
}
for kn := range nodes {
assign(kn, n)
}
}
{{- else if $e.OwnFK }}
ids := make([]{{ $e.Type.ID.Type }}, 0, len(nodes))
nodeids := make(map[{{ $e.Type.ID.Type }}][]*{{ $.Name }})
for i := range nodes {
{{- $fk := $e.ForeignKey }}
{{- if $fk.Field.Nillable }}
if nodes[i].{{ $fk.StructField }} == nil {
continue
}
{{- end }}
fk := {{ if $fk.Field.Nillable }}*{{ end }}nodes[i].{{ $fk.StructField }}
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where({{ $e.Type.Package }}.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "{{ $fk.Field.Name }}" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
{{- else }}
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[{{ $.ID.Type }}]*{{ $.Name }})
for i := range nodes {
{{- if $.ID.HasValueScanner }}
vv, err := {{ $.ID.ValueFunc }}(nodes[i].ID)
if err != nil {
return err
}
fks = append(fks, vv)
{{- else }}
fks = append(fks, nodes[i].ID)
{{- end }}
nodeids[nodes[i].ID] = nodes[i]
{{- if $e.O2M }}
if init != nil {
init(nodes[i])
}
{{- end }}
}
{{- with $e.Type.UnexportedForeignKeys }}
query.withFKs = true
{{- end }}
{{- /* In case of custom field selection, ensure edge-fields used for binding below were selected. */}}
{{- if and $e.Ref $e.Ref.Field }}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce({{ $e.Type.Package }}.{{ $e.Ref.Field.Constant }})
}
{{- end }}
query.Where(predicate.{{ $e.Type.Name }}(func(s *sql.Selector) {
s.Where(sql.InValues(s.C({{ $.Package }}.{{ $e.ColumnConstant }}), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
{{- $fk := $e.ForeignKey }}
fk := n.{{ $fk.StructField }}
{{- if $fk.Field.Nillable }}
if fk == nil {
return fmt.Errorf(`foreign-key "{{ $fk.Field.Name }}" is nil for node %v`, n.ID)
}
{{- end }}
node, ok := nodeids[{{ if $fk.Field.Nillable }}*{{ end }}fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "{{ $fk.Field.Name }}" returned %v for node %v`, {{ if $fk.Field.Nillable }}*{{ end }}fk, n{{ if $e.Type.HasOneFieldID }}.ID{{ end }})
}
assign(node, n)
}
{{- end }}
return nil
}
{{- end }}
func ({{ $receiver }} *{{ $builder }}) sqlCount(ctx context.Context) (int, error) {
_spec := {{ $receiver }}.querySpec()
{{- /* Allow mutating the sqlgraph.QuerySpec by ent extensions or user templates. */}}
{{- with $tmpls := matchTemplate "dialect/sql/query/spec/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- if $.HasCompositeID }}
{{- /* In case of an edge schema with composite primary-key, there is no need to SELECT DISTINCT. */}}
_spec.Unique = false
_spec.Node.Columns = nil
{{- else }}
_spec.Node.Columns = {{ $receiver }}.ctx.Fields
if len({{ $receiver }}.ctx.Fields) > 0 {
{{- /* In case of field selection, configure query to unique only if was explicitly set to true. */}}
_spec.Unique = {{ $receiver }}.ctx.Unique != nil && *{{ $receiver }}.ctx.Unique
}
{{- end }}
return sqlgraph.CountNodes(ctx, {{ $receiver }}.driver, _spec)
}
func ({{ $receiver }} *{{ $builder }}) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec({{ $.Package }}.Table, {{ $.Package }}.Columns, {{ if $.HasOneFieldID }}sqlgraph.NewFieldSpec({{ $.Package }}.{{ $.ID.Constant }}, field.{{ $.ID.Type.ConstName }}){{ else }}nil{{ end }})
{{- /* Setup any intermediate queries if exist (traversal path). */}}
_spec.From = {{ $receiver }}.sql
if unique := {{ $receiver }}.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if {{ $receiver }}.path != nil {
_spec.Unique = true
}
if fields := {{ $receiver }}.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
{{- if $.HasOneFieldID }}
_spec.Node.Columns = append(_spec.Node.Columns, {{ $.Package }}.{{ $.ID.Constant }})
for i := range fields {
if fields[i] != {{ $.Package }}.{{ $.ID.Constant }} {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
{{- else }}
for i := range fields {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
{{- end }}
{{- /* In case of custom field selection, ensure edge-fields are loaded in case their edges were loaded. */}}
{{- range $.FKEdges }}
{{- $f := .Field }}
{{- if not $f }}
{{- continue }}
{{- end }}
if {{ $receiver }}.{{ .EagerLoadField }} != nil {
_spec.Node.AddColumnOnce({{ $.Package }}.{{ $f.Constant }})
}
{{- end }}
}
if ps := {{ $receiver }}.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := {{ $receiver }}.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := {{ $receiver }}.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := {{ $receiver }}.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
{{ template "dialect/sql/query/selector" $ }}
{{- /* Allow adding methods to the query-builder by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/query/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}
{{ define "dialect/sql/query/selector" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
func ({{ $receiver }} *{{ $builder }}) sqlQuery(ctx context.Context) *sql.Selector {
{{- $builderV := "builder" }}{{ if eq $.Package $builderV }}{{ $builderV = "builderC" }}{{ end }}
{{ $builderV }} := sql.Dialect({{ $receiver }}.driver.Dialect())
t1 := {{ $builderV }}.Table({{ $.Package }}.Table)
columns := {{ $receiver }}.ctx.Fields
if len(columns) == 0 {
columns = {{ $.Package }}.Columns
}
selector := {{ $builderV }}.Select(t1.Columns(columns...)...).From(t1)
if {{ $receiver }}.sql != nil {
selector = {{ $receiver }}.sql
selector.Select(selector.Columns(columns...)...)
}
if {{ $receiver }}.ctx.Unique != nil && *{{ $receiver }}.ctx.Unique {
selector.Distinct()
}
{{- /* Allow mutating the sql.Selector by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/query/selector/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
for _, p := range {{ $receiver }}.predicates {
p(selector)
}
for _, p := range {{ $receiver }}.order {
p(selector)
}
if offset := {{ $receiver }}.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := {{ $receiver }}.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
{{ end }}
{{/* query/path defines the query generation for path of a given edge. */}}
{{ define "dialect/sql/query/path" }}
{{- $n := $ }} {{/* the node we start the query from. */}}
{{- $e := $.Scope.Edge }} {{/* the edge we need to generate the path to. */}}
{{- $ident := $.Scope.Ident -}}
{{- $receiver := $.Scope.Receiver }}
selector := {{ $receiver }}.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From({{ $n.Package }}.Table, {{ $n.Package }}.{{ if $n.HasCompositeID }}{{ $e.ColumnConstant }}{{ else }}{{ $n.ID.Constant }}{{ end }}, selector),
sqlgraph.To({{ $e.Type.Package }}.Table, {{ $e.Type.Package }}.{{ if $e.Type.HasCompositeID }}{{ $e.Ref.ColumnConstant }}{{ else }}{{ $e.Type.ID.Constant }}{{ end }}),
sqlgraph.Edge(sqlgraph.{{ $e.Rel.Type }}, {{ $e.IsInverse }}, {{ $n.Package }}.{{ $e.TableConstant }},
{{- if $e.M2M -}}
{{ $n.Package }}.{{ $e.PKConstant }}...
{{- else -}}
{{ $n.Package }}.{{ $e.ColumnConstant }}
{{- end -}}
),
)
{{- /* Allow mutating the sqlgraph.Step by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/query/path/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ $ident }} = sqlgraph.SetNeighbors({{ $receiver }}.driver.Dialect(), step)
{{ end }}
{{/* query/from defines the query generation for an edge query from a given node. */}}
{{ define "dialect/sql/query/from" }}
{{- $n := $ }} {{/* the node we start the query from. */}}
{{- $e := $.Scope.Edge }} {{/* the edge we need to genegrate the path to. */}}
{{- $ident := $.Scope.Ident -}}
{{- $receiver := $.Scope.Receiver -}}
{{- if $n.ID.HasValueScanner -}}
id := any({{ $receiver }}.ID)
vv, err := {{ $n.ID.ValueFunc }}({{ $receiver }}.ID)
if err != nil {
return nil, err
}
id = vv
{{- else -}}
id := {{ $receiver }}.ID
{{- end }}
step := sqlgraph.NewStep(
sqlgraph.From({{ $n.Package }}.Table, {{ $n.Package }}.{{ $n.ID.Constant }}, id),
sqlgraph.To({{ $e.Type.Package }}.Table, {{ $e.Type.Package }}.{{ if $e.Type.HasCompositeID }}{{ $e.Ref.ColumnConstant }}{{ else }}{{ $e.Type.ID.Constant }}{{ end }}),
sqlgraph.Edge(sqlgraph.{{ $e.Rel.Type }}, {{ $e.IsInverse }}, {{ $n.Package }}.{{ $e.TableConstant }},
{{- if $e.M2M -}}
{{ $n.Package }}.{{ $e.PKConstant }}...
{{- else -}}
{{ $n.Package }}.{{ $e.ColumnConstant }}
{{- end -}}
),
)
{{- /* Allow mutating the sqlgraph.Step by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/query/from/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ $ident }} = sqlgraph.Neighbors({{ $receiver }}.driver.Dialect(), step)
{{ end }}
{{ define "dialect/sql/query/eagerloading/m2massign" -}}
{{- $arg := $.Scope.Arg -}}
{{- $field := $.Scope.Field -}}
{{- $scantype := $.Scope.ScanType -}}
{{- $ident := $.Scope.Ident -}}
{{- if $field.HasValueScanner }}
if value, err := {{ $field.FromValueFunc }}({{ $arg }}); err != nil {
return err
} else {
{{ $ident }} = value
}
{{- else if hasPrefix $scantype "sql" -}}
{{ $ident }} = {{ printf "%s.(*%s)" $arg $scantype | $field.ScanTypeField }}
{{- else -}}
{{ $ident }} = {{ if not $field.Nillable }}*{{ end }}{{ printf "%s.(*%s)" $arg $scantype }}
{{- end }}
{{- end }}
{{ define "dialect/sql/query/eagerloading/m2massignexpr" -}}
{{- $arg := $.Scope.Arg -}}
{{- $field := $.Scope.Field -}}
{{- $scantype := $.Scope.ScanType -}}
{{- if hasPrefix $scantype "sql" -}}
{{ printf "%s.(*%s)" $arg $scantype | $field.ScanTypeField -}}
{{- else -}}
{{ if not $field.Nillable }}*{{ end }}{{ printf "%s.(*%s)" $arg $scantype }}
{{- end }}
{{- end }}
{{ define "dialect/sql/query/preparecheck" }}
{{- $pkg := $.Scope.Package }}
{{- $receiver := $.Scope.Receiver }}
for _, f := range {{ $receiver }}.ctx.Fields {
if !{{ $.Package }}.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("{{ $pkg }}: invalid field %q for query", f)}
}
}
{{- end }}

View File

@@ -0,0 +1,42 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{ define "dialect/sql/select" }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
func ({{ $receiver }} *{{ $builder }}) sqlScan(ctx context.Context, root *{{ $.QueryName }}, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len({{ $receiver}}.fns))
for _, fn := range {{ $receiver }}.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*{{ $receiver }}.selector.flds); {
{{- /* If no columns were selected, the default selection is the aggregation.*/}}
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := {{ $receiver }}.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
{{/* Allow adding methods to the select-builder by ent extensions or user templates.*/}}
{{ with $tmpls := matchTemplate "dialect/sql/select/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,31 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "dialect/sql/txoptions" }}
// BeginTx returns a transactional client with specified options.
func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
if _, ok := c.driver.(*txDriver); ok {
return nil, errors.New("ent: cannot start a transaction within a transaction")
}
tx, err := c.driver.(interface {
BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error)
}).BeginTx(ctx, opts)
if err != nil {
return nil, fmt.Errorf("ent: starting a transaction: %w", err)
}
cfg := c.config
cfg.driver = &txDriver{tx: tx, drv: c.driver}
return &Tx{
ctx: ctx,
config: cfg,
{{- range $n := $.Nodes }}
{{ $n.Name }}: New{{ $n.ClientName }}(cfg),
{{- end }}
}, nil
}
{{ end }}

View File

@@ -0,0 +1,395 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{/* Additional fields for the builder. */}}
{{ define "dialect/sql/update/fields" }}
{{- with $tmpls := matchTemplate "dialect/sql/update/fields/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- end }}
{{ define "dialect/sql/update" }}
{{ $pkg := $.Scope.Package }}
{{ $builder := pascal $.Scope.Builder }}
{{ $receiver := $.Scope.Receiver }}
{{ $mutation := print $receiver ".mutation" }}
{{ $one := hasSuffix $builder "One" }}
{{- $zero := 0 }}{{ if $one }}{{ $zero = "nil" }}{{ end }}
{{- /* Allow adding methods to the update-builder by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/update/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
func ({{ $receiver }} *{{ $builder }}) sqlSave(ctx context.Context) (_node {{ if $one }}*{{ $.Name }}{{ else }}int{{ end }}, err error) {
{{- if $.HasUpdateCheckers }}
if err := {{ $receiver }}.check(); err != nil {
return _node, err
}
{{- end }}
_spec := sqlgraph.NewUpdateSpec({{ $.Package }}.Table, {{ $.Package }}.Columns,
{{- if $.HasOneFieldID -}}
sqlgraph.NewFieldSpec({{ $.Package }}.{{ $.ID.Constant }}, field.{{ $.ID.Type.ConstName }})
{{- else -}}
{{- range $id := $.EdgeSchema.ID -}}
sqlgraph.NewFieldSpec({{ $.Package }}.{{ $id.Constant }}, field.{{ $id.Type.ConstName }}),
{{- end -}}
{{- end }})
{{- if $one }}
{{- if $.HasOneFieldID }}
id, ok := {{ $mutation }}.{{ $.ID.MutationGet }}()
if !ok {
return {{ $zero }}, &ValidationError{Name: "{{ $.ID.Name }}", err: errors.New(`{{ $pkg }}: missing "{{ $.Name }}.{{ $.ID.Name }}" for update`)}
}
{{- if $.ID.HasValueScanner }}
vv, err := {{ $.ID.ValueFunc }}(id)
if err != nil {
return {{ $zero }}, err
}
_spec.Node.ID.Value = vv
{{- else }}
_spec.Node.ID.Value = id
{{- end }}
if fields := {{ $receiver }}.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, {{ $.Package }}.{{ $.ID.Constant }})
for _, f := range fields {
if !{{ $.Package }}.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("{{ $pkg }}: invalid field %q for query", f)}
}
if f != {{ $.Package }}.{{ $.ID.Constant }} {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
{{- else }}{{/* Composite ID. */}}
{{- range $i, $id := $.EdgeSchema.ID }}
if id, ok := {{ $mutation }}.{{ $id.MutationGet }}(); !ok {
return {{ $zero }}, &ValidationError{Name: "{{ $id.Name }}", err: errors.New(`{{ $pkg }}: missing "{{ $.Name }}.{{ $id.Name }}" for update`)}
} else {
_spec.Node.CompositeID[{{ $i }}].Value = id
}
{{- end }}
if fields := {{ $receiver }}.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, len(fields))
for i, f := range fields {
if !{{ $.Package }}.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("{{ $pkg }}: invalid field %q for query", f)}
}
_spec.Node.Columns[i] = f
}
}
{{- end }}
{{- end }}
if ps := {{ $mutation }}.Predicates(); len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
{{- range $f := $.MutationFields }}
{{- if $f.IsBlobNoColumn }}{{ continue }}{{ end }}
{{- if or (not $f.Immutable) $f.UpdateDefault }}
if value, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
{{- if $f.HasValueScanner }}
vv, err := {{ $f.ValueFunc }}(value)
if err != nil {
return {{ $zero }}, err
}
_spec.SetField({{ $.Package }}.{{ $f.Constant }}, field.{{ $f.Type.ConstName }}, vv)
{{- else }}
_spec.SetField({{ $.Package }}.{{ $f.Constant }}, field.{{ $f.Type.ConstName }}, value)
{{- end }}
}
{{- if $f.SupportsMutationAdd }}
if value, ok := {{ $mutation }}.{{ $f.MutationAdded }}(); ok {
{{- if $f.HasValueScanner }}
vv, err := {{ $f.ValueFunc }}(value)
if err != nil {
return {{ $zero }}, err
}
_spec.AddField({{ $.Package }}.{{ $f.Constant }}, field.{{ $f.Type.ConstName }}, vv)
{{- else }}
_spec.AddField({{ $.Package }}.{{ $f.Constant }}, field.{{ $f.Type.ConstName }}, value)
{{- end }}
}
{{- end }}
{{- if $f.SupportsMutationAppend }}
if value, ok := {{ $mutation }}.{{ $f.MutationAppended }}(); ok {
_spec.AddModifier(func(u *sql.UpdateBuilder) {
sqljson.Append(u, {{ $.Package }}.{{ $f.Constant }}, value)
})
}
{{- end }}
{{- end }}
{{- if $f.Optional }}
if {{ $mutation }}.{{ $f.StructField }}Cleared() {
_spec.ClearField({{ $.Package }}.{{ $f.Constant }}, field.{{ $f.Type.ConstName }})
}
{{- end }}
{{- end }}
{{- range $e := $.EdgesWithID }}
{{- if $e.Immutable }}
{{- /* Skip to the next one as immutable edges cannot be updated. */}}
{{- continue }}
{{- end }}
if {{ $mutation }}.{{ $e.MutationCleared }}() {
{{- with extend $ "Edge" $e }}
{{ template "dialect/sql/defedge" . }}
{{- end }}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
{{- if not $e.Unique }}
if nodes := {{ $mutation }}.Removed{{ $e.StructField }}IDs(); len(nodes) > 0 && !{{ $mutation }}.{{ $e.MutationCleared }}() {
{{- with extend $ "Edge" $e "Nodes" true "Zero" $zero }}
{{ template "dialect/sql/defedge" . }}
{{- end }}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
{{- end }}
if nodes := {{ $mutation }}.{{ $e.StructField }}IDs(); len(nodes) > 0 {
{{- with extend $ "Edge" $e "Nodes" true "Zero" $zero }}
{{ template "dialect/sql/defedge" . }}
{{- end }}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
{{- end }}
{{- /* Allow mutating the sqlgraph.UpdateSpec by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/update/spec/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- if and $one $.HasBlobFields }}
_blobs := ent.NewBlobs({{ $mutation }}.blobOpeners.{{ $.Name }})
{{- range $f := $.BlobFields }}
{{- if not $f.IsBlobLazy }}
if value, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
{{- if $f.HasValueScanner }}
_blobDV, err := {{ $f.ValueFunc }}(value)
if err != nil {
return {{ $zero }}, fmt.Errorf("{{ $pkg }}: encoding {{ $f.Name }}: %w", err)
}
var _blobData []byte
switch v := _blobDV.(type) {
case []byte:
_blobData = v
case string:
_blobData = []byte(v)
default:
return {{ $zero }}, fmt.Errorf("{{ $pkg }}: encoding {{ $f.Name }}: expected []byte or string, got %T", _blobDV)
}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, _blobData,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- else if $f.IsBlobGoString }}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, []byte(value),
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- else }}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, value,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
{{- end }}
}
{{- if $f.Optional }}
if {{ $mutation }}.{{ $f.StructField }}Cleared() {
_blobs.SetCleared({{ $.Package }}.{{ $f.Constant }}, func() {
_spec.ClearField("{{ $f.BlobKeyColumn }}", field.TypeString)
})
}
{{- end }}
{{- else }}
if r, ok := {{ $mutation }}.{{ $f.StructField }}(); ok {
_blobData, err := io.ReadAll(r)
if err != nil {
return {{ $zero }}, fmt.Errorf("{{ $pkg }}: reading {{ $f.Name }}: %w", err)
}
_blobs.Set({{ $.Package }}.{{ $f.Constant }}, _blobData,
{{ if $f.HasBlobKey }}{{ $.Package }}.{{ $f.BlobKeyName }}{{ else }}defaultBlobKey{{ end }},
func(k string) {
_spec.SetField("{{ $f.BlobKeyColumn }}", field.TypeString, k)
},
)
}
{{- if $f.Optional }}
if {{ $mutation }}.{{ $f.MutationCleared }}() {
_blobs.SetCleared({{ $.Package }}.{{ $f.Constant }}, func() {
_spec.ClearField("{{ $f.BlobKeyColumn }}", field.TypeString)
})
}
{{- end }}
{{- end }}
{{- end }}
_blobResult, err := _blobs.Update(ctx, &sqlgraph.BlobSpec{
Driver: {{ $receiver }}.driver,
Table: {{ $.Package }}.Table,
Columns: map[string]string{
{{- range $f := $.BlobFields }}
{{ $.Package }}.{{ $f.Constant }}: "{{ $f.BlobKeyColumn }}",
{{- end }}
},
{{- if $.HasBlobRefCheckedFields }}
CheckRefs: map[string]bool{
{{- range $f := $.BlobRefCheckedFields }}
{{ $.Package }}.{{ $f.Constant }}: true,
{{- end }}
},
{{- end }}
Predicate: func(s *sql.Selector) {
s.Where(sql.EQ({{ $.Package }}.{{ $.ID.Constant }}, _spec.Node.ID.Value))
},
})
if err != nil {
return {{ $zero }}, err
}
{{- end }}
{{- if $one }}
_node = &{{ $.Name }}{config: {{ $receiver }}.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
{{- end }}
{{- if $one }}
if err = sqlgraph.UpdateNode(ctx, {{ $receiver }}.driver, _spec); err != nil {
{{- else }}
if _node, err = sqlgraph.UpdateNodes(ctx, {{ $receiver }}.driver, _spec); err != nil {
{{- end }}
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{ {{ $.Package }}.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
{{- if and $one $.HasBlobFields }}
return {{ $zero }}, errors.Join(err, _blobResult.Rollback(ctx))
{{- else }}
return {{ $zero }}, err
{{- end }}
}
{{ $mutation }}.done = true
{{- if and $one $.HasBlobFields }}
// Resolve which replaced blobs are unreferenced now that the row moved to its new
// keys, while the transaction is still open, and remove them once it commits.
_blobCleanup, err := _blobResult.Cleanup(ctx)
if err != nil {
return {{ $zero }}, err
}
if txd, ok := {{ $receiver }}.driver.(*txDriver); ok {
txd.mu.Lock()
txd.onCommit = append(txd.onCommit, func(next Committer) Committer {
return CommitFunc(func(ctx context.Context, tx *Tx) error {
if err := next.Commit(ctx, tx); err != nil {
return err
}
return _blobCleanup(ctx)
})
})
txd.onRollback = append(txd.onRollback, func(next Rollbacker) Rollbacker {
return RollbackFunc(func(ctx context.Context, tx *Tx) error {
err := next.Rollback(ctx, tx)
return errors.Join(err, _blobResult.Rollback(ctx))
})
})
txd.mu.Unlock()
} else if err := _blobCleanup(ctx); err != nil {
return {{ $zero }}, err
}
{{- end }}
{{- if and $one $.HasLoadOnScanFields }}
{{- /* Blob values are consistent after write — use mutation value for mutated fields
instead of re-reading from storage. For DualWrite fields, assignValues already
populated the value from the SQL column. For non-DualWrite fields not mutated,
read from blob storage. */}}
_blobReader := ent.NewBlobStore({{ $mutation }}.blobOpeners.{{ $.Name }})
{{- range $f := $.LoadOnScanFields }}
if value, ok := {{ $mutation }}.{{ $f.MutationGet }}(); ok {
_node.{{ $f.StructField }} = value
}
{{- if $f.IsBlobNoColumn }} else if _node.{{ $f.BlobKeyColumn }} != nil && *_node.{{ $f.BlobKeyColumn }} != "" {
data, err := _blobReader.Read(ctx, {{ $.Package }}.{{ $f.Constant }}, *_node.{{ $f.BlobKeyColumn }})
if err != nil {
return nil, errors.Join(fmt.Errorf("loading {{ $f.Name }} after update: %w", err), _blobReader.Close())
}
{{- if $f.HasValueScanner }}
sv := {{ $f.ScanValueFunc }}()
if err := sv.Scan(data); err != nil {
return nil, errors.Join(fmt.Errorf("scanning {{ $f.Name }} after update: %w", err), _blobReader.Close())
}
v, err := {{ $f.FromValueFunc }}(sv)
if err != nil {
return nil, errors.Join(fmt.Errorf("scanning {{ $f.Name }} after update: %w", err), _blobReader.Close())
}
_node.{{ $f.StructField }} = v
{{- else if $f.IsBlobGoString }}
_node.{{ $f.StructField }} = {{ $f.Type }}(data)
{{- else }}
_node.{{ $f.StructField }} = data
{{- end }}
}
{{- end }}
{{- end }}
if err := _blobReader.Close(); err != nil {
return nil, err
}
{{- end }}
return _node, nil
}
{{ end }}
{{ define "dialect/sql/defedge" }}
{{- $e := $.Scope.Edge -}}
{{- $receiver := $.Scope.Receiver -}}
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.{{ $e.Rel.Type }},
Inverse: {{ $e.IsInverse }},
Table: {{ $.Package }}.{{ $e.TableConstant }},
Columns: {{ if $e.M2M }}{{ $.Package }}.{{ $e.PKConstant }}{{ else }}[]string{ {{ $.Package }}.{{ $e.ColumnConstant }} }{{ end }},
Bidi: {{ $e.Bidi }},
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec({{ $e.Type.Package }}.{{ $e.Type.ID.Constant }}, field.{{ $e.Type.ID.Type.ConstName }}),
},
}
{{- /* Allow mutating the sqlgraph.EdgeSpec by ent extensions or user templates.*/}}
{{- with $tmpls := matchTemplate "dialect/sql/defedge/spec/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- with $.Scope.Nodes }}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
{{- end }}
{{- with $e.Through }}
{{- if .HasDefault }}
createE := &{{ .CreateName }}{config: {{ $receiver }}.config, mutation: new{{ .MutationName }}({{ $receiver }}.config, OpCreate)}
{{- /* Skip error handling here as this check was already handled. */}}
{{ if or .NumHooks .NumPolicy }}_ = {{ end }}createE.defaults()
_, specE := createE.createSpec()
edge.Target.Fields = specE.Fields
{{- if and .HasOneFieldID .ID.Default }}
if specE.ID.Value != nil {
edge.Target.Fields = append(edge.Target.Fields, specE.ID)
}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

291
entc/gen/template/ent.tmpl Normal file
View File

@@ -0,0 +1,291 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "model" }}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}
{{ with extend $ "Imports" $.SiblingImports }}
{{ template "import" . }}
{{ end }}
{{ template "model/comment" $ }}
{{- with $tmpls := matchTemplate "model/comment/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
type {{ $.Name }} struct {
config {{ template "model/omittags" $ }}
{{- if $.HasOneFieldID }}
// ID of the ent.
{{- if $.ID.Comment }}
{{- range $line := split $.ID.Comment "\n" }}
// {{ $line }}
{{- end }}
{{- end }}
ID {{ $.ID.Type }} {{ with $.Annotations.Fields.StructTag.id }}`{{ . }}`{{ else }}`{{ $.ID.StructTag }}`{{ end }}
{{- end }}
{{- range $f := $.Fields }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{- $tag := $f.StructTag }}{{ with $tags := $.Annotations.Fields.StructTag }}{{ with index $tags $f.Name }}{{ $tag = . }}{{ end }}{{ end }}
{{- template "model/fieldcomment" $f }}
{{ $f.StructField }} {{ if $f.NillableValue }}*{{ end }}{{ $f.Type }} {{ if not $f.Sensitive }}`{{ $tag }}`{{ else }}{{ template "model/omittags" $ }}{{ end }}
{{- end }}
{{- with $.Edges }}
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the {{ $.Name }}Query when eager-loading is set.
Edges {{ $.Name }}Edges {{ template "model/edgetags" $ }}
{{- end -}}
{{- /* Additional fields to add by the storage driver. */}}
{{- $tmpl := printf "dialect/%s/model/fields" $.Storage }}
{{- if hasTemplate $tmpl }}
{{- xtemplate $tmpl . }}
{{- end }}
{{- /* Additional fields to add by the user. */}}
{{- template "model/fields/additional" $ }}
}
{{- with $.Edges }}
{{- $edgesType := print $.Name "Edges"}}
// {{ $.Name }}Edges holds the relations/edges for other nodes in the graph.
type {{ $edgesType }} struct {
{{- range $e := . }}
{{- template "model/edgecomment" $e }}
{{ $e.StructField }} {{ if not $e.Unique }}[]{{ end }}*{{ $e.Type.Name }} {{ with $e.StructTag }}`{{ . }}`{{ end }}
{{- end }}
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [{{ len . }}]bool
{{- /* Additional fields to add by the user. */}}
{{- template "model/edges/fields/additional" $ }}
}
{{- 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 {{ $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 }}
{{- end }}
{{ $tmpl = printf "dialect/%s/decode/one" $.Storage }}
{{ xtemplate $tmpl $ }}
{{ $receiver := $.Receiver }}
{{ range $e := $.Edges }}
{{ $func := print "Query" $e.StructField }}
// {{ $func }} queries the "{{ $e.Name }}" edge of the {{ $.Name }} entity.
func ({{ $receiver }} *{{ $.Name }}) {{ $func }}() *{{ $e.Type.QueryName }} {
return New{{ $.ClientName }}({{ $receiver }}.config).{{ $func }}({{ $receiver }})
}
{{ end }}
{{- $blobModelTmpl := printf "dialect/%s/model/fields" $.Storage }}
{{- if hasTemplate $blobModelTmpl }}
{{- range $f := $.BlobFields }}
// {{ $f.StructField }}Reader opens a reader for the "{{ $f.Name }}" field from blob storage.
// The caller must close the returned reader when done.
func ({{ $receiver }} *{{ $.Name }}) {{ $f.StructField }}Reader(ctx context.Context) (io.ReadCloser, error) {
if {{ $receiver }}.{{ $f.BlobKeyColumn }} == nil || *{{ $receiver }}.{{ $f.BlobKeyColumn }} == "" {
return nil, fmt.Errorf("{{ $pkg }}: {{ $.Name }}.{{ $f.BlobKeyColumn }} is nil or empty")
}
if {{ $receiver }}.blobOpeners.{{ $.Name }} == nil {
return nil, fmt.Errorf("{{ $pkg }}: blob storage not configured (missing WithBlobOpeners)")
}
b, err := {{ $receiver }}.blobOpeners.{{ $.Name }}(ctx, {{ $.Package }}.{{ $f.Constant }})
if err != nil {
return nil, err
}
return ent.BlobReader(ctx, b, *{{ $receiver }}.{{ $f.BlobKeyColumn }})
}
{{- end }}
{{- end }}
{{- if not $.IsView }}
// Update returns a builder for updating this {{ $.Name }}.
// Note that you need to call {{ $.Name }}.Unwrap() before calling this method if this {{ $.Name }}
// was returned from a transaction, and the transaction was committed or rolled back.
func ({{ $receiver }} *{{ $.Name }}) Update() *{{ $.UpdateOneName }} {
return New{{ $.ClientName }}({{ $receiver }}.config).UpdateOne({{ $receiver }})
}
{{- end }}
// Unwrap unwraps the {{ $.Name }} entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func ({{ $receiver }} *{{ $.Name }}) Unwrap() *{{ $.Name }} {
_tx, ok := {{ $receiver }}.config.driver.(*txDriver)
if !ok {
panic("{{ $pkg }}: {{ $.Name }} is not a transactional entity")
}
{{ $receiver }}.config.driver = _tx.drv
return {{ $receiver }}
}
{{ template "model/stringer" $ }}
{{ template "model/additional" $ }}
{{ $slice := plural $.Name }}
// {{ $slice }} is a parsable slice of {{ $.Name }}.
type {{ $slice }} []*{{ $.Name }}
{{ with extend $ "Slice" $slice }}
{{ $tmpl := printf "dialect/%s/decode/many" $.Storage }}
{{ xtemplate $tmpl . }}
{{ end }}
{{ end }}
{{/* A template to generate a fmt.Stringer implementation. */}}
{{ define "model/stringer" }}
{{ $receiver := $.Receiver }}
// String implements the fmt.Stringer.
func ({{ $receiver }} *{{ $.Name }}) String() string {
var builder strings.Builder
builder.WriteString("{{ $.Name }}(")
{{- if $.HasOneFieldID }}
builder.WriteString(fmt.Sprintf("id=%v{{ if $.Fields }}, {{ end }}", {{ $receiver }}.ID))
{{- end }}
{{- range $i, $f := $.Fields }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{- if ne $i 0 }}
builder.WriteString(", ")
{{- end }}
{{- if $f.Sensitive }}
builder.WriteString("{{ $f.Name }}={{ print "<sensitive>" }}")
{{- else }}
{{- $sf := printf "%s.%s" $receiver $f.StructField }}
{{- if $f.Nillable }}
if v := {{ $sf }}; v != nil {
builder.WriteString("{{ $f.Name }}=")
{{- if and $f.IsTime (not $f.HasGoType) }}
builder.WriteString(v.Format(time.ANSIC))
{{- else if and $f.IsString (not $f.HasGoType) }}
builder.WriteString(*v)
{{- else }}
builder.WriteString(fmt.Sprintf("%v", *v))
{{- end }}
}
{{- else }}
builder.WriteString("{{ $f.Name }}=")
{{- if and $f.IsTime (not $f.HasGoType) }}
builder.WriteString({{ $sf }}.Format(time.ANSIC))
{{- else if and $f.IsString (not $f.HasGoType) }}
builder.WriteString({{ $sf }})
{{- else }}
builder.WriteString(fmt.Sprintf("%v", {{ $sf }}))
{{- end }}
{{- end }}
{{- end }}
{{- end }}
builder.WriteByte(')')
return builder.String()
}
{{ end }}
{{/* A template for generating the Entity's comment. */}}
{{- define "model/comment" }}
{{- if and $.Annotations.Comment $.Annotations.Comment.Text }}
{{- range $line := split $.Annotations.Comment.Text "\n" }}
// {{ $line }}
{{- end }}
{{- else }}
// {{ $.Name }} is the model entity for the {{ $.Name }} schema.
{{- end }}
{{- end }}
{{/* A template for generating the tag of the Edges struct-field. */}}
{{- define "model/edgetags" }}
{{- $tag := `json:"edges"` }}
{{- with $.Annotations.Edges.StructTag }}
{{- if tagLookup . "json" }}
{{- $tag = . }}
{{- else }}
{{- $tag = print $tag " " . }}
{{- end }}
{{- end }}
{{- print "`" $tag "`" }}
{{- end }}
{{/* A template for omitting struct-tags. */}}
{{ define "model/omittags" }}{{ with $.TagTypes }}`{{ range $i, $t := . }}{{ if ne $i 0 }} {{ end }}{{ $t }}:"-"{{ end }}`{{ end }}{{ end }}
{{/* A template that can be overridden in order to add additional fields to the each type.*/}}
{{ define "model/fields/additional" }}{{ end }}
{{/* A template for setting the field comment. */}}
{{- define "model/fieldcomment" }}
{{- if $.Comment }}
{{- range $line := split $.Comment "\n" }}
// {{ $line }}
{{- end }}
{{- else }}
// {{ $.StructField }} holds the value of the "{{ $.Name }}" field.
{{- end }}
{{- if $.IsDeprecated }}
//
// Deprecated: {{ with $.DeprecationReason }}{{ . }}{{ else }}Field "{{ $.Name }}" was marked as deprecated in the schema.{{ end }}
{{- end }}
{{- end }}
{{/* A template for setting the edge comment. */}}
{{- define "model/edgecomment" }}
{{- if $.Comment }}
{{- range $line := split $.Comment "\n" }}
// {{ $line }}
{{- end }}
{{- else }}
// {{ $.StructField }} holds the value of the {{ $.Name }} edge.
{{- end }}
{{- end }}
{{/* A template for adding additional methods or helpers for the generated model. */}}
{{ define "model/additional" }}
{{- with $tmpls := matchTemplate "model/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- with $tmpls := matchTemplate (printf "dialect/%s/model/additional/*" $.Storage) }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}
{{/* A template for adding additional fields for the Edges object of the generated model. */}}
{{ define "model/edges/fields/additional" }}
{{- with $tmpls := matchTemplate "model/edges/fields/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- with $tmpls := matchTemplate (printf "dialect/%s/model/edges/fields/additional/*" $.Storage) }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,111 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "enttest" }}
{{ $pkg := base $.Config.Package }}
{{ with extend $ "Package" "enttest" -}}
{{ template "header" . }}
{{ end }}
import (
"fmt"
"context"
"{{ $.Config.Package }}"
// required by schema hooks.
_ "{{ $.Config.Package }}/runtime"
{{ if $.SupportMigrate }}
"{{ $.Config.Package }}/migrate"
"entgo.io/ent/dialect/sql/schema"
{{ end }}
)
type (
// TestingT is the interface that is shared between
// testing.T and testing.B and used by enttest.
TestingT interface {
FailNow()
Error(...any)
}
// Option configures client creation.
Option func(*options)
options struct {
opts []{{ $pkg }}.Option
{{- if $.SupportMigrate }}
migrateOpts []schema.MigrateOption
{{- end }}
}
)
// WithOptions forwards options to client creation.
func WithOptions(opts ...{{ $pkg }}.Option) Option {
return func(o *options) {
o.opts = append(o.opts, opts...)
}
}
{{- if $.SupportMigrate }}
// WithMigrateOptions forwards options to auto migration.
func WithMigrateOptions(opts ...schema.MigrateOption) Option {
return func(o *options) {
o.migrateOpts = append(o.migrateOpts, opts...)
}
}
{{- end }}
func newOptions(opts []Option) *options {
o := &options{}
for _, opt := range opts {
opt(o)
}
return o
}
// Open calls {{ $pkg }}.Open and auto-run migration.
func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *{{ $pkg }}.Client {
o := newOptions(opts)
c, err := {{ $pkg }}.Open(driverName, dataSourceName, o.opts...)
if err != nil {
t.Error(err)
t.FailNow()
}
{{- if $.SupportMigrate }}
migrateSchema(t, c, o)
{{- end }}
return c
}
// NewClient calls {{ $pkg }}.NewClient and auto-run migration.
func NewClient(t TestingT, opts ...Option) *{{ $pkg }}.Client {
o := newOptions(opts)
c := {{ $pkg }}.NewClient(o.opts...)
{{- if $.SupportMigrate }}
migrateSchema(t, c, o)
{{- end }}
return c
}
{{- if $.SupportMigrate }}
func migrateSchema(t TestingT, c *{{ $pkg }}.Client, o *options) {
tables, err := schema.CopyTables(migrate.Tables)
if err != nil {
t.Error(err)
t.FailNow()
}
if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil {
t.Error(err)
t.FailNow()
}
}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,17 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "header" }}
{{ with $.Header }}{{ . }}{{ else }}// Code generated by ent, DO NOT EDIT.{{ end }}
{{ $pkg := base $.Config.Package }}
{{ if hasField $ "Scope" }}
{{ $pkg = $.Scope.Package }}
{{ end }}
package {{ $pkg }}
{{ end }}

192
entc/gen/template/hook.tmpl Normal file
View File

@@ -0,0 +1,192 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "hook" }}
{{ with extend $ "Package" "hook" }}
{{ template "header" . }}
{{ end }}
import "{{ $.Config.Package }}"
{{ $pkg := base $.Config.Package }}
{{ range $n := $.MutableNodes }}
{{ $name := print $n.Name "Func" }}
{{ $type := printf "*%s.%s" $pkg $n.MutationName }}
// The {{ $name }} type is an adapter to allow the use of ordinary
// function as {{ $n.Name }} mutator.
type {{ $name }} func(context.Context, {{ $type }}) ({{ $pkg }}.Value, error)
// Mutate calls f(ctx, m).
func (f {{ $name }}) Mutate(ctx context.Context, m {{ $pkg }}.Mutation) ({{ $pkg }}.Value, error) {
if mv, ok := m.({{ $type }}); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect {{ $type }}", m)
}
{{ end }}
// Condition is a hook condition function.
type Condition func(context.Context, {{ $pkg }}.Mutation) bool
// And groups conditions with the AND operator.
func And(first, second Condition, rest ...Condition) Condition {
return func(ctx context.Context, m {{ $pkg }}.Mutation) bool {
if !first(ctx, m) || !second(ctx, m) {
return false
}
for _, cond := range rest {
if !cond(ctx, m) {
return false
}
}
return true
}
}
// Or groups conditions with the OR operator.
func Or(first, second Condition, rest ...Condition) Condition {
return func(ctx context.Context, m {{ $pkg }}.Mutation) bool {
if first(ctx, m) || second(ctx, m) {
return true
}
for _, cond := range rest {
if cond(ctx, m) {
return true
}
}
return false
}
}
// Not negates a given condition.
func Not(cond Condition) Condition {
return func(ctx context.Context, m {{ $pkg }}.Mutation) bool {
return !cond(ctx, m)
}
}
// HasOp is a condition testing mutation operation.
func HasOp(op {{ $pkg }}.Op) Condition {
return func(_ context.Context, m {{ $pkg }}.Mutation) bool {
return m.Op().Is(op)
}
}
{{ $conds := dict "HasFields" "Field" "HasAddedFields" "AddedField" "HasClearedFields" "FieldCleared" -}}
{{- range $cond := keys $conds }}
{{ $method := get $conds $cond -}}
// {{ $cond }} is a condition validating `.{{ $method }}` on fields.
func {{ $cond }}(field string, fields ...string) Condition {
return func(_ context.Context, m {{ $pkg }}.Mutation) bool {
if {{ if ne $method "FieldCleared" }}_, {{ end }}exists := m.{{ $method }}(field); !exists {
return false
}
for _, field := range fields {
if {{ if ne $method "FieldCleared" }}_, {{ end }}exists := m.{{ $method }}(field); !exists {
return false
}
}
return true
}
}
{{- end }}
// If executes the given hook under condition.
//
// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...)))
//
func If(hk {{ $pkg }}.Hook, cond Condition) {{ $pkg }}.Hook {
return func(next {{ $pkg }}.Mutator) {{ $pkg }}.Mutator {
return {{ $pkg }}.MutateFunc(func(ctx context.Context, m {{ $pkg }}.Mutation) ({{ $pkg }}.Value, error) {
if cond(ctx, m) {
return hk(next).Mutate(ctx, m)
}
return next.Mutate(ctx, m)
})
}
}
// On executes the given hook only for the given operation.
//
// hook.On(Log, {{ $pkg }}.Delete|{{ $pkg }}.Create)
//
func On(hk {{ $pkg }}.Hook, op {{ $pkg }}.Op) {{ $pkg }}.Hook {
return If(hk, HasOp(op))
}
// Unless skips the given hook only for the given operation.
//
// hook.Unless(Log, {{ $pkg }}.Update|{{ $pkg }}.UpdateOne)
//
func Unless(hk {{ $pkg }}.Hook, op {{ $pkg }}.Op) {{ $pkg }}.Hook {
return If(hk, Not(HasOp(op)))
}
// FixedError is a hook returning a fixed error.
func FixedError(err error) {{ $pkg }}.Hook {
return func({{ $pkg }}.Mutator) {{ $pkg }}.Mutator {
return {{ $pkg }}.MutateFunc(func(context.Context, {{ $pkg }}.Mutation) ({{ $pkg }}.Value, error) {
return nil, err
})
}
}
// Reject returns a hook that rejects all operations that match op.
//
// func (T) Hooks() []{{ $pkg }}.Hook {
// return []{{ $pkg }}.Hook{
// Reject({{ $pkg }}.Delete|{{ $pkg }}.Update),
// }
// }
//
func Reject(op {{ $pkg }}.Op) {{ $pkg }}.Hook {
hk := FixedError(fmt.Errorf("%s operation is not allowed", op))
return On(hk, op)
}
// Chain acts as a list of hooks and is effectively immutable.
// Once created, it will always hold the same set of hooks in the same order.
type Chain struct {
hooks []{{ $pkg }}.Hook
}
// NewChain creates a new chain of hooks.
func NewChain(hooks ...{{ $pkg }}.Hook) Chain {
return Chain{append([]{{ $pkg }}.Hook(nil), hooks...)}
}
// Hook chains the list of hooks and returns the final hook.
func (c Chain) Hook() {{ $pkg }}.Hook {
return func(mutator {{ $pkg }}.Mutator) {{ $pkg }}.Mutator {
for i := len(c.hooks) - 1; i >= 0; i-- {
mutator = c.hooks[i](mutator)
}
return mutator
}
}
// Append extends a chain, adding the specified hook
// as the last ones in the mutation flow.
func (c Chain) Append(hooks ...{{ $pkg }}.Hook) Chain {
newHooks := make([]{{ $pkg }}.Hook, 0, len(c.hooks)+len(hooks))
newHooks = append(newHooks, c.hooks...)
newHooks = append(newHooks, hooks...)
return Chain{newHooks}
}
// Extend extends a chain, adding the specified chain
// as the last ones in the mutation flow.
func (c Chain) Extend(chain Chain) Chain {
return c.Append(chain.hooks...)
}
{{ end }}

View File

@@ -0,0 +1,87 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "import" }}
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"math"
"strings"
"sync"
"time"
{{- /* Ignore generating on graph templates (e.g. config.tmpl, client.tmpl, etc). */}}
{{- if ne $.Config.Package $.Package }}
"{{ $.Config.Package }}/predicate"
{{- if $.HasOneFieldID }}
{{- with $.ID.Type.PkgPath }}
{{- $name := $.ID.Type.PkgName }}
{{ if ne $name (base $.ID.Type.PkgPath) }}{{ $name }} {{ end }}"{{ . }}"
{{- end }}
{{- end }}
{{- /* Import external packages */}}
{{- template "import/types" $ }}
{{- end }}
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/schema/field"
{{- with $.Storage }}
{{- range $import := .Imports }}
"{{ $import }}"
{{- end }}
{{- end }}
{{- if and (hasField $ "Scope") ($.Scope.Imports) }}
{{- range $import := $.Scope.Imports }}
{{ $import.Alias }} "{{ $import.Path }}"
{{- end }}
{{- end }}
{{- template "import/additional" $ }}
)
{{ end }}
{{/* A template for importing fields with custom types */}}
{{ define "import/types" -}}
{{- $seen := dict }}
{{- $selfPkg := "" }}{{ if ne $.Config.Package $.Package }}{{ $selfPkg = print $.Config.Package "/" $.PackageDir }}{{ end }}
{{- $fields := $.Fields }}{{ if $.HasOneFieldID }}{{ if $.ID.UserDefined }}{{ $fields = append $fields $.ID }}{{ end }}{{ end }}
{{- range $f := $fields }}
{{- $pkg := $f.Type.PkgPath }}
{{- if and $pkg (not (hasImport (base $pkg))) (not (hasKey $seen $pkg)) (ne $pkg $selfPkg) }}
{{- $name := $f.Type.PkgName }}
{{ if ne $name (base $pkg) }}{{ $name }} {{ end}}"{{ $pkg }}"
{{- $seen = set $seen $pkg true }}
{{- end }}
{{- end }}
{{- /* Import packages for edge target ID types referenced in mutation fields. */}}
{{- range $e := $.EdgesWithID }}
{{- $pkg := $e.Type.ID.Type.PkgPath }}
{{- if and $pkg (not (hasImport (base $pkg))) (not (hasKey $seen $pkg)) (ne $pkg $selfPkg) }}
{{- $name := $e.Type.ID.Type.PkgName }}
{{ if ne $name (base $pkg) }}{{ $name }} {{ end}}"{{ $pkg }}"
{{- $seen = set $seen $pkg true }}
{{- end }}
{{- end }}
{{- end }}
{{/* A template for allowing additional imports by ent extensions or user templates.*/}}
{{ define "import/additional" }}
{{- with $tmpls := matchTemplate "import/additional/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- if $.Storage }}
{{- with $tmpls := matchTemplate (printf "dialect/%s/import/additional/*" $.Storage) }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,171 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "intercept" }}
{{ with extend $ "Package" "intercept" }}
{{ template "header" . }}
{{ end }}
import (
"fmt"
"context"
"entgo.io/ent/dialect/sql"
"{{ $.Config.Package }}"
"{{ $.Config.Package }}/predicate"
{{- range $n := $.Nodes }}
{{ $n.PackageAlias }} "{{ $n.Config.Package }}/{{ $n.PackageDir }}"
{{- end }}
)
{{ $pkg := base $.Config.Package }}
// The Query interface represents an operation that queries a graph.
// By using this interface, users can write generic code that manipulates
// query builders of different types.
type Query interface {
// Type returns the string representation of the query type.
Type() string
// Limit the number of records to be returned by this query.
Limit(int)
// Offset to start from.
Offset(int)
// Unique configures the query builder to filter duplicate records.
Unique(bool)
// Order specifies how the records should be ordered.
Order(...func({{ $.Storage.Builder }}))
// WhereP appends storage-level predicates to the query builder. Using this method, users
// can use type-assertion to append predicates that do not depend on any generated package.
WhereP(...func({{ $.Storage.Builder }}))
}
// The Func type is an adapter that allows ordinary functions to be used as interceptors.
// Unlike traversal functions, interceptors are skipped during graph traversals. Note that the
// implementation of Func is different from the one defined in entgo.io/ent.InterceptFunc.
type Func func(context.Context, Query) error
// Intercept calls f(ctx, q) and then applied the next Querier.
func (f Func) Intercept(next {{ $pkg }}.Querier) {{ $pkg }}.Querier {
return {{ $pkg }}.QuerierFunc(func(ctx context.Context, q {{ $pkg }}.Query) ({{ $pkg }}.Value, error) {
query, err := NewQuery(q)
if err != nil {
return nil, err
}
if err := f(ctx, query); err != nil {
return nil, err
}
return next.Query(ctx, q)
})
}
// The TraverseFunc type is an adapter to allow the use of ordinary function as Traverser.
// If f is a function with the appropriate signature, TraverseFunc(f) is a Traverser that calls f.
type TraverseFunc func(context.Context, Query) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseFunc) Intercept(next {{ $pkg }}.Querier) {{ $pkg }}.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseFunc) Traverse(ctx context.Context, q {{ $pkg }}.Query) error {
query, err := NewQuery(q)
if err != nil {
return err
}
return f(ctx, query)
}
{{- range $n := $.Nodes }}
{{ $name := print $n.Name "Func" }}
{{ $type := printf "*%s.%s" $pkg $n.QueryName }}
// The {{ $name }} type is an adapter to allow the use of ordinary function as a Querier.
type {{ $name }} func(context.Context, {{ $type }}) ({{ $pkg }}.Value, error)
// Query calls f(ctx, q).
func (f {{ $name }}) Query(ctx context.Context, q {{ $pkg }}.Query) ({{ $pkg }}.Value, error) {
if q, ok := q.({{ $type }}); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect {{ $type }}", q)
}
{{ $name = print "Traverse" $n.Name }}
// The {{ $name }} type is an adapter to allow the use of ordinary function as Traverser.
type {{ $name }} func(context.Context, {{ $type }}) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f {{ $name }}) Intercept(next {{ $pkg }}.Querier) {{ $pkg }}.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f {{ $name }}) Traverse(ctx context.Context, q {{ $pkg }}.Query) error {
if q, ok := q.({{ $type }}); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect {{ $type }}", q)
}
{{- end }}
// NewQuery returns the generic Query interface for the given typed query.
func NewQuery(q {{ $pkg }}.Query) (Query, error) {
switch q := q.(type) {
{{- range $n := $.Nodes }}
case *{{ $pkg }}.{{ $n.QueryName }}:
return &query[*{{ $pkg }}.{{ $n.QueryName }}, predicate.{{ $n.Name }}, {{ $n.Package }}.OrderOption]{typ: {{ $pkg }}.{{ $n.TypeName }}, tq: q}, nil
{{- end }}
default:
return nil, fmt.Errorf("unknown query type %T", q)
}
}
type query[T any, P ~func({{ $.Storage.Builder }}), R ~func({{ $.Storage.Builder }})] struct {
typ string
tq interface {
Limit(int) T
Offset(int) T
Unique(bool) T
Order(...R) T
Where(...P) T
}
}
func (q query[T, P, R]) Type() string {
return q.typ
}
func (q query[T, P, R]) Limit(limit int) {
q.tq.Limit(limit)
}
func (q query[T, P, R]) Offset(offset int) {
q.tq.Offset(offset)
}
func (q query[T, P, R]) Unique(unique bool) {
q.tq.Unique(unique)
}
func (q query[T, P, R]) Order(orders ...func({{ $.Storage.Builder }})) {
rs := make([]R, len(orders))
for i := range orders {
rs[i] = orders[i]
}
q.tq.Order(rs...)
}
func (q query[T, P, R]) WhereP(ps ...func({{ $.Storage.Builder }})) {
p := make([]P, len(ps))
for i := range ps {
p[i] = ps[i]
}
q.tq.Where(p...)
}
{{ end }}

View File

@@ -0,0 +1,28 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "internal/schema" }}
{{ with $.Header }}{{ . }}{{ else }}// Code generated by ent, DO NOT EDIT.{{ end }}
// +build tools
// Package internal holds a loadable version of the latest schema.
package internal
const Schema = {{ .SchemaSnapshot }}
{{ end }}
{{ define "internal/globalid" }}
{{ with $.Header }}{{ . }}{{ else }}// Code generated by ent, DO NOT EDIT.{{ end }}
package internal
const IncrementStarts = {{ .Annotations.IncrementStarts | json | quote }}
{{ end }}

189
entc/gen/template/meta.tmpl Normal file
View File

@@ -0,0 +1,189 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type*/}}
{{ define "meta" }}
{{- with extend $ "Package" $.Package -}}
{{ template "header" . }}
{{ end }}
{{ template "import" $ }}
const (
// Label holds the string label denoting the {{ lower $.Name }} type in the database.
Label = "{{ $.Label }}"
{{- if $.HasOneFieldID }}
// {{ $.ID.Constant }} holds the string denoting the id field in the database.
{{ $.ID.Constant }} = "{{ $.ID.StorageKey }}"
{{- end }}
{{- range $f := $.Fields }}
{{- $field := $f.Constant }}
// {{ $field }} holds the string denoting the {{ lower $f.Name }} field in the database.
{{ $field }} = "{{ $f.StorageKey }}"
{{- end }}
{{- range $e := $.Edges }}
{{- $edge := $e.Constant }}
// {{ $edge }} holds the string denoting the {{ lower $e.Name }} edge name in mutations.
{{ $edge }} = "{{ $e.Name }}"
{{- end }}
{{- $tmpl := printf "dialect/%s/meta/constants" $.Storage }}
{{- xtemplate $tmpl $ }}
)
{{ $tmpl = printf "dialect/%s/meta/variables" $.Storage }}
{{ if hasTemplate $tmpl }}
{{ xtemplate $tmpl $ }}
{{ end }}
{{ $tmpl = printf "dialect/%s/meta/functions" $.Storage }}
{{ if hasTemplate $tmpl }}
{{ xtemplate $tmpl $ }}
{{ end }}
{{/* Has at least one field (not enum) with default value */}}
{{ $fields := $.Fields }}{{ if $.HasOneFieldID }}{{ if $.ID.UserDefined }}{{ $fields = append $fields $.ID }}{{ end }}{{ end }}
{{ $hasDefault := false }}{{ range $f := $fields }}{{ if and $f.Default (not $f.IsEnum) }}{{ $hasDefault = true }}{{ end }}{{ end }}
{{/* Generate global variables for hooks, validators and policy checkers */}}
{{ if or $hasDefault $.HasUpdateDefault $.HasValidators $.NumHooks $.NumPolicy $.NumInterceptors $.HasValueScanner $.HasBlobFields }}
{{- $numHooks := $.NumHooks }}
{{- if $.NumPolicy }}
{{- $numHooks = add $numHooks 1 }}
{{- end }}
{{- if or $numHooks $.NumInterceptors }}
// Note that the variables below are initialized by the runtime
// package on the initialization of the application. Therefore,
// it should be imported in the main as follows:
//
// import _ "{{ $.Config.Package }}/runtime"
//
{{- end }}
var (
{{- if $n := $numHooks }}
Hooks [{{ $n }}]ent.Hook
{{- end }}
{{- if $n := $.NumInterceptors }}
Interceptors [{{ $n }}]ent.Interceptor
{{- end }}
{{- if $.NumPolicy }}
Policy ent.Policy
{{- end }}
{{- $fields := $.Fields }}{{ if $.HasOneFieldID }}{{ if $.ID.UserDefined }}{{ $fields = append $fields $.ID }}{{ end }}{{ end }}
{{- range $f := $fields }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{- if and $f.Default (not $f.IsEnum) }}
{{- $default := $f.DefaultName }}
// {{ $default }} holds the default value on creation for the "{{ $f.Name }}" field.
{{ $default }} {{ if $f.DefaultFunc }}func() {{ end }}{{ $f.Type }}
{{- end }}
{{- if $f.UpdateDefault }}
{{- $default := $f.UpdateDefaultName }}
// {{ $default }} holds the default value on update for the "{{ $f.Name }}" field.
{{ $default }} func() {{ $f.Type }}
{{- end }}
{{- with $f.Validators }}
{{- $name := $f.Validator }}
{{- $type := $f.Type.Type.String }}
{{- if $f.IsJSON }}
{{- $type = $f.Type.String }}
{{- end }}
// {{ $name }} is a validator for the "{{ $f.Name }}" field. It is called by the builders before save.
{{ $name }} {{ printf "func (%s) error" $type }}
{{- end }}
{{- end }}
{{- if hasTemplate (printf "dialect/%s/model/fields" $.Storage) }}
{{- range $f := $.BlobFields }}
{{- if $f.HasBlobKey }}
// {{ $f.BlobKeyName }} generates the blob storage key for the "{{ $f.Name }}" field.
{{ $f.BlobKeyName }} ent.BlobKeyFunc
{{- end }}
{{- end }}
{{- end }}
{{- if $.HasValueScanner }}
// ValueScanner of all {{ $.Name }} fields.
ValueScanner struct {
{{- $fields := $.Fields }}{{ if $.HasOneFieldID }}{{ if $.ID.UserDefined }}{{ $fields = append $fields $.ID }}{{ end }}{{ end }}
{{- range $f := $fields }}
{{- $idscan := and $.HasOneFieldID (eq $f.Name $.ID.Name) }}
{{- if and $f.HasValueScanner (or (not $idscan) (eq $.Storage.Name "sql")) }}
{{ $f.StructField }} field.TypeValueScanner[{{ $f.Type }}]
{{- end }}
{{- end }}
}
{{- end }}
)
{{ end }}
{{/* define custom type for enum fields */}}
{{ range $f := $.EnumFields }}
{{ $enum := $f.Type }}
{{ $receiver := receiver $f.BuilderField }}
{{ if $f.HasGoType }}
{{ if $f.Default }}
const {{ $f.DefaultName }} {{ $enum }} = "{{ $f.DefaultValue }}"
{{ end }}
{{ else }}
{{/* Omit the package name from the type. */}}
{{ $enum = trimPackage $f.Type.String $.Package }}
// {{ $enum }} defines the type for the "{{ $f.Name }}" enum field.
type {{ $enum }} string
{{- if $f.Default }}
{{- /* Find the enum that holds the default value. */ -}}
{{- range $e := $f.Enums }}
{{- if eq $e.Value $f.DefaultValue }}
// {{ $e.Name }} is the default value of the {{ $enum }} enum.
const {{ $f.DefaultName }} = {{ $e.Name }}
{{- end }}
{{- end }}
{{- end }}
// {{ $enum }} values.
const (
{{- range $e := $f.Enums }}
{{ $e.Name }} {{ $enum }} = "{{ $e.Value }}"
{{- end }}
)
func ({{ $receiver }} {{ $enum }}) String() string {
return string({{ $receiver }})
}
{{ end }}
{{ $name := $f.Validator }}
// {{ $name }} is a validator for the "{{ $f.Name }}" field enum values. It is called by the builders before save.
func {{ $name }}({{ $receiver }} {{ $enum }}) error {
switch {{ $receiver }}{{ if $f.Type.Stringer }}.String(){{ end }} {
case {{ range $i, $e := $f.Enums }}{{ if ne $i 0 }},{{ end }}{{ if $f.HasGoType }}"{{ $e.Value }}"{{ else }}{{ $e.Name }}{{ end }}{{ end }}:
return nil
default:
return fmt.Errorf("{{ $.Package }}: invalid enum value for {{ $f.Name }} field: %q", {{ $receiver }})
}
}
{{ end }}
{{ $tmpl = printf "dialect/%s/meta/order" $.Storage }}
// OrderOption defines the ordering options for the {{ $.Name }} queries.
type OrderOption func({{ $.Config.Storage.Builder }})
{{ if hasTemplate $tmpl }}
{{ xtemplate $tmpl $ }}
{{ end }}
{{ template "meta/additional" $ }}
{{ with $tmpls := matchTemplate "meta/additional/*" }}
{{ range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{ end }}
{{ end }}
{{ end }}
{{/* A template that can be overridden in order to add additional code for the type package. */}}
{{ define "meta/additional" }}{{ end }}

View File

@@ -0,0 +1,77 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "migrate" }}
{{- with extend $ "Package" "migrate" -}}
{{ template "header" . }}
{{ end }}
import (
"context"
"fmt"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql/schema"
)
var (
// WithGlobalUniqueID sets the universal ids options to the migration.
// If this option is enabled, ent migration will allocate a 1<<32 range
// for the ids of each entity (table).
// Note that this option cannot be applied on tables that already exist.
WithGlobalUniqueID = schema.WithGlobalUniqueID
// WithDropColumn sets the drop column option to the migration.
// If this option is enabled, ent migration will drop old columns
// that were used for both fields and edges. This defaults to false.
WithDropColumn = schema.WithDropColumn
// WithDropIndex sets the drop index option to the migration.
// If this option is enabled, ent migration will drop old indexes
// that were defined in the schema. This defaults to false.
// Note that unique constraints are defined using `UNIQUE INDEX`,
// and therefore, it's recommended to enable this option to get more
// flexibility in the schema changes.
WithDropIndex = schema.WithDropIndex
// WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true.
WithForeignKeys = schema.WithForeignKeys
)
// Schema is the API for creating, migrating and dropping a schema.
type Schema struct {
drv dialect.Driver
}
// NewSchema creates a new schema client.
func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} }
// Create creates all schema resources.
func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error {
return Create(ctx, s, Tables, opts...)
}
// Create creates all table resources using the given schema driver.
func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error {
migrate, err := schema.NewMigrate(s.drv, opts...)
if err != nil {
return fmt.Errorf("ent/migrate: %w", err)
}
return migrate.Create(ctx, tables...)
}
{{ if $.Config.FeatureEnabled "sql/versioned-migration" }}{{ template "migrate/diff" $ }}{{ end }}
// WriteTo writes the schema changes to w instead of running them against the database.
//
// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil {
// log.Fatal(err)
// }
//
func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error {
return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv,}}, Tables, opts...)
}
{{ end }}

View File

@@ -0,0 +1,245 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "schema" }}
{{- with extend $ "Package" "migrate" -}}
{{ template "header" . }}
{{ end }}
import (
{{ range $n := $.Nodes }}
{{ $n.PackageAlias }} "{{ $.Config.Package }}/{{ $n.PackageDir }}"
{{- end }}
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/dialect/sql/schema"
"entgo.io/ent/schema/field"
)
var (
{{- range $t := $.Tables }}
{{- /* find type node in the graph of this table, if exists */}}
{{- $node := index $.Nodes 0 }}
{{- range $n := $.Nodes }}{{ if eq $t.Name $n.Table }}{{ $node = $n }}{{ end }}{{ end }}
{{- $columns := pascal $t.Name | printf "%sColumns" }}
// {{ $columns }} holds the columns for the "{{ $t.Name }}" table.
{{ $columns }} = []*schema.Column{
{{- range $c := $t.Columns }}
{ Name: "{{ $c.Name }}", Type: field.{{ $c.Type.ConstName }},
{{- if $c.Unique }} Unique: true,{{ end }}
{{- if $c.Increment }} Increment: true,{{ end }}
{{- if $c.Nullable }} Nullable: {{ $c.Nullable }},{{ end }}
{{- with $c.Size }} Size: {{ . }},{{ end }}
{{- with $c.Comment }} Comment: "{{ $c.Comment }}",{{ end }}
{{- with $c.Attr }} Attr: "{{ . }}",{{ end }}
{{- with $c.Enums }} Enums: []string{ {{ range $e := . }}"{{ $e }}",{{ end }} },{{ end }}
{{- if not (isNil $c.Default) -}}
{{- $t := printf "%T" $c.Default -}}
{{- if eq $t "schema.Expr" -}}
Default: schema.Expr("{{ $c.Default }}"),
{{- else if eq $t "map[string]schema.Expr" -}}
Default: map[string]schema.Expr{ {{ range $k := keys $c.Default }} "{{ $k }}": "{{ index $c.Default $k }}",{{ end }} },
{{- else -}}
Default: {{ quote $c.Default }},
{{- end -}}
{{- end }}
{{- if $c.Collation }} Collation: "{{ $c.Collation }}",{{ end }}
{{- with $c.SchemaType }} SchemaType: map[string]string{ {{ range $k := keys . }}"{{ $k }}": "{{ index $c.SchemaType $k }}",{{ end }}}{{ end }}},
{{- end }}
}
{{- $table := pascal $t.Name | printf "%sTable" }}
// {{ $table }} holds the schema information for the "{{ $t.Name }}" table.
{{ $table }} = &schema.Table{
Name: "{{ $t.Name }}",
{{- with $t.Comment }}
Comment: "{{ . }}",
{{- end }}
Columns: {{ $columns }},
PrimaryKey: []*schema.Column{
{{- range $pk := $t.PrimaryKey }}
{{- range $i, $c := $t.Columns }}
{{- if eq $pk.Name $c.Name }}{{ $columns }}[{{ $i }}],{{ end }}
{{- end }}
{{- end }}
},
{{- with $fks := $t.ForeignKeys }}
ForeignKeys: []*schema.ForeignKey{
{{- range $fk := $fks }}
{
Symbol: "{{ $fk.Symbol }}",
Columns: []*schema.Column{
{{- range $c1 := $fk.Columns }}
{{- range $i, $c2 := $t.Columns }}
{{- if eq $c1.Name $c2.Name }}{{ $columns }}[{{ $i }}],{{ end }}
{{- end }}
{{- end }}
},
{{- /* postpone referencing to avoid typechecking loops */}}
RefColumns: []*schema.Column{
{{- range $c1 := $fk.RefColumns }}
{{- range $i, $c2 := $fk.RefTable.Columns }}
{{- if eq $c1.Name $c2.Name }}{{ pascal $fk.RefTable.Name | printf "%sColumns" }}[{{ $i }}],{{ end }}
{{- end }}
{{- end }}
},
{{- with $fk.OnUpdate.ConstName }}
OnUpdate: schema.{{ . }},
{{- end }}
{{- with $fk.OnDelete.ConstName }}
OnDelete: schema.{{ . }},
{{- end }}
},
{{- end }}
},
{{- end }}
{{- if $t.Indexes }}
Indexes: []*schema.Index{
{{- range $idx := $t.Indexes }}
{
Name: "{{ $idx.Name }}",
Unique: {{ $idx.Unique }},
Columns: []*schema.Column{
{{- range $c1 := $idx.Columns }}
{{- range $i, $c2 := $t.Columns }}
{{- if eq $c1.Name $c2.Name }}{{ $columns }}[{{ $i }}],{{ end }}
{{- end }}
{{- end }}
},
{{- with $ant := $idx.Annotation }}
Annotation: &entsql.IndexAnnotation{
{{- with $ant.Prefix }}
Prefix: {{ . }},
{{- end }}
{{- with $keys := keys $ant.PrefixColumns }}
PrefixColumns: map[string]uint{
{{- range $k := $keys }}
{{- /* Use the column reference instead of using raw string. */}}
{{- range $i, $c := $t.Columns }}
{{- if eq $k $c.Name }}
{{ $columns }}[{{ $i }}].Name: {{ index $ant.PrefixColumns $k }},
{{ end }}
{{- end }}
{{- end }}
},
{{- end }}
{{- with $ant.OpClass }}
OpClass: "{{ . }}",
{{- end }}
{{- with $keys := keys $ant.OpClassColumns }}
OpClassColumns: map[string]string{
{{- range $k := $keys }}
{{- /* Use the column reference instead of using raw string. */}}
{{- range $i, $c := $t.Columns }}
{{- if eq $k $c.Name }}
{{ $columns }}[{{ $i }}].Name: "{{ index $ant.OpClassColumns $k }}",
{{ end }}
{{- end }}
{{- end }}
},
{{- end }}
{{- with $ant.Desc }}
Desc: {{ . }},
{{- end }}
{{- with $keys := keys $ant.DescColumns }}
DescColumns: map[string]bool{
{{- range $k := $keys }}
{{- /* Use the column reference instead of using raw string. */}}
{{- range $i, $c := $t.Columns }}
{{- if eq $k $c.Name }}
{{ $columns }}[{{ $i }}].Name: {{ index $ant.DescColumns $k }},
{{ end }}
{{- end }}
{{- end }}
},
{{- end }}
{{- with $ant.IncludeColumns }}
IncludeColumns: []string{
{{- range $ic := . }}
{{- range $i, $c := $t.Columns }}
{{- if eq $ic $c.Name }}
{{ $columns }}[{{ $i }}].Name,
{{ end }}
{{- end }}
{{- end }}
},
{{- end }}
{{- with $ant.Type }}
Type: "{{ . }}",
{{- end }}
{{- with $keys := keys $ant.Types }}
Types: map[string]string{
{{- range $k := $keys }}
"{{ $k }}": "{{ index $ant.Types $k }}",
{{- end }}
},
{{- end }}
{{- with $ant.Where }}
Where: {{ quote . }},
{{- end }}
},
{{- end }}
},
{{- end }}
},
{{- end }}
}
{{- end }}
// Tables holds all the tables in the schema.
Tables = []*schema.Table{
{{- range $t := $.Tables }}
{{ pascal $t.Name | printf "%sTable" }},
{{- end }}
}
)
func init() {
{{- range $t := $.Tables }}
{{- $table := pascal $t.Name | printf "%sTable" }}
{{- range $i, $fk := $t.ForeignKeys }}
{{ $table }}.ForeignKeys[{{ $i }}].RefTable = {{ pascal $fk.RefTable.Name | printf "%sTable" }}
{{- end }}
{{- with $ant := $t.Annotation }}
{{- if not (allZero $ant.Table $ant.Charset $ant.Collation $ant.Options $ant.Check $ant.IncrementStart $ant.Incremental $ant.Checks) }}
{{ $table }}.Annotation = &entsql.Annotation{
{{- with $ant.Table }}
Table: "{{ . }}",
{{- end }}
{{- with $ant.Charset }}
Charset: "{{ . }}",
{{- end }}
{{- with $ant.Collation }}
Collation: "{{ . }}",
{{- end }}
{{- with $ant.Options }}
Options: {{ quote . }},
{{- end }}
{{- with $ant.Check }}
Check: {{ quote . }},
{{- end }}
{{- with $ant.IncrementStart }}
IncrementStart: func(i int) *int { return &i }({{ . }}),
{{- end }}
}
{{- with $ant.Incremental }}
{{ $table }}.Annotation.Incremental = new(bool)
*{{ $table }}.Annotation.Incremental = {{ with indirect . }}true{{ else }}false{{ end }}
{{- end }}
{{- with $keys := keys $ant.Checks }}
{{ $table }}.Annotation.Checks = map[string]string{
{{- range $k := $keys }}
"{{ $k }}": {{ index $ant.Checks $k | quote }},
{{- end }}
}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
}
{{ end }}

View File

@@ -0,0 +1,654 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "mutation" }}
{{- with extend $ "Package" $.Package -}}
{{ template "header" . }}
{{ end }}
{{ template "import" $ }}
{{/* localType strips the current package prefix from type strings, so that
types defined in this package (e.g. enums) are referenced without the
package qualifier. */}}
{{ $pkgPrefix := print $.Package "." }}
{{ $blobSupported := hasTemplate (printf "dialect/%s/model/fields" $.Storage) }}
// Mutation represents an operation that mutates the {{ $.Name }} nodes in the graph.
type Mutation struct {
op ent.Op
typ string
{{- range $f := $.MutationFields }}
{{ $f.BuilderField }} *{{ replace $f.Type.String $pkgPrefix "" }}
{{- if $f.SupportsMutationAdd }}
add{{ $f.BuilderField }} *{{ replace (print $f.SignedType) $pkgPrefix "" }}
{{- end }}
{{- if $f.SupportsMutationAppend }}
append{{ $f.BuilderField }} {{ replace $f.Type.String $pkgPrefix "" }}
{{- end }}
{{- end }}
{{- if hasTemplate (printf "dialect/%s/model/fields" $.Storage) }}
{{- range $f := $.BlobFields }}
{{- if not $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ $f.BuilderField }} io.Reader
{{- end }}
{{- end }}
clearedFields map[string]struct{}
{{- range $e := $.EdgesWithID }}
{{- if $e.Unique }}
{{ $e.BuilderField }} *{{ $e.Type.ID.Type }}
{{- else }}
{{ $e.BuilderField }} map[{{ $e.Type.ID.Type }}]struct{}
removed{{ $e.BuilderField }} map[{{ $e.Type.ID.Type }}]struct{}
{{- end }}
cleared{{ $e.BuilderField }} bool
{{- end }}
predicates []predicate.{{ $.Name }}
{{- /* Additional fields can be added by user templates. */}}
{{- with $tmpls := matchTemplate "mutation/fields/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
}
// NewMutation creates a new Mutation for the {{ $.Name }} entity.
func NewMutation(op ent.Op) *Mutation {
return &Mutation{
op: op,
typ: "{{ $.Name }}",
clearedFields: make(map[string]struct{}),
}
}
// Predicates returns the list of predicates set on the mutation.
func (m *Mutation) Predicates() []predicate.{{ $.Name }} {
return m.predicates
}
{{ range $f := $.Fields }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ $const := $f.Constant }}
{{ $type := replace $f.Type.String $pkgPrefix "" }}
{{ $p := receiver $f.Type.String }}{{ if eq $p "m" }} {{ $p = "value" }} {{ end }}
{{ $func := $f.MutationSet }}
// {{ $func }} sets the "{{ $f.Name }}" field.
func (m *Mutation) {{ $func }}({{ $p }} {{ $type }}) {
m.{{ $f.BuilderField }} = &{{ $p }}
{{- /* Setting numeric type override previous calls to Add. */}}
{{- if $f.SupportsMutationAdd }}
m.add{{ $f.BuilderField }} = nil
{{- end }}
{{- /* Setting JSON type override previous calls to Append. */}}
{{- if $f.SupportsMutationAppend }}
m.append{{ $f.BuilderField }} = nil
{{- end }}
}
// {{ $f.MutationGet }} returns the value of the "{{ $f.Name }}" field in the mutation.
func (m *Mutation) {{ $f.MutationGet }}() (r {{ $type }}, exists bool) {
v := m.{{ $f.BuilderField }}
if v == nil {
return
}
return *v, true
}
{{ if $f.SupportsMutationAdd }}
{{ $signedType := replace (print $f.SignedType) $pkgPrefix "" }}
// {{ $f.MutationAdd }} adds {{ $p }} to the "{{ $f.Name }}" field.
func (m *Mutation) {{ $f.MutationAdd }}({{ $p }} {{ $signedType }}) {
{{- $structField := print "m.add" $f.BuilderField }}
if {{ $structField }} != nil {
{{ $f.MutationAddAssignExpr $structField $p }}
} else {
{{ $structField }} = &{{ $p }}
}
}
// {{ $f.MutationAdded }} returns the value that was added to the "{{ $f.Name }}" field in this mutation.
func (m *Mutation) {{ $f.MutationAdded }}() (r {{ $signedType }}, exists bool) {
v := m.add{{ $f.BuilderField }}
if v == nil {
return
}
return *v, true
}
{{ end }}
{{ if $f.SupportsMutationAppend }}
{{- $structField := print "m.append" $f.BuilderField }}
// {{ $f.MutationAppend }} adds {{ $p }} to the "{{ $f.Name }}" field.
func (m *Mutation) {{ $f.MutationAppend }}({{ $p }} {{ $type }}) {
{{ $structField }} = append({{ $structField }}, {{ $p }}...)
}
// {{ $f.MutationAppended }} returns the list of values that were appended to the "{{ $f.Name }}" field in this mutation.
func (m *Mutation) {{ $f.MutationAppended }}() ({{ $type }}, bool) {
if len({{ $structField }}) == 0 {
return nil, false
}
return {{ $structField }}, true
}
{{ end }}
{{ if $f.Optional }}
// {{ $f.MutationClear }} clears the value of the "{{ $f.Name }}" field.
func (m *Mutation) {{ $f.MutationClear }}() {
m.{{ $f.BuilderField }} = nil
{{- if $f.SupportsMutationAdd }}
m.add{{ $f.BuilderField }} = nil
{{- end }}
{{- if $f.SupportsMutationAppend }}
m.append{{ $f.BuilderField }} = nil
{{- end }}
m.clearedFields[{{ $const }}] = struct{}{}
}
// {{ $f.MutationCleared }} returns if the "{{ $f.Name }}" field was cleared in this mutation.
func (m *Mutation) {{ $f.MutationCleared }}() bool {
_, ok := m.clearedFields[{{ $const }}]
return ok
}
{{ end }}
// {{ $f.MutationReset }} resets all changes to the "{{ $f.Name }}" field.
func (m *Mutation) {{ $f.MutationReset }}() {
m.{{ $f.BuilderField }} = nil
{{- if $f.SupportsMutationAdd }}
m.add{{ $f.BuilderField }} = nil
{{- end }}
{{- if $f.SupportsMutationAppend }}
m.append{{ $f.BuilderField }} = nil
{{- end }}
{{- if $f.Optional }}
delete(m.clearedFields, {{ $const }})
{{- end }}
}
{{ end }}
{{- if hasTemplate (printf "dialect/%s/model/fields" $.Storage) }}
{{- range $f := $.BlobFields }}
{{- if not $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ $const := $f.Constant }}
{{ $func := print "Set" $f.StructField }}
// {{ $func }} sets the "{{ $f.Name }}" field.
func (m *Mutation) {{ $func }}(r io.Reader) {
m.{{ $f.BuilderField }} = r
}
// {{ $f.StructField }} returns the value of the "{{ $f.Name }}" field in the mutation.
func (m *Mutation) {{ $f.StructField }}() (r io.Reader, exists bool) {
v := m.{{ $f.BuilderField }}
if v == nil {
return
}
return v, true
}
{{- if $f.Optional }}
// {{ $f.MutationClear }} clears the value of the "{{ $f.Name }}" field.
func (m *Mutation) {{ $f.MutationClear }}() {
m.{{ $f.BuilderField }} = nil
m.clearedFields[{{ $const }}] = struct{}{}
}
// {{ $f.MutationCleared }} returns if the "{{ $f.Name }}" field was cleared in this mutation.
func (m *Mutation) {{ $f.MutationCleared }}() bool {
_, ok := m.clearedFields[{{ $const }}]
return ok
}
{{- end }}
// {{ $f.MutationReset }} resets all changes to the "{{ $f.Name }}" field.
func (m *Mutation) {{ $f.MutationReset }}() {
m.{{ $f.BuilderField }} = nil
{{- if $f.Optional }}
delete(m.clearedFields, {{ $const }})
{{- end }}
}
{{- end }}
{{- end }}
{{ range $e := $.EdgesWithID }}
{{ $op := "add" }}{{ $idsFunc := $e.MutationAdd }}{{ if $e.Unique }}{{ $op = "set" }}{{ $idsFunc = $e.MutationSet }}{{ end }}
{{/* Check if this setter was already defined by the field-setters (e.g. edge-field with the same name). */}}
{{ $withSetGet := not $e.HasFieldSetter }}
{{ if $withSetGet }}
// {{ $idsFunc }} {{ $op }}s the "{{ $e.Name }}" edge to the {{ $e.Type.Name }} entity by id{{ if not $e.Unique }}s{{ end }}.
func (m *Mutation) {{ $idsFunc }}({{ if $e.Unique }}id{{ else }}ids ...{{ end }} {{ $e.Type.ID.Type }}) {
{{- if $e.Unique }}
m.{{ $e.BuilderField }} = &id
{{- else }}
if m.{{ $e.BuilderField }} == nil {
m.{{ $e.BuilderField }} = make(map[{{ $e.Type.ID.Type }}]struct{})
}
for i := range ids {
m.{{ $e.BuilderField }}[ids[i]] = struct{}{}
}
{{- end }}
}
{{ end }}
{{ $func := $e.MutationClear }}
// {{ $func }} clears the "{{ $e.Name }}" edge to the {{ $e.Type.Name }} entity.
func (m *Mutation) {{ $func }}() {
m.cleared{{ $e.BuilderField }} = true
{{- if $e.Field }}
{{- $const := $e.Field.Constant }}
m.clearedFields[{{ $const }}] = struct{}{}
{{- end }}
}
{{ $func = $e.MutationCleared }}
// {{ $func }} reports if the "{{ $e.Name }}" edge to the {{ $e.Type.Name }} entity was cleared.
func (m *Mutation) {{ $func }}() bool {
return {{ with $e.Field }}{{ if .Optional }}m.{{ .MutationCleared }}() || {{ end }}{{ end }}m.cleared{{ $e.BuilderField }}
}
{{ if not $e.Unique }}
{{ $p := lower (printf "%.1s" $e.Type.Name) }}
// {{ $e.MutationRemove }} removes the "{{ $e.Name }}" edge to the {{ $e.Type.Name }} entity by IDs.
func (m *Mutation) {{ $e.MutationRemove }}(ids ...{{ $e.Type.ID.Type }}) {
if m.removed{{ $e.BuilderField }} == nil {
m.removed{{ $e.BuilderField }} = make(map[{{ $e.Type.ID.Type }}]struct{})
}
for i := range ids {
delete(m.{{ $e.BuilderField }}, ids[i])
m.removed{{ $e.BuilderField }}[ids[i]] = struct{}{}
}
}
{{ $func := print "Removed" $e.StructField }}
// {{ $func }} returns the removed IDs of the "{{ $e.Name }}" edge to the {{ $e.Type.Name }} entity.
func (m *Mutation) {{ $func }}IDs() (ids []{{ $e.Type.ID.Type }}) {
for id := range m.removed{{ $e.BuilderField }} {
ids = append(ids, id)
}
return
}
{{ else if and $e.Unique $withSetGet }}
// {{ $e.StructField }}ID returns the "{{ $e.Name }}" edge ID in the mutation.
func (m *Mutation) {{ $e.StructField }}ID() (id {{ $e.Type.ID.Type }}, exists bool) {
if m.{{ $e.BuilderField }} != nil {
return *m.{{ $e.BuilderField }}, true
}
return
}
{{ end }}
// {{ $e.StructField }}IDs returns the "{{ $e.Name }}" edge IDs in the mutation.
{{- if $e.Unique }}
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
// {{ $e.StructField }}ID instead. It exists only for internal usage by the builders.
{{- end }}
func (m *Mutation) {{ $e.StructField }}IDs() (ids []{{ $e.Type.ID.Type }}) {
{{- if $e.Unique }}
if id := m.{{ $e.BuilderField }}; id != nil {
ids = append(ids, *id)
}
{{- else }}
for id := range m.{{ $e.BuilderField }} {
ids = append(ids, id)
}
{{- end}}
return
}
{{ $func = $e.MutationReset }}
// {{ $func }} resets all changes to the "{{ $e.Name }}" edge.
func (m *Mutation) {{ $func }}() {
m.{{ $e.BuilderField }} = nil
m.cleared{{ $e.BuilderField }} = false
{{- if not $e.Unique }}
m.removed{{ $e.BuilderField }} = nil
{{- end }}
}
{{ end }}
// Where appends a list predicates to the Mutation builder.
func (m *Mutation) Where(ps ...predicate.{{ $.Name }}) {
m.predicates = append(m.predicates, ps...)
}
// WhereP appends storage-level predicates to the Mutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
func (m *Mutation) WhereP(ps ...func({{ $.Storage.Builder }})) {
p := make([]predicate.{{ $.Name }}, len(ps))
for i := range ps {
p[i] = ps[i]
}
m.Where(p...)
}
// Op returns the operation name.
func (m *Mutation) Op() ent.Op {
return m.op
}
// SetOp allows setting the mutation operation.
func (m *Mutation) SetOp(op ent.Op) {
m.op = op
}
// Type returns the node type of this mutation ({{ $.Name }}).
func (m *Mutation) Type() string {
return m.typ
}
// Fields returns all fields that were changed during this mutation. Note that in
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *Mutation) Fields() []string {
fields := make([]string, 0, {{ len $.Fields }})
{{- range $f := $.Fields }}
{{- if not $f.IsBlobLazy }}
if m.{{ $f.BuilderField }} != nil {
fields = append(fields, {{ $f.Constant }})
}
{{- end }}
{{- end }}
return fields
}
// Field returns the value of a field with the given name. The second boolean
// return value indicates that this field was not set, or was not defined in the
// schema.
func (m *Mutation) Field(name string) (ent.Value, bool) {
{{- with $.Fields }}
switch name {
{{- range $f := $.Fields }}
{{- if not $f.IsBlobLazy }}
case {{ $f.Constant }}:
return m.{{ $f.MutationGet }}()
{{- end }}
{{- end }}
}
{{- end }}
return nil, false
}
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
func (m *Mutation) OldField(ctx context.Context, name string) (ent.Value, error) {
{{- if $.HasCompositeID }}
return nil, errors.New("edge schema {{ $.Name }} does not support getting old values")
{{- else }}
return nil, fmt.Errorf("unknown {{ $.Name }} field %s", name)
{{- end }}
}
// SetField sets the value of a field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
func (m *Mutation) SetField(name string, value ent.Value) error {
switch name {
{{- range $f := $.Fields }}
{{- if not $f.IsBlobLazy }}
{{- $type := replace $f.Type.String $pkgPrefix "" }}
case {{ $f.Constant }}:
v, ok := value.({{ $type }})
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.{{ $f.MutationSet }}(v)
return nil
{{- end }}
{{- end }}
}
return fmt.Errorf("unknown {{ $.Name }} field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
func (m *Mutation) AddedFields() []string {
{{- if $.HasNumeric }}
var fields []string
{{- range $f := $.Fields }}
{{- if $f.SupportsMutationAdd }}
if m.add{{ $f.BuilderField }} != nil {
fields = append(fields, {{ $f.Constant }})
}
{{- end }}
{{- end }}
return fields
{{- else }}
return nil
{{- end }}
}
// AddedField returns the numeric value that was incremented/decremented on a field
// with the given name. The second boolean return value indicates that this field
// was not set, or was not defined in the schema.
func (m *Mutation) AddedField(name string) (ent.Value, bool) {
{{- if $.HasNumeric }}
switch name {
{{- range $f := $.Fields }}
{{- if $f.SupportsMutationAdd }}
case {{ $f.Constant }}:
return m.Added{{ $f.StructField }}()
{{- end }}
{{- end }}
}
{{- end }}
return nil, false
}
// AddField adds the value to the field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
func (m *Mutation) AddField(name string, value ent.Value) error {
{{- with $.Fields }}
switch name {
{{- range $f := $.Fields }}
{{- if $f.SupportsMutationAdd }}
{{- $signedType := replace (print $f.SignedType) $pkgPrefix "" }}
case {{ $f.Constant }}:
v, ok := value.({{ $signedType }})
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.Add{{ $f.StructField }}(v)
return nil
{{- end }}
{{- end }}
}
{{- end }}
return fmt.Errorf("unknown {{ $.Name }} numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *Mutation) ClearedFields() []string {
{{- if $.HasOptional }}
var fields []string
{{- range $f := $.Fields }}
{{- if $f.Optional }}
if m.FieldCleared({{ $f.Constant }}) {
fields = append(fields, {{ $f.Constant }})
}
{{- end }}
{{- end }}
return fields
{{- else }}
return nil
{{- end }}
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
func (m *Mutation) FieldCleared(name string) bool {
_, ok := m.clearedFields[name]
return ok
}
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
func (m *Mutation) ClearField(name string) error {
{{- if $.HasOptional }}
switch name {
{{- range $f := $.Fields }}
{{- if and $f.IsBlobLazy (not $blobSupported) }}{{ continue }}{{ end }}
{{- if $f.Optional }}
case {{ $f.Constant }}:
m.Clear{{ $f.StructField }}()
return nil
{{- end }}
{{- end }}
}
{{- end }}
return fmt.Errorf("unknown {{ $.Name }} nullable field %s", name)
}
// ResetField resets all changes in the mutation for the field with the given name.
// It returns an error if the field is not defined in the schema.
func (m *Mutation) ResetField(name string) error {
{{- with $.Fields }}
switch name {
{{- range $f := $.Fields }}
{{- if and $f.IsBlobLazy (not $blobSupported) }}{{ continue }}{{ end }}
case {{ $f.Constant }}:
m.{{ $f.MutationReset }}()
return nil
{{- end }}
}
{{- end }}
return fmt.Errorf("unknown {{ $.Name }} field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *Mutation) AddedEdges() []string {
edges := make([]string, 0, {{ len $.EdgesWithID }})
{{- range $e := $.EdgesWithID }}
if m.{{ $e.BuilderField }} != nil {
edges = append(edges, {{ $e.Constant }})
}
{{- end }}
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
func (m *Mutation) AddedIDs(name string) []ent.Value {
{{- with $.EdgesWithID }}
switch name {
{{- range $e := $.EdgesWithID }}
case {{ $e.Constant }}:
{{- if $e.Unique }}
if id := m.{{ $e.BuilderField }}; id != nil {
return []ent.Value{*id}
}
{{- else }}
ids := make([]ent.Value, 0, len(m.{{ $e.BuilderField }}))
for id := range m.{{ $e.BuilderField }} {
ids = append(ids, id)
}
return ids
{{- end }}
{{- end }}
}
{{- end }}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *Mutation) RemovedEdges() []string {
edges := make([]string, 0, {{ len $.EdgesWithID }})
{{- range $e := $.EdgesWithID }}
{{- if not $e.Unique }}
if m.removed{{ $e.BuilderField }} != nil {
edges = append(edges, {{ $e.Constant }})
}
{{- end }}
{{- end }}
return edges
}
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
// the given name in this mutation.
func (m *Mutation) RemovedIDs(name string) []ent.Value {
{{- $edges := list }}{{ range $e := $.EdgesWithID }}{{ if not $e.Unique }}{{ $edges = append $edges $e }}{{ end }}{{ end }}
{{- with $edges }}
switch name {
{{- range $e := $edges }}
case {{ $e.Constant }}:
ids := make([]ent.Value, 0, len(m.removed{{ $e.BuilderField }}))
for id := range m.removed{{ $e.BuilderField }} {
ids = append(ids, id)
}
return ids
{{- end }}
}
{{- end }}
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *Mutation) ClearedEdges() []string {
edges := make([]string, 0, {{ len $.EdgesWithID }})
{{- range $e := $.EdgesWithID }}
if m.cleared{{ $e.BuilderField }} {
edges = append(edges, {{ $e.Constant }})
}
{{- end }}
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
func (m *Mutation) EdgeCleared(name string) bool {
{{- with $.EdgesWithID }}
switch name {
{{- range $e := $.EdgesWithID }}
case {{ $e.Constant }}:
return m.cleared{{ $e.BuilderField }}
{{- end }}
}
{{- end }}
return false
}
// ClearEdge clears the value of the edge with the given name. It returns an error
// if that edge is not defined in the schema.
func (m *Mutation) ClearEdge(name string) error {
{{- with $.EdgesWithID }}
switch name {
{{- range $e := $.EdgesWithID }}
{{- if $e.Unique }}
case {{ $e.Constant }}:
m.{{ $e.MutationClear }}()
return nil
{{- end }}
{{- end }}
}
{{- end }}
return fmt.Errorf("unknown {{ $.Name }} unique edge %s", name)
}
// ResetEdge resets all changes to the edge with the given name in this mutation.
// It returns an error if the edge is not defined in the schema.
func (m *Mutation) ResetEdge(name string) error {
{{- with $.EdgesWithID }}
switch name {
{{- range $e := $.EdgesWithID }}
case {{ $e.Constant }}:
m.{{ $e.MutationReset }}()
return nil
{{- end }}
}
{{- end }}
return fmt.Errorf("unknown {{ $.Name }} edge %s", name)
}
{{ end }}

View File

@@ -0,0 +1,39 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "predicate" }}
{{- with extend $ "Package" "predicate" -}}
{{ template "header" . }}
{{ end }}
import (
"fmt"
"entgo.io/ent/dialect/gremlin/graph/dsl"
"entgo.io/ent/dialect/sql"
)
{{ range $n := $.Nodes -}}
// {{ $n.Name }} is the predicate function for {{ $n.Package }} builders.
type {{ $n.Name }} func({{ $.Storage.Builder }})
{{- with $n.HasValueScanner }}
// {{ $n.Name }}OrErr calls the predicate only if the error is not nit.
func {{ $n.Name }}OrErr(p {{ $n.Name }}, err error) {{ $n.Name }} {
return func(s {{ $.Storage.Builder }}) {
if err != nil {
s.AddError(err)
return
}
p(s)
}
}
{{- end }}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,69 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "privacy/filter" }}
{{ $pkg := base $.Config.Package }}
type (
// Filter is the interface that wraps the Where function
// for filtering nodes in queries and mutations.
Filter interface {
// Where applies a filter on the executed query/mutation.
Where(entql.P)
}
// The FilterFunc type is an adapter that allows the use of ordinary
// functions as filters for query and mutation types.
FilterFunc func(context.Context, Filter) error
)
// EvalQuery calls f(ctx, q) if the query implements the Filter interface, otherwise it is denied.
func (f FilterFunc) EvalQuery(ctx context.Context, q {{ $pkg }}.Query) error {
fr, err := queryFilter(q)
if err != nil {
return err
}
return f(ctx, fr)
}
// EvalMutation calls f(ctx, q) if the mutation implements the Filter interface, otherwise it is denied.
func (f FilterFunc) EvalMutation(ctx context.Context, m {{ $pkg }}.Mutation) error {
fr, err := mutationFilter(m)
if err != nil {
return err
}
return f(ctx, fr)
}
var _ QueryMutationRule = FilterFunc(nil)
func queryFilter(q {{ $pkg }}.Query) (Filter, error) {
switch q := q.(type) {
{{- range $n := $.Nodes }}
case *{{ $pkg }}.{{ $n.QueryName }}:
return q.Filter(), nil
{{- end }}
default:
return nil, Denyf("{{ $pkg }}/privacy: unexpected query type %T for query filter", q)
}
}
func mutationFilter(m {{ $pkg }}.Mutation) (Filter, error) {
switch m := m.(type) {
{{- range $n := $.MutableNodes }}
case *{{ $pkg }}.{{ $n.MutationName }}:
return m.Filter(), nil
{{- end }}
default:
return nil, Denyf("{{ $pkg }}/privacy: unexpected mutation type %T for mutation filter", m)
}
}
{{ end }}

View File

@@ -0,0 +1,160 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "privacy" }}
{{ with extend $ "Package" "privacy" -}}
{{ template "header" . }}
{{ end }}
import (
"context"
"{{ $.Config.Package }}"
"entgo.io/ent/privacy"
"entgo.io/ent/entql"
)
{{ $pkg := base $.Config.Package }}
var (
// Allow may be returned by rules to indicate that the policy
// evaluation should terminate with allow decision.
Allow = privacy.Allow
// Deny may be returned by rules to indicate that the policy
// evaluation should terminate with deny decision.
Deny = privacy.Deny
// Skip may be returned by rules to indicate that the policy
// evaluation should continue to the next rule.
Skip = privacy.Skip
)
{{- range $decision := list "Allow" "Deny" "Skip" }}
// {{ $decision }}f returns a formatted wrapped {{ $decision }} decision.
func {{ $decision }}f(format string, a ...any) error {
return privacy.{{ $decision }}f(format, a...)
}
{{- end }}
// DecisionContext creates a new context from the given parent context with
// a policy decision attach to it.
func DecisionContext(parent context.Context, decision error) context.Context {
return privacy.DecisionContext(parent, decision)
}
// DecisionFromContext retrieves the policy decision from the context.
func DecisionFromContext(ctx context.Context) (error, bool) {
return privacy.DecisionFromContext(ctx)
}
type (
// Policy groups query and mutation policies.
Policy = privacy.Policy
// QueryRule defines the interface deciding whether a
// query is allowed and optionally modify it.
QueryRule = privacy.QueryRule
// QueryPolicy combines multiple query rules into a single policy.
QueryPolicy = privacy.QueryPolicy
// MutationRule defines the interface which decides whether a
// mutation is allowed and optionally modifies it.
MutationRule = privacy.MutationRule
// MutationPolicy combines multiple mutation rules into a single policy.
MutationPolicy = privacy.MutationPolicy
// MutationRuleFunc type is an adapter which allows the use of
// ordinary functions as mutation rules.
MutationRuleFunc = privacy.MutationRuleFunc
// QueryMutationRule is an interface which groups query and mutation rules.
QueryMutationRule = privacy.QueryMutationRule
)
// QueryRuleFunc type is an adapter to allow the use of
// ordinary functions as query rules.
type QueryRuleFunc func(context.Context, {{ $pkg }}.Query) error
// Eval returns f(ctx, q).
func (f QueryRuleFunc) EvalQuery(ctx context.Context, q {{ $pkg }}.Query) error {
return f(ctx, q)
}
// AlwaysAllowRule returns a rule that returns an allow decision.
func AlwaysAllowRule() QueryMutationRule {
return privacy.AlwaysAllowRule()
}
// AlwaysDenyRule returns a rule that returns a deny decision.
func AlwaysDenyRule() QueryMutationRule {
return privacy.AlwaysDenyRule()
}
// ContextQueryMutationRule creates a query/mutation rule from a context eval func.
func ContextQueryMutationRule(eval func(context.Context) error) QueryMutationRule {
return privacy.ContextQueryMutationRule(eval)
}
// OnMutationOperation evaluates the given rule only on a given mutation operation.
func OnMutationOperation(rule MutationRule, op {{ $pkg }}.Op) MutationRule {
return privacy.OnMutationOperation(rule, op)
}
// DenyMutationOperationRule returns a rule denying specified mutation operation.
func DenyMutationOperationRule(op {{ $pkg }}.Op) MutationRule {
rule := MutationRuleFunc(func(_ context.Context, m {{ $pkg }}.Mutation) error {
return Denyf("{{ $pkg }}/privacy: operation %s is not allowed", m.Op())
})
return OnMutationOperation(rule, op)
}
{{- range $n := $.Nodes }}
{{ $name := print $n.Name "QueryRuleFunc" }}
{{ $type := printf "*%s.%s" $pkg $n.QueryName }}
// The {{ $name }} type is an adapter to allow the use of ordinary
// functions as a query rule.
type {{ $name }} func(context.Context, {{ $type }}) error
// EvalQuery return f(ctx, q).
func (f {{ $name }}) EvalQuery(ctx context.Context, q {{ $pkg }}.Query) error {
if q, ok := q.({{ $type }}); ok {
return f(ctx, q)
}
return Denyf("{{ $pkg }}/privacy: unexpected query type %T, expect {{ $type }}", q)
}
{{ if not $n.IsView }}
{{ $name = print $n.Name "MutationRuleFunc" }}
{{ $type = printf "*%s.%s" $pkg $n.MutationName }}
// The {{ $name }} type is an adapter to allow the use of ordinary
// functions as a mutation rule.
type {{ $name }} func(context.Context, {{ $type }}) error
// EvalMutation calls f(ctx, m).
func (f {{ $name }}) EvalMutation(ctx context.Context, m {{ $pkg }}.Mutation) error {
if m, ok := m.({{ $type }}); ok {
return f(ctx, m)
}
return Denyf("{{ $pkg }}/privacy: unexpected mutation type %T, expect {{ $type }}", m)
}
{{- end }}
{{- end }}
{{- if $.FeatureEnabled "entql" }}
{{ template "privacy/filter" $ }}
{{- end }}
{{- with $tmpls := matchTemplate "privacy/additional/*" "privacy/additional/*/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,250 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{/*
The runtime template generates the schema-stitching logic and holds the
module version that was used to generate the assets.
It has 2 formats. A "runtime" package that should be empty-imported in the
main package for schemas with hooks or policies (potential cyclic-import).
The second format is generated under the "ent" package, and empty-import is
not necessary (no option for cyclic-import). The second format used to keep
backwards-compatibility with previous versions of ent.
*/}}
{{ define "runtime/ent" }}
{{ template "runtime" $ }}
{{ end }}
{{ define "runtime/pkg" }}
{{ with extend $ "Package" "runtime" }}
{{ template "runtime" . }}
{{ end }}
{{ $module := $.ModuleInfo }}
{{ if or $module.Version $module.Sum }}
const (
{{- with $module.Version }}
Version = "{{ . }}" // Version of ent codegen.
{{- end }}
{{- with $module.Sum }}
Sum = "{{ . }}" // Sum of ent codegen.
{{- end }}
)
{{ end }}
{{ end }}
{{ define "runtime" }}
{{ template "header" . }}
{{ $hooks := 0 }}
{{ range $n := $.Nodes }}
{{ $numHooks := add $n.NumHooks $n.NumInterceptors }}{{ if $n.NumPolicy }}{{ $numHooks = add $numHooks 1 }}{{ end }}
{{ $hooks = add $hooks $numHooks }}
{{ end }}
{{ $rtpkg := false }}{{ if hasField $ "Scope" }}{{ $rtpkg = eq $.Scope.Package "runtime" }}{{ end }}
{{ if $rtpkg }} {{/* 1st format - runtime/runtime.go */}}
{{ if $hooks }}
{{ template "runtime/register" $ }}
{{ else }}
// The schema-stitching logic is generated in {{ $.Config.Package }}/runtime.go
{{ end }}
{{ else }}{{/* 2nd format - ent/runtime.go */}}
{{ if not $hooks }}
{{ template "runtime/register" $ }}
{{ else }}
// The schema-stitching logic is generated in {{ $.Config.Package }}/runtime/runtime.go
{{ end }}
{{ end }}
{{ end }}
{{/* register schema handlers to type packages */}}
{{ define "runtime/register" }}
import (
"time"
"context"
{{- with $.Config.Schema }}
"{{ . }}"
{{- end }}
{{- range $n := $.Nodes }}
{{ $n.PackageAlias }} "{{ $.Config.Package }}/{{ $n.PackageDir }}"
{{- /* Import external packages */}}
{{- template "import/types" $n }}
{{- end }}
"entgo.io/ent"
"entgo.io/ent/privacy"
"entgo.io/ent/schema/field"
)
// The init function reads all schema descriptors with runtime code
// (default values, validators, hooks and policies) and stitches it
// to their package variables.
func init() {
{{- range $n := $.Nodes }}
{{- $pkg := $n.Package }}
{{- $schema := base $.Config.Schema }}
{{- with $n.RuntimeMixin }}
{{ $pkg }}Mixin := {{ $schema }}.{{ $n.Name }}{}.Mixin()
{{- end }}
{{- with $policies := $n.PolicyPositions }}
{{- /* Policies defined in schema and mixins. */}}
{{ $pkg }}.Policy = privacy.NewPolicies({{ range $idx := $n.MixedInPolicies }}{{ $pkg }}Mixin[{{ $idx }}],{{ end }}{{ $schema }}.{{ $n.Name }}{})
{{ $pkg }}.Hooks[0] = func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if err := {{ $pkg }}.Policy.EvalMutation(ctx, m); err != nil {
return nil, err
}
return next.Mutate(ctx, m)
})
}
{{- end }}
{{- with $hooks := $n.HookPositions }}
{{- /* Hooks defined in schema mixins. */}}
{{- with $idx := $n.MixedInHooks }}
{{- range $i := $idx }}
{{ print $pkg "MixinHooks" $i }} := {{ $pkg }}Mixin[{{ $i }}].Hooks()
{{- end }}
{{- end }}
{{- /* If there are hooks defined in the schema. */}}
{{- $schemaHooks := false }}{{ range $p := $hooks }}{{ if not $p.MixedIn }}{{ $schemaHooks = true }}{{ end }}{{ end }}
{{- if $schemaHooks }}
{{ print $pkg "Hooks" }} := {{ $schema }}.{{ $n.Name }}{}.Hooks()
{{- end }}
{{- range $i, $p := $hooks }}
{{- if $n.NumPolicy }}
{{ $i = add $i 1 }}
{{- end }}
{{- if $p.MixedIn }}
{{ print $pkg ".Hooks" }}[{{ $i }}] = {{ print $pkg "MixinHooks" $p.MixinIndex }}[{{ $p.Index }}]
{{- else }}
{{ print $pkg ".Hooks" }}[{{ $i }}] = {{ print $pkg "Hooks" }}[{{ $p.Index }}]
{{- end }}
{{- end }}
{{- end }}
{{- with $inters := $n.InterceptorPositions }}
{{- /* Interceptors defined in schema mixins. */}}
{{- with $idx := $n.MixedInInterceptors }}
{{- range $i := $idx }}
{{ print $pkg "MixinInters" $i }} := {{ $pkg }}Mixin[{{ $i }}].Interceptors()
{{- end }}
{{- end }}
{{- /* If there are interceptors defined in the schema. */}}
{{- $schemaInters := false }}{{ range $p := $inters }}{{ if not $p.MixedIn }}{{ $schemaInters = true }}{{ end }}{{ end }}
{{- if $schemaInters }}
{{ print $pkg "Inters" }} := {{ $schema }}.{{ $n.Name }}{}.Interceptors()
{{- end }}
{{- range $i, $p := $inters }}
{{- if $p.MixedIn }}
{{ print $pkg ".Interceptors" }}[{{ $i }}] = {{ print $pkg "MixinInters" $p.MixinIndex }}[{{ $p.Index }}]
{{- else }}
{{ print $pkg ".Interceptors" }}[{{ $i }}] = {{ print $pkg "Inters" }}[{{ $p.Index }}]
{{- end }}
{{- end }}
{{- end }}
{{- if or $n.HasDefault $n.HasUpdateDefault $n.HasValidators $n.HasValueScanner $n.HasBlobFields }}
{{- with $idx := $n.MixedInFields }}
{{- range $i := $idx }}
{{ print $pkg "MixinFields" $i }} := {{ $pkg }}Mixin[{{ $i }}].Fields()
_ = {{ $pkg }}MixinFields{{ $i }}
{{- end }}
{{- end }}
{{- $fields := $n.Fields }}{{ if $n.HasOneFieldID }}{{ if $n.ID.UserDefined }}{{ $fields = append $fields $n.ID }}{{ end }}{{ end }}
{{- with $fields }}
{{ $pkg }}Fields := {{ $schema }}.{{ $n.Name }}{}.Fields()
_ = {{ $pkg }}Fields
{{- end }}
{{- range $i, $f := $fields }}
{{- if $f.IsBlobLazy }}{{ continue }}{{ end }}
{{- $desc := print $pkg "Desc" $f.StructField }}
{{- $idscan := and $n.HasOneFieldID (eq $f.Name $n.ID.Name) }}
{{- /* enum default values handled near their declarations (in type package). */}}
{{- if or (and $f.Default (not $f.IsEnum)) $f.UpdateDefault $f.Validators (and $f.HasValueScanner (or (not $idscan) (eq $n.Storage.Name "sql"))) }}
// {{ $desc }} is the schema descriptor for {{ $f.Name }} field.
{{- if $f.Position.MixedIn }}
{{ $desc }} := {{ print $pkg "MixinFields" $f.Position.MixinIndex }}[{{ $f.Position.Index }}].Descriptor()
{{- else }}
{{ $desc }} := {{ $pkg }}Fields[{{ $f.Position.Index }}].Descriptor()
{{- end }}
{{- end }}
{{- if and $f.Default (not $f.IsEnum) }}
{{- $default := print $pkg "." $f.DefaultName }}
// {{ $default }} holds the default value on creation for the {{ $f.Name }} field.
{{- $defaultType := print $f.Type.Type }}{{ if $f.DefaultFunc }}{{ $defaultType = print "func() " $f.Type }}{{ end }}
{{- if and $f.HasGoType (not (hasPrefix $defaultType "func")) }}
{{- if or $f.IsJSON $f.IsOther }}
{{ $default }} = {{ $desc }}.Default.({{ $f.Type }})
{{- else }}
{{ $default }} = {{ $f.Type }}({{ $desc }}.Default.({{ $defaultType }}))
{{- end }}
{{- else }}
{{ $default }} = {{ $desc }}.Default.({{ $defaultType }})
{{- end }}
{{- end }}
{{- if $f.UpdateDefault }}
{{- $default := print $pkg "." $f.UpdateDefaultName }}
// {{ $default }} holds the default value on update for the {{ $f.Name }} field.
{{ $default }} = {{ $desc }}.UpdateDefault.(func() {{ $f.Type }})
{{- end }}
{{- if and $f.HasValueScanner (or (not $idscan) (eq $n.Storage.Name "sql")) }}
{{- $valuescan := print $pkg ".ValueScanner." $f.StructField }}
{{ $valuescan }} = {{ $desc }}.ValueScanner.(field.TypeValueScanner[{{ $f.Type }}])
{{- end }}
{{- with $f.Validators }}
{{- $name := print $pkg "." $f.Validator }}
{{- $type := $f.Type.Type.String }}
{{- if $f.IsJSON }}
{{- $type = $f.Type.String }}
{{- end }}
// {{ $name }} is a validator for the "{{ $f.Name }}" field. It is called by the builders before save.
{{- if eq $f.Validators 1 }}
{{ $name }} = {{ $desc }}.Validators[0].({{ printf "func (%s) error" $type }})
{{- else }}
{{ $name }} = func() {{ printf "func (%s) error" $type }} {
validators := {{ $desc }}.Validators
fns := [...]func({{ $f.Type.Type }}) error {
{{- range $j, $n := xrange $f.Validators }}
validators[{{ $j }}].(func({{ $f.Type.Type }}) error),
{{- end }}
}
return func({{ $f.BuilderField }} {{ $f.Type.Type }}) error {
for _, fn := range fns {
if err := fn({{ $f.BuilderField }}); err != nil {
return err
}
}
return nil
}
}()
{{- end }}
{{- end }}
{{- end }}
{{- if hasTemplate (printf "dialect/%s/model/fields" $.Storage) }}
{{- range $i, $f := $n.BlobFields }}
{{- if $f.HasBlobKey }}
{{- $bkName := print $pkg "." $f.BlobKeyName }}
{{- $desc := print $pkg "BlobDesc" $f.StructField }}
// {{ $desc }} is the schema descriptor for {{ $f.Name }} blob field.
{{- if $f.Position.MixedIn }}
{{ $desc }} := {{ print $pkg "MixinFields" $f.Position.MixinIndex }}[{{ $f.Position.Index }}].Descriptor()
{{- else }}
{{ $desc }} := {{ $pkg }}Fields[{{ $f.Position.Index }}].Descriptor()
{{- end }}
// {{ $bkName }} generates the blob storage key for the {{ $f.Name }} field.
{{ $bkName }} = ent.BlobKeyFunc({{ $desc }}.BlobKey)
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
}
{{ end }}

182
entc/gen/template/tx.tmpl Normal file
View File

@@ -0,0 +1,182 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
{{ define "tx" }}
{{ template "header" $ }}
import (
"context"
"sync"
"entgo.io/ent/dialect"
)
// Tx is a transactional client that is created by calling Client.Tx().
type Tx struct {
config
{{- range $n := $.Nodes }}
// {{ $n.Name }} is the client for interacting with the {{ $n.Name }} builders.
{{ $n.Name }} *{{ $n.ClientName }}
{{- end }}
// lazily loaded.
client *Client
clientOnce sync.Once
// ctx lives for the life of the transaction. It is
// the same context used by the underlying connection.
ctx context.Context
}
{{ $funcs := dict "Commit" "Committer" "Rollback" "Rollbacker" }}
{{ range $func := keys $funcs }}
{{ $iface := get $funcs $func }}
type (
// {{ $iface }} is the interface that wraps the {{ $func }} method.
{{ $iface }} interface {
{{ $func }}(context.Context, *Tx) error
}
// The {{ $func }}Func type is an adapter to allow the use of ordinary
// function as a {{ $iface }}. If f is a function with the appropriate
// signature, {{ $func }}Func(f) is a {{ $iface }} that calls f.
{{ $func }}Func func(context.Context, *Tx) error
// {{ $func }}Hook defines the "{{ lower $func }} middleware". A function that gets a {{ $iface }}
// and returns a {{ $iface }}. For example:
//
// hook := func(next ent.{{ $iface }}) ent.{{ $iface }} {
// return ent.{{ $func }}Func(func(ctx context.Context, tx *ent.Tx) error {
// // Do some stuff before.
// if err := next.{{ $func }}(ctx, tx); err != nil {
// return err
// }
// // Do some stuff after.
// return nil
// })
// }
//
{{ $func }}Hook func({{ $iface }} ) {{ $iface }}
)
// {{ $func }} calls f(ctx, m).
func (f {{ $func }}Func) {{ $func }}(ctx context.Context, tx *Tx) error {
return f(ctx, tx)
}
{{- $onFuncs := print "on" $func }}
// {{ $func }} {{ lower $func }}s the transaction.
func (tx *Tx) {{ $func }}() error {
txDriver := tx.config.driver.(*txDriver)
var fn {{ $iface }} = {{ $func }}Func(func(context.Context, *Tx) error {
return txDriver.tx.{{ $func }}()
})
txDriver.mu.Lock()
hooks := append([]{{ $func }}Hook(nil), txDriver.{{ $onFuncs }}...)
txDriver.mu.Unlock()
for i := len(hooks) - 1; i >= 0; i-- {
fn = hooks[i](fn)
}
return fn.{{ $func }}(tx.ctx, tx)
}
// On{{ $func }} adds a hook to call on {{ lower $func }}.
func (tx *Tx) On{{ $func }}(f {{ $func }}Hook) {
txDriver := tx.config.driver.(*txDriver)
txDriver.mu.Lock()
txDriver.{{ $onFuncs }} = append(txDriver.{{ $onFuncs }}, f)
txDriver.mu.Unlock()
}
{{- end }}
// Client returns a Client that binds to current transaction.
func (tx *Tx) Client() *Client {
tx.clientOnce.Do(func() {
tx.client = &Client{config: tx.config}
tx.client.init()
})
return tx.client
}
func (tx *Tx) init() {
{{- range $n := $.Nodes }}
tx.{{ $n.Name }} = New{{ $n.ClientName }}(tx.config)
{{- end }}
}
{{/* first node for doc example */}}
{{- $first := index $.Nodes 0 }}
// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation.
// The idea is to support transactions without adding any extra code to the builders.
// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance.
// Commit and Rollback are nop for the internal builders and the user must call one
// of them in order to commit or rollback the transaction.
//
// If a closed transaction is embedded in one of the generated entities, and the entity
// applies a query, for example: {{ $first.Name }}.QueryXXX(), the query will be executed
// through the driver which created this transaction.
//
// Note that txDriver is not goroutine safe.
type txDriver struct {
// the driver we started the transaction from.
drv dialect.Driver
// tx is the underlying transaction.
tx dialect.Tx
// completion hooks.
mu sync.Mutex
onCommit []CommitHook
onRollback []RollbackHook
}
// newTx creates a new transactional driver.
func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) {
tx, err := drv.Tx(ctx)
if err != nil {
return nil, err
}
return &txDriver{tx: tx, drv: drv}, nil
}
// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls
// from the internal builders. Should be called only by the internal builders.
func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil }
// Dialect returns the dialect of the driver we started the transaction from.
func (tx *txDriver) Dialect() string { return tx.drv.Dialect() }
// Close is a nop close.
func (*txDriver) Close() error { return nil }
// Commit is a nop commit for the internal builders.
// User must call `Tx.Commit` in order to commit the transaction.
func (*txDriver) Commit() error { return nil }
// Rollback is a nop rollback for the internal builders.
// User must call `Tx.Rollback` in order to rollback the transaction.
func (*txDriver) Rollback() error { return nil }
// Exec calls tx.Exec.
func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error {
return tx.tx.Exec(ctx, query, args, v)
}
// Query calls tx.Query.
func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error {
return tx.tx.Query(ctx, query, args, v)
}
var _ dialect.Driver = (*txDriver)(nil)
{{- with $tmpls := matchTemplate "tx/additional/*" "tx/additional/*/*" }}
{{- range $tmpl := $tmpls }}
{{- xtemplate $tmpl $ }}
{{- end }}
{{- end }}
{{ end }}

View File

@@ -0,0 +1,237 @@
{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}
{{/* gotype: entgo.io/ent/entc/gen.Type */}}
{{ define "where" }}
{{- with extend $ "Package" $.Package -}}
{{ template "header" . }}
{{ end }}
{{ template "import" $ }}
{{ if .HasOneFieldID }}
// ID filters vertices based on their ID field.
func ID(id {{ $.ID.Type }}) predicate.{{ $.Name }} {
{{- if and $.ID.HasValueScanner (eq $.Storage.Name "sql") }}
vc, err := ValueScanner.{{ $.ID.StructField }}.Value(id)
return predicate.{{ $.Name }}OrErr(
{{- with extend $ "Arg" "vc" -}}
{{- $tmpl := printf "dialect/%s/predicate/id" $.Storage }}
{{- xtemplate $tmpl . -}}
{{- end -}}, err)
{{- else }}
return predicate.{{ $.Name }}(
{{- with extend $ "Arg" "id" -}}
{{- $tmpl := printf "dialect/%s/predicate/id" $.Storage }}
{{- xtemplate $tmpl . -}}
{{- end -}}
)
{{- end }}
}
{{ range $op := $.ID.Ops }}
{{ $arg := "id" }}{{ if $op.Variadic }}{{ $arg = "ids" }}{{ end }}
{{ $stringOp := eq $op.Name "EqualFold" "Contains" "ContainsFold" "HasPrefix" "HasSuffix" }}
{{ $func := printf "ID%s" $op.Name }}
// {{ $func }} applies the {{ $op.Name }} predicate on the ID field.
func {{ $func }}({{ $arg }} {{ if $op.Variadic }}...{{ end }}{{ $.ID.Type }}) predicate.{{ $.Name }} {
{{- if and (eq $.Storage.Name "sql") $.ID.HasValueScanner (or $op.Variadic (not $op.Niladic)) }}
{{- if $op.Variadic }}
var (
err error
vcs = make([]any, len({{ $arg }}))
)
for i := range vcs {
if vcs[i], err = ValueScanner.{{ $.ID.StructField }}.Value({{ print $arg "[i]" }}); err != nil {
break
}
}
{{- $arg = "vcs" }}
{{- else }}
vc, err := ValueScanner.{{ $.ID.StructField }}.Value({{ $arg }})
{{- $arg = "vc" }}
{{- if $stringOp }}
{{- $arg = "vcs" }}
vcs, ok := vc.(string)
if err == nil && !ok {
err = fmt.Errorf("{{ $.ID.Name }} value is not a string: %T", vc)
}
{{- end }}
{{- end }}
return predicate.{{ $.Name }}OrErr(
{{- with extend $ "Arg" $arg "Op" $op "Storage" $.Storage -}}
{{ $tmpl := printf "dialect/%s/predicate/id/ops" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end }}, err)
{{- else }}
return predicate.{{ $.Name }}(
{{- with extend $ "Arg" $arg "Op" $op "Storage" $.Storage -}}
{{ $tmpl := printf "dialect/%s/predicate/id/ops" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end -}}
)
{{- end }}
}
{{ end }}
{{ end }}
{{ range $f := $.Fields }}
{{ if or $f.IsBlobNoColumn $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ $func := $f.StructField }}
{{/* JSON cannot be compared using "=" and Enum has a type defined with the field name */}}
{{ $hasP := not (or $f.IsJSON $f.IsEnum) }}
{{ $comparable := or $f.ConvertedToBasic $f.Type.Valuer }}
{{ $undeclared := (and (ne $func "Label") (ne $func "OrderOption") (ne $func "Hooks") (ne $func "Policy") (ne $func "Table") (ne $func "FieldID")) }}
{{- if and $hasP $comparable $undeclared }}
{{ $arg := "v" }}
// {{ $func }} applies equality check predicate on the {{ quote $f.Name }} field. It's identical to {{ $func }}EQ.
func {{ $func }}({{ $arg }} {{ $f.Type }}) predicate.{{ $.Name }} {
{{- if $f.HasValueScanner }}
vc, err := ValueScanner.{{ $f.StructField }}.Value({{ $arg }})
{{- $arg = "vc" }}
return predicate.{{ $.Name }}OrErr(
{{- with extend $ "Arg" $arg "Field" $f -}}
{{ $tmpl := printf "dialect/%s/predicate/field" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end }}, err)
{{- else }}
{{- if and $f.HasGoType (not $f.Type.Valuer) }}
vc := {{ $f.BasicType "v" }}
{{- $arg = "vc" }}
{{- end }}
return predicate.{{ $.Name }}(
{{- with extend $ "Arg" $arg "Field" $f -}}
{{ $tmpl := printf "dialect/%s/predicate/field" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end -}}
)
{{- end }}
}
{{- end }}
{{ end }}
{{ range $f := $.Fields }}
{{ if or $f.IsBlobNoColumn $f.IsBlobLazy }}{{ continue }}{{ end }}
{{ range $op := $f.Ops }}
{{ $arg := "v" }}{{ if $op.Variadic }}{{ $arg = "vs" }}{{ end }}
{{ $stringOp := eq $op.Name "EqualFold" "Contains" "ContainsFold" "HasPrefix" "HasSuffix" }}
{{ $func := print $f.StructField $op.Name }}
{{ $type := $f.Type.String }}{{ if $f.IsEnum }}{{ $type = trimPackage $type $.Package }}{{ end }}
// {{ $func }} applies the {{ $op.Name }} predicate on the {{ quote $f.Name }} field.
func {{ $func }}({{ if not $op.Niladic }}{{ $arg }} {{ if $op.Variadic }}...{{ end }}{{ $type }}{{ end }}) predicate.{{ $.Name }} {
{{- if and $f.HasValueScanner (or $op.Variadic (not $op.Niladic)) }}
{{- if $op.Variadic }}
var (
err error
v = make([]any, len({{ $arg }}))
)
for i := range v {
if v[i], err = ValueScanner.{{ $f.StructField }}.Value({{ print $arg "[i]" }}); err != nil {
break
}
}
{{- $arg = "v" }}
{{- else }}
vc, err := ValueScanner.{{ $f.StructField }}.Value({{ $arg }})
{{- $arg = "vc" }}
{{- if $stringOp }}
{{- $arg = "vcs" }}
vcs, ok := vc.(string)
if err == nil && !ok {
err = fmt.Errorf("{{ $f.Name }} value is not a string: %T", vc)
}
{{- end }}
{{- end }}
return predicate.{{ $.Name }}OrErr(
{{- with extend $ "Arg" $arg "Field" $f "Op" $op "Storage" $.Storage -}}
{{ $tmpl := printf "dialect/%s/predicate/field/ops" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end }}, err)
{{- else }}
{{- if and $f.HasGoType (or $stringOp (not $f.Type.Valuer)) }}
{{- if $op.Variadic }}
v := make([]any, len({{ $arg }}))
for i := range v {
v[i] = {{ $f.BasicType (printf "%s[i]" $arg) }}
}
{{- $arg = "v" }}
{{- else if not $op.Niladic }}
vc := {{ $f.BasicType "v" }}
{{- $arg = "vc" }}
{{- end }}
{{- end }}
return predicate.{{ $.Name }}(
{{- with extend $ "Arg" $arg "Field" $f "Op" $op "Storage" $.Storage -}}
{{ $tmpl := printf "dialect/%s/predicate/field/ops" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end -}}
)
{{- end }}
}
{{ end }}
{{ end }}
{{ range $e := $.Edges }}
{{ $func := print "Has" $e.StructField }}
// {{ $func }} applies the HasEdge predicate on the {{ quote $e.Name }} edge.
func {{ $func }}() predicate.{{ $.Name }} {
return predicate.{{ $.Name }}(
{{- with extend $ "Edge" $e -}}
{{ $tmpl := printf "dialect/%s/predicate/edge/has" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end -}}
)
}
{{ $func = printf "%sWith" $func }}
// {{ $func }} applies the HasEdge predicate on the {{ quote $e.Name }} edge with a given conditions (other predicates).
func {{ $func }}(preds ...predicate.{{ $e.Type.Name }}) predicate.{{ $.Name }} {
return predicate.{{ $.Name }}(
{{- with extend $ "Edge" $e -}}
{{ $tmpl := printf "dialect/%s/predicate/edge/haswith" $.Storage }}
{{- xtemplate $tmpl . }}
{{- end -}}
)
}
{{ end }}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.{{ $.Name }}) predicate.{{ $.Name }} {
return predicate.{{ $.Name }}(
{{- $tmpl := printf "dialect/%s/predicate/and" $.Storage }}
{{- xtemplate $tmpl . -}}
)
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.{{ $.Name }}) predicate.{{ $.Name }} {
return predicate.{{ $.Name }}(
{{- $tmpl = printf "dialect/%s/predicate/or" $.Storage }}
{{- xtemplate $tmpl . -}}
)
}
// Not applies the not operator on the given predicate.
func Not(p predicate.{{ $.Name }}) predicate.{{ $.Name }} {
return predicate.{{ $.Name }}(
{{- $tmpl = printf "dialect/%s/predicate/not" $.Storage }}
{{- xtemplate $tmpl . -}}
)
}
{{ template "where/additional" $ }}
{{ with $tmpls := matchTemplate "where/additional/*" }}
{{ range $tmpl := $tmpls }}
{{ xtemplate $tmpl $ }}
{{ end }}
{{ end }}
{{ end }}
{{/* A template that can be overridden in order to add additional code for the type package. */}}
{{ define "where/additional" }}{{ end }}