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

428
entc/entc.go Normal file
View File

@@ -0,0 +1,428 @@
// 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.
// Package entc provides an interface for interacting with
// entc (ent codegen) as a package rather than an executable.
package entc
import (
"errors"
"fmt"
"go/token"
"path"
"path/filepath"
"reflect"
"strings"
"entgo.io/ent/entc/gen"
"entgo.io/ent/entc/internal"
"entgo.io/ent/entc/load"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"golang.org/x/tools/go/packages"
)
// LoadGraph loads the schema package from the given schema path,
// and constructs a *gen.Graph.
func LoadGraph(schemaPath string, cfg *gen.Config) (*gen.Graph, error) {
spec, err := (&load.Config{Path: schemaPath, BuildFlags: cfg.BuildFlags}).Load()
if err != nil {
return nil, err
}
cfg.Schema = spec.PkgPath
if cfg.Package == "" {
// default package-path for codegen is one package
// before the schema package (`<project>/ent/schema`).
cfg.Package = path.Dir(spec.PkgPath)
}
if err := defaultTarget(schemaPath, cfg); err != nil {
return nil, err
}
return gen.NewGraph(cfg, spec.Schemas...)
}
// Generate runs the codegen on the schema path. The default target
// directory for the assets, is one directory above the schema path.
// Hence, if the schema package resides in "<project>/ent/schema",
// the base directory for codegen will be "<project>/ent".
//
// If no storage driver provided by option, SQL driver will be used.
//
// entc.Generate("./ent/path", &gen.Config{
// Header: "// Custom header",
// IDType: &field.TypeInfo{Type: field.TypeInt},
// })
func Generate(schemaPath string, cfg *gen.Config, options ...Option) error {
if err := defaultTarget(schemaPath, cfg); err != nil {
return err
}
for _, opt := range options {
if err := opt(cfg); err != nil {
return err
}
}
if cfg.Storage == nil {
driver, err := gen.NewStorage("sql")
if err != nil {
return err
}
cfg.Storage = driver
}
undo, err := gen.PrepareEnv(cfg)
if err != nil {
return err
}
defer func() {
if err != nil {
_ = undo()
}
}()
return generate(schemaPath, cfg)
}
func normalizePkg(c *gen.Config) error {
base := path.Base(c.Package)
if strings.ContainsRune(base, '-') {
base = strings.ReplaceAll(base, "-", "_")
c.Package = path.Join(path.Dir(c.Package), base)
}
if !token.IsIdentifier(base) {
return fmt.Errorf("invalid package identifier: %q", base)
}
return nil
}
// Option allows for managing codegen configuration using functional options.
type Option func(*gen.Config) error
// Storage sets the storage-driver type to support by the codegen.
func Storage(typ string) Option {
return func(cfg *gen.Config) error {
storage, err := gen.NewStorage(typ)
if err != nil {
return err
}
cfg.Storage = storage
return nil
}
}
// FeatureNames enables sets of features by their names.
func FeatureNames(names ...string) Option {
return func(cfg *gen.Config) error {
for _, name := range names {
for _, feat := range gen.AllFeatures {
if name == feat.Name {
cfg.Features = append(cfg.Features, feat)
}
}
}
return nil
}
}
// Annotation is used to attach arbitrary metadata to the schema objects in codegen.
// Unlike schema annotations, being serializable to JSON raw value is not mandatory.
//
// Template extensions can retrieve this metadata and use it inside their execution.
// Read more about it in ent website: https://entgo.io/docs/templates/#annotations.
type Annotation = schema.Annotation
// Annotations appends the given annotations to the codegen config.
func Annotations(annotations ...Annotation) Option {
return func(cfg *gen.Config) error {
if cfg.Annotations == nil {
cfg.Annotations = gen.Annotations{}
}
for _, ant := range annotations {
name := ant.Name()
if curr, ok := cfg.Annotations[name]; !ok {
cfg.Annotations[name] = ant
} else if m, ok := curr.(schema.Merger); ok {
cfg.Annotations[name] = m.Merge(ant)
} else {
return fmt.Errorf("duplicate annotations with name %q", name)
}
}
return nil
}
}
// BuildFlags appends the given build flags to the codegen config.
func BuildFlags(flags ...string) Option {
return func(cfg *gen.Config) error {
cfg.BuildFlags = append(cfg.BuildFlags, flags...)
return nil
}
}
// BuildTags appends the given build tags as build flags to the codegen
// config.
func BuildTags(tags ...string) Option {
return BuildFlags("-tags", strings.Join(tags, ","))
}
// TemplateFiles parses the named files and associates the resulting templates
// with codegen templates.
func TemplateFiles(filenames ...string) Option {
return templateOption(func(t *gen.Template) (*gen.Template, error) {
return t.ParseFiles(filenames...)
})
}
// TemplateGlob parses the template definitions from the files identified
// by the pattern and associates the resulting templates with codegen templates.
func TemplateGlob(pattern string) Option {
return templateOption(func(t *gen.Template) (*gen.Template, error) {
return t.ParseGlob(pattern)
})
}
// TemplateDir parses the template definitions from the files in the directory
// and associates the resulting templates with codegen templates.
func TemplateDir(path string) Option {
return templateOption(func(t *gen.Template) (*gen.Template, error) {
return t.ParseDir(path)
})
}
// Extension describes an Ent code generation extension that
// allows customizing the code generation and integrate with
// other tools and libraries (e.g. GraphQL, gRPC, OpenAPI) by
// registering hooks, templates and global annotations in one
// simple call.
//
// ex, err := entgql.NewExtension(
// entgql.WithConfig("../gqlgen.yml"),
// entgql.WithSchema("../schema.graphql"),
// )
// if err != nil {
// log.Fatalf("creating graphql extension: %v", err)
// }
// err = entc.Generate("./schema", &gen.Config{
// Templates: entswag.Templates,
// }, entc.Extensions(ex))
// if err != nil {
// log.Fatalf("running ent codegen: %v", err)
// }
type Extension interface {
// Hooks holds an optional list of Hooks to apply
// on the graph before/after the code-generation.
Hooks() []gen.Hook
// Annotations injects global annotations to the gen.Config object that
// can be accessed globally in all templates. Unlike schema annotations,
// being serializable to JSON raw value is not mandatory.
//
// {{- with $.Config.Annotations.GQL }}
// {{/* Annotation usage goes here. */}}
// {{- end }}
//
Annotations() []Annotation
// Templates specifies a list of alternative templates
// to execute or to override the default.
Templates() []*gen.Template
// Options specifies a list of entc.Options to evaluate on
// the gen.Config before executing the code generation.
Options() []Option
}
// Extensions evaluates the list of Extensions on the gen.Config.
func Extensions(extensions ...Extension) Option {
return func(cfg *gen.Config) error {
for _, ex := range extensions {
cfg.Hooks = append(cfg.Hooks, ex.Hooks()...)
cfg.Templates = append(cfg.Templates, ex.Templates()...)
for _, opt := range ex.Options() {
if err := opt(cfg); err != nil {
return err
}
}
if err := Annotations(ex.Annotations()...)(cfg); err != nil {
return err
}
}
return nil
}
}
// DefaultExtension is the default implementation for entc.Extension.
//
// Embedding this type allow third-party packages to create extensions
// without implementing all methods.
//
// type Extension struct {
// entc.DefaultExtension
// }
type DefaultExtension struct{}
// Hooks of the extensions.
func (DefaultExtension) Hooks() []gen.Hook { return nil }
// Annotations of the extensions.
func (DefaultExtension) Annotations() []Annotation { return nil }
// Templates of the extensions.
func (DefaultExtension) Templates() []*gen.Template { return nil }
// Options of the extensions.
func (DefaultExtension) Options() []Option { return nil }
var _ Extension = (*DefaultExtension)(nil)
// DependencyOption allows configuring optional dependencies using functional options.
type DependencyOption func(*gen.Dependency) error
// DependencyType sets the type of the struct field in
// the generated builders for the configured dependency.
func DependencyType(v any) DependencyOption {
return func(d *gen.Dependency) error {
if v == nil {
return errors.New("nil dependency type")
}
t := reflect.TypeOf(v)
tv := indirect(t)
d.Type = &field.TypeInfo{
Ident: t.String(),
PkgPath: tv.PkgPath(),
RType: &field.RType{
Kind: t.Kind(),
Name: tv.Name(),
Ident: tv.String(),
PkgPath: tv.PkgPath(),
},
}
return nil
}
}
// DependencyTypeInfo is similar to DependencyType, but
// allows setting the field.TypeInfo explicitly.
func DependencyTypeInfo(t *field.TypeInfo) DependencyOption {
return func(d *gen.Dependency) error {
if t == nil {
return errors.New("nil dependency type info")
}
d.Type = t
return nil
}
}
// DependencyName sets the struct field and the option name
// of the dependency in the generated builders.
func DependencyName(name string) DependencyOption {
return func(d *gen.Dependency) error {
d.Field = name
d.Option = name
return nil
}
}
// Dependency allows configuring optional dependencies as struct fields on the
// generated builders. For example:
//
// opts := []entc.Option{
// entc.Dependency(
// entc.DependencyType(&http.Client{}),
// ),
// entc.Dependency(
// entc.DependencyName("DB"),
// entc.DependencyType(&sql.DB{}),
// )
// }
// if err := entc.Generate("./ent/path", &gen.Config{}, opts...); err != nil {
// log.Fatalf("running ent codegen: %v", err)
// }
func Dependency(opts ...DependencyOption) Option {
return func(cfg *gen.Config) error {
d := &gen.Dependency{}
for _, opt := range opts {
if err := opt(d); err != nil {
return err
}
}
if err := d.Build(); err != nil {
return err
}
return Annotations(gen.Dependencies{d})(cfg)
}
}
// templateOption ensures the template instantiate
// once for config and execute the given Option.
func templateOption(next func(t *gen.Template) (*gen.Template, error)) Option {
return func(cfg *gen.Config) (err error) {
tmpl, err := next(gen.NewTemplate("external"))
if err != nil {
return err
}
cfg.Templates = append(cfg.Templates, tmpl)
return nil
}
}
// generate loads the given schema and run codegen.
func generate(schemaPath string, cfg *gen.Config) error {
graph, err := LoadGraph(schemaPath, cfg)
if err != nil {
if err := mayRecover(err, schemaPath, cfg); err != nil {
return err
}
if graph, err = LoadGraph(schemaPath, cfg); err != nil {
return err
}
}
if err := normalizePkg(cfg); err != nil {
return err
}
return graph.Gen()
}
func mayRecover(err error, schemaPath string, cfg *gen.Config) error {
if ok, _ := cfg.FeatureEnabled(gen.FeatureSnapshot.Name); !ok {
return err
}
if !errors.As(err, &packages.Error{}) && !internal.IsBuildError(err) {
return err
}
// If the build error comes from the schema package.
if err := internal.CheckDir(schemaPath); err != nil {
return fmt.Errorf("schema failure: %w", err)
}
if ok, _ := cfg.FeatureEnabled(gen.FeatureGlobalID.Name); ok {
if internal.CheckDir(filepath.Dir(gen.IncrementStartsFilePath(cfg.Target))) != nil {
// Resolve the conflict by accepting the remote version of the file.
if err := gen.ResolveIncrementStartsConflict(cfg.Target); err != nil {
return err
}
}
}
target := filepath.Join(cfg.Target, "internal/schema.go")
return (&internal.Snapshot{Path: target, Config: cfg}).Restore()
}
// indirect returns the type at the end of indirection.
func indirect(t reflect.Type) reflect.Type {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
// defaultTarget computes and sets the default target-path for codegen (one level above schema-path).
func defaultTarget(schemaPath string, cfg *gen.Config) error {
if cfg.Target != "" {
return nil
}
abs, err := filepath.Abs(schemaPath)
if err != nil {
return err
}
// Default target-path for codegen is one dir above the schema.
cfg.Target = filepath.Dir(abs)
return nil
}

41
entc/gen/bench_test.go Normal file
View File

@@ -0,0 +1,41 @@
// 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.
package gen_test
import (
"os"
"path/filepath"
"testing"
"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
"entgo.io/ent/schema/field"
"github.com/stretchr/testify/require"
)
func BenchmarkGraph_Gen(b *testing.B) {
target := filepath.Join(os.TempDir(), "ent")
require.NoError(b, os.MkdirAll(target, os.ModePerm), "creating tmpdir")
defer os.RemoveAll(target)
storage, err := gen.NewStorage("sql")
require.NoError(b, err)
graph, err := entc.LoadGraph("../integration/ent/schema", &gen.Config{
Storage: storage,
IDType: &field.TypeInfo{Type: field.TypeInt},
Target: target,
Package: "entgo.io/ent/entc/integration/ent",
Templates: []*gen.Template{
gen.MustParse(gen.NewTemplate("template").
Funcs(gen.Funcs).
ParseGlob("../integration/ent/template/*.tmpl")),
},
})
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := graph.Gen()
require.NoError(b, err)
}
}

249
entc/gen/feature.go Normal file
View File

@@ -0,0 +1,249 @@
// 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.
package gen
import (
"os"
"path/filepath"
)
var (
// FeaturePrivacy provides a feature-flag for the privacy extension.
FeaturePrivacy = Feature{
Name: "privacy",
Stage: Alpha,
Default: false,
Description: "Privacy provides a privacy layer for ent through the schema configuration",
cleanup: func(c *Config) error {
return os.RemoveAll(filepath.Join(c.Target, "privacy"))
},
}
// FeatureIntercept provides a feature-flag for the interceptors' extension.
FeatureIntercept = Feature{
Name: "intercept",
Stage: Alpha,
Default: false,
Description: "Intercept generates a helper package to make working with interceptors easier",
cleanup: func(c *Config) error {
return os.RemoveAll(filepath.Join(c.Target, "intercept"))
},
}
// FeatureEntQL provides a feature-flag for the EntQL extension.
FeatureEntQL = Feature{
Name: "entql",
Stage: Experimental,
Default: false,
Description: "EntQL provides a generic filtering capability at runtime",
cleanup: func(c *Config) error {
return os.RemoveAll(filepath.Join(c.Target, "entql.go"))
},
}
// FeatureNamedEdges provides a feature-flag for eager-loading edges with dynamic names.
FeatureNamedEdges = Feature{
Name: "namedges",
Stage: Experimental,
Default: false,
Description: "NamedEdges provides an API for eager-loading edges with dynamic names",
}
// FeatureBidiEdgeRefs provides a feature-flag for sql dialect to set two-way
// references when loading (unique) edges. Note, users that use the standard
// encoding/json.MarshalJSON should detach the circular references before marshaling.
FeatureBidiEdgeRefs = Feature{
Name: "bidiedges",
Stage: Experimental,
Default: false,
Description: "This features guides Ent to set two-way references when loading (O2M/O2O) edges",
}
// FeatureSnapshot stores a snapshot of ent/schema and auto-solve merge-conflict (issue #852).
FeatureSnapshot = Feature{
Name: "schema/snapshot",
Stage: Experimental,
Default: false,
Description: "Schema snapshot stores a snapshot of ent/schema and auto-solve merge-conflict (issue #852)",
GraphTemplates: []GraphTemplate{
{
Name: "internal/schema",
Format: "internal/schema.go",
},
},
cleanup: func(c *Config) error {
return remove(filepath.Join(c.Target, "internal"), "schema.go")
},
}
// FeatureSchemaConfig allows users to pass init time alternate schema names
// for each ent model. This is useful if your SQL tables are spread out against
// multiple databases.
FeatureSchemaConfig = Feature{
Name: "sql/schemaconfig",
Stage: Stable,
Default: false,
Description: "Allows alternate schema names for each ent model. Useful if SQL tables are spread out against multiple databases",
GraphTemplates: []GraphTemplate{
{
Name: "dialect/sql/internal/schemaconfig",
Format: "internal/schemaconfig.go",
},
},
cleanup: func(c *Config) error {
return remove(filepath.Join(c.Target, "internal"), "schemaconfig.go")
},
}
// featureMultiSchema indicates that ent/schema is annotated with multiple schemas.
// This feature-flag is enabled by default by the storage driver and exists to pass
// this info to the templates.
featureMultiSchema = Feature{
Name: "sql/multischema",
Stage: Beta,
}
// FeatureLock provides a feature-flag for sql locking extension.
FeatureLock = Feature{
Name: "sql/lock",
Stage: Experimental,
Default: false,
Description: "Allows users to use row-level locking in SQL using the 'FOR {UPDATE|SHARE}' clauses",
}
// FeatureModifier provides a feature-flag for adding query modifiers.
FeatureModifier = Feature{
Name: "sql/modifier",
Stage: Experimental,
Default: false,
Description: "Allows users to attach custom modifiers to queries",
}
// FeatureExecQuery provides a feature-flag for exposing the ExecContext/QueryContext methods of the underlying SQL drivers.
FeatureExecQuery = Feature{
Name: "sql/execquery",
Stage: Experimental,
Default: false,
Description: "Allows users to execute statements using the ExecContext/QueryContext methods of the underlying driver",
}
// FeatureUpsert provides a feature-flag for adding upsert (ON CONFLICT) capabilities to create builders.
FeatureUpsert = Feature{
Name: "sql/upsert",
Stage: Experimental,
Default: false,
Description: "Allows users to configure the `ON CONFLICT`/`ON DUPLICATE KEY` clause for `INSERT` statements",
}
FeatureVersionedMigration = Feature{
Name: "sql/versioned-migration",
Stage: Experimental,
Default: false,
Description: "Allows users to work with versioned migrations / migration files",
}
FeatureGlobalID = Feature{
Name: "sql/globalid",
Stage: Experimental,
Default: false,
Description: "Ensures all nodes have a unique global identifier", GraphTemplates: []GraphTemplate{
{
Name: "internal/globalid",
Format: "internal/globalid.go",
},
},
cleanup: func(c *Config) error {
return remove(filepath.Join(c.Target, "internal"), "globalid.go")
},
}
// AllFeatures holds a list of all feature-flags.
AllFeatures = []Feature{
FeaturePrivacy,
FeatureIntercept,
FeatureEntQL,
FeatureNamedEdges,
FeatureBidiEdgeRefs,
FeatureSnapshot,
FeatureSchemaConfig,
FeatureLock,
FeatureModifier,
FeatureExecQuery,
FeatureUpsert,
FeatureVersionedMigration,
FeatureGlobalID,
}
// allFeatures includes all public and private features.
allFeatures = append(AllFeatures, featureMultiSchema)
)
// FeatureStage describes the stage of the codegen feature.
type FeatureStage int
const (
_ FeatureStage = iota
// Experimental features are in development, and actively being tested in the
// integration environment.
Experimental
// Alpha features are features whose initial development was finished, tested
// on the infra of the ent team, but we expect breaking-changes to their APIs.
Alpha
// Beta features are Alpha features that were added to the entgo.io
// documentation, and no breaking-changes are expected for them.
Beta
// Stable features are Beta features that were running for a while on ent
// infra.
Stable
)
// A Feature of the ent codegen.
type Feature struct {
// Name of the feature.
Name string
// Stage of the feature.
Stage FeatureStage
// Default values indicates if this feature is enabled by default.
Default bool
// A Description of this feature.
Description string
// Templates defines list of templates for extending or overriding the default
// templates. In order to write the template output to a standalone file, use
// the GraphTemplates below.
Templates []*Template
// GraphTemplates defines optional templates to be executed on the graph
// and will their output will be written to the configured destination.
GraphTemplates []GraphTemplate
// cleanup used to cleanup all changes when a feature-flag is removed.
// e.g. delete files from previous codegen runs.
cleanup func(*Config) error
}
// remove file (if exists) and its dir if it's empty.
func remove(dir, file string) error {
if err := os.Remove(filepath.Join(dir, file)); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
infos, err := os.ReadDir(dir)
if err != nil {
return err
}
if len(infos) == 0 {
return os.Remove(dir)
}
return nil
}

553
entc/gen/func.go Normal file
View File

@@ -0,0 +1,553 @@
// 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.
package gen
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"go/token"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"text/template"
"unicode"
"entgo.io/ent/schema/field"
"github.com/go-openapi/inflect"
)
var (
// Funcs are the predefined template
// functions used by the codegen.
Funcs = template.FuncMap{
"ops": fieldOps,
"add": add,
"append": reflect.Append,
"appends": reflect.AppendSlice,
"order": order,
"camel": camel,
"snake": snake,
"pascal": pascal,
"extend": extend,
"xrange": xrange,
"receiver": receiver,
"plural": plural,
"aggregate": aggregate,
"primitives": primitives,
"singular": rules.Singularize,
"quote": quote,
"base": filepath.Base,
"keys": keys,
"indexOf": indexOf,
"join": join,
"joinWords": joinWords,
"json": jsonString,
"isNil": isNil,
"lower": strings.ToLower,
"upper": strings.ToUpper,
"trim": strings.Trim,
"hasField": hasField,
"hasImport": hasImport,
"indirect": indirect,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"trimPackage": trimPackage,
"xtemplate": xtemplate,
"hasTemplate": hasTemplate,
"matchTemplate": matchTemplate,
"split": strings.Split,
"tagLookup": tagLookup,
"toString": toString,
"dict": dict,
"get": get,
"set": set,
"unset": unset,
"hasKey": hasKey,
"list": list[any],
"slist": list[string],
"fail": fail,
"replace": strings.ReplaceAll,
"allZero": allZero,
}
rules = ruleset()
acronyms = make(map[string]struct{})
)
// joinWords with spaces and add linebreaks to ensure lines do not exceed the given maxSize.
func joinWords(words []string, maxSize int) string {
if len(words) == 0 {
return ""
}
b := &strings.Builder{}
b.WriteString(words[0])
n := len(words[0])
for _, w := range words[1:] {
if n+len(w)+1 > maxSize {
b.WriteByte('\n')
n = 0
}
b.WriteString(" ")
b.WriteString(w)
n += len(w) + 1
}
return b.String()
}
// indexOf returns the index of the given string in the slice.
func indexOf(s []string, v string) int {
for i, x := range s {
if x == v {
return i
}
}
return -1
}
// quote only strings.
func quote(v any) any {
if s, ok := v.(string); ok {
return strconv.Quote(s)
}
return v
}
// fieldOps returns all predicate operations for a given field.
func fieldOps(f *Field) (ops []Op) {
switch t := f.Type.Type; {
case f.HasGoType() && !f.ConvertedToBasic() && !f.Type.Valuer():
case t == field.TypeJSON:
case t == field.TypeBool:
ops = boolOps
case t == field.TypeString && strings.ToLower(f.Name) != "id":
ops = stringOps
if f.HasGoType() && !f.ConvertedToBasic() && (f.Type.Valuer() || f.HasValueScanner()) {
ops = numericOps
}
case t == field.TypeEnum || f.IsEdgeField():
ops = enumOps
default:
ops = numericOps
}
if f.Optional {
ops = append(ops, nillableOps...)
}
return ops
}
// xrange generates a slice of len n.
func xrange(n int) (a []int) {
for i := 0; i < n; i++ {
a = append(a, i)
}
return
}
// plural a name.
func plural(name string) string {
p := rules.Pluralize(name)
if p == name {
p += "Slice"
}
return p
}
func isSeparator(r rune) bool {
return r == '_' || r == '-' || unicode.IsSpace(r)
}
func pascalWords(words []string) string {
for i, w := range words {
upper := strings.ToUpper(w)
if _, ok := acronyms[upper]; ok {
words[i] = upper
} else {
words[i] = rules.Capitalize(w)
}
}
return strings.Join(words, "")
}
// pascal converts the given name into a PascalCase.
//
// user_info => UserInfo
// full_name => FullName
// user_id => UserID
// full-admin => FullAdmin
func pascal(s string) string {
words := strings.FieldsFunc(s, isSeparator)
return pascalWords(words)
}
// camel converts the given name into a camelCase.
//
// user_info => userInfo
// full_name => fullName
// user_id => userID
// full-admin => fullAdmin
func camel(s string) string {
words := strings.FieldsFunc(s, isSeparator)
if len(words) == 1 {
return strings.ToLower(words[0])
}
return strings.ToLower(words[0]) + pascalWords(words[1:])
}
// snake converts the given struct or field name into a snake_case.
//
// Username => username
// FullName => full_name
// HTTPCode => http_code
func snake(s string) string {
var (
j int
b strings.Builder
)
for i := 0; i < len(s); i++ {
r := rune(s[i])
// Put '_' if it is not a start or end of a word, current letter is uppercase,
// and previous is lowercase (cases like: "UserInfo"), or next letter is also
// a lowercase and previous letter is not "_".
if i > 0 && i < len(s)-1 && unicode.IsUpper(r) {
if unicode.IsLower(rune(s[i-1])) ||
j != i-1 && unicode.IsLower(rune(s[i+1])) && unicode.IsLetter(rune(s[i-1])) {
j = i
b.WriteString("_")
}
}
b.WriteRune(unicode.ToLower(r))
}
return b.String()
}
// receiver returns the receiver name of the given type.
//
// []T => t
// [1]T => t
// User => u
// UserQuery => uq
func receiver(s string) (r string) {
// Trim invalid tokens for identifier prefix.
s = strings.Trim(s, "[]*&0123456789")
parts := strings.Split(snake(s), "_")
min := len(parts[0])
for _, w := range parts[1:] {
if len(w) < min {
min = len(w)
}
}
for i := 1; i < min; i++ {
r := parts[0][:i]
for _, w := range parts[1:] {
r += w[:i]
}
if _, ok := importPkg[r]; !ok {
s = r
break
}
}
name := strings.ToLower(s)
if token.Lookup(name).IsKeyword() {
name = "_" + name
}
return name
}
// typeScope wraps the Type object with extended scope.
type typeScope struct {
*Type
Scope map[any]any
}
// graphScope wraps the Graph object with extended scope.
type graphScope struct {
*Graph
Scope map[any]any
}
// extend extends the parent block with a KV pairs.
//
// {{ with $scope := extend $ "key" "value" }}
// {{ template "setters" $scope }}
// {{ end}}
func extend(v any, kv ...any) (any, error) {
if len(kv)%2 != 0 {
return nil, fmt.Errorf("invalid number of parameters: %d", len(kv))
}
scope := make(map[any]any, len(kv)/2)
for i := 0; i < len(kv); i += 2 {
scope[kv[i]] = kv[i+1]
}
switch v := v.(type) {
case *Type:
return &typeScope{Type: v, Scope: scope}, nil
case *Graph:
return &graphScope{Graph: v, Scope: scope}, nil
case *typeScope:
for k := range v.Scope {
scope[k] = v.Scope[k]
}
return &typeScope{Type: v.Type, Scope: scope}, nil
case *graphScope:
for k := range v.Scope {
scope[k] = v.Scope[k]
}
return &graphScope{Graph: v.Graph, Scope: scope}, nil
default:
return nil, fmt.Errorf("invalid type for extend: %T", v)
}
}
// add calculates summarize list of variables.
func add(xs ...int) (n int) {
for _, x := range xs {
n += x
}
return
}
func ruleset() *inflect.Ruleset {
rules := inflect.NewDefaultRuleset()
// Add common initialism from golint and more.
for _, w := range []string{
"ACL", "API", "ASCII", "AWS", "CPU", "CSS", "DNS", "EOF", "GB", "GUID",
"HCL", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "KB", "LHS", "MAC",
"MB", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SQL", "SSH", "SSO",
"TCP", "TLS", "TTL", "UDP", "UI", "UID", "URI", "URL", "UTF8", "UUID",
"VM", "XML", "XMPP", "XSRF", "XSS",
} {
acronyms[w] = struct{}{}
rules.AddAcronym(w)
}
return rules
}
// AddAcronym adds initialism to the global ruleset.
func AddAcronym(word string) {
acronyms[word] = struct{}{}
rules.AddAcronym(word)
}
// order returns a map of sort orders.
// The key is the function name, and the value its database keyword.
func order() map[string]string {
return map[string]string{
"asc": "incr",
"desc": "decr",
}
}
// aggregate returns a map between all agg-functions and if they accept a field name as a parameter or not.
func aggregate() map[string]bool {
return map[string]bool{
"min": true,
"max": true,
"sum": true,
"mean": true,
"count": false,
}
}
// keys returns the given map keys.
func keys(v reflect.Value) ([]string, error) {
v = indirect(v)
if k := v.Type().Kind(); k != reflect.Map {
return nil, fmt.Errorf("expect map for keys, got: %s", k)
}
keys := make([]string, v.Len())
for i, v := range v.MapKeys() {
keys[i] = v.String()
}
sort.Strings(keys)
return keys, nil
}
// primitives returns all primitives types.
func primitives() []string {
return []string{field.TypeString.String(), field.TypeInt.String(), field.TypeFloat64.String(), field.TypeBool.String()}
}
// join is a wrapper around strings.Join to provide consistent output.
func join(a []string, sep string) string {
sort.Strings(a)
return strings.Join(a, sep)
}
// xtemplate dynamically executes templates by their names.
func xtemplate(name string, v any) (string, error) {
buf := bytes.NewBuffer(nil)
if err := templates.ExecuteTemplate(buf, name, v); err != nil {
return "", err
}
return buf.String(), nil
}
// hasTemplate checks whether a template exists in the loaded templates.
func hasTemplate(name string) bool {
for _, t := range templates.Templates() {
if t.Name() == name {
return true
}
}
return false
}
// matchTemplate returns all template names that match the given patterns.
func matchTemplate(patterns ...string) []string {
var (
names []string
exists = make(map[string]struct{})
)
for _, pattern := range patterns {
for _, t := range templates.Templates() {
name := t.Name()
if _, ok := exists[name]; ok {
continue
}
if match, _ := filepath.Match(pattern, name); match {
names = append(names, name)
exists[name] = struct{}{}
}
}
}
sort.Strings(names)
return names
}
// hasField determines if a struct has a field with the given name.
func hasField(v any, name string) bool {
vr := reflect.Indirect(reflect.ValueOf(v))
return vr.FieldByName(name).IsValid()
}
// hasImport reports if the package name exists in the predefined import packages.
func hasImport(name string) bool {
_, ok := importPkg[name]
return ok
}
// trimPackage trims the package name from the given identifier.
func trimPackage(ident, pkg string) string {
return strings.TrimPrefix(ident, pkg+".")
}
// isNil reports whether its argument v is nil.
func isNil(v any) bool {
rv := indirect(reflect.ValueOf(v))
if !rv.IsValid() {
return true
}
switch rv.Kind() {
case reflect.Invalid:
return true
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return rv.IsNil()
default:
return false
}
}
// indirect returns the item at the end of indirection.
func indirect(v reflect.Value) reflect.Value {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
}
return v
}
// tagLookup returns the value associated with key in the tag string.
func tagLookup(tag, key string) string {
v, _ := reflect.StructTag(tag).Lookup(key)
return v
}
// toString converts `v` to a string.
func toString(v any) string {
switch v := v.(type) {
case string:
return v
case []byte:
return string(v)
case error:
return v.Error()
case fmt.Stringer:
return v.String()
default:
return fmt.Sprint(v)
}
}
// dict creates a dictionary from a list of pairs.
func dict(v ...any) map[string]any {
lenv := len(v)
dict := make(map[string]any, lenv/2)
for i := 0; i < lenv; i += 2 {
key := toString(v[i])
if i+1 >= lenv {
dict[key] = ""
continue
}
dict[key] = v[i+1]
}
return dict
}
// get the value from the dict for key.
func get(d map[string]any, key string) any {
if val, ok := d[key]; ok {
return val
}
return ""
}
// set adds a value to the dict for key.
func set(d map[string]any, key string, value any) map[string]any {
d[key] = value
return d
}
// unset removes a key from the dict.
func unset(d map[string]any, key string) map[string]any {
delete(d, key)
return d
}
// hasKey tests whether a key is found in dict.
func hasKey(d map[string]any, key string) bool {
_, ok := d[key]
return ok
}
// list creates a list from values.
func list[T any](v ...T) []T {
return v
}
// fail unconditionally returns an empty string and an error with the specified text.
func fail(msg string) (string, error) {
return "", errors.New(msg)
}
// jsonString returns a json encoded value as string.
func jsonString(v any) (string, error) {
b, err := json.Marshal(v)
if err != nil {
return "", nil
}
return string(b), nil
}
// allZero reports whether all given values are the zero value of their type.
func allZero(v ...any) bool {
for _, x := range v {
if !reflect.ValueOf(x).IsZero() {
return false
}
}
return true
}

208
entc/gen/globalid.go Normal file
View File

@@ -0,0 +1,208 @@
// 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.
package gen
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"entgo.io/ent/dialect/entsql"
)
const incrementIdent = "const IncrementStarts"
// IncrementStarts holds the autoincrement start value for each type.
type IncrementStarts map[string]int
// IncrementStartAnnotation assigns a unique range to each node in the graph.
func IncrementStartAnnotation(g *Graph) error {
// To ensure we keep the existing type ranges, load the current global id sequence, if there is any.
var (
r = make(IncrementStarts)
path = IncrementStartsFilePath(g.Target)
buf, err = os.ReadFile(path)
)
switch {
case os.IsNotExist(err): // first time generation
case err != nil:
return err
default:
if ok, _ := g.FeatureEnabled(FeatureSnapshot.Name); ok {
if err = ResolveIncrementStartsConflict(g.Target); err != nil {
return err
}
buf, err = os.ReadFile(path)
if err != nil {
return err
}
}
var (
matches = make([][]byte, 0, 2)
lines = bytes.Split(buf, []byte("\n"))
)
for i := 0; i < len(lines); i++ {
if l := lines[i]; bytes.HasPrefix(l, []byte(incrementIdent)) {
matches = append(matches, l)
}
}
if len(matches) != 1 {
return fmt.Errorf("expect to have exactly 1 ranges in %s", path)
}
var (
line = matches[0]
start = bytes.IndexByte(line, '"')
end = bytes.LastIndexByte(line, '"')
)
if start == -1 || start >= end {
return fmt.Errorf("unexpected line %s", line)
}
l, err := strconv.Unquote(string(line[start : end+1]))
if err != nil {
return err
}
if err := json.Unmarshal([]byte(l), &r); err != nil {
return fmt.Errorf("unmarshal ranges: %w", err)
}
}
// Range over all nodes and assign the increment starting value.
var (
need []*Type
lastIdx = -1
)
for _, n := range g.Nodes {
a := n.EntSQL()
if a == nil {
a = &entsql.Annotation{}
}
switch v, ok := r[n.Table()]; {
case a.IncrementStart != nil:
// In case the start value is defined by an annotation already, it has precedence.
r[n.Table()] = *a.IncrementStart
case ok:
// In case this node has no annotation but an existing entry in the increments file.
a.IncrementStart = &v
default:
// In case this is a new node, it gets the next free increment range (highest value << 32).
need = append(need, n)
}
if v, ok := r[n.Table()]; ok {
lastIdx = max(lastIdx, v/(1<<32-1))
}
if err := setAnnotation(n, a); err != nil {
return err
}
}
// Compute new ranges and write them back to the file.
for i, n := range need {
r[n.Table()] = (lastIdx + i + 1) << 32
a := n.EntSQL()
a.IncrementStart = func(i int) *int { return &i }(r[n.Table()]) // copy to not override previous values
if err := setAnnotation(n, a); err != nil {
return err
}
}
// Ensure increment ranges are exactly of size 1<<32 with no overlaps.
d := make(map[int]string)
for t, s := range r {
switch t1, ok := d[s]; {
case ok:
return fmt.Errorf("duplicated increment start value %d for types %s and %s", s, t1, t)
case s%(1<<32) != 0:
return fmt.Errorf(
"unexpected increment start value %d for type %s, expected multiple of %d (1<<32)", s, t, 1<<32,
)
}
d[s] = t
}
if g.Annotations == nil {
g.Annotations = make(Annotations)
}
g.Annotations[r.Name()] = r
return nil
}
// Name implements Annotation interface.
func (IncrementStarts) Name() string {
return "IncrementStarts"
}
// WriteToDisk writes the increment starts to the disk.
func (i IncrementStarts) WriteToDisk(target string) error {
initTemplates()
p := IncrementStartsFilePath(target)
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
return err
}
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
return templates.Lookup("internal/globalid").
Execute(f, &Config{
Target: target,
Annotations: Annotations{"IncrementStarts": i},
})
}
func IncrementStartsFilePath(dir string) string {
return filepath.Join(dir, "internal", "globalid.go")
}
// ResolveIncrementStartsConflict resolves git/mercurial conflicts by "accepting theirs".
func ResolveIncrementStartsConflict(dir string) error {
// Expect 2 ranges in the file, accept the second one, since this is the remote content.
p := IncrementStartsFilePath(dir)
fi, err := os.Stat(p)
if err != nil {
return err
}
c, err := os.ReadFile(p)
if err != nil {
return err
}
var (
fixed [][]byte
conflict, skipped bool
lines = bytes.Split(c, []byte("\n"))
)
for _, l := range lines {
switch {
case bytes.HasPrefix(l, []byte("<<<<<<<")):
conflict = true
case bytes.HasPrefix(l, []byte("=======")), bytes.HasPrefix(l, []byte(">>>>>>>")):
case bytes.HasPrefix(l, []byte(incrementIdent)) && conflict && !skipped:
skipped = true
default:
fixed = append(fixed, l)
}
}
return os.WriteFile(p, bytes.Join(fixed, []byte("\n")), fi.Mode())
}
func ToMap(a *entsql.Annotation) (map[string]any, error) {
buf, err := json.Marshal(a)
if err != nil {
return nil, err
}
m := make(map[string]any)
if err = json.Unmarshal(buf, &m); err != nil {
return nil, err
}
return m, nil
}
func setAnnotation(n *Type, a *entsql.Annotation) error {
m, err := ToMap(a)
if err != nil {
return err
}
n.Annotations.Set(a.Name(), m)
return nil
}

128
entc/gen/globalid_test.go Normal file
View File

@@ -0,0 +1,128 @@
// 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.
package gen_test
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"testing"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/entc/gen"
"entgo.io/ent/entc/internal"
"entgo.io/ent/entc/load"
"github.com/stretchr/testify/require"
)
func TestIncrementStartAnnotation(t *testing.T) {
var (
p = func(i int) *int { return &i }
a = &entsql.Annotation{IncrementStart: p(100)}
s = []*load.Schema{
{
Name: "T1",
Annotations: map[string]any{a.Name(): must(gen.ToMap(a))},
},
}
c = &gen.Config{
Package: "entc/gen",
Target: t.TempDir(),
}
)
// Arbitrary increment starting values allowed if feature is not enabled.
g, err := gen.NewGraph(c, s...)
require.NoError(t, err)
require.NotNil(t, g)
// Increments must be a multiple of 1<<32.
c.Features = []gen.Feature{gen.FeatureGlobalID}
g, err = gen.NewGraph(c, s...)
require.EqualError(t, err, "unexpected increment start value 100 for type t1s, expected multiple of 4294967296 (1<<32)")
require.Nil(t, g)
a = &entsql.Annotation{IncrementStart: p(1 << 32)}
s[0].Annotations[a.Name()] = must(gen.ToMap(a))
g, err = gen.NewGraph(c, s...)
require.NoError(t, err)
require.NotNil(t, g)
// Duplicated increment starting values are not allowed.
s = append(s, &load.Schema{Name: "T2"}, &load.Schema{
Name: "T3",
Annotations: map[string]any{a.Name(): must(gen.ToMap(a))},
})
g, err = gen.NewGraph(c, s...)
require.ErrorContains(t, err, "duplicated increment start value 4294967296 for types")
require.Nil(t, g)
// Respects existing increment starting values loaded from file.
s = []*load.Schema{{Name: "A"}, {Name: "B"}, {Name: "C"}}
c.Target = t.TempDir()
is := gen.IncrementStarts{"bs": 0, "as": 1 << 32, "cs": 2 << 32}
require.NoError(t, is.WriteToDisk(c.Target))
g, err = gen.NewGraph(c, s...)
require.NoError(t, err)
require.Equal(t, is, g.Annotations[is.Name()])
}
func TestResolveConflicts(t *testing.T) {
var (
c = &gen.Config{
Package: "entc/gen",
Target: t.TempDir(),
Features: []gen.Feature{gen.FeatureGlobalID},
}
s = []*load.Schema{{Name: "A"}, {Name: "B"}, {Name: "C"}, {Name: "D"}}
cflct = fmt.Sprintf(`
package internal
<<<<<<< HEAD:globalid.go
const IncrementStarts = %s
=======
const IncrementStarts = %s
>>>>>>> 1234567:globalid.go
`,
// We added the c table which got the range 2<<32
marshal(t, gen.IncrementStarts{"bs": 0, "as": 1 << 32, "cs": 2 << 32}),
// In the meantime someone else added the d table which got the
// range 2<<32 as well, and they merged before we did.
marshal(t, gen.IncrementStarts{"bs": 0, "as": 1 << 32, "ds": 2 << 32}),
)
p = filepath.Join(c.Target, "internal", "globalid.go")
)
require.NoError(t, os.MkdirAll(filepath.Dir(p), 0755))
require.NoError(t, os.WriteFile(p, []byte(cflct), 0644))
// Expect an error when there is a file conflict.
_, err := gen.NewGraph(c, s...)
require.Error(t, err)
// Conflict is resolved to "accept theirs".
require.NoError(t, gen.ResolveIncrementStartsConflict(c.Target))
require.NoError(t, internal.CheckDir(filepath.Dir(p)))
g, err := gen.NewGraph(c, s...)
require.NoError(t, err)
// Expect the conflict to be resolved with the remote table d keeping
// its range and our newly added table c gets the next one (3<<32).
require.Equal(t,
gen.IncrementStarts{"bs": 0, "as": 1 << 32, "cs": 3 << 32, "ds": 2 << 32},
g.Annotations[(&gen.IncrementStarts{}).Name()],
)
}
func must[T any](t T, err error) T {
if err != nil {
panic(err)
}
return t
}
func marshal(t *testing.T, v any) string {
t.Helper()
b, err := json.Marshal(v)
require.NoError(t, err)
return strconv.Quote(string(b))
}

1258
entc/gen/graph.go Normal file

File diff suppressed because it is too large Load Diff

770
entc/gen/graph_test.go Normal file
View File

@@ -0,0 +1,770 @@
// 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.
package gen
import (
"fmt"
"os"
"path/filepath"
"reflect"
"slices"
"testing"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/dialect/sql/schema"
"entgo.io/ent/entc/load"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"github.com/stretchr/testify/require"
)
var (
T1 = &load.Schema{
Name: "T1",
Fields: []*load.Field{
{Name: "age", Info: &field.TypeInfo{Type: field.TypeInt}, Optional: true},
{Name: "expired_at", Info: &field.TypeInfo{Type: field.TypeTime}, Nillable: true, Optional: true},
{Name: "name", Info: &field.TypeInfo{Type: field.TypeString}, Default: true},
},
Edges: []*load.Edge{
{Name: "t2", Type: "T2", Required: true},
{Name: "t1", Type: "T1", Unique: true},
// Bidirectional unique edge (unique/"has-a" in both sides).
{Name: "t2_o2o", Type: "T2", Unique: true},
// Unidirectional non-unique edge ("has-many"). The reference is on the "many" side.
// For example: A user "has-many" books, but a book "has-an" owner (and only one).
{Name: "o2m", Type: "T2"},
// Unidirectional unique edge ("has-one").
// For example: A user "has-an" address (and only one), but an address "has-many" users.
{Name: "m2o", Type: "T2", Unique: true},
// Bidirectional unique edge ("has-one" in T1 side, and "has-many" in T2 side).
{Name: "t2_m2o", Type: "T2", Unique: true},
// Bidirectional non-unique edge ("has-many" in T1 side, and "has-one" in T2 side).
{Name: "t2_o2m", Type: "T2"},
// Bidirectional non-unique edge ("has-many" in both side).
{Name: "t2_m2m", Type: "T2"},
// Unidirectional non-unique edge for the same type.
{Name: "t1_m2m", Type: "T1"},
},
}
T2 = &load.Schema{
Name: "T2",
Annotations: dict("GQL", map[string]string{"Name": "T2"}),
Fields: []*load.Field{
{Name: "active", Info: &field.TypeInfo{Type: field.TypeBool}},
},
Edges: []*load.Edge{
{Name: "t1", Type: "T1", RefName: "t2", Inverse: true},
{Name: "t1_o2o", Type: "T1", RefName: "t2_o2o", Unique: true, Inverse: true},
{Name: "t1_o2m", Type: "T1", RefName: "t2_m2o", Inverse: true},
{Name: "t1_m2o", Type: "T1", RefName: "t2_o2m", Unique: true, Inverse: true},
{Name: "t1_m2m", Type: "T1", RefName: "t2_m2m", Inverse: true},
{Name: "t2_m2m_from", Type: "T2", Ref: &load.Edge{Name: "t2_m2m_to", Type: "T2", Annotations: dict("GQL", map[string]string{"Name": "To"})}, Inverse: true, Annotations: dict("GQL", map[string]string{"Name": "From"})},
},
}
)
func TestNewGraph(t *testing.T) {
require := require.New(t)
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, T1)
require.Error(err, "should fail due to missing types")
graph, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, T1, T2)
require.NoError(err)
require.NotNil(graph)
require.Len(graph.Nodes, 2)
t1 := graph.Nodes[0]
// check fields.
require.Equal("T1", t1.Name)
require.Len(t1.Fields, 3)
for i, name := range []string{"age", "expired_at", "name"} {
require.Equal(name, t1.Fields[i].Name)
}
for i, typ := range []string{"int", "time.Time", "string"} {
require.Equal(typ, t1.Fields[i].Type.String())
}
for i, optional := range []bool{true, true, false} {
require.Equal(optional, t1.Fields[i].Optional)
}
for i, nullable := range []bool{false, true, false} {
require.Equal(nullable, t1.Fields[i].Nillable)
}
for i, value := range []bool{false, false, true} {
require.Equal(value, t1.Fields[i].Default)
}
// check edges.
require.Len(t1.Edges, 9)
for i, name := range []string{"t2", "t1"} {
require.Equal(name, t1.Edges[i].Name)
}
for i, typ := range []*Type{graph.Nodes[1], graph.Nodes[0]} {
require.Equal(typ, t1.Edges[i].Type, "edge should point to the right type")
}
for i, optional := range []bool{false, true} {
require.Equal(optional, t1.Edges[i].Optional)
}
for i, unique := range []bool{false, true} {
require.Equal(unique, t1.Edges[i].Unique)
}
for i, inverse := range []bool{false, false} {
require.Equal(inverse, t1.Edges[i].IsInverse())
}
t2 := graph.Nodes[1]
require.Equal(map[string]string{"Name": "T2"}, t2.Annotations["GQL"])
f1, e1 := t2.Fields[0], t2.Edges[0]
require.Equal("bool", f1.Type.String())
require.Equal("active", f1.Name)
require.Equal("t1", e1.Name)
require.True(e1.IsInverse())
require.Equal("t2", e1.Inverse)
require.Equal(graph.Nodes[0], e1.Type)
require.Equal("t2_m2m_from", t2.Edges[5].Name)
require.Equal("t2_m2m_to", t2.Edges[5].Inverse)
require.Equal("t2_m2m_to", t2.Edges[6].Name)
require.Empty(t2.Edges[6].Inverse)
require.Equal(t2.Edges[6], t2.Edges[5].Ref)
require.Equal(t2.Edges[5], t2.Edges[6].Ref)
require.Equal(map[string]string{"Name": "From"}, t2.Edges[5].Annotations["GQL"])
require.Equal(map[string]string{"Name": "To"}, t2.Edges[6].Annotations["GQL"])
}
func TestNewGraphRequiredLoop(t *testing.T) {
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, &load.Schema{
Name: "T1",
Edges: []*load.Edge{
{Name: "parent", Type: "T1", Unique: true, Required: true},
{Name: "children", Type: "T1", Inverse: true, RefName: "parent", Required: true},
},
})
require.Error(t, err, "require loop")
_, err = NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]},
&load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "pets", Type: "Pet", Required: true},
},
},
&load.Schema{
Name: "Pet",
Edges: []*load.Edge{
{Name: "owner", Type: "User", Inverse: true, RefName: "pets", Unique: true, Required: true},
},
})
require.Error(t, err, "require loop")
}
func TestNewGraphBadInverse(t *testing.T) {
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]},
&load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "pets", Type: "Pet"},
{Name: "groups", Type: "Group"},
},
},
&load.Schema{
Name: "Pet",
Edges: []*load.Edge{
{Name: "owner", Type: "User", Unique: true, Required: true, RefName: "pets", Inverse: true},
},
},
&load.Schema{
Name: "Group",
Edges: []*load.Edge{
{Name: "users", Type: "User", RefName: "pets", Inverse: true},
},
})
require.Errorf(t, err, "mismatch type for back-reference")
}
func TestNewGraphDuplicateEdges(t *testing.T) {
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]},
&load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "groups", Type: "Group"},
{Name: "groups", Type: "Group", RefName: "owner", Inverse: true},
},
},
&load.Schema{
Name: "Group",
Edges: []*load.Edge{
{Name: "users", Type: "User", RefName: "groups", Inverse: true},
{Name: "owner", Type: "User", Unique: true},
},
})
require.EqualError(t, err, `entc/gen: User schema contains multiple "groups" edges`)
}
func TestNewGraphDuplicateEdgeField(t *testing.T) {
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]},
&load.Schema{
Name: "User",
Fields: []*load.Field{
{Name: "parent", Info: &field.TypeInfo{Type: field.TypeInt}},
},
Edges: []*load.Edge{
{Name: "parent", Type: "User"},
},
})
require.EqualError(t, err, `entc/gen: User schema cannot contain field and edge with the same name "parent"`)
}
func TestNewGraphThroughUndefinedType(t *testing.T) {
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, &load.Schema{
Name: "T1",
Edges: []*load.Edge{
{Name: "groups", Type: "T1", Required: true, Through: &struct{ N, T string }{N: "groups_edge", T: "T2"}},
},
})
require.EqualError(t, err, `entc/gen: resolving edges: edge T1.groups defined with Through("groups_edge", T2.Type), but type T2 was not found`)
}
func TestNewGraphThroughInvalidRel(t *testing.T) {
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, &load.Schema{
Name: "T1",
Edges: []*load.Edge{
{Name: "groups", Type: "T1", Unique: true, Required: true, Through: &struct{ N, T string }{N: "groups_edge", T: "T2"}},
},
})
require.EqualError(t, err, `entc/gen: resolving edges: edge T1.groups Through("groups_edge", T2.Type) is allowed only on M2M edges, but got: "O2O"`)
}
func TestNewGraphThroughDuplicates(t *testing.T) {
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]},
&load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "groups", Type: "Group", Through: &struct{ N, T string }{N: "group_edges", T: "T1"}},
{Name: "group_edges", Type: "Group"},
},
},
&load.Schema{
Name: "Group",
Edges: []*load.Edge{
{Name: "users", Type: "User", Inverse: true, RefName: "groups", Through: &struct{ N, T string }{N: "user_edges", T: "T1"}},
},
},
&load.Schema{
Name: "T1",
},
)
require.EqualError(t, err, `entc/gen: resolving edges: edge User.groups defined with Through("group_edges", T1.Type), but schema User already has an edge named group_edges`)
}
func TestRelation(t *testing.T) {
require := require.New(t)
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, T1)
require.Error(err, "should fail due to missing types")
graph, err := NewGraph(&Config{Package: "entc/gen"}, T1, T2)
require.NoError(err)
require.NotNil(graph)
require.Len(graph.Nodes, 2)
t1, t2 := graph.Nodes[0], graph.Nodes[1]
// unidirectional one 2 one.
require.Equal(O2O, t1.Edges[1].Rel.Type)
// bidirectional one to one.
require.Equal(O2O, t1.Edges[2].Rel.Type)
require.Equal(O2O, t2.Edges[1].Rel.Type)
// unidirectional one 2 many.
require.Equal(O2M, t1.Edges[3].Rel.Type)
// unidirectional many 2 one.
require.Equal(M2O, t1.Edges[4].Rel.Type)
// bidirectional many 2 one.
require.Equal(M2O, t1.Edges[5].Rel.Type)
require.Equal(O2M, t2.Edges[2].Rel.Type)
// bidirectional one 2 many.
require.Equal(O2M, t1.Edges[6].Rel.Type)
require.Equal(M2O, t2.Edges[3].Rel.Type)
// bidirectional many 2 many.
require.Equal(M2M, t1.Edges[7].Rel.Type)
require.Equal(M2M, t2.Edges[4].Rel.Type)
// unidirectional many 2 many.
require.Equal(M2M, t1.Edges[8].Rel.Type)
}
func TestFKColumns(t *testing.T) {
user := &load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "pets", Type: "Pet"},
{Name: "pet", Type: "Pet", Unique: true},
{Name: "parent", Type: "User", Unique: true},
},
}
require := require.New(t)
graph, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user, &load.Schema{Name: "Pet"})
require.NoError(err)
t1 := graph.Nodes[0]
for i, r := range []Relation{
{Type: O2M, Table: "pets", Columns: []string{"user_pets"}},
{Type: M2O, Table: "users", Columns: []string{"user_pet"}},
{Type: O2O, Table: "users", Columns: []string{"user_parent"}},
} {
require.Equal(r.Type, t1.Edges[i].Rel.Type)
require.Equal(r.Table, t1.Edges[i].Rel.Table)
require.Equal(r.Columns, t1.Edges[i].Rel.Columns)
}
// Adding inverse edges.
graph, err = NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user,
&load.Schema{
Name: "Pet",
Edges: []*load.Edge{
{Name: "owner", Type: "User", RefName: "pets", Inverse: true, Unique: true},
{Name: "team", Type: "User", RefName: "pet", Inverse: true},
},
},
)
require.NoError(err)
t1, t2 := graph.Nodes[0], graph.Nodes[1]
for i, r := range []Relation{
{Type: O2M, Table: "pets", Columns: []string{"user_pets"}},
{Type: M2O, Table: "users", Columns: []string{"user_pet"}},
} {
require.Equal(r.Type, t1.Edges[i].Rel.Type)
require.Equal(r.Table, t1.Edges[i].Rel.Table)
require.Equal(r.Columns, t1.Edges[i].Rel.Columns)
}
for i, r := range []Relation{
{Type: M2O, Table: "pets", Columns: []string{"user_pets"}},
{Type: O2M, Table: "users", Columns: []string{"user_pet"}},
} {
require.Equal(r.Type, t2.Edges[i].Rel.Type)
require.Equal(r.Table, t2.Edges[i].Rel.Table)
require.Equal(r.Columns, t2.Edges[i].Rel.Columns)
}
}
func TestBlobDualWriteGoType(t *testing.T) {
require := require.New(t)
doc := &load.Schema{
Name: "Doc",
Fields: []*load.Field{
{
Name: "config",
Info: &field.TypeInfo{
Type: field.TypeBlob,
Ident: "*Config",
PkgPath: "example.com/app",
PkgName: "app",
RType: &field.RType{
Name: "Config",
Ident: "Config",
Kind: reflect.Struct,
PkgPath: "example.com/app",
},
},
ValueScanner: true,
BlobDualWrite: true,
},
},
}
g, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, doc)
require.NoError(err)
require.Len(g.Nodes, 1)
f := g.Nodes[0].Fields[0]
require.True(f.IsBlob())
require.False(f.IsBlobNoColumn())
// After graph initialization, the blob field should be TypeBytes but preserve GoType info.
require.Equal(field.TypeBytes, f.Type.Type)
require.True(f.HasGoType(), "GoType should be preserved for DualWrite blob fields")
require.Equal("*Config", f.Type.String(), "Type.String() should return the custom GoType")
require.Equal("example.com/app", f.Type.PkgPath)
require.NotNil(f.Type.RType)
require.Equal("Config", f.Type.RType.Name)
}
func TestAbortDuplicateFK(t *testing.T) {
var (
user = &load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "pets", Type: "Pet", StorageKey: &edge.StorageKey{Symbols: []string{"owner_id"}}},
{Name: "cars", Type: "Car", StorageKey: &edge.StorageKey{Symbols: []string{"owner_id"}}},
},
}
pet = &load.Schema{
Name: "Pet",
Fields: []*load.Field{
{Name: "owner_id", Info: &field.TypeInfo{Type: field.TypeInt}, Nillable: true, Optional: true},
},
Edges: []*load.Edge{
{Name: "owner", Type: "User", RefName: "pets", Inverse: true, Unique: true},
},
}
car = &load.Schema{
Name: "Car",
Fields: []*load.Field{
{Name: "owner_id", Info: &field.TypeInfo{Type: field.TypeInt}, Nillable: true, Optional: true},
},
Edges: []*load.Edge{
{Name: "owner", Type: "User", RefName: "cars", Inverse: true, Unique: true},
},
}
)
g, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user, pet, car)
require.NoError(t, err)
_, err = g.Tables()
require.EqualError(t, err, `duplicate foreign-key symbol "owner_id" found in tables "cars" and "pets"`)
}
func TestPosition(t *testing.T) {
antFn := func(s string) map[string]any {
return map[string]any{entsql.Annotation{}.Name(): map[string]string{"schema": s}}
}
var (
user = &load.Schema{
Name: "User",
Pos: "user.go:1",
Edges: []*load.Edge{
{Name: "pets", Type: "Pet"},
{Name: "cars", Type: "Car", Through: &struct{ N, T string }{N: "car_edge", T: "CarOwner"}},
},
Annotations: antFn("one"),
}
pet = &load.Schema{
Name: "Pet",
Pos: "pet.go:10",
Edges: []*load.Edge{
{Name: "owner", Type: "User", RefName: "pets", Inverse: true},
},
Annotations: antFn("two"),
}
petView = &load.Schema{
View: true,
Name: "PetView",
Pos: "pet_view.go:10",
Annotations: antFn("two"),
}
car = &load.Schema{
Name: "Car",
Pos: "car.go:100",
Edges: []*load.Edge{
{Name: "owners", Type: "User", RefName: "cars", Inverse: true},
},
Annotations: antFn("two"),
}
carOwner = &load.Schema{
Name: "CarOwner",
Pos: "car_owner.go:1000",
Fields: []*load.Field{
{Name: "user_id", Info: &field.TypeInfo{Type: field.TypeInt}},
{Name: "car_id", Info: &field.TypeInfo{Type: field.TypeInt}},
},
Edges: []*load.Edge{
{Name: "owner", Type: "User", Field: "user_id", Unique: true, Required: true},
{Name: "car", Type: "User", Field: "car_id", Unique: true, Required: true},
},
Annotations: antFn("two"),
}
)
g, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user, pet, petView, car, carOwner)
require.NoError(t, err)
ts, err := g.Tables()
require.NoError(t, err)
require.Len(t, ts, 5)
require.Equal(t, ts[0].Pos, "user.go:1")
require.Equal(t, ts[1].Pos, "pet.go:10")
require.Equal(t, ts[2].Pos, "car.go:100")
require.Equal(t, ts[3].Pos, "car_owner.go:1000") // edge schema has its own position
require.Equal(t, ts[4].Pos, "user.go:1") // user owns the pet edge -> user position
vs, err := g.Views()
require.NoError(t, err)
require.Len(t, vs, 1)
require.Equal(t, vs[0].Pos, "pet_view.go:10")
}
func TestMultiSchemaAnnotation(t *testing.T) {
antFn := func(s string) map[string]any {
return map[string]any{entsql.Annotation{}.Name(): map[string]string{"schema": s}}
}
var (
user = &load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "pets", Type: "Pet"},
{Name: "cars", Type: "Car", Annotations: antFn("two")},
},
Annotations: antFn("one"),
}
pet = &load.Schema{
Name: "Pet",
Edges: []*load.Edge{
{Name: "owner", Type: "User", RefName: "pets", Inverse: true},
},
Annotations: antFn("two"),
}
car = &load.Schema{
Name: "Car",
Edges: []*load.Edge{
{Name: "owner", Type: "User", RefName: "cars", Inverse: true},
},
Annotations: antFn("two"),
}
)
g, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user, pet, car)
require.NoError(t, err)
ts, err := g.Tables()
require.NoError(t, err)
require.Len(t, ts, 5)
require.Equal(t, "one", ts[0].Schema) // user
require.Equal(t, "two", ts[1].Schema) // pet
require.Equal(t, "two", ts[2].Schema) // car
require.Equal(t, "one", ts[3].Schema) // user<>pets join table user lives in owner schema
require.Equal(t, "two", ts[4].Schema) // user<>cars edge has annotation and lives in specified schema
}
func TestEnsureCorrectFK(t *testing.T) {
var (
user = &load.Schema{
Name: "User",
Edges: []*load.Edge{
{Name: "pets", Type: "Pet", StorageKey: &edge.StorageKey{Columns: []string{"owner_id"}}},
},
}
pet = &load.Schema{
Name: "Pet",
Fields: []*load.Field{
{Name: "owner_id", Info: &field.TypeInfo{Type: field.TypeInt}, Nillable: true, Optional: true},
},
Edges: []*load.Edge{
{Name: "owner", Type: "User", RefName: "pets", Inverse: true, Unique: true},
},
}
)
_, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user, pet)
require.EqualError(t, err, `entc/gen: set "User" foreign-keys: column "owner_id" definition on edge "pets" should be replaced with Field("owner_id") on its reference "owner"`)
user.Edges[0].StorageKey = nil
pet.Edges[0].Field = "owner_id"
_, err = NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user, pet)
require.NoError(t, err)
}
func TestGraph_Gen(t *testing.T) {
require := require.New(t)
target := filepath.Join(t.TempDir(), "ent")
external := MustParse(NewTemplate("external").Parse("package external"))
skipped := MustParse(NewTemplate("skipped").SkipIf(func(*Graph) bool { return true }).Parse("package external"))
schemas := []*load.Schema{
{
Name: "T1",
Fields: []*load.Field{
{Name: "age", Info: &field.TypeInfo{Type: field.TypeInt}, Optional: true},
{Name: "expired_at", Info: &field.TypeInfo{Type: field.TypeTime}, Nillable: true, Optional: true},
{Name: "name", Info: &field.TypeInfo{Type: field.TypeString}},
},
Edges: []*load.Edge{
{Name: "t1", Type: "T1", Unique: true},
},
},
{Name: "T2"},
{Name: "T3"},
}
graph, err := NewGraph(&Config{
Package: "entc/gen",
Target: target,
Storage: drivers[0],
Templates: []*Template{external, skipped},
IDType: &field.TypeInfo{Type: field.TypeInt},
Features: AllFeatures,
}, schemas...)
require.NoError(err)
require.NotNil(graph)
require.NoError(graph.Gen())
// Ensure globalid feature added annotations.
a := IncrementStarts{"t1s": 0, "t2s": 1 << 32, "t3s": 2 << 32}
require.Equal(a, graph.Annotations[a.Name()])
for i, n := range graph.Nodes {
require.Equal(i<<32, *n.EntSQL().IncrementStart)
}
// Ensure graph files were generated.
for _, name := range []string{"ent", "client"} {
_, err := os.Stat(fmt.Sprintf("%s/%s.go", target, name))
require.NoError(err)
}
// Ensure entity files were generated.
for _, format := range []string{"%s", "%s_create", "%s_update", "%s_delete", "%s_query"} {
_, err := os.Stat(fmt.Sprintf(fmt.Sprintf("%s/%s.go", target, format), "t1"))
require.NoError(err)
_, err = os.Stat(fmt.Sprintf(fmt.Sprintf("%s/%s.go", target, format), "t2"))
require.NoError(err)
}
_, err = os.Stat(filepath.Join(target, "external.go"))
require.NoError(err)
_, err = os.Stat(filepath.Join(target, "skipped.go"))
require.True(os.IsNotExist(err))
// Generated feature templates.
_, err = os.Stat(filepath.Join(target, "internal", "schema.go"))
require.NoError(err)
_, err = os.Stat(filepath.Join(target, "internal", "schemaconfig.go"))
require.NoError(err)
c, err := os.ReadFile(filepath.Join(target, "internal", "globalid.go"))
require.NoError(err)
require.Contains(string(c), fmt.Sprintf(`"{\"t1s\":0,\"t2s\":%d,\"t3s\":%d}"`, 1<<32, 2<<32))
// Rerun codegen with only one feature-flag.
graph.Features = []Feature{FeatureSnapshot}
require.NoError(graph.Gen())
// Generated feature templates.
_, err = os.Stat(filepath.Join(target, "internal", "schema.go"))
require.NoError(err)
_, err = os.Stat(filepath.Join(target, "internal", "schemaconfig.go"))
require.True(os.IsNotExist(err))
_, err = os.Stat(filepath.Join(target, "internal", "globalid.go"))
require.True(os.IsNotExist(err))
// Rerun codegen without any feature-flags.
graph.Features = nil
require.NoError(graph.Gen())
_, err = os.Stat(filepath.Join(target, "internal"))
require.True(os.IsNotExist(err))
schemas = schemas[:1]
graph, err = NewGraph(&Config{
Package: "entc/gen",
Target: target,
Storage: drivers[0],
Templates: []*Template{external, skipped},
IDType: &field.TypeInfo{Type: field.TypeInt},
Features: AllFeatures,
}, schemas...)
require.NoError(err)
require.NotNil(graph)
require.NoError(graph.Gen())
// Ensure entity files were generated.
for _, format := range []string{"%s", "%s_create", "%s_update", "%s_delete", "%s_query"} {
_, err := os.Stat(fmt.Sprintf(fmt.Sprintf("%s/%s.go", target, format), "t1"))
require.NoError(err)
_, err = os.Stat(fmt.Sprintf(fmt.Sprintf("%s/%s.go", target, format), "t2"))
require.Error(err)
}
}
func ensureStructTag(name string) Hook {
return func(next Generator) Generator {
return GenerateFunc(func(g *Graph) error {
// Ensure all fields have a specific tag.
for _, node := range g.Nodes {
for _, f := range node.Fields {
tag := reflect.StructTag(f.StructTag)
if _, ok := tag.Lookup(name); !ok {
return fmt.Errorf("struct tag %q is missing for field %s.%s", name, node.Name, f.Name)
}
}
}
return next.Generate(g)
})
}
}
func TestGraph_Hooks(t *testing.T) {
require := require.New(t)
graph, err := NewGraph(&Config{
Package: "entc/gen",
Storage: drivers[0],
IDType: &field.TypeInfo{Type: field.TypeInt},
Hooks: []Hook{ensureStructTag("yaml")},
}, &load.Schema{
Name: "T1",
Fields: []*load.Field{
{Name: "age", Info: &field.TypeInfo{Type: field.TypeInt}, Optional: true},
{Name: "expired_at", Info: &field.TypeInfo{Type: field.TypeTime}, Nillable: true, Optional: true},
{Name: "name", Info: &field.TypeInfo{Type: field.TypeString}},
},
Edges: []*load.Edge{
{Name: "t1", Type: "T1", Unique: true},
},
})
require.NoError(err)
require.NotNil(graph)
require.EqualError(graph.Gen(), `struct tag "yaml" is missing for field T1.age`)
}
func TestDependencyAnnotation_Build(t *testing.T) {
tests := []struct {
typ *field.TypeInfo
field string
}{
{
typ: &field.TypeInfo{
Ident: "*http.Client",
},
field: "HTTPClient",
},
{
typ: &field.TypeInfo{
Ident: "[]*http.Client",
RType: &field.RType{
Kind: reflect.Slice,
},
},
field: "HTTPClients",
},
{
typ: &field.TypeInfo{
Ident: "[]*url.URL",
RType: &field.RType{
Kind: reflect.Slice,
},
},
field: "URLs",
},
{
typ: &field.TypeInfo{
Ident: "*net.Conn",
},
field: "NetConn",
},
}
for _, tt := range tests {
d := &Dependency{Type: tt.typ}
require.NoError(t, d.Build())
require.Equal(t, tt.field, d.Field)
}
}
func TestEdgeFieldCollation(t *testing.T) {
var (
user = &load.Schema{
Name: "User",
Fields: []*load.Field{
{Name: "id", Info: &field.TypeInfo{Type: field.TypeString}, Annotations: dict("EntSQL", dict("collation", "utf8mb4_bin"))},
},
}
post = &load.Schema{
Name: "Post",
Fields: []*load.Field{
{Name: "author_id", Info: &field.TypeInfo{Type: field.TypeString}, Annotations: dict("EntSQL", dict("collation", "utf8mb4_bin"))},
},
Edges: []*load.Edge{
{Name: "author", Type: "User", Field: "author_id", Unique: true, Required: true},
},
}
)
g, err := NewGraph(&Config{Package: "entc/gen", Storage: drivers[0]}, user, post)
require.NoError(t, err)
tables, err := g.Tables()
require.NoError(t, err)
// Find the post table
idx := slices.IndexFunc(tables, func(t *schema.Table) bool { return t.Name == "posts" })
require.NotEqual(t, -1, idx, "posts table should exist")
postTable := tables[idx]
// Verify author_id column preserves collation from field annotation
col, ok := postTable.Column("author_id")
require.True(t, ok)
require.Equal(t, "utf8mb4_bin", col.Collation)
}

72
entc/gen/predicate.go Normal file
View File

@@ -0,0 +1,72 @@
// 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.
package gen
// Op represents a predicate operation for schema fields.
type Op int
// List of all builtin predicates.
const (
EQ Op = iota // =
NEQ // <>
GT // >
GTE // >=
LT // <
LTE // <=
IsNil // IS NULL / has
NotNil // IS NOT NULL / hasNot
In // within
NotIn // without
EqualFold // equals case-insensitive
Contains // containing
ContainsFold // containing case-insensitive
HasPrefix // startingWith
HasSuffix // endingWith
)
// Name returns the string representation of an operator.
func (o Op) Name() string {
if int(o) < len(opText) {
return opText[o]
}
return "Unknown"
}
// Variadic reports if the operator is a variadic function (takes a varying number of arguments).
func (o Op) Variadic() bool {
return o == In || o == NotIn
}
// Niladic reports if the operator is niladic (takes no arguments).
func (o Op) Niladic() bool {
return o == IsNil || o == NotNil
}
var (
// operations text.
opText = [...]string{
EQ: "EQ",
NEQ: "NEQ",
GT: "GT",
GTE: "GTE",
LT: "LT",
LTE: "LTE",
IsNil: "IsNil",
NotNil: "NotNil",
EqualFold: "EqualFold",
Contains: "Contains",
ContainsFold: "ContainsFold",
HasPrefix: "HasPrefix",
HasSuffix: "HasSuffix",
In: "In",
NotIn: "NotIn",
}
// operations per type.
boolOps = []Op{EQ, NEQ}
enumOps = append(boolOps, In, NotIn)
numericOps = append(enumOps, GT, GTE, LT, LTE)
stringOps = append(numericOps, Contains, HasPrefix, HasSuffix)
nillableOps = []Op{IsNil, NotNil}
)

200
entc/gen/storage.go Normal file
View File

@@ -0,0 +1,200 @@
// 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.
package gen
import (
"fmt"
"maps"
"reflect"
"slices"
"strings"
"entgo.io/ent/dialect/gremlin/graph/dsl"
"entgo.io/ent/dialect/sql"
)
// A SchemaMode defines what type of schema feature a storage driver support.
type SchemaMode uint
const (
// Unique defines field and edge uniqueness support.
Unique SchemaMode = 1 << iota
// Indexes defines indexes support.
Indexes
// Cascade defines cascading operations (e.g. cascade deletion).
Cascade
// Migrate defines static schema and migration support (e.g. SQL-based).
Migrate
)
// Support reports whether m support the given mode.
func (m SchemaMode) Support(mode SchemaMode) bool { return m&mode != 0 }
// Storage driver type for codegen.
type Storage struct {
Name string // storage name.
Builder reflect.Type // query builder type.
Dialects []string // supported dialects.
IdentName string // identifier name (fields and funcs).
Imports []string // import packages needed.
SchemaMode SchemaMode // schema mode support.
Ops func(*Field) []Op // storage specific operations.
OpCode func(Op) string // operation code for predicates.
Init func(*Graph) error // optional init function.
}
// StorageDrivers holds the storage driver options for entc.
var drivers = []*Storage{
{
Name: "sql",
IdentName: "SQL",
Builder: reflect.TypeOf(&sql.Selector{}),
Dialects: []string{"dialect.SQLite", "dialect.MySQL", "dialect.Postgres"},
Imports: []string{
"database/sql/driver",
"entgo.io/ent/dialect/sql",
"entgo.io/ent/dialect/sql/sqlgraph",
"entgo.io/ent/dialect/sql/sqljson",
"entgo.io/ent/schema/field",
},
SchemaMode: Unique | Indexes | Cascade | Migrate,
Ops: func(f *Field) []Op {
if f.IsString() && f.ConvertedToBasic() {
return []Op{EqualFold, ContainsFold}
}
return nil
},
OpCode: opCodes(sqlCode[:]),
Init: func(g *Graph) error {
var with, without []string
for _, n := range g.Nodes {
if s, err := n.TableSchema(); err == nil && s != "" {
with = append(with, n.Name)
} else {
without = append(without, n.Name)
}
}
switch {
case len(with) == 0:
return nil
case len(without) > 0:
return fmt.Errorf("missing schema annotation for %s", strings.Join(without, ", "))
default:
if !g.featureEnabled(FeatureSchemaConfig) {
g.Features = append(g.Features, FeatureSchemaConfig)
}
if !g.featureEnabled(featureMultiSchema) {
g.Features = append(g.Features, featureMultiSchema)
}
return nil
}
},
},
{
Name: "gremlin",
IdentName: "Gremlin",
Builder: reflect.TypeOf(&dsl.Traversal{}),
Dialects: []string{"dialect.Gremlin"},
Imports: []string{
"entgo.io/ent/dialect/gremlin",
"entgo.io/ent/dialect/gremlin/graph/dsl",
"entgo.io/ent/dialect/gremlin/graph/dsl/__",
"entgo.io/ent/dialect/gremlin/graph/dsl/g",
"entgo.io/ent/dialect/gremlin/graph/dsl/p",
"entgo.io/ent/dialect/gremlin/encoding/graphson",
},
SchemaMode: Unique,
OpCode: opCodes(gremlinCode[:]),
Init: func(*Graph) error { return nil }, // Noop.
},
}
// NewStorage returns the storage driver type from the given string.
// It fails if the provided string is not a valid option. this function
// is here in order to remove the validation logic from entc command line.
func NewStorage(s string) (*Storage, error) {
for _, d := range drivers {
if s == d.Name {
return d, nil
}
}
return nil, fmt.Errorf("entc/gen: invalid storage driver %q", s)
}
// String implements the fmt.Stringer interface for template usage.
func (s *Storage) String() string { return s.Name }
var (
// exceptional operation names in sql.
sqlCode = [...]string{
IsNil: "IsNull",
NotNil: "NotNull",
}
// exceptional operation names in gremlin.
gremlinCode = [...]string{
IsNil: "HasNot",
NotNil: "Has",
In: "Within",
NotIn: "Without",
Contains: "Containing",
HasPrefix: "StartingWith",
HasSuffix: "EndingWith",
}
)
func opCodes(codes []string) func(Op) string {
return func(o Op) string {
if int(o) < len(codes) && codes[o] != "" {
return codes[o]
}
return o.Name()
}
}
// TableSchemas returns all table schemas in ent/schema (intentionally exported).
func (g *Graph) TableSchemas() ([]string, error) {
all := make(map[string]struct{})
for _, n := range g.Nodes {
s, err := n.TableSchema()
if err != nil {
return nil, err
}
all[s] = struct{}{}
for _, e := range n.Edges {
// {{- if and $e.M2M (not $e.Inverse) (not $e.Through) }}
if e.M2M() && !e.IsInverse() && e.Through == nil {
s, err := e.TableSchema()
if err != nil {
return nil, err
}
all[s] = struct{}{}
}
}
}
return slices.Sorted(maps.Keys(all)), nil
}
// TableSchema returns the schema name of where the type table resides (intentionally exported).
func (t *Type) TableSchema() (string, error) {
switch ant := t.EntSQL(); {
case ant == nil || ant.Schema == "":
return "", fmt.Errorf("atlas: missing schema annotation for node %q", t.Name)
default:
return ant.Schema, nil
}
}
// TableSchema returns the schema name of where the type table resides (intentionally exported).
func (e *Edge) TableSchema() (string, error) {
switch ant := e.EntSQL(); {
case ant == nil || ant.Schema == "":
return e.Owner.TableSchema()
default:
return ant.Schema, nil
}
}

466
entc/gen/template.go Normal file
View File

@@ -0,0 +1,466 @@
// 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.
package gen
import (
"bytes"
"embed"
"errors"
"fmt"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"text/template"
"text/template/parse"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
)
type (
// TypeTemplate specifies a template that is executed with
// each Type object of the graph.
TypeTemplate struct {
Name string // template name.
Cond func(*Type) bool // condition to apply the template.
Format func(*Type) string // file name format.
ExtendPatterns []string // extend patterns.
}
// GraphTemplate specifies a template that is executed with
// the Graph object.
GraphTemplate struct {
Name string // template name.
Skip func(*Graph) bool // skip condition (storage constraints or gated by a feature-flag).
Format string // file name format.
ExtendPatterns []string // extend patterns.
}
)
var (
// Templates holds the template information for a file that the graph is generating.
Templates = []TypeTemplate{
{
Name: "create",
Cond: notView,
Format: pkgf("%s_create.go"),
ExtendPatterns: []string{
"dialect/*/create/fields/additional/*",
"dialect/*/create_bulk/fields/additional/*",
},
},
{
Name: "update",
Cond: notView,
Format: pkgf("%s_update.go"),
},
{
Name: "delete",
Cond: notView,
Format: pkgf("%s_delete.go"),
},
{
Name: "query",
Format: pkgf("%s_query.go"),
ExtendPatterns: []string{
"dialect/*/query/fields/additional/*",
},
},
{
Name: "model",
Format: pkgf("%s.go"),
},
{
Name: "where",
Format: pkgf("%s/where.go"),
ExtendPatterns: []string{
"where/additional/*",
},
},
{
Name: "meta",
Format: func(t *Type) string {
return fmt.Sprintf("%[1]s/%[1]s.go", t.PackageDir())
},
ExtendPatterns: []string{
"meta/additional/*",
},
},
{
Name: "mutation",
Cond: notView,
Format: func(t *Type) string {
return fmt.Sprintf("%s/mutation.go", t.PackageDir())
},
ExtendPatterns: []string{
"mutation/fields/*",
},
},
}
// GraphTemplates holds the templates applied on the graph.
GraphTemplates = []GraphTemplate{
{
Name: "base",
Format: "ent.go",
},
{
Name: "client",
Format: "client.go",
ExtendPatterns: []string{
"client/fields/additional/*",
"dialect/*/query/fields/init/*",
},
},
{
Name: "tx",
Format: "tx.go",
},
{
Name: "mutation_graph",
Format: "mutation.go",
},
{
Name: "migrate",
Format: "migrate/migrate.go",
Skip: func(g *Graph) bool { return !g.SupportMigrate() },
},
{
Name: "schema",
Format: "migrate/schema.go",
Skip: func(g *Graph) bool { return !g.SupportMigrate() },
},
{
Name: "predicate",
Format: "predicate/predicate.go",
},
{
Name: "hook",
Format: "hook/hook.go",
},
{
Name: "privacy",
Format: "privacy/privacy.go",
Skip: func(g *Graph) bool {
return !g.featureEnabled(FeaturePrivacy)
},
},
{
Name: "intercept",
Format: "intercept/intercept.go",
Skip: func(g *Graph) bool {
return !g.featureEnabled(FeatureIntercept)
},
},
{
Name: "entql",
Format: "entql.go",
Skip: func(g *Graph) bool {
return !g.featureEnabled(FeatureEntQL)
},
},
{
Name: "runtime/ent",
Format: "runtime.go",
},
{
Name: "enttest",
Format: "enttest/enttest.go",
},
{
Name: "runtime/pkg",
Format: "runtime/runtime.go",
},
}
// template files that were deleted and should be removed by the codegen.
deletedTemplates = []string{"config.go", "context.go"}
// patterns for extending partial-templates (included by other templates).
partialPatterns = [...]string{
"client/additional/*",
"client/additional/*/*",
"config/*/*",
"config/*/*/*",
"create/additional/*",
"delete/additional/*",
"dialect/*/*/*/spec/*",
"dialect/*/*/spec/*",
"dialect/*/config/*/*",
"dialect/*/import/additional/*",
"dialect/*/query/selector/*",
"dialect/sql/create/additional/*",
"dialect/sql/create_bulk/additional/*",
"dialect/sql/meta/constants/*",
"dialect/sql/model/additional/*",
"dialect/sql/model/edges/*",
"dialect/sql/model/edges/fields/additional/*",
"dialect/sql/model/fields/*",
"dialect/sql/select/additional/*",
"dialect/sql/predicate/edge/*/*",
"dialect/sql/query/additional/*",
"dialect/sql/query/all/nodes/*",
"dialect/sql/query/from/*",
"dialect/sql/query/path/*",
"dialect/sql/query/*/*/*",
"import/additional/*",
"model/additional/*",
"model/comment/additional/*",
"model/edges/fields/additional/*",
"tx/additional/*",
"tx/additional/*/*",
"update/additional/*",
"query/additional/*",
"privacy/additional/*",
"privacy/additional/*/*",
"mutation/fields/*",
}
// templates holds the Go templates for the code generation.
templates *Template
//go:embed template/*
templateDir embed.FS
// importPkg are the import packages used for code generation.
// Extended by the function below on generation initialization.
importPkg = map[string]string{
"context": "context",
"driver": "database/sql/driver",
"errors": "errors",
"fmt": "fmt",
"math": "math",
"strings": "strings",
"time": "time",
"ent": "entgo.io/ent",
"dialect": "entgo.io/ent/dialect",
"field": "entgo.io/ent/schema/field",
}
)
// notView reports if the given type is not a view.
func notView(t *Type) bool { return !t.IsView() }
func initTemplates() {
templates = MustParse(NewTemplate("templates").
ParseFS(templateDir, "template/*.tmpl", "template/*/*.tmpl", "template/*/*/*.tmpl", "template/*/*/*/*.tmpl"))
b := bytes.NewBuffer([]byte("package main\n"))
check(templates.ExecuteTemplate(b, "import", Type{Config: &Config{}}), "load imports")
f, err := parser.ParseFile(token.NewFileSet(), "", b, parser.ImportsOnly)
check(err, "parse imports")
for _, spec := range f.Imports {
path, err := strconv.Unquote(spec.Path.Value)
check(err, "unquote import path")
importPkg[filepath.Base(path)] = path
}
for _, s := range drivers {
for _, path := range s.Imports {
importPkg[filepath.Base(path)] = path
}
}
}
// Template wraps the standard template.Template to
// provide additional functionality for ent extensions.
type Template struct {
*template.Template
FuncMap template.FuncMap
condition func(*Graph) bool
}
// NewTemplate creates an empty template with the standard codegen functions.
func NewTemplate(name string) *Template {
t := &Template{Template: template.New(name)}
return t.Funcs(Funcs)
}
// Funcs merges the given funcMap with the template functions.
func (t *Template) Funcs(funcMap template.FuncMap) *Template {
t.Template.Funcs(funcMap)
if t.FuncMap == nil {
t.FuncMap = template.FuncMap{}
}
for name, f := range funcMap {
if _, ok := t.FuncMap[name]; !ok {
t.FuncMap[name] = f
}
}
return t
}
// SkipIf allows registering a function to determine if the template needs to be skipped or not.
func (t *Template) SkipIf(cond func(*Graph) bool) *Template {
t.condition = cond
return t
}
// Parse parses text as a template body for t.
func (t *Template) Parse(text string) (*Template, error) {
if _, err := t.Template.Parse(text); err != nil {
return nil, err
}
return t, nil
}
// ParseFiles parses a list of files as templates and associate them with t.
// Each file can be a standalone template.
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
if _, err := t.Template.ParseFiles(filenames...); err != nil {
return nil, err
}
return t, nil
}
// ParseGlob parses the files that match the given pattern as templates and
// associate them with t.
func (t *Template) ParseGlob(pattern string) (*Template, error) {
if _, err := t.Template.ParseGlob(pattern); err != nil {
return nil, err
}
return t, nil
}
// ParseDir walks on the given dir path and parses the given matches with aren't Go files.
func (t *Template) ParseDir(path string) (*Template, error) {
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("walk path %s: %w", path, err)
}
if info.IsDir() || strings.HasSuffix(path, ".go") {
return nil
}
_, err = t.ParseFiles(path)
return err
})
return t, err
}
// ParseFS is like ParseFiles or ParseGlob but reads from the file system fsys
// instead of the host operating system's file system.
func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
if _, err := t.Template.ParseFS(fsys, patterns...); err != nil {
return nil, err
}
return t, nil
}
// AddParseTree adds the given parse tree to the template.
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
if _, err := t.Template.AddParseTree(name, tree); err != nil {
return nil, err
}
return t, nil
}
// MustParse is a helper that wraps a call to a function returning (*Template, error)
// and panics if the error is non-nil.
func MustParse(t *Template, err error) *Template {
if err != nil {
panic(err)
}
return t
}
type (
// Dependencies wraps a list of dependencies as codegen
// annotation.
Dependencies []*Dependency
// Dependency allows configuring optional dependencies as struct fields on the
// generated builders. For example:
//
// DependencyAnnotation{
// Field: "HTTPClient",
// Type: "*http.Client",
// Option: "WithClient",
// }
//
// Although the Dependency and the DependencyAnnotation are exported, used should
// use the entc.Dependency option in order to build this annotation.
Dependency struct {
// Field defines the struct field name on the builders.
// It defaults to the full type name. For example:
//
// http.Client => HTTPClient
// net.Conn => NetConn
// url.URL => URL
//
Field string
// Type defines the type identifier. For example, `*http.Client`.
Type *field.TypeInfo
// Option defines the name of the config option.
// It defaults to the field name.
Option string
}
)
// Name describes the annotation name.
func (Dependencies) Name() string {
return "Dependencies"
}
// Merge implements the schema.Merger interface.
func (d Dependencies) Merge(other schema.Annotation) schema.Annotation {
if deps, ok := other.(Dependencies); ok {
return append(d, deps...)
}
return d
}
var _ interface {
schema.Annotation
schema.Merger
} = (*Dependencies)(nil)
// Build builds the annotation and fails if it is invalid.
func (d *Dependency) Build() error {
if d.Type == nil {
return errors.New("entc/gen: missing dependency type")
}
if d.Field == "" {
name, err := d.defaultName()
if err != nil {
return err
}
d.Field = name
}
if d.Option == "" {
d.Option = d.Field
}
return nil
}
func (d *Dependency) defaultName() (string, error) {
var pkg, name string
switch parts := strings.Split(strings.TrimLeft(d.Type.Ident, "[]*"), "."); len(parts) {
case 1:
name = parts[0]
case 2:
name = parts[1]
// Avoid stuttering.
if !strings.EqualFold(parts[0], name) {
pkg = parts[0]
}
default:
return "", fmt.Errorf("entc/gen: unexpected number of parts: %q", parts)
}
if r := d.Type.RType; r != nil && (r.Kind == reflect.Array || r.Kind == reflect.Slice) {
name = plural(name)
}
return pascal(pkg) + pascal(name), nil
}
func pkgf(s string) func(t *Type) string {
return func(t *Type) string { return fmt.Sprintf(s, t.PackageDir()) }
}
// match reports if the given name matches the extended pattern.
func match(patterns []string, name string) bool {
for _, pat := range patterns {
matched, _ := filepath.Match(pat, name)
if matched {
return true
}
}
return false
}

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 }}

2502
entc/gen/type.go Normal file

File diff suppressed because it is too large Load Diff

466
entc/gen/type_test.go Normal file
View File

@@ -0,0 +1,466 @@
// 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.
package gen
import (
"testing"
"entgo.io/ent/entc/load"
"entgo.io/ent/schema/field"
"github.com/stretchr/testify/require"
)
func TestType(t *testing.T) {
require := require.New(t)
typ, err := NewType(&Config{Package: "entc/gen"}, T1)
require.NoError(err)
require.NotNil(typ)
require.Equal("T1", typ.Name)
require.Equal("t1", typ.Label())
require.Equal("t1", typ.Package())
require.Equal("_m", typ.Receiver())
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Fields: []*load.Field{
{Name: "foo", Unique: true, Default: true, Info: &field.TypeInfo{Type: field.TypeInt}},
},
})
require.EqualError(err, "unique field \"foo\" cannot have default value", "unique field can not have default")
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Fields: []*load.Field{
{Name: "foo", Sensitive: true, Tag: `yaml:"pwd"`, Info: &field.TypeInfo{Type: field.TypeString}},
},
})
require.EqualError(err, "sensitive field \"foo\" cannot have struct tags", "sensitive field cannot have tags")
typ, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Fields: []*load.Field{
{Name: "id", Info: &field.TypeInfo{Type: field.TypeString}, Annotations: dict("EntSQL", dict("collation", "utf8_ci_bin"))},
},
})
require.NoError(err)
require.NotNil(typ)
require.NotNil(t, typ.ID)
pkCol := typ.ID.PK()
require.NotNil(pkCol)
require.Equal("utf8_ci_bin", pkCol.Collation)
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "T",
Fields: []*load.Field{
{Name: "foo", Unique: true, Info: &field.TypeInfo{Type: field.TypeInt}},
{Name: "foo", Unique: true, Info: &field.TypeInfo{Type: field.TypeInt}},
},
})
require.EqualError(err, "field \"foo\" redeclared for type \"T\"", "field foo redeclared")
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "T",
Fields: []*load.Field{
{Name: "enums", Info: &field.TypeInfo{Type: field.TypeEnum}, Enums: []struct{ N, V string }{{V: "v"}, {V: "v"}}},
},
})
require.EqualError(err, "duplicate values \"v\" for enum field \"enums\"", "duplicate enums")
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "T",
Fields: []*load.Field{
{Name: "enums", Info: &field.TypeInfo{Type: field.TypeEnum}, Enums: []struct{ N, V string }{{}}},
},
})
require.EqualError(err, "\"enums\" field value cannot be empty", "empty value for enums")
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "T",
Fields: []*load.Field{
{Name: "", Info: &field.TypeInfo{Type: field.TypeInt}},
},
})
require.EqualError(err, "field name cannot be empty", "empty field name")
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "T",
Fields: []*load.Field{
{Name: "id", Info: &field.TypeInfo{Type: field.TypeInt}, Optional: true},
},
})
require.EqualError(err, "id field cannot be optional", "id field cannot be optional")
typ, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "T",
Fields: []*load.Field{
{Name: "id", Info: &field.TypeInfo{Type: field.TypeString}, ValueScanner: true},
},
})
require.NoError(err)
require.True(typ.HasValueScanner())
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{Name: "Type"})
require.EqualError(err, "schema lowercase name conflicts with Go keyword \"type\"")
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{Name: "Int"})
require.EqualError(err, "schema lowercase name conflicts with Go predeclared identifier \"int\"")
_, err = NewType(&Config{Package: "entc/gen"}, &load.Schema{Name: "Value"})
require.EqualError(err, "schema name conflicts with ent predeclared identifier \"Value\"")
}
func TestType_Label(t *testing.T) {
tests := []struct {
name string
label string
}{
{"User", "user"},
{"UserInfo", "user_info"},
{"PHBOrg", "phb_org"},
{"UserID", "user_id"},
{"HTTPCode", "http_code"},
{"UserIDs", "user_ids"},
}
for _, tt := range tests {
typ := &Type{Name: tt.name}
require.Equal(t, tt.label, typ.Label())
}
}
func TestType_Table(t *testing.T) {
tests := []struct {
name string
label string
}{
{"User", "users"},
{"Device", "devices"},
{"UserInfo", "user_infos"},
{"PHBOrg", "phb_orgs"},
{"HTTPCode", "http_codes"},
}
for _, tt := range tests {
typ := &Type{Name: tt.name}
require.Equal(t, tt.label, typ.Table())
}
}
func TestField_EnumName(t *testing.T) {
tests := []struct {
name string
enum string
}{
{"GIF", "TypeGIF"},
{"SVG", "TypeSVG"},
{"PNG", "TypePNG"},
{"MP4", "TypeMP4"},
{"unknown", "TypeUnknown"},
{"user_data", "TypeUserData"},
{"test user", "TypeTestUser"},
}
for _, tt := range tests {
require.Equal(t, tt.enum, Field{Name: "Type"}.EnumName(tt.name))
}
}
func TestType_WithRuntimeMixin(t *testing.T) {
position := &load.Position{MixedIn: true}
typ := &Type{
ID: &Field{},
Fields: []*Field{
{Default: true, Position: position},
{UpdateDefault: true, Position: position},
{Validators: 1, Position: position},
},
}
require.True(t, typ.RuntimeMixin())
}
func TestType_TagTypes(t *testing.T) {
typ := &Type{
Fields: []*Field{
{StructTag: `json:"age"`},
{StructTag: `json:"name,omitempty`},
{StructTag: `json:"name,omitempty" sql:"nothing"`},
{StructTag: `sql:"nothing" yaml:"ignore"`},
{StructTag: `sql:"nothing" yaml:"ignore"`},
{StructTag: `invalid`},
{StructTag: `"invalid"`},
},
}
tags := typ.TagTypes()
require.Equal(t, []string{"json", "sql", "yaml"}, tags)
}
func TestType_Package(t *testing.T) {
tests := []struct {
name string
pkg string
}{
{"User", "user"},
{"UserInfo", "userinfo"},
{"PHBOrg", "phborg"},
{"UserID", "userid"},
{"HTTPCode", "httpcode"},
}
for _, tt := range tests {
typ := &Type{Name: tt.name}
require.Equal(t, tt.pkg, typ.Package())
}
}
func TestType_AddIndex(t *testing.T) {
size := int64(1024)
typ, err := NewType(&Config{}, &load.Schema{
Name: "User",
Fields: []*load.Field{
{Name: "name", Info: &field.TypeInfo{Type: field.TypeString}},
{Name: "text", Info: &field.TypeInfo{Type: field.TypeString}, Size: &size},
},
})
require.NoError(t, err)
typ.Edges = append(typ.Edges,
&Edge{Name: "next", Rel: Relation{Type: O2O, Columns: []string{"prev_id"}}},
&Edge{Name: "prev", Inverse: "next", Rel: Relation{Type: O2O, Columns: []string{"prev_id"}}},
&Edge{Name: "owner", Inverse: "files", Rel: Relation{Type: M2O, Columns: []string{"file_id"}}},
)
err = typ.AddIndex(&load.Index{Unique: true})
require.Error(t, err, "missing fields or edges")
err = typ.AddIndex(&load.Index{Unique: true, Fields: []string{"unknown"}})
require.Error(t, err, "unknown field for index")
err = typ.AddIndex(&load.Index{Unique: true, Fields: []string{"id"}})
require.NoError(t, err, "valid index for ID field")
err = typ.AddIndex(&load.Index{Unique: true, Fields: []string{"name"}, Edges: []string{"parent"}})
require.Error(t, err, "missing edge")
err = typ.AddIndex(&load.Index{Unique: true, Fields: []string{"name"}, Edges: []string{"next"}})
require.Error(t, err, "not an inverse edge for O2O relation")
err = typ.AddIndex(&load.Index{Unique: true, Edges: []string{"prev", "owner"}})
require.NoError(t, err, "valid index defined only on edges")
err = typ.AddIndex(&load.Index{Unique: true, Fields: []string{"name"}, Edges: []string{"prev"}})
require.NoError(t, err, "valid index on O2O relation and field")
err = typ.AddIndex(&load.Index{Unique: true, Fields: []string{"name"}, Edges: []string{"owner"}})
require.NoError(t, err, "valid index on M2O relation and field")
}
func TestField_Constant(t *testing.T) {
tests := []struct {
name string
constant string
}{
{"user", "FieldUser"},
{"user_id", "FieldUserID"},
{"user_name", "FieldUserName"},
}
for _, tt := range tests {
typ := &Field{Name: tt.name}
require.Equal(t, tt.constant, typ.Constant())
}
}
func TestField_DefaultName(t *testing.T) {
tests := []struct {
name string
constant string
}{
{"active", "DefaultActive"},
{"expired_at", "DefaultExpiredAt"},
{"group_name", "DefaultGroupName"},
}
for _, tt := range tests {
typ := &Field{Name: tt.name}
require.Equal(t, tt.constant, typ.DefaultName())
}
}
func TestField_incremental(t *testing.T) {
tests := []struct {
annotations map[string]any
def bool
expected bool
}{
{dict("EntSQL", nil), false, false},
{dict("EntSQL", nil), true, true},
{dict("EntSQL", dict("incremental", true)), false, true},
{dict("EntSQL", dict("incremental", false)), true, false},
}
for _, tt := range tests {
typ := &Field{Annotations: tt.annotations}
require.Equal(t, tt.expected, typ.incremental(tt.def))
}
}
func TestBuilderField(t *testing.T) {
tests := []struct {
name string
field string
}{
{"active", "active"},
{"type", "_type"},
{"config", "_config"},
{"SSOCert", "_SSOCert"},
{"driver", "_driver"},
}
for _, tt := range tests {
require.Equal(t, tt.field, Edge{Name: tt.name}.BuilderField())
require.Equal(t, tt.field, Field{Name: tt.name}.BuilderField())
}
}
func TestEdge(t *testing.T) {
u, g := &Type{Name: "User"}, &Type{Name: "Group"}
groups := &Edge{Name: "groups", Type: g, Owner: u, Rel: Relation{Type: M2M}}
users := &Edge{Name: "users", Inverse: "groups", Type: u, Owner: u, Rel: Relation{Type: M2M}}
require.True(t, users.IsInverse())
require.False(t, groups.IsInverse())
require.Equal(t, "GroupsLabel", users.LabelConstant())
require.Equal(t, "GroupsLabel", groups.LabelConstant())
require.Equal(t, "UsersInverseLabel", users.InverseLabelConstant())
require.Equal(t, "user_groups", users.Label())
require.Equal(t, "user_groups", groups.Label())
}
func TestValidSchemaName(t *testing.T) {
err := ValidSchemaName("Config")
require.Error(t, err)
err = ValidSchemaName("Mutation")
require.Error(t, err)
err = ValidSchemaName("Boring")
require.NoError(t, err)
err = ValidSchemaName("Order")
require.NoError(t, err)
}
func TestField_Blob(t *testing.T) {
require := require.New(t)
// Test creating a type with blob-stored fields.
typ, err := NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "Document",
Fields: []*load.Field{
{
Name: "content",
Info: &field.TypeInfo{Type: field.TypeBlob},
Optional: true,
Comment: "blob content",
},
{
Name: "thumbnail",
Info: &field.TypeInfo{Type: field.TypeBlob},
},
{
Name: "title",
Info: &field.TypeInfo{Type: field.TypeString},
},
},
})
require.NoError(err)
require.NotNil(typ)
require.Equal("Document", typ.Name)
// Find blob fields.
require.True(typ.HasBlobFields())
blobFields := typ.BlobFields()
require.Len(blobFields, 2)
// First blob field: content.
f0 := blobFields[0]
require.Equal("content", f0.Name)
require.True(f0.IsBlob())
require.True(f0.Optional)
// Second blob field: thumbnail.
f1 := blobFields[1]
require.Equal("thumbnail", f1.Name)
require.True(f1.IsBlob())
// Non-blob field should not be blob-stored.
titleField := typ.Fields[2]
require.Equal("title", titleField.Name)
require.False(titleField.IsBlob())
// MutationFields should exclude only lazy blob fields.
mutFields := typ.MutationFields()
for _, mf := range mutFields {
require.False(mf.IsBlobLazy(), "lazy blob field %s should not be in MutationFields", mf.Name)
}
// Non-lazy blob fields (content, thumbnail) should be in MutationFields
// because their mutation struct fields are used by the blob hook.
var blobInMut int
for _, mf := range mutFields {
if mf.IsBlob() {
blobInMut++
}
}
require.Equal(2, blobInMut)
// Type without blob fields.
typ2, err := NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "Simple",
Fields: []*load.Field{
{Name: "name", Info: &field.TypeInfo{Type: field.TypeString}},
},
})
require.NoError(err)
require.False(typ2.HasBlobFields())
require.Empty(typ2.BlobFields())
}
func TestField_BlobScanType(t *testing.T) {
require := require.New(t)
typ, err := NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "Doc",
Fields: []*load.Field{
{
Name: "data",
Info: &field.TypeInfo{Type: field.TypeBlob},
},
},
})
require.NoError(err)
f := typ.Fields[0]
require.True(f.IsBlob())
require.True(f.IsBlobNoColumn())
require.False(f.IsBlobLazy())
// Non-lazy blob fields appear in MutationFields (for blob hook usage),
// but are excluded from SQL columns by IsBlobNoColumn.
require.NotEmpty(typ.MutationFields())
}
func TestField_BlobCheckRefs(t *testing.T) {
require := require.New(t)
typ, err := NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "Doc",
Fields: []*load.Field{
{Name: "checked", Info: &field.TypeInfo{Type: field.TypeBlob}, BlobCheckRefs: true},
{Name: "unchecked", Info: &field.TypeInfo{Type: field.TypeBlob}},
{Name: "title", Info: &field.TypeInfo{Type: field.TypeString}},
},
})
require.NoError(err)
require.True(typ.HasBlobRefCheckedFields())
checked := typ.BlobRefCheckedFields()
require.Len(checked, 1)
require.Equal("checked", checked[0].Name)
require.True(checked[0].IsBlobRefChecked())
require.False(typ.fields["unchecked"].IsBlobRefChecked())
// CheckRefs is meaningless on a non-blob field.
require.False(typ.fields["title"].IsBlobRefChecked())
// A type with no opted-in blob field reports none, so codegen can skip the lookup.
typ2, err := NewType(&Config{Package: "entc/gen"}, &load.Schema{
Name: "Plain",
Fields: []*load.Field{{Name: "data", Info: &field.TypeInfo{Type: field.TypeBlob}}},
})
require.NoError(err)
require.False(typ2.HasBlobRefCheckedFields())
require.Empty(typ2.BlobRefCheckedFields())
}

View File

@@ -0,0 +1,34 @@
### entc integration tests
#### Regenerating assets
If you edited one of the files in `entc/gen/template` or `entc/load/template`,
run the following command:
```
go generate ./...
```
#### Running the integration tests
```
docker-compose up -d --scale gremlin=0
go test .
```
In order to run the Gremlin tests, run:
```
docker-compose up -d gremlin
go test ./gremlin/...
```
Use the `-run` flag for running specific test or set of tests. For example:
```
go test -run=MySQL
go test -run=MySQL/8/Sanity
go test -run=SQLite/Sanity
```

View File

@@ -0,0 +1,85 @@
// 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.
// Package blob provides a gocloud.dev/blob adapter for ent's generated Blob
// interface. It is used by the integration tests and serves as a reference
// implementation for wiring blob storage into ent-generated code.
package blob
import (
"context"
"io"
"io/fs"
goblob "gocloud.dev/blob"
"gocloud.dev/gcerrors"
)
// GoBucket wraps a gocloud.dev/blob.Bucket and implements the generated Blob interface.
type GoBucket struct {
b *goblob.Bucket
readerOpts *goblob.ReaderOptions
writerOpts *goblob.WriterOptions
}
// OpenBucket opens a gocloud.dev/blob bucket by URL and returns it as a [*GoBucket].
func OpenBucket(ctx context.Context, url string) (*GoBucket, error) {
b, err := goblob.OpenBucket(ctx, url)
if err != nil {
return nil, err
}
return &GoBucket{b: b}, nil
}
// Prefixed returns a new GoBucket scoped to the given key prefix.
// The original bucket is consumed and must not be used after this call.
func (b *GoBucket) Prefixed(prefix string) *GoBucket {
return &GoBucket{
b: goblob.PrefixedBucket(b.b, prefix),
readerOpts: b.readerOpts,
writerOpts: b.writerOpts,
}
}
// WithReaderOptions returns a new GoBucket that uses the given reader options.
func (b *GoBucket) WithReaderOptions(opts *goblob.ReaderOptions) *GoBucket {
return &GoBucket{b: b.b, writerOpts: b.writerOpts, readerOpts: opts}
}
// WithWriterOptions returns a new GoBucket that uses the given writer options.
func (b *GoBucket) WithWriterOptions(opts *goblob.WriterOptions) *GoBucket {
return &GoBucket{b: b.b, writerOpts: opts, readerOpts: b.readerOpts}
}
// NewReader opens a reader for the blob stored at key.
// It returns [fs.ErrNotExist] if the key does not exist.
func (b *GoBucket) NewReader(ctx context.Context, key string) (io.ReadCloser, error) {
switch r, err := b.b.NewReader(ctx, key, b.readerOpts); {
case gcerrors.Code(err) == gcerrors.NotFound:
return nil, fs.ErrNotExist
case err != nil:
return nil, err
default:
return r, nil
}
}
// NewWriter opens a writer for the blob stored at key.
func (b *GoBucket) NewWriter(ctx context.Context, key string) (io.WriteCloser, error) {
return b.b.NewWriter(ctx, key, b.writerOpts)
}
// Delete removes the blob at key. Returns nil if the key does not exist.
func (b *GoBucket) Delete(ctx context.Context, key string) error {
err := b.b.Delete(ctx, key)
if gcerrors.Code(err) == gcerrors.NotFound {
return nil
}
return err
}
// Close releases the underlying gocloud bucket resources.
func (b *GoBucket) Close() error {
return b.b.Close()
}

View File

@@ -0,0 +1,145 @@
// 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.
package blob
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"io"
)
// tenantKey is the context key for the tenant name.
type tenantKey struct{}
// WithTenant returns a context carrying the given tenant name.
func WithTenant(ctx context.Context, tenant string) context.Context {
return context.WithValue(ctx, tenantKey{}, tenant)
}
// TenantFrom extracts the tenant name from the context.
// Returns "" if no tenant is set.
func TenantFrom(ctx context.Context) string {
v, _ := ctx.Value(tenantKey{}).(string)
return v
}
// Encrypted wraps a GoBucket and transparently encrypts/decrypts blob data
// using AES-CTR with a per-tenant derived key.
//
// The encryption key is derived from the tenant name (from context) combined
// with a master seed using SHA-256. This ensures that data written by one
// tenant cannot be decrypted by another tenant.
//
// A random IV is prepended to the ciphertext on write and read back on open.
//
// NOTE: This is a test-only implementation. AES-CTR provides confidentiality
// but no integrity/authentication — ciphertext can be tampered with undetected.
// Production systems should use an AEAD mode such as AES-GCM or add a MAC
// over the IV and ciphertext to detect tampering on read.
//
// Usage:
//
// bucket, _ := blob.OpenBucket(ctx, url)
// enc := blob.NewEncrypted(bucket, masterSeed)
// // Use enc as the Blob implementation in BlobOpeners.
// // Ensure the context carries the tenant: blob.WithTenant(ctx, "acme")
type Encrypted struct {
inner *GoBucket
seed []byte
}
// NewEncrypted creates an encrypting wrapper around the given bucket.
// The seed is combined with the tenant name (from context) at each operation
// to derive a per-tenant AES-256 key via SHA-256.
func NewEncrypted(bucket *GoBucket, seed []byte) *Encrypted {
return &Encrypted{inner: bucket, seed: seed}
}
// deriveKey produces a 32-byte AES-256 key from the tenant + seed.
func (e *Encrypted) deriveKey(tenant string) (cipher.Block, error) {
if tenant == "" {
return nil, errors.New("blob: encryption requires a tenant in context (use blob.WithTenant)")
}
h := sha256.New()
h.Write([]byte(tenant))
h.Write(e.seed)
b, err := aes.NewCipher(h.Sum(nil)) // 32 bytes → AES-256
if err != nil {
return nil, fmt.Errorf("blob: deriving encryption key: %w", err)
}
return b, nil
}
// NewReader opens and decrypts the blob at key. The first [aes.BlockSize] bytes
// are treated as the IV; the rest is decrypted with AES-CTR using the
// tenant-derived key.
// Returns [fs.ErrNotExist] if the key does not exist.
func (e *Encrypted) NewReader(ctx context.Context, key string) (io.ReadCloser, error) {
block, err := e.deriveKey(TenantFrom(ctx))
if err != nil {
return nil, err
}
rc, err := e.inner.NewReader(ctx, key)
if err != nil {
return nil, err
}
iv := make([]byte, aes.BlockSize)
switch _, err := io.ReadFull(rc, iv); {
case err == io.EOF, err == io.ErrUnexpectedEOF:
return nil, errors.Join(fmt.Errorf("blob: ciphertext too short for key %q", key), rc.Close())
case err != nil:
return nil, errors.Join(err, rc.Close())
}
return &decryptionReader{
Reader: cipher.StreamReader{S: cipher.NewCTR(block, iv), R: rc},
closer: rc,
}, nil
}
// NewWriter opens an encrypting writer for the blob at key. A random IV is
// written first, followed by AES-CTR encrypted data using the tenant-derived key.
func (e *Encrypted) NewWriter(ctx context.Context, key string) (io.WriteCloser, error) {
block, err := e.deriveKey(TenantFrom(ctx))
if err != nil {
return nil, err
}
wc, err := e.inner.NewWriter(ctx, key)
if err != nil {
return nil, err
}
iv := make([]byte, aes.BlockSize)
if _, err := rand.Read(iv); err != nil {
return nil, errors.Join(fmt.Errorf("blob: generating IV: %w", err), wc.Close())
}
if _, err := wc.Write(iv); err != nil {
return nil, errors.Join(fmt.Errorf("blob: writing IV: %w", err), wc.Close())
}
return cipher.StreamWriter{S: cipher.NewCTR(block, iv), W: wc}, nil
}
// Close releases the underlying bucket resources.
func (e *Encrypted) Close() error {
return e.inner.Close()
}
// Delete removes the blob at key. Encryption is irrelevant for deletion.
func (e *Encrypted) Delete(ctx context.Context, key string) error {
return e.inner.Delete(ctx, key)
}
// decryptionReader decrypts on Read and closes the underlying storage decryptionReader.
type decryptionReader struct {
io.Reader
closer io.Closer
}
func (r *decryptionReader) Close() error {
return r.closer.Close()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
// 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.
package cascadelete
import (
"context"
"testing"
"entgo.io/ent/entc/integration/cascadelete/ent"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/require"
)
func TestCascadeDelete(t *testing.T) {
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
require.NoError(t, err)
defer client.Close()
ctx := context.Background()
require.NoError(t, client.Schema.Create(ctx))
author := client.User.Create().SaveX(ctx)
posts := client.Post.CreateBulk(
client.Post.Create(),
client.Post.Create().SetAuthor(author),
client.Post.Create().SetAuthor(author),
).SaveX(ctx)
comments := client.Comment.CreateBulk(
client.Comment.Create().SetText("Go").SetPost(posts[0]),
client.Comment.Create().SetText("Ent").SetPost(posts[1]),
client.Comment.Create().SetText("GraphQL").SetPost(posts[1]),
).SaveX(ctx)
t.Log("Delete the author with its 2 posts and their comments")
client.User.DeleteOne(author).ExecX(ctx)
require.Zero(t, client.User.Query().CountX(ctx))
require.Equal(t, posts[0].ID, client.Post.Query().OnlyIDX(ctx))
require.Equal(t, comments[0].ID, client.Comment.Query().OnlyIDX(ctx))
}

View File

@@ -0,0 +1,695 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"log"
"reflect"
"entgo.io/ent"
"entgo.io/ent/entc/integration/cascadelete/ent/migrate"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
"entgo.io/ent/entc/integration/cascadelete/ent/user"
)
// Client is the client that holds all ent builders.
type Client struct {
config
// Schema is the client for creating, migrating and dropping schema.
Schema *migrate.Schema
// Comment is the client for interacting with the Comment builders.
Comment *CommentClient
// Post is the client for interacting with the Post builders.
Post *PostClient
// User is the client for interacting with the User builders.
User *UserClient
}
// 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() {
c.Schema = migrate.NewSchema(c.driver)
c.Comment = NewCommentClient(c.config)
c.Post = NewPostClient(c.config)
c.User = NewUserClient(c.config)
}
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
}
// 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{}}
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
}
}
// 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 dialect.MySQL, dialect.Postgres, dialect.SQLite:
drv, err := sql.Open(driverName, dataSourceName)
if err != nil {
return nil, err
}
return NewClient(append(options, Driver(drv))...), nil
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("ent: 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("ent: starting a transaction: %w", err)
}
cfg := c.config
cfg.driver = tx
return &Tx{
ctx: ctx,
config: cfg,
Comment: NewCommentClient(cfg),
Post: NewPostClient(cfg),
User: NewUserClient(cfg),
}, nil
}
// 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,
Comment: NewCommentClient(cfg),
Post: NewPostClient(cfg),
User: NewUserClient(cfg),
}, nil
}
// Debug returns a new debug-client. It's used to get verbose logging on specific operations.
//
// client.Debug().
// Comment.
// 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) {
c.Comment.Use(hooks...)
c.Post.Use(hooks...)
c.User.Use(hooks...)
}
// 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) {
c.Comment.Intercept(interceptors...)
c.Post.Intercept(interceptors...)
c.User.Intercept(interceptors...)
}
// Mutate implements the ent.Mutator interface.
func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
switch m := m.(type) {
case *CommentMutation:
return c.Comment.mutate(ctx, m)
case *PostMutation:
return c.Post.mutate(ctx, m)
case *UserMutation:
return c.User.mutate(ctx, m)
default:
return nil, fmt.Errorf("ent: unknown mutation type %T", m)
}
}
// CommentClient is a client for the Comment schema.
type CommentClient struct {
config
}
// NewCommentClient returns a client for the Comment from the given config.
func NewCommentClient(c config) *CommentClient {
return &CommentClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `comment.Hooks(f(g(h())))`.
func (c *CommentClient) Use(hooks ...Hook) {
c.hooks.Comment = append(c.hooks.Comment, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `comment.Intercept(f(g(h())))`.
func (c *CommentClient) Intercept(interceptors ...Interceptor) {
c.inters.Comment = append(c.inters.Comment, interceptors...)
}
// Create returns a builder for creating a Comment entity.
func (c *CommentClient) Create() *CommentCreate {
mutation := newCommentMutation(c.config, OpCreate)
return &CommentCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of Comment entities.
func (c *CommentClient) CreateBulk(builders ...*CommentCreate) *CommentCreateBulk {
return &CommentCreateBulk{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 *CommentClient) MapCreateBulk(slice any, setFunc func(*CommentCreate, int)) *CommentCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &CommentCreateBulk{err: fmt.Errorf("calling to CommentClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*CommentCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &CommentCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Comment.
func (c *CommentClient) Update() *CommentUpdate {
mutation := newCommentMutation(c.config, OpUpdate)
return &CommentUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *CommentClient) UpdateOne(_m *Comment) *CommentUpdateOne {
mutation := newCommentMutation(c.config, OpUpdateOne, withComment(_m))
return &CommentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *CommentClient) UpdateOneID(id int) *CommentUpdateOne {
mutation := newCommentMutation(c.config, OpUpdateOne, withCommentID(id))
return &CommentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for Comment.
func (c *CommentClient) Delete() *CommentDelete {
mutation := newCommentMutation(c.config, OpDelete)
return &CommentDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *CommentClient) DeleteOne(_m *Comment) *CommentDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *CommentClient) DeleteOneID(id int) *CommentDeleteOne {
builder := c.Delete().Where(comment.ID(id))
builder.mutation.id = &id
builder.mutation.SetOp(OpDeleteOne)
return &CommentDeleteOne{builder}
}
// Query returns a query builder for Comment.
func (c *CommentClient) Query() *CommentQuery {
return &CommentQuery{
config: c.config,
ctx: &QueryContext{Type: TypeComment},
inters: c.Interceptors(),
}
}
// Get returns a Comment entity by its id.
func (c *CommentClient) Get(ctx context.Context, id int) (*Comment, error) {
return c.Query().Where(comment.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *CommentClient) GetX(ctx context.Context, id int) *Comment {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryPost queries the post edge of a Comment.
func (c *CommentClient) QueryPost(_m *Comment) *PostQuery {
query := (&PostClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(comment.Table, comment.FieldID, id),
sqlgraph.To(post.Table, post.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, comment.PostTable, comment.PostColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *CommentClient) Hooks() []Hook {
return c.hooks.Comment
}
// Interceptors returns the client interceptors.
func (c *CommentClient) Interceptors() []Interceptor {
return c.inters.Comment
}
func (c *CommentClient) mutate(ctx context.Context, m *CommentMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&CommentCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&CommentUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&CommentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&CommentDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown Comment mutation op: %q", m.Op())
}
}
// PostClient is a client for the Post schema.
type PostClient struct {
config
}
// NewPostClient returns a client for the Post from the given config.
func NewPostClient(c config) *PostClient {
return &PostClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `post.Hooks(f(g(h())))`.
func (c *PostClient) Use(hooks ...Hook) {
c.hooks.Post = append(c.hooks.Post, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `post.Intercept(f(g(h())))`.
func (c *PostClient) Intercept(interceptors ...Interceptor) {
c.inters.Post = append(c.inters.Post, interceptors...)
}
// Create returns a builder for creating a Post entity.
func (c *PostClient) Create() *PostCreate {
mutation := newPostMutation(c.config, OpCreate)
return &PostCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of Post entities.
func (c *PostClient) CreateBulk(builders ...*PostCreate) *PostCreateBulk {
return &PostCreateBulk{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 *PostClient) MapCreateBulk(slice any, setFunc func(*PostCreate, int)) *PostCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &PostCreateBulk{err: fmt.Errorf("calling to PostClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*PostCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &PostCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Post.
func (c *PostClient) Update() *PostUpdate {
mutation := newPostMutation(c.config, OpUpdate)
return &PostUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *PostClient) UpdateOne(_m *Post) *PostUpdateOne {
mutation := newPostMutation(c.config, OpUpdateOne, withPost(_m))
return &PostUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *PostClient) UpdateOneID(id int) *PostUpdateOne {
mutation := newPostMutation(c.config, OpUpdateOne, withPostID(id))
return &PostUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for Post.
func (c *PostClient) Delete() *PostDelete {
mutation := newPostMutation(c.config, OpDelete)
return &PostDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *PostClient) DeleteOne(_m *Post) *PostDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *PostClient) DeleteOneID(id int) *PostDeleteOne {
builder := c.Delete().Where(post.ID(id))
builder.mutation.id = &id
builder.mutation.SetOp(OpDeleteOne)
return &PostDeleteOne{builder}
}
// Query returns a query builder for Post.
func (c *PostClient) Query() *PostQuery {
return &PostQuery{
config: c.config,
ctx: &QueryContext{Type: TypePost},
inters: c.Interceptors(),
}
}
// Get returns a Post entity by its id.
func (c *PostClient) Get(ctx context.Context, id int) (*Post, error) {
return c.Query().Where(post.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *PostClient) GetX(ctx context.Context, id int) *Post {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryAuthor queries the author edge of a Post.
func (c *PostClient) QueryAuthor(_m *Post) *UserQuery {
query := (&UserClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(post.Table, post.FieldID, id),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, post.AuthorTable, post.AuthorColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryComments queries the comments edge of a Post.
func (c *PostClient) QueryComments(_m *Post) *CommentQuery {
query := (&CommentClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(post.Table, post.FieldID, id),
sqlgraph.To(comment.Table, comment.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, post.CommentsTable, post.CommentsColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *PostClient) Hooks() []Hook {
return c.hooks.Post
}
// Interceptors returns the client interceptors.
func (c *PostClient) Interceptors() []Interceptor {
return c.inters.Post
}
func (c *PostClient) mutate(ctx context.Context, m *PostMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&PostCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&PostUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&PostUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&PostDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown Post mutation op: %q", m.Op())
}
}
// UserClient is a client for the User schema.
type UserClient struct {
config
}
// NewUserClient returns a client for the User from the given config.
func NewUserClient(c config) *UserClient {
return &UserClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `user.Hooks(f(g(h())))`.
func (c *UserClient) Use(hooks ...Hook) {
c.hooks.User = append(c.hooks.User, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `user.Intercept(f(g(h())))`.
func (c *UserClient) Intercept(interceptors ...Interceptor) {
c.inters.User = append(c.inters.User, interceptors...)
}
// Create returns a builder for creating a User entity.
func (c *UserClient) Create() *UserCreate {
mutation := newUserMutation(c.config, OpCreate)
return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of User entities.
func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk {
return &UserCreateBulk{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 *UserClient) MapCreateBulk(slice any, setFunc func(*UserCreate, int)) *UserCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &UserCreateBulk{err: fmt.Errorf("calling to UserClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*UserCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &UserCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for User.
func (c *UserClient) Update() *UserUpdate {
mutation := newUserMutation(c.config, OpUpdate)
return &UserUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *UserClient) UpdateOne(_m *User) *UserUpdateOne {
mutation := newUserMutation(c.config, OpUpdateOne, withUser(_m))
return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *UserClient) UpdateOneID(id int) *UserUpdateOne {
mutation := newUserMutation(c.config, OpUpdateOne, withUserID(id))
return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for User.
func (c *UserClient) Delete() *UserDelete {
mutation := newUserMutation(c.config, OpDelete)
return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *UserClient) DeleteOne(_m *User) *UserDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *UserClient) DeleteOneID(id int) *UserDeleteOne {
builder := c.Delete().Where(user.ID(id))
builder.mutation.id = &id
builder.mutation.SetOp(OpDeleteOne)
return &UserDeleteOne{builder}
}
// Query returns a query builder for User.
func (c *UserClient) Query() *UserQuery {
return &UserQuery{
config: c.config,
ctx: &QueryContext{Type: TypeUser},
inters: c.Interceptors(),
}
}
// Get returns a User entity by its id.
func (c *UserClient) Get(ctx context.Context, id int) (*User, error) {
return c.Query().Where(user.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *UserClient) GetX(ctx context.Context, id int) *User {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryPosts queries the posts edge of a User.
func (c *UserClient) QueryPosts(_m *User) *PostQuery {
query := (&PostClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, id),
sqlgraph.To(post.Table, post.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.PostsTable, user.PostsColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *UserClient) Hooks() []Hook {
return c.hooks.User
}
// Interceptors returns the client interceptors.
func (c *UserClient) Interceptors() []Interceptor {
return c.inters.User
}
func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&UserCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&UserUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&UserDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown User mutation op: %q", m.Op())
}
}
// hooks and interceptors per client, for fast access.
type (
hooks struct {
Comment, Post, User []ent.Hook
}
inters struct {
Comment, Post, User []ent.Interceptor
}
)

View File

@@ -0,0 +1,147 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
)
// Comment is the model entity for the Comment schema.
type Comment struct {
config `json:"-"`
// ID of the ent.
ID int `json:"id,omitempty"`
// Text holds the value of the "text" field.
Text string `json:"text,omitempty"`
// PostID holds the value of the "post_id" field.
PostID int `json:"post_id,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the CommentQuery when eager-loading is set.
Edges CommentEdges `json:"edges"`
selectValues sql.SelectValues
}
// CommentEdges holds the relations/edges for other nodes in the graph.
type CommentEdges struct {
// Post holds the value of the post edge.
Post *Post `json:"post,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// PostOrErr returns the Post value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e CommentEdges) PostOrErr() (*Post, error) {
if e.Post != nil {
return e.Post, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: post.Label}
}
return nil, &NotLoadedError{edge: "post"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Comment) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case comment.FieldID, comment.FieldPostID:
values[i] = new(sql.NullInt64)
case comment.FieldText:
values[i] = new(sql.NullString)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Comment fields.
func (_m *Comment) 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] {
case comment.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int(value.Int64)
case comment.FieldText:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field text", values[i])
} else if value.Valid {
_m.Text = value.String
}
case comment.FieldPostID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field post_id", values[i])
} else if value.Valid {
_m.PostID = int(value.Int64)
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the Comment.
// This includes values selected through modifiers, order, etc.
func (_m *Comment) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryPost queries the "post" edge of the Comment entity.
func (_m *Comment) QueryPost() *PostQuery {
return NewCommentClient(_m.config).QueryPost(_m)
}
// Update returns a builder for updating this Comment.
// Note that you need to call Comment.Unwrap() before calling this method if this Comment
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *Comment) Update() *CommentUpdateOne {
return NewCommentClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the Comment 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 (_m *Comment) Unwrap() *Comment {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: Comment is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *Comment) String() string {
var builder strings.Builder
builder.WriteString("Comment(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("text=")
builder.WriteString(_m.Text)
builder.WriteString(", ")
builder.WriteString("post_id=")
builder.WriteString(fmt.Sprintf("%v", _m.PostID))
builder.WriteByte(')')
return builder.String()
}
// Comments is a parsable slice of Comment.
type Comments []*Comment

View File

@@ -0,0 +1,83 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package comment
import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the comment type in the database.
Label = "comment"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldText holds the string denoting the text field in the database.
FieldText = "text"
// FieldPostID holds the string denoting the post_id field in the database.
FieldPostID = "post_id"
// EdgePost holds the string denoting the post edge name in mutations.
EdgePost = "post"
// Table holds the table name of the comment in the database.
Table = "comments"
// PostTable is the table that holds the post relation/edge.
PostTable = "comments"
// PostInverseTable is the table name for the Post entity.
// It exists in this package in order to avoid circular dependency with the "post" package.
PostInverseTable = "posts"
// PostColumn is the table column denoting the post relation/edge.
PostColumn = "post_id"
)
// Columns holds all SQL columns for comment fields.
var Columns = []string{
FieldID,
FieldText,
FieldPostID,
}
// 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
}
}
return false
}
// OrderOption defines the ordering options for the Comment queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByText orders the results by the text field.
func ByText(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldText, opts...).ToFunc()
}
// ByPostID orders the results by the post_id field.
func ByPostID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPostID, opts...).ToFunc()
}
// ByPostField orders the results by post field.
func ByPostField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newPostStep(), sql.OrderByField(field, opts...))
}
}
func newPostStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(PostInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, PostTable, PostColumn),
)
}

View File

@@ -0,0 +1,325 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package comment
import (
"context"
"fmt"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/entc/integration/cascadelete/ent/predicate"
)
// Mutation represents an operation that mutates the Comment nodes in the graph.
type Mutation struct {
op ent.Op
typ string
text *string
clearedFields map[string]struct{}
post *int
clearedpost bool
predicates []predicate.Comment
}
// NewMutation creates a new Mutation for the Comment entity.
func NewMutation(op ent.Op) *Mutation {
return &Mutation{
op: op,
typ: "Comment",
clearedFields: make(map[string]struct{}),
}
}
// Predicates returns the list of predicates set on the mutation.
func (m *Mutation) Predicates() []predicate.Comment {
return m.predicates
}
// SetText sets the "text" field.
func (m *Mutation) SetText(s string) {
m.text = &s
}
// Text returns the value of the "text" field in the mutation.
func (m *Mutation) Text() (r string, exists bool) {
v := m.text
if v == nil {
return
}
return *v, true
}
// ResetText resets all changes to the "text" field.
func (m *Mutation) ResetText() {
m.text = nil
}
// SetPostID sets the "post_id" field.
func (m *Mutation) SetPostID(i int) {
m.post = &i
}
// PostID returns the value of the "post_id" field in the mutation.
func (m *Mutation) PostID() (r int, exists bool) {
v := m.post
if v == nil {
return
}
return *v, true
}
// ResetPostID resets all changes to the "post_id" field.
func (m *Mutation) ResetPostID() {
m.post = nil
}
// ClearPost clears the "post" edge to the Post entity.
func (m *Mutation) ClearPost() {
m.clearedpost = true
m.clearedFields[FieldPostID] = struct{}{}
}
// PostCleared reports if the "post" edge to the Post entity was cleared.
func (m *Mutation) PostCleared() bool {
return m.clearedpost
}
// PostIDs returns the "post" edge IDs in the mutation.
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
// PostID instead. It exists only for internal usage by the builders.
func (m *Mutation) PostIDs() (ids []int) {
if id := m.post; id != nil {
ids = append(ids, *id)
}
return
}
// ResetPost resets all changes to the "post" edge.
func (m *Mutation) ResetPost() {
m.post = nil
m.clearedpost = false
}
// Where appends a list predicates to the Mutation builder.
func (m *Mutation) Where(ps ...predicate.Comment) {
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(*sql.Selector)) {
p := make([]predicate.Comment, 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 (Comment).
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, 2)
if m.text != nil {
fields = append(fields, FieldText)
}
if m.post != nil {
fields = append(fields, FieldPostID)
}
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) {
switch name {
case FieldText:
return m.Text()
case FieldPostID:
return m.PostID()
}
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) {
return nil, fmt.Errorf("unknown Comment field %s", name)
}
// 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 {
case FieldText:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetText(v)
return nil
case FieldPostID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetPostID(v)
return nil
}
return fmt.Errorf("unknown Comment field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
func (m *Mutation) AddedFields() []string {
var fields []string
return fields
}
// 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) {
switch name {
}
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 {
switch name {
}
return fmt.Errorf("unknown Comment numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *Mutation) ClearedFields() []string {
return nil
}
// 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 {
return fmt.Errorf("unknown Comment 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 {
switch name {
case FieldText:
m.ResetText()
return nil
case FieldPostID:
m.ResetPostID()
return nil
}
return fmt.Errorf("unknown Comment field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *Mutation) AddedEdges() []string {
edges := make([]string, 0, 1)
if m.post != nil {
edges = append(edges, EdgePost)
}
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 {
switch name {
case EdgePost:
if id := m.post; id != nil {
return []ent.Value{*id}
}
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *Mutation) RemovedEdges() []string {
edges := make([]string, 0, 1)
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 {
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *Mutation) ClearedEdges() []string {
edges := make([]string, 0, 1)
if m.clearedpost {
edges = append(edges, EdgePost)
}
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 {
switch name {
case EdgePost:
return m.clearedpost
}
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 {
switch name {
case EdgePost:
m.ClearPost()
return nil
}
return fmt.Errorf("unknown Comment 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 {
switch name {
case EdgePost:
m.ResetPost()
return nil
}
return fmt.Errorf("unknown Comment edge %s", name)
}

View File

@@ -0,0 +1,191 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package comment
import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int) predicate.Comment {
return predicate.Comment(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int) predicate.Comment {
return predicate.Comment(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int) predicate.Comment {
return predicate.Comment(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int) predicate.Comment {
return predicate.Comment(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int) predicate.Comment {
return predicate.Comment(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int) predicate.Comment {
return predicate.Comment(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int) predicate.Comment {
return predicate.Comment(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int) predicate.Comment {
return predicate.Comment(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int) predicate.Comment {
return predicate.Comment(sql.FieldLTE(FieldID, id))
}
// Text applies equality check predicate on the "text" field. It's identical to TextEQ.
func Text(v string) predicate.Comment {
return predicate.Comment(sql.FieldEQ(FieldText, v))
}
// PostID applies equality check predicate on the "post_id" field. It's identical to PostIDEQ.
func PostID(v int) predicate.Comment {
return predicate.Comment(sql.FieldEQ(FieldPostID, v))
}
// TextEQ applies the EQ predicate on the "text" field.
func TextEQ(v string) predicate.Comment {
return predicate.Comment(sql.FieldEQ(FieldText, v))
}
// TextNEQ applies the NEQ predicate on the "text" field.
func TextNEQ(v string) predicate.Comment {
return predicate.Comment(sql.FieldNEQ(FieldText, v))
}
// TextIn applies the In predicate on the "text" field.
func TextIn(vs ...string) predicate.Comment {
return predicate.Comment(sql.FieldIn(FieldText, vs...))
}
// TextNotIn applies the NotIn predicate on the "text" field.
func TextNotIn(vs ...string) predicate.Comment {
return predicate.Comment(sql.FieldNotIn(FieldText, vs...))
}
// TextGT applies the GT predicate on the "text" field.
func TextGT(v string) predicate.Comment {
return predicate.Comment(sql.FieldGT(FieldText, v))
}
// TextGTE applies the GTE predicate on the "text" field.
func TextGTE(v string) predicate.Comment {
return predicate.Comment(sql.FieldGTE(FieldText, v))
}
// TextLT applies the LT predicate on the "text" field.
func TextLT(v string) predicate.Comment {
return predicate.Comment(sql.FieldLT(FieldText, v))
}
// TextLTE applies the LTE predicate on the "text" field.
func TextLTE(v string) predicate.Comment {
return predicate.Comment(sql.FieldLTE(FieldText, v))
}
// TextContains applies the Contains predicate on the "text" field.
func TextContains(v string) predicate.Comment {
return predicate.Comment(sql.FieldContains(FieldText, v))
}
// TextHasPrefix applies the HasPrefix predicate on the "text" field.
func TextHasPrefix(v string) predicate.Comment {
return predicate.Comment(sql.FieldHasPrefix(FieldText, v))
}
// TextHasSuffix applies the HasSuffix predicate on the "text" field.
func TextHasSuffix(v string) predicate.Comment {
return predicate.Comment(sql.FieldHasSuffix(FieldText, v))
}
// TextEqualFold applies the EqualFold predicate on the "text" field.
func TextEqualFold(v string) predicate.Comment {
return predicate.Comment(sql.FieldEqualFold(FieldText, v))
}
// TextContainsFold applies the ContainsFold predicate on the "text" field.
func TextContainsFold(v string) predicate.Comment {
return predicate.Comment(sql.FieldContainsFold(FieldText, v))
}
// PostIDEQ applies the EQ predicate on the "post_id" field.
func PostIDEQ(v int) predicate.Comment {
return predicate.Comment(sql.FieldEQ(FieldPostID, v))
}
// PostIDNEQ applies the NEQ predicate on the "post_id" field.
func PostIDNEQ(v int) predicate.Comment {
return predicate.Comment(sql.FieldNEQ(FieldPostID, v))
}
// PostIDIn applies the In predicate on the "post_id" field.
func PostIDIn(vs ...int) predicate.Comment {
return predicate.Comment(sql.FieldIn(FieldPostID, vs...))
}
// PostIDNotIn applies the NotIn predicate on the "post_id" field.
func PostIDNotIn(vs ...int) predicate.Comment {
return predicate.Comment(sql.FieldNotIn(FieldPostID, vs...))
}
// HasPost applies the HasEdge predicate on the "post" edge.
func HasPost() predicate.Comment {
return predicate.Comment(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, PostTable, PostColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasPostWith applies the HasEdge predicate on the "post" edge with a given conditions (other predicates).
func HasPostWith(preds ...predicate.Post) predicate.Comment {
return predicate.Comment(func(s *sql.Selector) {
step := newPostStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Comment) predicate.Comment {
return predicate.Comment(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Comment) predicate.Comment {
return predicate.Comment(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Comment) predicate.Comment {
return predicate.Comment(sql.NotPredicates(p))
}

View File

@@ -0,0 +1,222 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
"entgo.io/ent/schema/field"
)
// CommentCreate is the builder for creating a Comment entity.
type CommentCreate struct {
config
mutation *CommentMutation
hooks []Hook
}
// SetText sets the "text" field.
func (_c *CommentCreate) SetText(v string) *CommentCreate {
_c.mutation.SetText(v)
return _c
}
// SetPostID sets the "post_id" field.
func (_c *CommentCreate) SetPostID(v int) *CommentCreate {
_c.mutation.SetPostID(v)
return _c
}
// SetPost sets the "post" edge to the Post entity.
func (_c *CommentCreate) SetPost(v *Post) *CommentCreate {
return _c.SetPostID(v.ID)
}
// Mutation returns the CommentMutation object of the builder.
func (_c *CommentCreate) Mutation() *CommentMutation {
return _c.mutation
}
// Save creates the Comment in the database.
func (_c *CommentCreate) Save(ctx context.Context) (*Comment, error) {
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *CommentCreate) SaveX(ctx context.Context) *Comment {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *CommentCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *CommentCreate) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *CommentCreate) check() error {
if _, ok := _c.mutation.Text(); !ok {
return &ValidationError{Name: "text", err: errors.New(`ent: missing required field "Comment.text"`)}
}
if _, ok := _c.mutation.PostID(); !ok {
return &ValidationError{Name: "post_id", err: errors.New(`ent: missing required field "Comment.post_id"`)}
}
if len(_c.mutation.PostIDs()) == 0 {
return &ValidationError{Name: "post", err: errors.New(`ent: missing required edge "Comment.post"`)}
}
return nil
}
func (_c *CommentCreate) sqlSave(ctx context.Context) (*Comment, error) {
if err := _c.check(); err != nil {
return nil, err
}
_node, _spec := _c.createSpec()
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
id := _spec.ID.Value.(int64)
_node.ID = int(id)
_c.mutation.id = &_node.ID
_c.mutation.done = true
return _node, nil
}
func (_c *CommentCreate) createSpec() (*Comment, *sqlgraph.CreateSpec) {
var (
_node = &Comment{config: _c.config}
_spec = sqlgraph.NewCreateSpec(comment.Table, sqlgraph.NewFieldSpec(comment.FieldID, field.TypeInt))
)
if value, ok := _c.mutation.Text(); ok {
_spec.SetField(comment.FieldText, field.TypeString, value)
_node.Text = value
}
if nodes := _c.mutation.PostIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: comment.PostTable,
Columns: []string{comment.PostColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.PostID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// CommentCreateBulk is the builder for creating many Comment entities in bulk.
type CommentCreateBulk struct {
config
err error
builders []*CommentCreate
}
// Save creates the Comment entities in the database.
func (_c *CommentCreateBulk) Save(ctx context.Context) ([]*Comment, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*Comment, len(_c.builders))
mutators := make([]Mutator, len(_c.builders))
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*CommentMutation)
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] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
if specs[i].ID.Value != nil {
id := specs[i].ID.Value.(int64)
nodes[i].ID = int(id)
}
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, _c.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (_c *CommentCreateBulk) SaveX(ctx context.Context) []*Comment {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *CommentCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *CommentCreateBulk) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}

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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/predicate"
"entgo.io/ent/schema/field"
)
// CommentDelete is the builder for deleting a Comment entity.
type CommentDelete struct {
config
hooks []Hook
mutation *CommentMutation
}
// Where appends a list predicates to the CommentDelete builder.
func (_d *CommentDelete) Where(ps ...predicate.Comment) *CommentDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *CommentDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *CommentDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *CommentDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(comment.Table, sqlgraph.NewFieldSpec(comment.FieldID, field.TypeInt))
if ps := _d.mutation.Predicates(); len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// CommentDeleteOne is the builder for deleting a single Comment entity.
type CommentDeleteOne struct {
_d *CommentDelete
}
// Where appends a list predicates to the CommentDelete builder.
func (_d *CommentDeleteOne) Where(ps ...predicate.Comment) *CommentDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *CommentDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{comment.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *CommentDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,610 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
"entgo.io/ent/entc/integration/cascadelete/ent/predicate"
"entgo.io/ent/schema/field"
)
// CommentQuery is the builder for querying Comment entities.
type CommentQuery struct {
config
ctx *QueryContext
order []comment.OrderOption
inters []Interceptor
predicates []predicate.Comment
withPost *PostQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the CommentQuery builder.
func (_q *CommentQuery) Where(ps ...predicate.Comment) *CommentQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *CommentQuery) Limit(limit int) *CommentQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *CommentQuery) Offset(offset int) *CommentQuery {
_q.ctx.Offset = &offset
return _q
}
// 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 (_q *CommentQuery) Unique(unique bool) *CommentQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *CommentQuery) Order(o ...comment.OrderOption) *CommentQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryPost chains the current query on the "post" edge.
func (_q *CommentQuery) QueryPost() *PostQuery {
query := (&PostClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(comment.Table, comment.FieldID, selector),
sqlgraph.To(post.Table, post.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, comment.PostTable, comment.PostColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Comment entity from the query.
// Returns a *NotFoundError when no Comment was found.
func (_q *CommentQuery) First(ctx context.Context) (*Comment, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{comment.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *CommentQuery) FirstX(ctx context.Context) *Comment {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first Comment ID from the query.
// Returns a *NotFoundError when no Comment ID was found.
func (_q *CommentQuery) FirstID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{comment.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *CommentQuery) FirstIDX(ctx context.Context) int {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single Comment entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one Comment entity is found.
// Returns a *NotFoundError when no Comment entities are found.
func (_q *CommentQuery) Only(ctx context.Context) (*Comment, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{comment.Label}
default:
return nil, &NotSingularError{comment.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *CommentQuery) OnlyX(ctx context.Context) *Comment {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only Comment ID in the query.
// Returns a *NotSingularError when more than one Comment ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *CommentQuery) OnlyID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{comment.Label}
default:
err = &NotSingularError{comment.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *CommentQuery) OnlyIDX(ctx context.Context) int {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of Comments.
func (_q *CommentQuery) All(ctx context.Context) ([]*Comment, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Comment, *CommentQuery]()
return withInterceptors[[]*Comment](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *CommentQuery) AllX(ctx context.Context) []*Comment {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of Comment IDs.
func (_q *CommentQuery) IDs(ctx context.Context) (ids []int, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(comment.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *CommentQuery) IDsX(ctx context.Context) []int {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *CommentQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*CommentQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *CommentQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *CommentQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *CommentQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the CommentQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *CommentQuery) Clone() *CommentQuery {
if _q == nil {
return nil
}
return &CommentQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]comment.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.Comment{}, _q.predicates...),
withPost: _q.withPost.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithPost tells the query-builder to eager-load the nodes that are connected to
// the "post" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *CommentQuery) WithPost(opts ...func(*PostQuery)) *CommentQuery {
query := (&PostClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withPost = query
return _q
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// Text string `json:"text,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.Comment.Query().
// GroupBy(comment.FieldText).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *CommentQuery) GroupBy(field string, fields ...string) *CommentGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &CommentGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = comment.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// Text string `json:"text,omitempty"`
// }
//
// client.Comment.Query().
// Select(comment.FieldText).
// Scan(ctx, &v)
func (_q *CommentQuery) Select(fields ...string) *CommentSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &CommentSelect{CommentQuery: _q}
sbuild.label = comment.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a CommentSelect configured with the given aggregations.
func (_q *CommentQuery) Aggregate(fns ...AggregateFunc) *CommentSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *CommentQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !comment.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *CommentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Comment, error) {
var (
nodes = []*Comment{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withPost != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*Comment).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &Comment{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := _q.withPost; query != nil {
if err := _q.loadPost(ctx, query, nodes, nil,
func(n *Comment, e *Post) { n.Edges.Post = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *CommentQuery) loadPost(ctx context.Context, query *PostQuery, nodes []*Comment, init func(*Comment), assign func(*Comment, *Post)) error {
ids := make([]int, 0, len(nodes))
nodeids := make(map[int][]*Comment)
for i := range nodes {
fk := nodes[i].PostID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(post.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 "post_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *CommentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *CommentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(comment.Table, comment.Columns, sqlgraph.NewFieldSpec(comment.FieldID, field.TypeInt))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, comment.FieldID)
for i := range fields {
if fields[i] != comment.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withPost != nil {
_spec.Node.AddColumnOnce(comment.FieldPostID)
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *CommentQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(comment.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = comment.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.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 := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// CommentGroupBy is the group-by builder for Comment entities.
type CommentGroupBy struct {
selector
build *CommentQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *CommentGroupBy) Aggregate(fns ...AggregateFunc) *CommentGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *CommentGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*CommentQuery, *CommentGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *CommentGroupBy) sqlScan(ctx context.Context, root *CommentQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// CommentSelect is the builder for selecting fields of Comment entities.
type CommentSelect struct {
*CommentQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *CommentSelect) Aggregate(fns ...AggregateFunc) *CommentSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *CommentSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*CommentQuery, *CommentSelect](ctx, _s.CommentQuery, _s, _s.inters, v)
}
func (_s *CommentSelect) sqlScan(ctx context.Context, root *CommentQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
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 := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@@ -0,0 +1,344 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
"entgo.io/ent/entc/integration/cascadelete/ent/predicate"
"entgo.io/ent/schema/field"
)
// CommentUpdate is the builder for updating Comment entities.
type CommentUpdate struct {
config
hooks []Hook
mutation *CommentMutation
}
// Where appends a list predicates to the CommentUpdate builder.
func (_u *CommentUpdate) Where(ps ...predicate.Comment) *CommentUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetText sets the "text" field.
func (_u *CommentUpdate) SetText(v string) *CommentUpdate {
_u.mutation.SetText(v)
return _u
}
// SetNillableText sets the "text" field if the given value is not nil.
func (_u *CommentUpdate) SetNillableText(v *string) *CommentUpdate {
if v != nil {
_u.SetText(*v)
}
return _u
}
// SetPostID sets the "post_id" field.
func (_u *CommentUpdate) SetPostID(v int) *CommentUpdate {
_u.mutation.SetPostID(v)
return _u
}
// SetNillablePostID sets the "post_id" field if the given value is not nil.
func (_u *CommentUpdate) SetNillablePostID(v *int) *CommentUpdate {
if v != nil {
_u.SetPostID(*v)
}
return _u
}
// SetPost sets the "post" edge to the Post entity.
func (_u *CommentUpdate) SetPost(v *Post) *CommentUpdate {
return _u.SetPostID(v.ID)
}
// Mutation returns the CommentMutation object of the builder.
func (_u *CommentUpdate) Mutation() *CommentMutation {
return _u.mutation
}
// ClearPost clears the "post" edge to the Post entity.
func (_u *CommentUpdate) ClearPost() *CommentUpdate {
_u.mutation.ClearPost()
return _u
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *CommentUpdate) Save(ctx context.Context) (int, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *CommentUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *CommentUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *CommentUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *CommentUpdate) check() error {
if _u.mutation.PostCleared() && len(_u.mutation.PostIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Comment.post"`)
}
return nil
}
func (_u *CommentUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(comment.Table, comment.Columns, sqlgraph.NewFieldSpec(comment.FieldID, field.TypeInt))
if ps := _u.mutation.Predicates(); len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.Text(); ok {
_spec.SetField(comment.FieldText, field.TypeString, value)
}
if _u.mutation.PostCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: comment.PostTable,
Columns: []string{comment.PostColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.PostIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: comment.PostTable,
Columns: []string{comment.PostColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{comment.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// CommentUpdateOne is the builder for updating a single Comment entity.
type CommentUpdateOne struct {
config
fields []string
hooks []Hook
mutation *CommentMutation
}
// SetText sets the "text" field.
func (_u *CommentUpdateOne) SetText(v string) *CommentUpdateOne {
_u.mutation.SetText(v)
return _u
}
// SetNillableText sets the "text" field if the given value is not nil.
func (_u *CommentUpdateOne) SetNillableText(v *string) *CommentUpdateOne {
if v != nil {
_u.SetText(*v)
}
return _u
}
// SetPostID sets the "post_id" field.
func (_u *CommentUpdateOne) SetPostID(v int) *CommentUpdateOne {
_u.mutation.SetPostID(v)
return _u
}
// SetNillablePostID sets the "post_id" field if the given value is not nil.
func (_u *CommentUpdateOne) SetNillablePostID(v *int) *CommentUpdateOne {
if v != nil {
_u.SetPostID(*v)
}
return _u
}
// SetPost sets the "post" edge to the Post entity.
func (_u *CommentUpdateOne) SetPost(v *Post) *CommentUpdateOne {
return _u.SetPostID(v.ID)
}
// Mutation returns the CommentMutation object of the builder.
func (_u *CommentUpdateOne) Mutation() *CommentMutation {
return _u.mutation
}
// ClearPost clears the "post" edge to the Post entity.
func (_u *CommentUpdateOne) ClearPost() *CommentUpdateOne {
_u.mutation.ClearPost()
return _u
}
// Where appends a list predicates to the CommentUpdate builder.
func (_u *CommentUpdateOne) Where(ps ...predicate.Comment) *CommentUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *CommentUpdateOne) Select(field string, fields ...string) *CommentUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated Comment entity.
func (_u *CommentUpdateOne) Save(ctx context.Context) (*Comment, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *CommentUpdateOne) SaveX(ctx context.Context) *Comment {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *CommentUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *CommentUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *CommentUpdateOne) check() error {
if _u.mutation.PostCleared() && len(_u.mutation.PostIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Comment.post"`)
}
return nil
}
func (_u *CommentUpdateOne) sqlSave(ctx context.Context) (_node *Comment, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(comment.Table, comment.Columns, sqlgraph.NewFieldSpec(comment.FieldID, field.TypeInt))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Comment.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, comment.FieldID)
for _, f := range fields {
if !comment.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != comment.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.Predicates(); len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.Text(); ok {
_spec.SetField(comment.FieldText, field.TypeString, value)
}
if _u.mutation.PostCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: comment.PostTable,
Columns: []string{comment.PostColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.PostIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: comment.PostTable,
Columns: []string{comment.PostColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(post.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Comment{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{comment.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

View File

@@ -0,0 +1,616 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"reflect"
"sync"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
"entgo.io/ent/entc/integration/cascadelete/ent/user"
)
// 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)
}
// 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{
comment.Table: comment.ValidColumn,
post.Table: post.ValidColumn,
user.Table: user.ValidColumn,
})
})
return columnCheck(t, c)
}
// Asc applies the given fields in ASC order.
func Asc(fields ...string) func(*sql.Selector) {
return func(s *sql.Selector) {
for _, f := range fields {
if err := checkColumn(s.TableName(), f); err != nil {
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
}
s.OrderBy(sql.Asc(s.C(f)))
}
}
}
// Desc applies the given fields in DESC order.
func Desc(fields ...string) func(*sql.Selector) {
return func(s *sql.Selector) {
for _, f := range fields {
if err := checkColumn(s.TableName(), f); err != nil {
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
}
s.OrderBy(sql.Desc(s.C(f)))
}
}
}
// AggregateFunc applies an aggregation step on the group-by traversal/selector.
type AggregateFunc func(*sql.Selector) string
// As is a pseudo aggregation function for renaming another other functions with custom names. For example:
//
// GroupBy(field1, field2).
// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")).
// Scan(ctx, &v)
func As(fn AggregateFunc, end string) AggregateFunc {
return func(s *sql.Selector) string {
return sql.As(fn(s), end)
}
}
// Count applies the "count" aggregation function on each group.
func Count() AggregateFunc {
return func(s *sql.Selector) string {
return sql.Count("*")
}
}
// Max applies the "max" aggregation function on the given field of each group.
func Max(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Max(s.C(field))
}
}
// Mean applies the "mean" aggregation function on the given field of each group.
func Mean(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Avg(s.C(field))
}
}
// Min applies the "min" aggregation function on the given field of each group.
func Min(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Min(s.C(field))
}
}
// Sum applies the "sum" aggregation function on the given field of each group.
func Sum(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Sum(s.C(field))
}
}
// 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 "ent: " + 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 "ent: " + 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 "ent: " + 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 "ent: 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)
}
}
// Strings returns list of strings from a selector. It is only allowed when selecting one field.
func (s *selector) Strings(ctx context.Context) ([]string, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Strings is not achievable when selecting more than 1 field")
}
var v []string
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// StringsX is like Strings, but panics if an error occurs.
func (s *selector) StringsX(ctx context.Context) []string {
v, err := s.Strings(ctx)
if err != nil {
panic(err)
}
return v
}
// String returns a single string from a selector. It is only allowed when selecting one field.
func (s *selector) String(ctx context.Context) (_ string, err error) {
var v []string
if v, err = s.Strings(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Strings returned %d results when one was expected", len(v))
}
return
}
// StringX is like String, but panics if an error occurs.
func (s *selector) StringX(ctx context.Context) string {
v, err := s.String(ctx)
if err != nil {
panic(err)
}
return v
}
// Ints returns list of ints from a selector. It is only allowed when selecting one field.
func (s *selector) Ints(ctx context.Context) ([]int, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Ints is not achievable when selecting more than 1 field")
}
var v []int
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// IntsX is like Ints, but panics if an error occurs.
func (s *selector) IntsX(ctx context.Context) []int {
v, err := s.Ints(ctx)
if err != nil {
panic(err)
}
return v
}
// Int returns a single int from a selector. It is only allowed when selecting one field.
func (s *selector) Int(ctx context.Context) (_ int, err error) {
var v []int
if v, err = s.Ints(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Ints returned %d results when one was expected", len(v))
}
return
}
// IntX is like Int, but panics if an error occurs.
func (s *selector) IntX(ctx context.Context) int {
v, err := s.Int(ctx)
if err != nil {
panic(err)
}
return v
}
// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
func (s *selector) Float64s(ctx context.Context) ([]float64, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Float64s is not achievable when selecting more than 1 field")
}
var v []float64
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// Float64sX is like Float64s, but panics if an error occurs.
func (s *selector) Float64sX(ctx context.Context) []float64 {
v, err := s.Float64s(ctx)
if err != nil {
panic(err)
}
return v
}
// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
func (s *selector) Float64(ctx context.Context) (_ float64, err error) {
var v []float64
if v, err = s.Float64s(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Float64s returned %d results when one was expected", len(v))
}
return
}
// Float64X is like Float64, but panics if an error occurs.
func (s *selector) Float64X(ctx context.Context) float64 {
v, err := s.Float64(ctx)
if err != nil {
panic(err)
}
return v
}
// Bools returns list of bools from a selector. It is only allowed when selecting one field.
func (s *selector) Bools(ctx context.Context) ([]bool, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Bools is not achievable when selecting more than 1 field")
}
var v []bool
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// BoolsX is like Bools, but panics if an error occurs.
func (s *selector) BoolsX(ctx context.Context) []bool {
v, err := s.Bools(ctx)
if err != nil {
panic(err)
}
return v
}
// Bool returns a single bool from a selector. It is only allowed when selecting one field.
func (s *selector) Bool(ctx context.Context) (_ bool, err error) {
var v []bool
if v, err = s.Bools(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Bools returned %d results when one was expected", len(v))
}
return
}
// BoolX is like Bool, but panics if an error occurs.
func (s *selector) BoolX(ctx context.Context) bool {
v, err := s.Bool(ctx)
if err != nil {
panic(err)
}
return v
}
// 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) {
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 {
sqlAll(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.sqlAll(ctx)
})
}
func querierCount[Q interface {
sqlCount(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.sqlCount(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 {
sqlScan(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.sqlScan(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
}
// queryHook describes an internal hook for the different sqlAll methods.
type queryHook func(context.Context, *sqlgraph.QuerySpec)

View File

@@ -0,0 +1,88 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package enttest
import (
"context"
"entgo.io/ent/entc/integration/cascadelete/ent"
// required by schema hooks.
_ "entgo.io/ent/entc/integration/cascadelete/ent/runtime"
"entgo.io/ent/dialect/sql/schema"
"entgo.io/ent/entc/integration/cascadelete/ent/migrate"
)
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 []ent.Option
migrateOpts []schema.MigrateOption
}
)
// WithOptions forwards options to client creation.
func WithOptions(opts ...ent.Option) Option {
return func(o *options) {
o.opts = append(o.opts, opts...)
}
}
// WithMigrateOptions forwards options to auto migration.
func WithMigrateOptions(opts ...schema.MigrateOption) Option {
return func(o *options) {
o.migrateOpts = append(o.migrateOpts, opts...)
}
}
func newOptions(opts []Option) *options {
o := &options{}
for _, opt := range opts {
opt(o)
}
return o
}
// Open calls ent.Open and auto-run migration.
func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client {
o := newOptions(opts)
c, err := ent.Open(driverName, dataSourceName, o.opts...)
if err != nil {
t.Error(err)
t.FailNow()
}
migrateSchema(t, c, o)
return c
}
// NewClient calls ent.NewClient and auto-run migration.
func NewClient(t TestingT, opts ...Option) *ent.Client {
o := newOptions(opts)
c := ent.NewClient(o.opts...)
migrateSchema(t, c, o)
return c
}
func migrateSchema(t TestingT, c *ent.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()
}
}

View File

@@ -0,0 +1,7 @@
// 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.
package ent
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --header "// Copyright 2019-present Facebook Inc. All rights reserved.\n// This source code is licensed under the Apache 2.0 license found\n// in the LICENSE file in the root directory of this source tree.\n\n// Code generated by ent, DO NOT EDIT." ./schema

View File

@@ -0,0 +1,227 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package hook
import (
"context"
"fmt"
"entgo.io/ent/entc/integration/cascadelete/ent"
)
// The CommentFunc type is an adapter to allow the use of ordinary
// function as Comment mutator.
type CommentFunc func(context.Context, *ent.CommentMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f CommentFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.CommentMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.CommentMutation", m)
}
// The PostFunc type is an adapter to allow the use of ordinary
// function as Post mutator.
type PostFunc func(context.Context, *ent.PostMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PostFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PostMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PostMutation", m)
}
// The UserFunc type is an adapter to allow the use of ordinary
// function as User mutator.
type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.UserMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m)
}
// Condition is a hook condition function.
type Condition func(context.Context, ent.Mutation) bool
// And groups conditions with the AND operator.
func And(first, second Condition, rest ...Condition) Condition {
return func(ctx context.Context, m ent.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 ent.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 ent.Mutation) bool {
return !cond(ctx, m)
}
}
// HasOp is a condition testing mutation operation.
func HasOp(op ent.Op) Condition {
return func(_ context.Context, m ent.Mutation) bool {
return m.Op().Is(op)
}
}
// HasAddedFields is a condition validating `.AddedField` on fields.
func HasAddedFields(field string, fields ...string) Condition {
return func(_ context.Context, m ent.Mutation) bool {
if _, exists := m.AddedField(field); !exists {
return false
}
for _, field := range fields {
if _, exists := m.AddedField(field); !exists {
return false
}
}
return true
}
}
// HasClearedFields is a condition validating `.FieldCleared` on fields.
func HasClearedFields(field string, fields ...string) Condition {
return func(_ context.Context, m ent.Mutation) bool {
if exists := m.FieldCleared(field); !exists {
return false
}
for _, field := range fields {
if exists := m.FieldCleared(field); !exists {
return false
}
}
return true
}
}
// HasFields is a condition validating `.Field` on fields.
func HasFields(field string, fields ...string) Condition {
return func(_ context.Context, m ent.Mutation) bool {
if _, exists := m.Field(field); !exists {
return false
}
for _, field := range fields {
if _, exists := m.Field(field); !exists {
return false
}
}
return true
}
}
// If executes the given hook under condition.
//
// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...)))
func If(hk ent.Hook, cond Condition) ent.Hook {
return func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.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, ent.Delete|ent.Create)
func On(hk ent.Hook, op ent.Op) ent.Hook {
return If(hk, HasOp(op))
}
// Unless skips the given hook only for the given operation.
//
// hook.Unless(Log, ent.Update|ent.UpdateOne)
func Unless(hk ent.Hook, op ent.Op) ent.Hook {
return If(hk, Not(HasOp(op)))
}
// FixedError is a hook returning a fixed error.
func FixedError(err error) ent.Hook {
return func(ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) {
return nil, err
})
}
}
// Reject returns a hook that rejects all operations that match op.
//
// func (T) Hooks() []ent.Hook {
// return []ent.Hook{
// Reject(ent.Delete|ent.Update),
// }
// }
func Reject(op ent.Op) ent.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 []ent.Hook
}
// NewChain creates a new chain of hooks.
func NewChain(hooks ...ent.Hook) Chain {
return Chain{append([]ent.Hook(nil), hooks...)}
}
// Hook chains the list of hooks and returns the final hook.
func (c Chain) Hook() ent.Hook {
return func(mutator ent.Mutator) ent.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 ...ent.Hook) Chain {
newHooks := make([]ent.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...)
}

View File

@@ -0,0 +1,68 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package migrate
import (
"context"
"fmt"
"io"
"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...)
}
// 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...)
}

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.
// Code generated by ent, DO NOT EDIT.
package migrate
import (
"entgo.io/ent/dialect/sql/schema"
"entgo.io/ent/schema/field"
)
var (
// CommentsColumns holds the columns for the "comments" table.
CommentsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "text", Type: field.TypeString},
{Name: "post_id", Type: field.TypeInt},
}
// CommentsTable holds the schema information for the "comments" table.
CommentsTable = &schema.Table{
Name: "comments",
Columns: CommentsColumns,
PrimaryKey: []*schema.Column{CommentsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "comments_posts_comments",
Columns: []*schema.Column{CommentsColumns[2]},
RefColumns: []*schema.Column{PostsColumns[0]},
OnDelete: schema.Cascade,
},
},
}
// PostsColumns holds the columns for the "posts" table.
PostsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "text", Type: field.TypeString, Default: "What's on your mind?"},
{Name: "author_id", Type: field.TypeInt, Nullable: true},
}
// PostsTable holds the schema information for the "posts" table.
PostsTable = &schema.Table{
Name: "posts",
Columns: PostsColumns,
PrimaryKey: []*schema.Column{PostsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "posts_users_posts",
Columns: []*schema.Column{PostsColumns[2]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.Cascade,
},
},
}
// UsersColumns holds the columns for the "users" table.
UsersColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "name", Type: field.TypeString, Default: "Unknown"},
}
// UsersTable holds the schema information for the "users" table.
UsersTable = &schema.Table{
Name: "users",
Columns: UsersColumns,
PrimaryKey: []*schema.Column{UsersColumns[0]},
}
// Tables holds all the tables in the schema.
Tables = []*schema.Table{
CommentsTable,
PostsTable,
UsersTable,
}
)
func init() {
CommentsTable.ForeignKeys[0].RefTable = PostsTable
PostsTable.ForeignKeys[0].RefTable = UsersTable
}

View File

@@ -0,0 +1,470 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"sync"
"entgo.io/ent"
"entgo.io/ent/entc/integration/cascadelete/ent/comment"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
"entgo.io/ent/entc/integration/cascadelete/ent/user"
)
const (
// Operation types.
OpCreate = ent.OpCreate
OpDelete = ent.OpDelete
OpDeleteOne = ent.OpDeleteOne
OpUpdate = ent.OpUpdate
OpUpdateOne = ent.OpUpdateOne
// Node types.
TypeComment = "Comment"
TypePost = "Post"
TypeUser = "User"
)
// CommentMutation represents an operation that mutates the Comment nodes in the graph.
type CommentMutation struct {
comment.Mutation
config
id *int
done bool
oldValue func(context.Context) (*Comment, error)
}
var _ ent.Mutation = (*CommentMutation)(nil)
// commentOption allows management of the mutation configuration using functional options.
type commentOption func(*CommentMutation)
// newCommentMutation creates new mutation for the Comment entity.
func newCommentMutation(c config, op Op, opts ...commentOption) *CommentMutation {
m := &CommentMutation{
Mutation: *comment.NewMutation(op),
config: c,
}
for _, opt := range opts {
opt(m)
}
return m
}
// 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 *CommentMutation) ID() (id int, exists bool) {
if m.id == nil {
return
}
return *m.id, true
}
// withCommentID sets the ID field of the mutation.
func withCommentID(id int) commentOption {
return func(m *CommentMutation) {
var (
err error
once sync.Once
value *Comment
)
m.oldValue = func(ctx context.Context) (*Comment, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
value, err = m.Client().Comment.Get(ctx, id)
}
})
return value, err
}
m.id = &id
}
}
// withComment sets the old Comment of the mutation.
func withComment(node *Comment) commentOption {
return func(m *CommentMutation) {
m.oldValue = func(context.Context) (*Comment, error) {
return node, nil
}
m.id = &node.ID
}
}
// 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 CommentMutation) 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 CommentMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
tx := &Tx{config: m.config}
tx.init()
return tx, nil
}
// 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 *CommentMutation) IDs(ctx context.Context) ([]int, error) {
switch {
case m.Op().Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
if exists {
return []int{id}, nil
}
fallthrough
case m.Op().Is(OpUpdate | OpDelete):
return m.Client().Comment.Query().Where(m.Predicates()...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.Op())
}
}
// OldText returns the old "text" field's value of the Comment entity.
// If the Comment 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 *CommentMutation) OldText(ctx context.Context) (v string, err error) {
if !m.Op().Is(OpUpdateOne) {
return v, errors.New("OldText is only allowed on UpdateOne operations")
}
if _, exists := m.ID(); !exists || m.oldValue == nil {
return v, errors.New("OldText requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldText: %w", err)
}
return oldValue.Text, nil
}
// OldPostID returns the old "post_id" field's value of the Comment entity.
// If the Comment 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 *CommentMutation) OldPostID(ctx context.Context) (v int, err error) {
if !m.Op().Is(OpUpdateOne) {
return v, errors.New("OldPostID is only allowed on UpdateOne operations")
}
if _, exists := m.ID(); !exists || m.oldValue == nil {
return v, errors.New("OldPostID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldPostID: %w", err)
}
return oldValue.PostID, nil
}
// 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 *CommentMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
case comment.FieldText:
return m.OldText(ctx)
case comment.FieldPostID:
return m.OldPostID(ctx)
}
return nil, fmt.Errorf("unknown Comment field %s", name)
}
// PostMutation represents an operation that mutates the Post nodes in the graph.
type PostMutation struct {
post.Mutation
config
id *int
done bool
oldValue func(context.Context) (*Post, error)
}
var _ ent.Mutation = (*PostMutation)(nil)
// postOption allows management of the mutation configuration using functional options.
type postOption func(*PostMutation)
// newPostMutation creates new mutation for the Post entity.
func newPostMutation(c config, op Op, opts ...postOption) *PostMutation {
m := &PostMutation{
Mutation: *post.NewMutation(op),
config: c,
}
for _, opt := range opts {
opt(m)
}
return m
}
// 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 *PostMutation) ID() (id int, exists bool) {
if m.id == nil {
return
}
return *m.id, true
}
// withPostID sets the ID field of the mutation.
func withPostID(id int) postOption {
return func(m *PostMutation) {
var (
err error
once sync.Once
value *Post
)
m.oldValue = func(ctx context.Context) (*Post, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
value, err = m.Client().Post.Get(ctx, id)
}
})
return value, err
}
m.id = &id
}
}
// withPost sets the old Post of the mutation.
func withPost(node *Post) postOption {
return func(m *PostMutation) {
m.oldValue = func(context.Context) (*Post, error) {
return node, nil
}
m.id = &node.ID
}
}
// 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 PostMutation) 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 PostMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
tx := &Tx{config: m.config}
tx.init()
return tx, nil
}
// 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 *PostMutation) IDs(ctx context.Context) ([]int, error) {
switch {
case m.Op().Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
if exists {
return []int{id}, nil
}
fallthrough
case m.Op().Is(OpUpdate | OpDelete):
return m.Client().Post.Query().Where(m.Predicates()...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.Op())
}
}
// OldText returns the old "text" field's value of the Post entity.
// If the Post 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 *PostMutation) OldText(ctx context.Context) (v string, err error) {
if !m.Op().Is(OpUpdateOne) {
return v, errors.New("OldText is only allowed on UpdateOne operations")
}
if _, exists := m.ID(); !exists || m.oldValue == nil {
return v, errors.New("OldText requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldText: %w", err)
}
return oldValue.Text, nil
}
// OldAuthorID returns the old "author_id" field's value of the Post entity.
// If the Post 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 *PostMutation) OldAuthorID(ctx context.Context) (v int, err error) {
if !m.Op().Is(OpUpdateOne) {
return v, errors.New("OldAuthorID is only allowed on UpdateOne operations")
}
if _, exists := m.ID(); !exists || m.oldValue == nil {
return v, errors.New("OldAuthorID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldAuthorID: %w", err)
}
return oldValue.AuthorID, nil
}
// 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 *PostMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
case post.FieldText:
return m.OldText(ctx)
case post.FieldAuthorID:
return m.OldAuthorID(ctx)
}
return nil, fmt.Errorf("unknown Post field %s", name)
}
// UserMutation represents an operation that mutates the User nodes in the graph.
type UserMutation struct {
user.Mutation
config
id *int
done bool
oldValue func(context.Context) (*User, error)
}
var _ ent.Mutation = (*UserMutation)(nil)
// userOption allows management of the mutation configuration using functional options.
type userOption func(*UserMutation)
// newUserMutation creates new mutation for the User entity.
func newUserMutation(c config, op Op, opts ...userOption) *UserMutation {
m := &UserMutation{
Mutation: *user.NewMutation(op),
config: c,
}
for _, opt := range opts {
opt(m)
}
return m
}
// 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 *UserMutation) ID() (id int, exists bool) {
if m.id == nil {
return
}
return *m.id, true
}
// withUserID sets the ID field of the mutation.
func withUserID(id int) userOption {
return func(m *UserMutation) {
var (
err error
once sync.Once
value *User
)
m.oldValue = func(ctx context.Context) (*User, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
value, err = m.Client().User.Get(ctx, id)
}
})
return value, err
}
m.id = &id
}
}
// withUser sets the old User of the mutation.
func withUser(node *User) userOption {
return func(m *UserMutation) {
m.oldValue = func(context.Context) (*User, error) {
return node, nil
}
m.id = &node.ID
}
}
// 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 UserMutation) 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 UserMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
tx := &Tx{config: m.config}
tx.init()
return tx, nil
}
// 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 *UserMutation) IDs(ctx context.Context) ([]int, error) {
switch {
case m.Op().Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
if exists {
return []int{id}, nil
}
fallthrough
case m.Op().Is(OpUpdate | OpDelete):
return m.Client().User.Query().Where(m.Predicates()...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.Op())
}
}
// OldName returns the old "name" field's value of the User entity.
// If the User 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 *UserMutation) OldName(ctx context.Context) (v string, err error) {
if !m.Op().Is(OpUpdateOne) {
return v, errors.New("OldName is only allowed on UpdateOne operations")
}
if _, exists := m.ID(); !exists || m.oldValue == nil {
return v, errors.New("OldName requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldName: %w", err)
}
return oldValue.Name, nil
}
// 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 *UserMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
case user.FieldName:
return m.OldName(ctx)
}
return nil, fmt.Errorf("unknown User field %s", name)
}

View File

@@ -0,0 +1,163 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/entc/integration/cascadelete/ent/post"
"entgo.io/ent/entc/integration/cascadelete/ent/user"
)
// Post is the model entity for the Post schema.
type Post struct {
config `json:"-"`
// ID of the ent.
ID int `json:"id,omitempty"`
// Text holds the value of the "text" field.
Text string `json:"text,omitempty"`
// AuthorID holds the value of the "author_id" field.
AuthorID int `json:"author_id,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the PostQuery when eager-loading is set.
Edges PostEdges `json:"edges"`
selectValues sql.SelectValues
}
// PostEdges holds the relations/edges for other nodes in the graph.
type PostEdges struct {
// Author holds the value of the author edge.
Author *User `json:"author,omitempty"`
// Comments holds the value of the comments edge.
Comments []*Comment `json:"comments,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [2]bool
}
// AuthorOrErr returns the Author value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e PostEdges) AuthorOrErr() (*User, error) {
if e.Author != nil {
return e.Author, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: user.Label}
}
return nil, &NotLoadedError{edge: "author"}
}
// CommentsOrErr returns the Comments value or an error if the edge
// was not loaded in eager-loading.
func (e PostEdges) CommentsOrErr() ([]*Comment, error) {
if e.loadedTypes[1] {
return e.Comments, nil
}
return nil, &NotLoadedError{edge: "comments"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Post) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case post.FieldID, post.FieldAuthorID:
values[i] = new(sql.NullInt64)
case post.FieldText:
values[i] = new(sql.NullString)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Post fields.
func (_m *Post) 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] {
case post.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int(value.Int64)
case post.FieldText:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field text", values[i])
} else if value.Valid {
_m.Text = value.String
}
case post.FieldAuthorID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field author_id", values[i])
} else if value.Valid {
_m.AuthorID = int(value.Int64)
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the Post.
// This includes values selected through modifiers, order, etc.
func (_m *Post) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryAuthor queries the "author" edge of the Post entity.
func (_m *Post) QueryAuthor() *UserQuery {
return NewPostClient(_m.config).QueryAuthor(_m)
}
// QueryComments queries the "comments" edge of the Post entity.
func (_m *Post) QueryComments() *CommentQuery {
return NewPostClient(_m.config).QueryComments(_m)
}
// Update returns a builder for updating this Post.
// Note that you need to call Post.Unwrap() before calling this method if this Post
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *Post) Update() *PostUpdateOne {
return NewPostClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the Post 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 (_m *Post) Unwrap() *Post {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: Post is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *Post) String() string {
var builder strings.Builder
builder.WriteString("Post(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("text=")
builder.WriteString(_m.Text)
builder.WriteString(", ")
builder.WriteString("author_id=")
builder.WriteString(fmt.Sprintf("%v", _m.AuthorID))
builder.WriteByte(')')
return builder.String()
}
// Posts is a parsable slice of Post.
type Posts []*Post

View File

@@ -0,0 +1,432 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package post
import (
"context"
"fmt"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/entc/integration/cascadelete/ent/predicate"
)
// Mutation represents an operation that mutates the Post nodes in the graph.
type Mutation struct {
op ent.Op
typ string
text *string
clearedFields map[string]struct{}
author *int
clearedauthor bool
comments map[int]struct{}
removedcomments map[int]struct{}
clearedcomments bool
predicates []predicate.Post
}
// NewMutation creates a new Mutation for the Post entity.
func NewMutation(op ent.Op) *Mutation {
return &Mutation{
op: op,
typ: "Post",
clearedFields: make(map[string]struct{}),
}
}
// Predicates returns the list of predicates set on the mutation.
func (m *Mutation) Predicates() []predicate.Post {
return m.predicates
}
// SetText sets the "text" field.
func (m *Mutation) SetText(s string) {
m.text = &s
}
// Text returns the value of the "text" field in the mutation.
func (m *Mutation) Text() (r string, exists bool) {
v := m.text
if v == nil {
return
}
return *v, true
}
// ResetText resets all changes to the "text" field.
func (m *Mutation) ResetText() {
m.text = nil
}
// SetAuthorID sets the "author_id" field.
func (m *Mutation) SetAuthorID(i int) {
m.author = &i
}
// AuthorID returns the value of the "author_id" field in the mutation.
func (m *Mutation) AuthorID() (r int, exists bool) {
v := m.author
if v == nil {
return
}
return *v, true
}
// ClearAuthorID clears the value of the "author_id" field.
func (m *Mutation) ClearAuthorID() {
m.author = nil
m.clearedFields[FieldAuthorID] = struct{}{}
}
// AuthorIDCleared returns if the "author_id" field was cleared in this mutation.
func (m *Mutation) AuthorIDCleared() bool {
_, ok := m.clearedFields[FieldAuthorID]
return ok
}
// ResetAuthorID resets all changes to the "author_id" field.
func (m *Mutation) ResetAuthorID() {
m.author = nil
delete(m.clearedFields, FieldAuthorID)
}
// ClearAuthor clears the "author" edge to the User entity.
func (m *Mutation) ClearAuthor() {
m.clearedauthor = true
m.clearedFields[FieldAuthorID] = struct{}{}
}
// AuthorCleared reports if the "author" edge to the User entity was cleared.
func (m *Mutation) AuthorCleared() bool {
return m.AuthorIDCleared() || m.clearedauthor
}
// AuthorIDs returns the "author" edge IDs in the mutation.
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
// AuthorID instead. It exists only for internal usage by the builders.
func (m *Mutation) AuthorIDs() (ids []int) {
if id := m.author; id != nil {
ids = append(ids, *id)
}
return
}
// ResetAuthor resets all changes to the "author" edge.
func (m *Mutation) ResetAuthor() {
m.author = nil
m.clearedauthor = false
}
// AddCommentIDs adds the "comments" edge to the Comment entity by ids.
func (m *Mutation) AddCommentIDs(ids ...int) {
if m.comments == nil {
m.comments = make(map[int]struct{})
}
for i := range ids {
m.comments[ids[i]] = struct{}{}
}
}
// ClearComments clears the "comments" edge to the Comment entity.
func (m *Mutation) ClearComments() {
m.clearedcomments = true
}
// CommentsCleared reports if the "comments" edge to the Comment entity was cleared.
func (m *Mutation) CommentsCleared() bool {
return m.clearedcomments
}
// RemoveCommentIDs removes the "comments" edge to the Comment entity by IDs.
func (m *Mutation) RemoveCommentIDs(ids ...int) {
if m.removedcomments == nil {
m.removedcomments = make(map[int]struct{})
}
for i := range ids {
delete(m.comments, ids[i])
m.removedcomments[ids[i]] = struct{}{}
}
}
// RemovedComments returns the removed IDs of the "comments" edge to the Comment entity.
func (m *Mutation) RemovedCommentsIDs() (ids []int) {
for id := range m.removedcomments {
ids = append(ids, id)
}
return
}
// CommentsIDs returns the "comments" edge IDs in the mutation.
func (m *Mutation) CommentsIDs() (ids []int) {
for id := range m.comments {
ids = append(ids, id)
}
return
}
// ResetComments resets all changes to the "comments" edge.
func (m *Mutation) ResetComments() {
m.comments = nil
m.clearedcomments = false
m.removedcomments = nil
}
// Where appends a list predicates to the Mutation builder.
func (m *Mutation) Where(ps ...predicate.Post) {
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(*sql.Selector)) {
p := make([]predicate.Post, 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 (Post).
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, 2)
if m.text != nil {
fields = append(fields, FieldText)
}
if m.author != nil {
fields = append(fields, FieldAuthorID)
}
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) {
switch name {
case FieldText:
return m.Text()
case FieldAuthorID:
return m.AuthorID()
}
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) {
return nil, fmt.Errorf("unknown Post field %s", name)
}
// 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 {
case FieldText:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetText(v)
return nil
case FieldAuthorID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetAuthorID(v)
return nil
}
return fmt.Errorf("unknown Post field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
func (m *Mutation) AddedFields() []string {
var fields []string
return fields
}
// 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) {
switch name {
}
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 {
switch name {
}
return fmt.Errorf("unknown Post numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *Mutation) ClearedFields() []string {
var fields []string
if m.FieldCleared(FieldAuthorID) {
fields = append(fields, FieldAuthorID)
}
return fields
}
// 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 {
switch name {
case FieldAuthorID:
m.ClearAuthorID()
return nil
}
return fmt.Errorf("unknown Post 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 {
switch name {
case FieldText:
m.ResetText()
return nil
case FieldAuthorID:
m.ResetAuthorID()
return nil
}
return fmt.Errorf("unknown Post field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *Mutation) AddedEdges() []string {
edges := make([]string, 0, 2)
if m.author != nil {
edges = append(edges, EdgeAuthor)
}
if m.comments != nil {
edges = append(edges, EdgeComments)
}
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 {
switch name {
case EdgeAuthor:
if id := m.author; id != nil {
return []ent.Value{*id}
}
case EdgeComments:
ids := make([]ent.Value, 0, len(m.comments))
for id := range m.comments {
ids = append(ids, id)
}
return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *Mutation) RemovedEdges() []string {
edges := make([]string, 0, 2)
if m.removedcomments != nil {
edges = append(edges, EdgeComments)
}
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 {
switch name {
case EdgeComments:
ids := make([]ent.Value, 0, len(m.removedcomments))
for id := range m.removedcomments {
ids = append(ids, id)
}
return ids
}
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *Mutation) ClearedEdges() []string {
edges := make([]string, 0, 2)
if m.clearedauthor {
edges = append(edges, EdgeAuthor)
}
if m.clearedcomments {
edges = append(edges, EdgeComments)
}
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 {
switch name {
case EdgeAuthor:
return m.clearedauthor
case EdgeComments:
return m.clearedcomments
}
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 {
switch name {
case EdgeAuthor:
m.ClearAuthor()
return nil
}
return fmt.Errorf("unknown Post 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 {
switch name {
case EdgeAuthor:
m.ResetAuthor()
return nil
case EdgeComments:
m.ResetComments()
return nil
}
return fmt.Errorf("unknown Post edge %s", name)
}

View File

@@ -0,0 +1,118 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package post
import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the post type in the database.
Label = "post"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldText holds the string denoting the text field in the database.
FieldText = "text"
// FieldAuthorID holds the string denoting the author_id field in the database.
FieldAuthorID = "author_id"
// EdgeAuthor holds the string denoting the author edge name in mutations.
EdgeAuthor = "author"
// EdgeComments holds the string denoting the comments edge name in mutations.
EdgeComments = "comments"
// Table holds the table name of the post in the database.
Table = "posts"
// AuthorTable is the table that holds the author relation/edge.
AuthorTable = "posts"
// AuthorInverseTable is the table name for the User entity.
// It exists in this package in order to avoid circular dependency with the "user" package.
AuthorInverseTable = "users"
// AuthorColumn is the table column denoting the author relation/edge.
AuthorColumn = "author_id"
// CommentsTable is the table that holds the comments relation/edge.
CommentsTable = "comments"
// CommentsInverseTable is the table name for the Comment entity.
// It exists in this package in order to avoid circular dependency with the "comment" package.
CommentsInverseTable = "comments"
// CommentsColumn is the table column denoting the comments relation/edge.
CommentsColumn = "post_id"
)
// Columns holds all SQL columns for post fields.
var Columns = []string{
FieldID,
FieldText,
FieldAuthorID,
}
// 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
}
}
return false
}
var (
// DefaultText holds the default value on creation for the "text" field.
DefaultText string
)
// OrderOption defines the ordering options for the Post queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByText orders the results by the text field.
func ByText(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldText, opts...).ToFunc()
}
// ByAuthorID orders the results by the author_id field.
func ByAuthorID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAuthorID, opts...).ToFunc()
}
// ByAuthorField orders the results by author field.
func ByAuthorField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newAuthorStep(), sql.OrderByField(field, opts...))
}
}
// ByCommentsCount orders the results by comments count.
func ByCommentsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newCommentsStep(), opts...)
}
}
// ByComments orders the results by comments terms.
func ByComments(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newCommentsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newAuthorStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AuthorInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, AuthorTable, AuthorColumn),
)
}
func newCommentsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(CommentsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, CommentsTable, CommentsColumn),
)
}

View File

@@ -0,0 +1,224 @@
// 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.
// Code generated by ent, DO NOT EDIT.
package post
import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/entc/integration/cascadelete/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int) predicate.Post {
return predicate.Post(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int) predicate.Post {
return predicate.Post(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int) predicate.Post {
return predicate.Post(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int) predicate.Post {
return predicate.Post(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int) predicate.Post {
return predicate.Post(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int) predicate.Post {
return predicate.Post(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int) predicate.Post {
return predicate.Post(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int) predicate.Post {
return predicate.Post(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int) predicate.Post {
return predicate.Post(sql.FieldLTE(FieldID, id))
}
// Text applies equality check predicate on the "text" field. It's identical to TextEQ.
func Text(v string) predicate.Post {
return predicate.Post(sql.FieldEQ(FieldText, v))
}
// AuthorID applies equality check predicate on the "author_id" field. It's identical to AuthorIDEQ.
func AuthorID(v int) predicate.Post {
return predicate.Post(sql.FieldEQ(FieldAuthorID, v))
}
// TextEQ applies the EQ predicate on the "text" field.
func TextEQ(v string) predicate.Post {
return predicate.Post(sql.FieldEQ(FieldText, v))
}
// TextNEQ applies the NEQ predicate on the "text" field.
func TextNEQ(v string) predicate.Post {
return predicate.Post(sql.FieldNEQ(FieldText, v))
}
// TextIn applies the In predicate on the "text" field.
func TextIn(vs ...string) predicate.Post {
return predicate.Post(sql.FieldIn(FieldText, vs...))
}
// TextNotIn applies the NotIn predicate on the "text" field.
func TextNotIn(vs ...string) predicate.Post {
return predicate.Post(sql.FieldNotIn(FieldText, vs...))
}
// TextGT applies the GT predicate on the "text" field.
func TextGT(v string) predicate.Post {
return predicate.Post(sql.FieldGT(FieldText, v))
}
// TextGTE applies the GTE predicate on the "text" field.
func TextGTE(v string) predicate.Post {
return predicate.Post(sql.FieldGTE(FieldText, v))
}
// TextLT applies the LT predicate on the "text" field.
func TextLT(v string) predicate.Post {
return predicate.Post(sql.FieldLT(FieldText, v))
}
// TextLTE applies the LTE predicate on the "text" field.
func TextLTE(v string) predicate.Post {
return predicate.Post(sql.FieldLTE(FieldText, v))
}
// TextContains applies the Contains predicate on the "text" field.
func TextContains(v string) predicate.Post {
return predicate.Post(sql.FieldContains(FieldText, v))
}
// TextHasPrefix applies the HasPrefix predicate on the "text" field.
func TextHasPrefix(v string) predicate.Post {
return predicate.Post(sql.FieldHasPrefix(FieldText, v))
}
// TextHasSuffix applies the HasSuffix predicate on the "text" field.
func TextHasSuffix(v string) predicate.Post {
return predicate.Post(sql.FieldHasSuffix(FieldText, v))
}
// TextEqualFold applies the EqualFold predicate on the "text" field.
func TextEqualFold(v string) predicate.Post {
return predicate.Post(sql.FieldEqualFold(FieldText, v))
}
// TextContainsFold applies the ContainsFold predicate on the "text" field.
func TextContainsFold(v string) predicate.Post {
return predicate.Post(sql.FieldContainsFold(FieldText, v))
}
// AuthorIDEQ applies the EQ predicate on the "author_id" field.
func AuthorIDEQ(v int) predicate.Post {
return predicate.Post(sql.FieldEQ(FieldAuthorID, v))
}
// AuthorIDNEQ applies the NEQ predicate on the "author_id" field.
func AuthorIDNEQ(v int) predicate.Post {
return predicate.Post(sql.FieldNEQ(FieldAuthorID, v))
}
// AuthorIDIn applies the In predicate on the "author_id" field.
func AuthorIDIn(vs ...int) predicate.Post {
return predicate.Post(sql.FieldIn(FieldAuthorID, vs...))
}
// AuthorIDNotIn applies the NotIn predicate on the "author_id" field.
func AuthorIDNotIn(vs ...int) predicate.Post {
return predicate.Post(sql.FieldNotIn(FieldAuthorID, vs...))
}
// AuthorIDIsNil applies the IsNil predicate on the "author_id" field.
func AuthorIDIsNil() predicate.Post {
return predicate.Post(sql.FieldIsNull(FieldAuthorID))
}
// AuthorIDNotNil applies the NotNil predicate on the "author_id" field.
func AuthorIDNotNil() predicate.Post {
return predicate.Post(sql.FieldNotNull(FieldAuthorID))
}
// HasAuthor applies the HasEdge predicate on the "author" edge.
func HasAuthor() predicate.Post {
return predicate.Post(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, AuthorTable, AuthorColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasAuthorWith applies the HasEdge predicate on the "author" edge with a given conditions (other predicates).
func HasAuthorWith(preds ...predicate.User) predicate.Post {
return predicate.Post(func(s *sql.Selector) {
step := newAuthorStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasComments applies the HasEdge predicate on the "comments" edge.
func HasComments() predicate.Post {
return predicate.Post(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, CommentsTable, CommentsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasCommentsWith applies the HasEdge predicate on the "comments" edge with a given conditions (other predicates).
func HasCommentsWith(preds ...predicate.Comment) predicate.Post {
return predicate.Post(func(s *sql.Selector) {
step := newCommentsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Post) predicate.Post {
return predicate.Post(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Post) predicate.Post {
return predicate.Post(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Post) predicate.Post {
return predicate.Post(sql.NotPredicates(p))
}

Some files were not shown because too many files have changed in this diff Show More