snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
57
entc/gen/template/dialect/gremlin/by.tmpl
Normal file
57
entc/gen/template/dialect/gremlin/by.tmpl
Normal 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 }}
|
||||
95
entc/gen/template/dialect/gremlin/create.tmpl
Normal file
95
entc/gen/template/dialect/gremlin/create.tmpl
Normal 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 }}
|
||||
87
entc/gen/template/dialect/gremlin/decode.tmpl
Normal file
87
entc/gen/template/dialect/gremlin/decode.tmpl
Normal 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 }}
|
||||
32
entc/gen/template/dialect/gremlin/delete.tmpl
Normal file
32
entc/gen/template/dialect/gremlin/delete.tmpl
Normal 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 }}
|
||||
54
entc/gen/template/dialect/gremlin/errors.tmpl
Normal file
54
entc/gen/template/dialect/gremlin/errors.tmpl
Normal 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 }}
|
||||
14
entc/gen/template/dialect/gremlin/globals.tmpl
Normal file
14
entc/gen/template/dialect/gremlin/globals.tmpl
Normal 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 }}
|
||||
46
entc/gen/template/dialect/gremlin/group.tmpl
Normal file
46
entc/gen/template/dialect/gremlin/group.tmpl
Normal 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 }}
|
||||
19
entc/gen/template/dialect/gremlin/meta.tmpl
Normal file
19
entc/gen/template/dialect/gremlin/meta.tmpl
Normal 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 }}
|
||||
24
entc/gen/template/dialect/gremlin/open.tmpl
Normal file
24
entc/gen/template/dialect/gremlin/open.tmpl
Normal 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 }}
|
||||
135
entc/gen/template/dialect/gremlin/predicate.tmpl
Normal file
135
entc/gen/template/dialect/gremlin/predicate.tmpl
Normal 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 }}
|
||||
107
entc/gen/template/dialect/gremlin/query.tmpl
Normal file
107
entc/gen/template/dialect/gremlin/query.tmpl
Normal 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 }}
|
||||
44
entc/gen/template/dialect/gremlin/select.tmpl
Normal file
44
entc/gen/template/dialect/gremlin/select.tmpl
Normal 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 }}
|
||||
198
entc/gen/template/dialect/gremlin/update.tmpl
Normal file
198
entc/gen/template/dialect/gremlin/update.tmpl
Normal 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 }}
|
||||
Reference in New Issue
Block a user