pgcel 0.1.0

PostgreSQL plugin to provide Google's Common Expression Language in queries
docs.rs failed to build pgcel-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

pgcel

A PostgreSQL extension that implements Google's Common Expression Language (CEL) for use in database schemas.

This extension uses:

  • cel-rust - A Rust implementation of CEL
  • pgrx - A framework for building PostgreSQL extensions in Rust

Features

  • celprogram type: A custom PostgreSQL type that stores CEL source for reuse
  • cel_compile: Compile CEL expression strings into celprogram values
  • cel_eval: Evaluate programs against JSON context objects, returning boolean results
  • cel_eval_json: Evaluate programs and return results as JSONB (for non-boolean expressions)
  • In-memory caching: Reuses compiled executables within each backend; shares validation hashes across backends

Installation

Prerequisites

  • Rust (>= 2021 edition)
  • PostgreSQL supported by pgrx and >= 14
  • cargo-pgrx

Build & Install

For PostgreSQL 14 (use the matching version and pg_config for another PostgreSQL release):

cargo install cargo-pgrx --version "0.12.9" --locked
cargo pgrx init --pg14=$(which pg_config)
cd pgcel
cargo pgrx install --release

Usage

Basic Example

CREATE EXTENSION pgcel;

CREATE TABLE example (
    id int PRIMARY KEY,
    program celprogram NOT NULL
);

INSERT INTO example (id, program) VALUES (1, cel_compile('ctx.a.startsWith("hello")'));
INSERT INTO example (id, program) VALUES (2, cel_compile('ctx.a.startsWith("world")'));

SELECT *
FROM example e
WHERE cel_eval(e.program, '{ "a": "hello world!" }');
-- Returns the first row we inserted, but not the other one.

Available Functions

cel_compile(expression text) → celprogram

Compiles a CEL expression string into a celprogram.

SELECT cel_compile('x > 10 && y < 20');

cel_eval(program celprogram, context jsonb) → boolean

Evaluates a CEL program against a JSON context. The context is available as ctx in the expression.

SELECT cel_eval(
    cel_compile('ctx.value > 10'),
    '{"value": 42}'
);
-- Returns: true

cel_eval_json(program celprogram, context jsonb) → jsonb

Evaluates a CEL program and returns the result as JSONB. Useful for expressions that return non-boolean values.

SELECT cel_eval_json(
    cel_compile('ctx.a + ctx.b'),
    '{"a": 1, "b": 2}'
);
-- Returns: 3

CEL Expression Examples

CEL supports a rich expression language:

-- String operations
SELECT cel_compile('ctx.name.startsWith("John")');
SELECT cel_compile('ctx.email.contains("@")');
SELECT cel_compile('ctx.text.size() > 100');

-- Numeric comparisons
SELECT cel_compile('ctx.age >= 18 && ctx.age <= 65');
SELECT cel_compile('ctx.price * ctx.quantity > 1000');

-- List operations
SELECT cel_compile('ctx.roles.exists(r, r == "admin")');
SELECT cel_compile('"premium" in ctx.features');
SELECT cel_compile('ctx.items.size() > 0');

-- Conditional expressions
SELECT cel_compile('ctx.status == "active" ? ctx.score : 0');

-- Boolean logic
SELECT cel_compile('ctx.enabled && (ctx.verified || ctx.admin)');

Architecture

celprogram persists the expression source. At runtime the extension uses three caches:

  1. Backend-local executable LRU: Reuses compiled programs by source until evicted.
  2. Backend-local validation hashes: Avoids revalidating recently seen sources in the same backend.
  3. Shared validation hashes: A fixed-size, 1,024-entry PgLwLock map records validated source hashes across backends; it does not hold executable programs.

Compiled executables cannot be shared across PostgreSQL processes; a validation-hash hit in one backend still requires it to compile if it lacks the local executable.

Cache configuration

pgcel.program_cache_size controls the number of compiled programs retained by each PostgreSQL backend. It defaults to 1024, accepts values from 1 to 65536, and can be set globally or per session:

SET pgcel.program_cache_size = 4096;

Benchmark results

Run on an M2 Macbook Air, 8GB of RAM, MacOS Tahoe 26.2, using PostgreSQL 16 with pgcel.program_cache_size = 1024:

Benchmark Latency Throughput
cel_compile (simple) 0.682 ms 1,467 ops/sec
cel_compile (complex) 0.709 ms 1,411 ops/sec
cel_eval (one cached program, 10,000 contexts) 3.0 µs 331,642 ops/sec
cel_eval_json (cached arithmetic) 3.8 µs 260,213 ops/sec
CEL interpreter evaluation (simple / medium / complex) 1.50 / 1.71 / 1.98 µs —
CEL interpreter, 1,000-context batch 1.60 ms 624k eval/sec

bench.rs measurements exercise cel-interpreter directly; the SQL measurements include the PostgreSQL extension path and report single-backend throughput, not concurrent aggregate capacity or tail latency. The SQL stress script now pairs programs with compatible contexts; its full suite passed against a disposable PostgreSQL 16 cluster. The figures above are from an earlier local run, not the disposable verification run.

bench/run_benchmarks.sh defaults to the pg14 feature; set PG_VERSION=pg16 (or another supported feature) to select a matching pgrx build and managed server. SQL benchmarks use libpq connection variables such as PGHOST and PGPORT; when a connection target is specified, the runner will not start a managed server. Run SQL suites only against a dedicated benchmark database.

To regenerate local logs, run PG_VERSION=pg16 bench/run_benchmarks.sh against a dedicated benchmark database. It writes bench/criterion_results.txt, bench/sql_results.txt, and bench/concurrent_results_*.txt; run psql -v ON_ERROR_STOP=1 -f bench/join_benchmark.sql > bench/join_results.txt 2>&1 separately for the large JOIN workload, also against a dedicated database. The SQL scripts recreate only their own pgcel_stress_bench or pgcel_join_bench schema (erasing prior results there), never drop the extension, and leave the schemas for inspection. Remove them explicitly with DROP SCHEMA pgcel_stress_bench CASCADE; and DROP SCHEMA pgcel_join_bench CASCADE; when finished. These logs are ignored and are not reproducible snapshots; record the PostgreSQL version, hardware, cache setting, and build mode when comparing runs. Use RUN_SQL=false RUN_CONCURRENT=false for a Criterion-only run. Cargo.lock is retained to pin Rust dependencies.

Development

Building

cargo build --release --features pg14

Running Tests

cargo pgrx test pg14  # Adjust for your PG version

With pgcel installed and preloaded on a test server, run bash tests/session_contracts.sh to check SQL source round-tripping, invalid input, the cache GUC, and separate backend sessions. It uses the current libpq connection settings and does not create database objects.

License

MIT License