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

464
entql/entql.go Normal file
View File

@@ -0,0 +1,464 @@
// 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 entql provides an experimental API for interacting dynamically
// with ent queries. For more info, search for it in https://entgo.io.
package entql
import (
"encoding/json"
"fmt"
"strings"
)
// An Op represents a predicate operator.
type Op int
// Builtin operators.
const (
OpAnd Op = iota // logical and.
OpOr // logical or.
OpNot // logical negation.
OpEQ // =
OpNEQ // <>
OpGT // >
OpGTE // >=
OpLT // <
OpLTE // <=
OpIn // IN
OpNotIn // NOT IN
)
var ops = [...]string{
OpAnd: "&&",
OpOr: "||",
OpNot: "!",
OpEQ: "==",
OpNEQ: "!=",
OpGT: ">",
OpGTE: ">=",
OpLT: "<",
OpLTE: "<=",
OpIn: "in",
OpNotIn: "not in",
}
// String returns the text representation of an operator.
func (o Op) String() string {
if o >= 0 && int(o) < len(ops) {
return ops[o]
}
return "<invalid>"
}
// A Func represents a function expression.
type Func string
// Builtin functions.
const (
FuncEqualFold Func = "equal_fold" // equals case-insensitive
FuncContains Func = "contains" // containing
FuncContainsFold Func = "contains_fold" // containing case-insensitive
FuncHasPrefix Func = "has_prefix" // startingWith
FuncHasSuffix Func = "has_suffix" // endingWith
FuncHasEdge Func = "has_edge" // HasEdge
)
type (
// Expr represents an entql expression. All expressions implement the Expr interface.
Expr interface {
expr()
fmt.Stringer
}
// P represents an expression that returns a boolean value depending on its variables.
P interface {
Expr
Negate() P
}
)
type (
// A UnaryExpr represents a unary expression.
UnaryExpr struct {
Op Op
X Expr
}
// A BinaryExpr represents a binary expression.
BinaryExpr struct {
Op Op
X, Y Expr
}
// A NaryExpr represents a n-ary expression.
NaryExpr struct {
Op Op
Xs []Expr
}
// A CallExpr represents a function call with its arguments.
CallExpr struct {
Func Func
Args []Expr
}
// A Field represents a node field.
Field struct {
Name string
}
// An Edge represents an edge in the graph.
Edge struct {
Name string
}
// A Value represents an arbitrary value.
Value struct {
V any
}
)
// Not returns a predicate that represents the logical negation of the given predicate.
func Not(x P) P {
return &UnaryExpr{
Op: OpNot,
X: x,
}
}
// And returns a composed predicate that represents the logical AND predicate.
func And(x, y P, z ...P) P {
if len(z) == 0 {
return &BinaryExpr{
Op: OpAnd,
X: x,
Y: y,
}
}
return &NaryExpr{
Op: OpAnd,
Xs: append([]Expr{x, y}, p2expr(z)...),
}
}
// Or returns a composed predicate that represents the logical OR predicate.
func Or(x, y P, z ...P) P {
if len(z) == 0 {
return &BinaryExpr{
Op: OpOr,
X: x,
Y: y,
}
}
return &NaryExpr{
Op: OpOr,
Xs: append([]Expr{x, y}, p2expr(z)...),
}
}
// F returns a field expression for the given name.
func F(name string) *Field {
return &Field{Name: name}
}
// EQ returns a predicate to check if the expressions are equal.
func EQ(x, y Expr) P {
return &BinaryExpr{
Op: OpEQ,
X: x,
Y: y,
}
}
// FieldEQ returns a predicate to check if a field is equivalent to a given value.
func FieldEQ(name string, v any) P {
return &BinaryExpr{
Op: OpEQ,
X: &Field{Name: name},
Y: &Value{V: v},
}
}
// NEQ returns a predicate to check if the expressions are not equal.
func NEQ(x, y Expr) P {
return &BinaryExpr{
Op: OpNEQ,
X: x,
Y: y,
}
}
// FieldNEQ returns a predicate to check if a field is not equivalent to a given value.
func FieldNEQ(name string, v any) P {
return &BinaryExpr{
Op: OpNEQ,
X: &Field{Name: name},
Y: &Value{V: v},
}
}
// GT returns a predicate to check if the expression x > than expression y.
func GT(x, y Expr) P {
return &BinaryExpr{
Op: OpGT,
X: x,
Y: y,
}
}
// FieldGT returns a predicate to check if a field is > than the given value.
func FieldGT(name string, v any) P {
return &BinaryExpr{
Op: OpGT,
X: &Field{Name: name},
Y: &Value{V: v},
}
}
// GTE returns a predicate to check if the expression x >= than expression y.
func GTE(x, y Expr) P {
return &BinaryExpr{
Op: OpGTE,
X: x,
Y: y,
}
}
// FieldGTE returns a predicate to check if a field is >= than the given value.
func FieldGTE(name string, v any) P {
return &BinaryExpr{
Op: OpGTE,
X: &Field{Name: name},
Y: &Value{V: v},
}
}
// LT returns a predicate to check if the expression x < than expression y.
func LT(x, y Expr) P {
return &BinaryExpr{
Op: OpLT,
X: x,
Y: y,
}
}
// FieldLT returns a predicate to check if a field is < than the given value.
func FieldLT(name string, v any) P {
return &BinaryExpr{
Op: OpLT,
X: &Field{Name: name},
Y: &Value{V: v},
}
}
// LTE returns a predicate to check if the expression x <= than expression y.
func LTE(x, y Expr) P {
return &BinaryExpr{
Op: OpLTE,
X: x,
Y: y,
}
}
// FieldLTE returns a predicate to check if a field is <= >than the given value.
func FieldLTE(name string, v any) P {
return &BinaryExpr{
Op: OpLTE,
X: &Field{Name: name},
Y: &Value{V: v},
}
}
// FieldContains returns a predicate to check if the field value contains a substr.
func FieldContains(name, substr string) P {
return &CallExpr{
Func: FuncContains,
Args: []Expr{&Field{Name: name}, &Value{V: substr}},
}
}
// FieldContainsFold returns a predicate to check if the field value contains a substr under case-folding.
func FieldContainsFold(name, substr string) P {
return &CallExpr{
Func: FuncContainsFold,
Args: []Expr{&Field{Name: name}, &Value{V: substr}},
}
}
// FieldEqualFold returns a predicate to check if the field is equal to the given string under case-folding.
func FieldEqualFold(name, v string) P {
return &CallExpr{
Func: FuncEqualFold,
Args: []Expr{&Field{Name: name}, &Value{V: v}},
}
}
// FieldHasPrefix returns a predicate to check if the field starts with the given prefix.
func FieldHasPrefix(name, prefix string) P {
return &CallExpr{
Func: FuncHasPrefix,
Args: []Expr{&Field{Name: name}, &Value{V: prefix}},
}
}
// FieldHasSuffix returns a predicate to check if the field ends with the given suffix.
func FieldHasSuffix(name, suffix string) P {
return &CallExpr{
Func: FuncHasSuffix,
Args: []Expr{&Field{Name: name}, &Value{V: suffix}},
}
}
// FieldIn returns a predicate to check if the field value matches any value in the given list.
func FieldIn(name string, vs ...any) P {
return &BinaryExpr{
Op: OpIn,
X: &Field{Name: name},
Y: &Value{V: vs},
}
}
// FieldNotIn returns a predicate to check if the field value doesn't match any value in the given list.
func FieldNotIn(name string, vs ...any) P {
return &BinaryExpr{
Op: OpNotIn,
X: &Field{Name: name},
Y: &Value{V: vs},
}
}
// FieldNil returns a predicate to check if a field is nil (null in databases).
func FieldNil(name string) P {
return &BinaryExpr{
Op: OpEQ,
X: &Field{Name: name},
Y: (*Value)(nil),
}
}
// FieldNotNil returns a predicate to check if a field is not nil (not null in databases).
func FieldNotNil(name string) P {
return &BinaryExpr{
Op: OpNEQ,
X: &Field{Name: name},
Y: (*Value)(nil),
}
}
// HasEdge returns a predicate to check if an edge exists (not null in databases).
func HasEdge(name string) P {
return &CallExpr{
Func: FuncHasEdge,
Args: []Expr{&Edge{Name: name}},
}
}
// HasEdgeWith returns a predicate to check if the "other nodes" that are connected to the
// edge returns true on the provided predicate.
func HasEdgeWith(name string, p ...P) P {
return &CallExpr{
Func: FuncHasEdge,
Args: append([]Expr{&Edge{Name: name}}, p2expr(p)...),
}
}
// Negate negates the predicate.
func (e *BinaryExpr) Negate() P {
return Not(e)
}
// Negate negates the predicate.
func (e *NaryExpr) Negate() P {
return Not(e)
}
// Negate negates the predicate.
func (e *UnaryExpr) Negate() P {
return Not(e)
}
// Negate negates the predicate.
func (e *CallExpr) Negate() P {
return Not(e)
}
// String returns the text representation of a binary expression.
func (e *BinaryExpr) String() string {
return fmt.Sprintf("%s %s %s", e.X, e.Op, e.Y)
}
// String returns the text representation of a unary expression.
func (e *UnaryExpr) String() string {
return fmt.Sprintf("%s(%s)", e.Op, e.X)
}
// String returns the text representation of an n-ary expression.
func (e *NaryExpr) String() string {
var s strings.Builder
s.WriteByte('(')
for i, x := range e.Xs {
if i > 0 {
s.WriteByte(' ')
s.WriteString(e.Op.String())
s.WriteByte(' ')
}
s.WriteString(x.String())
}
s.WriteByte(')')
return s.String()
}
// String returns the text representation of a call expression.
func (e *CallExpr) String() string {
var s strings.Builder
s.WriteString(string(e.Func))
s.WriteByte('(')
for i, x := range e.Args {
if i > 0 {
s.WriteString(", ")
}
s.WriteString(x.String())
}
s.WriteByte(')')
return s.String()
}
// String returns the text representation of a field.
func (f *Field) String() string {
return f.Name
}
// String returns the text representation of an edge.
func (e *Edge) String() string {
return e.Name
}
// String returns the text representation of a value.
func (v *Value) String() string {
if v == nil {
return "nil"
}
buf, err := json.Marshal(v.V)
if err != nil {
return fmt.Sprint(v.V)
}
return string(buf)
}
func p2expr(ps []P) []Expr {
expr := make([]Expr, len(ps))
for i := range ps {
expr[i] = ps[i]
}
return expr
}
func (*Edge) expr() {}
func (*Field) expr() {}
func (*Value) expr() {}
func (*CallExpr) expr() {}
func (*NaryExpr) expr() {}
func (*UnaryExpr) expr() {}
func (*BinaryExpr) expr() {}

81
entql/entql_test.go Normal file
View File

@@ -0,0 +1,81 @@
// 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 entql_test
import (
"strconv"
"testing"
"entgo.io/ent/entql"
"github.com/stretchr/testify/assert"
)
func TestPString(t *testing.T) {
tests := []struct {
P entql.P
S string
}{
{
P: entql.And(
entql.FieldEQ("name", "a8m"),
entql.FieldIn("org", "fb", "ent"),
),
S: `name == "a8m" && org in ["fb","ent"]`,
},
{
P: entql.Or(
entql.Not(entql.FieldEQ("name", "mashraki")),
entql.FieldIn("org", "fb", "ent"),
),
S: `!(name == "mashraki") || org in ["fb","ent"]`,
},
{
P: entql.HasEdgeWith(
"groups",
entql.HasEdgeWith(
"admins",
entql.Not(entql.FieldEQ("name", "a8m")),
),
),
S: `has_edge(groups, has_edge(admins, !(name == "a8m")))`,
},
{
P: entql.And(
entql.FieldGT("age", 30),
entql.FieldContains("workplace", "fb"),
),
S: `age > 30 && contains(workplace, "fb")`,
},
{
P: entql.Not(entql.FieldLT("score", 32.23)),
S: `!(score < 32.23)`,
},
{
P: entql.And(
entql.FieldNil("active"),
entql.FieldNotNil("name"),
),
S: `active == nil && name != nil`,
},
{
P: entql.Or(
entql.FieldNotIn("id", 1, 2, 3),
entql.FieldHasSuffix("name", "admin"),
),
S: `id not in [1,2,3] || has_suffix(name, "admin")`,
},
{
P: entql.EQ(entql.F("current"), entql.F("total")).Negate(),
S: `!(current == total)`,
},
}
for i := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
s := tests[i].P.String()
assert.Equal(t, tests[i].S, s)
})
}
}

96
entql/internal/gen.go Normal file
View File

@@ -0,0 +1,96 @@
// 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.
// A codegen cmd for generating builder types from template.
package main
import (
"bytes"
"go/format"
"log"
"os"
"strings"
"text/template"
"entgo.io/ent/schema/field"
)
func main() {
buf, err := os.ReadFile("internal/types.tmpl")
if err != nil {
log.Fatal("reading template file:", err)
}
tmpl := template.Must(template.New("types").
Funcs(template.FuncMap{
"ops": ops,
"title": strings.Title,
"ident": ident,
"type": typ,
}).
Parse(string(buf)))
b := &bytes.Buffer{}
if err := tmpl.Execute(b, struct {
Types []field.Type
}{
Types: []field.Type{
field.TypeBool,
field.TypeBytes,
field.TypeTime,
field.TypeUint,
field.TypeUint8,
field.TypeUint16,
field.TypeUint32,
field.TypeUint64,
field.TypeInt,
field.TypeInt8,
field.TypeInt16,
field.TypeInt32,
field.TypeInt64,
field.TypeFloat32,
field.TypeFloat64,
field.TypeString,
field.TypeUUID,
field.TypeOther,
},
}); err != nil {
log.Fatal("executing template:", err)
}
if buf, err = format.Source(b.Bytes()); err != nil {
log.Fatal("formatting output:", err)
}
if err := os.WriteFile("types.go", buf, 0644); err != nil {
log.Fatal("writing go file:", err)
}
}
func ops(t field.Type) []string {
switch t {
case field.TypeBool, field.TypeBytes, field.TypeUUID, field.TypeOther:
return []string{"EQ", "NEQ"}
default:
return []string{"EQ", "NEQ", "LT", "LTE", "GT", "GTE"}
}
}
func ident(t field.Type) string {
switch t {
case field.TypeBytes:
return "bytes"
case field.TypeTime:
return "time"
case field.TypeUUID:
return "value"
case field.TypeOther:
return "other"
default:
return t.String()
}
}
func typ(t field.Type) string {
if t == field.TypeUUID || t == field.TypeOther {
return "driver.Valuer"
}
return t.String()
}

108
entql/internal/types.tmpl Normal file
View File

@@ -0,0 +1,108 @@
{{ define "types" }}
// 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 internal/gen.go, DO NOT EDIT.
package entql
import (
"database/sql/driver"
"time"
)
//go:generate go run internal/gen.go
// Fielder is the interface for creating a predicate (entql.P)
// by a field name from the different builder types below.
type Fielder interface {
Field(string) P
}
{{ range $t := $.Types }}
{{ $titled := ident $t | title }}
{{ $iface := print $titled "P" }}
{{ $builder := print (ident $t) "P" }}
// {{ $iface }} is the interface for predicates of type {{ $t }} (`type P[{{ $t }}]`).
type {{ $iface }} interface {
Fielder
{{ ident $t }}()
}
// {{ $builder }} implements the {{ $iface }} interface.
type {{ $builder }} struct {
P
done func(string)
}
func (p *{{ $builder }}) Field(name string) P {
p.done(name)
return p.P
}
func (*{{ $builder }}) {{ ident $t }}() {}
// {{ $titled }}Nil applies the Nil operation
func {{ $titled }}Nil() {{ $iface }} {
field := &Field{}
done := func(name string) { field.Name = name }
return &{{ $builder }}{P: EQ(field, (*Value)(nil)), done: done}
}
// {{ $titled }}NotNil applies the NotNil operation
func {{ $titled }}NotNil() {{ $iface }} {
field := &Field{}
done := func(name string) { field.Name = name }
return &{{ $builder }}{P: NEQ(field, (*Value)(nil)), done: done}
}
{{ range $op := ops $t }}
// {{ $titled }}{{ $op }} applies the {{ $op }} operation on the given value.
func {{ $titled }}{{ $op }}(v {{ type $t }}) {{ $iface }} {
field := &Field{}
value := &Value{V: v}
done := func(name string) { field.Name = name }
return &{{ $builder }}{P: {{ $op }}(field, value), done: done}
}
{{ end }}
// {{ $titled }}Or returns a composed predicate that represents the logical OR predicate.
func {{ $titled }}Or(x, y {{ $iface }}, z ...{{ $iface }}) {{ $iface }} {
expr := &{{ $builder }}{}
expr.done = func(name string) {
zs := make([]P, len(z))
for i := range z {
zs[i] = z[i].Field(name)
}
expr.P = Or(x.Field(name), y.Field(name), zs...)
}
return expr
}
// {{ $titled }}And returns a composed predicate that represents the logical AND predicate.
func {{ $titled }}And(x, y {{ $iface }}, z ...{{ $iface }}) {{ $iface }} {
expr := &{{ $builder }}{}
expr.done = func(name string) {
zs := make([]P, len(z))
for i := range z {
zs[i] = z[i].Field(name)
}
expr.P = And(x.Field(name), y.Field(name), zs...)
}
return expr
}
// {{ $titled }}Not returns a predicate that represents the logical negation of the given predicate.
func {{ $titled }}Not(x {{ $iface }}) {{ $iface }} {
expr := &{{ $builder }}{}
expr.done = func(name string) {
expr.P = Not(x.Field(name))
}
return expr
}
{{ end }}
{{ end }}

1980
entql/types.go Normal file

File diff suppressed because it is too large Load Diff

84
entql/types_test.go Normal file
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.
package entql
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFielder(t *testing.T) {
tests := []struct {
input Fielder
expected string
}{
{
input: StringEQ("value"),
expected: `field == "value"`,
},
{
input: StringOr(
StringEQ("a"),
StringEQ("b"),
StringEQ("c"),
),
expected: `(field == "a" || field == "b" || field == "c")`,
},
{
input: StringAnd(
StringEQ("a"),
StringNot(
StringOr(
StringEQ("b"),
StringGT("c"),
StringNEQ("d"),
),
),
),
expected: `field == "a" && !((field == "b" || field > "c" || field != "d"))`,
},
{
input: IntGT(1),
expected: `field > 1`,
},
{
input: IntGTE(1),
expected: `field >= 1`,
},
{
input: IntLT(1),
expected: `field < 1`,
},
{
input: IntLTE(1),
expected: `field <= 1`,
},
{
input: IntGT(1),
expected: `field > 1`,
},
{
input: IntNot(IntGTE(1)),
expected: `!(field >= 1)`,
},
{
input: BoolNot(
BoolOr(
BoolEQ(true),
BoolEQ(false),
),
),
expected: `!(field == true || field == false)`,
},
}
for i := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
p := tests[i].input.Field("field")
assert.Equal(t, tests[i].expected, p.String())
})
}
}