Files
ent/entql/entql.go
2022-08-19 18:23:04 +03:00

465 lines
9.8 KiB
Go

// 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() {}