Skip to content

pgx-contrib/pgxcel

Repository files navigation

pgxcel

Go Reference License Go

pgxcel converts a checked CEL AST into a Postgres WHERE fragment with positional bind placeholders. It is deliberately small: one walker over the standard CEL expression protobuf, a fail-closed identifier allow-list, and time.Time / time.Duration bindings for the timestamp(...) / duration(...) literals.

The package accepts any *cel.Ast regardless of how it was produced. That includes ASTs translated back from an AIP-160 filter via cel.CheckedExprToAst, so the same transpiler powers both direct CEL and AIP filtering on top of Postgres.

Installation

go get github.com/pgx-contrib/pgxcel

Usage

env, _ := cel.NewEnv(
    cel.Variable("name", cel.StringType),
    cel.Variable("age", cel.IntType),
)
ast, iss := env.Compile(`name == "Alice" && age > 30`)
if iss.Err() != nil {
    return iss.Err()
}

columns := map[string]string{
    "name": "users.name",
    "age":  "users.age",
}
where, args, err := pgxcel.Transpile(ast, pgxcel.WithColumns(columns))
// where: ("users"."name" = $1 AND "users"."age" > $2)
// args:  []any{"Alice", int64(30)}

Options

  • pgxcel.WithColumns(map[string]string) — the path → DB-column allow-list. Lookup is fail-closed: any identifier the AST references that is not in the map causes Transpile to return an error. When omitted, every ident in the AST errors. Never feed user input as a column name; the value of each map entry is emitted into the SQL after only identifier quoting.
  • pgxcel.WithFunctions(map[string]string) — alias → canonical function-name map applied before dispatch. Use it to feed in ASTs produced by parsers other than cel-go (for example einride/aip-go emits "=" / "AND" / "NOT" instead of the cel-go operator names). Unknown aliases pass through unchanged.
  • pgxcel.WithParamOffset(int) — the first placeholder number. Defaults to 1. Use a higher value when splicing the fragment into a query that already has bound values.

A nil ast returns ("", nil, nil). An unchecked ast (ast.IsChecked() == false) returns an error.

Operator coverage

CEL Postgres fragment
==, !=, <, <=, >, >= col op $N (or col op col)
&&, || (lhs AND rhs) / (lhs OR rhs)
! (NOT expr)
x in [a, b, c] x IN ($1, $2, $3) (empty → FALSE)
s.contains(x) s LIKE '%' || $N || '%'
s.startsWith(x) s LIKE $N || '%'
s.endsWith(x) s LIKE '%' || $N
s.matches(re) s ~ $N (POSIX regex)
timestamp("2025-01-02T03:04:05Z") $N bound as time.Time
duration("1h30m") $N bound as time.Duration
unary -<literal> bound as signed numeric literal

Development

go test ./...
go vet ./...

License

MIT

About

CEL → PostgreSQL WHERE clause transpiler for pgx. Fail-closed column allow-list, parameterized $N placeholders.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors