entcel

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: MIT Imports: 17 Imported by: 0

README

entcel

Compile CEL filters into entgo SQL predicates.

entcel lets HTTP and API filters use a small, declared CEL subset while your application keeps control over which fields are queryable and how they map to entgo SQL selectors.

Quick Start

schema := entcel.Schema{
	"id":   entcel.Int(entcel.Column("id")),
	"name": entcel.String(entcel.Column("name")),
}

env, err := entcel.NewEnv(schema)
if err != nil {
	return err
}

predicate, err := env.Compile(ctx, `name.contains("leo") && id in [1, 2, 3]`)
if err != nil {
	return err
}

selector := sql.Select().From(sql.Table("users"))
predicate(selector)

query, args := selector.Query()

Supported CEL Subset

Feature CEL examples SQL behavior
Comparisons id == 1, age >= 18, "leo" != name Column comparisons with literal values
Boolean logic active == true && name != "bot", !(deleted_at != null) AND, OR, and NOT predicate groups
Membership id in [1, 2, 3] IN over literal lists
Strings name.contains("leo"), name.startsWith("l"), name.endsWith("o") entgo string predicates using LIKE
Null checks deleted_at == null, deleted_at != null IS NULL and IS NOT NULL
Relation exists exists("packages", "status == 'shipped'") Correlated EXISTS subquery

Relation Exists

Declare relations with a target table, join columns, and a nested schema:

schema := entcel.Schema{
	"tenant_id": entcel.Int(entcel.Column("tenant_id")),
	"packages": entcel.Relation(entcel.RelationConfig{
		Table: "packages",
		Join: entcel.JoinColumns{
			"shipment_id": "shipment_id",
		},
		Schema: entcel.Schema{
			"tracking_number": entcel.String(entcel.Column("tracking_number")),
			"status":          entcel.String(entcel.Column("status")),
		},
	}),
}

predicate, err := env.Compile(ctx,
	`tenant_id == 42 && exists("packages", "status == 'shipped'")`,
)

Relation filters are compiled with the relation schema, so parent fields are not available inside the nested filter.

Converters

Use converters when CEL literal values need to become domain or database values before SQL is built:

schema := entcel.Schema{
	"created_at": entcel.Time(
		entcel.Column("created_at"),
		entcel.Convert(entcel.ConvertTime(time.RFC3339)),
	),
}

Security

Only fields declared in Schema are queryable. Unknown fields fail compilation, and generated SQL arguments go through the entgo SQL builder rather than string concatenation.

Custom PredicateBuilder hooks can add specialized predicates, but they should be deterministic and limited to the current selector. Avoid remote calls, repository lookups, or request-dependent side effects inside predicate builders.

Non-Goals

  • Generic SQL backend support.
  • Query execution.
  • Cross-service field resolution.
  • Expression rewrite fallback for unsupported CEL.
  • Arbitrary CEL runtime evaluation.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrEmptyFilter           = errors.New("empty filter")
	ErrUnknownField          = errors.New("unknown field")
	ErrUnsupportedOperator   = errors.New("unsupported operator")
	ErrUnsupportedExpression = errors.New("unsupported expression")
	ErrInvalidLiteral        = errors.New("invalid literal")
	ErrUnknownRelation       = errors.New("unknown relation")
	ErrInvalidRelationFilter = errors.New("invalid relation filter")
	ErrConvertValue          = errors.New("convert value")
)

Functions

func ConvertInt

func ConvertInt(value any) (any, error)

func ConvertInt64

func ConvertInt64(value any) (any, error)

Types

type ColumnResolver

type ColumnResolver func(*sql.Selector) string

ColumnResolver resolves a field to an entgo SQL column expression for a selector.

type Converter

type Converter func(any) (any, error)

Converter converts a CEL literal into the value used by the SQL predicate.

func ConvertTime

func ConvertTime(layout string) Converter

type Env

type Env struct {
	// contains filtered or unexported fields
}

func NewEnv

func NewEnv(schema Schema, options ...EnvOption) (*Env, error)

func (*Env) Compile

func (env *Env) Compile(ctx context.Context, filter string) (PredicateFunc, error)

type EnvOption

type EnvOption func(*Env)

func AllowEmptyFilter

func AllowEmptyFilter() EnvOption

type Error

type Error struct {
	Kind     error
	Filter   string
	Field    string
	Operator Operator
	Message  string
	Err      error
}

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Field

type Field struct {
	// contains filtered or unexported fields
}

Field describes a queryable CEL field or relation.

func Any

func Any(options ...FieldOption) Field

Any declares a dynamically typed field.

func Bool

func Bool(options ...FieldOption) Field

Bool declares a boolean field.

func Bytes

func Bytes(options ...FieldOption) Field

Bytes declares a bytes field.

func Double

func Double(options ...FieldOption) Field

Double declares a double-precision numeric field.

func Int

func Int(options ...FieldOption) Field

Int declares an integer field.

func Relation

func Relation(config RelationConfig) Field

func String

func String(options ...FieldOption) Field

String declares a string field.

func Time

func Time(options ...FieldOption) Field

Time declares a timestamp field.

type FieldOption

type FieldOption func(*Field)

FieldOption configures a Field constructor.

func Column

func Column(name string) FieldOption

Column maps a field to a selector-qualified SQL column name.

func ColumnExpr

func ColumnExpr(resolver ColumnResolver) FieldOption

ColumnExpr maps a field to a custom SQL column expression.

func Convert

func Convert(converter Converter) FieldOption

Convert configures a field literal converter.

func Predicate

func Predicate(builder PredicateBuilder) FieldOption

Predicate configures a custom SQL predicate builder for a field.

type JoinColumns

type JoinColumns map[string]string

type Operator

type Operator string
const (
	OperatorEQ         Operator = "eq"
	OperatorNEQ        Operator = "neq"
	OperatorLT         Operator = "lt"
	OperatorLTE        Operator = "lte"
	OperatorGT         Operator = "gt"
	OperatorGTE        Operator = "gte"
	OperatorIn         Operator = "in"
	OperatorContains   Operator = "contains"
	OperatorStartsWith Operator = "startsWith"
	OperatorEndsWith   Operator = "endsWith"
)

type PredicateBuilder

type PredicateBuilder func(context.Context, Operator, any, *sql.Selector) error

PredicateBuilder adds a custom SQL predicate for a field and operator.

Predicate builders should be deterministic and limited to the current selector, with no remote calls or repository lookups.

type PredicateFunc

type PredicateFunc func(*sql.Selector)

type RelationConfig

type RelationConfig struct {
	Table  string
	Join   JoinColumns
	Schema Schema
}

type Schema

type Schema map[string]Field

Schema declares the CEL-visible fields and relations that may be compiled.

Directories

Path Synopsis
examples
basic command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL