From c68c4b436dbdd7881c76ef1afa155da6b53fe67c Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 21 May 2026 13:30:37 +1000 Subject: [PATCH 01/93] docs: design encrypted domain type prototype --- ...026-05-12-encrypted-domain-types-design.md | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md diff --git a/docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md b/docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md new file mode 100644 index 00000000..914f8577 --- /dev/null +++ b/docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md @@ -0,0 +1,195 @@ +# High-Level Encrypted Domain Types Prototype Design + +## Context + +EQL currently exposes one public encrypted column type, `public.eql_v2_encrypted`, +implemented as a composite type with a single `jsonb` payload field. Query behavior +is selected dynamically from the encrypted payload terms that are present (`hm`, +`bf`, `ob`, `opf`, `opv`, `sv`, etc.). + +The new goal is to add high-level SQL column types such as `encrypted_text`, +`encrypted_jsonb`, and `encrypted_int4`. These types should make application DDL +clearer and give each plaintext shape a static, predictable SQL operator surface. +They should not rely on the broad dynamic dispatch behavior of +`eql_v2_encrypted`. + +The prototype is intentionally limited to: + +- `public.encrypted_text` +- `public.encrypted_jsonb` +- `public.encrypted_int4` + +Configuration inference, automatic registration, broad type coverage, and +production migration behavior are out of scope for the prototype. The prototype +exists to prove whether `jsonb` domain types can provide a clean client-facing +DDL surface while still producing indexable query plans without operator +classes. + +## History And Spike Findings + +A previous branch tried changing `eql_v2_encrypted` itself from a composite type +to a `jsonb` domain. That PR closed unmerged with failing CI, and there is no +clear written rationale for the failure. Separately, EQL has kept +`public.eql_v2_encrypted` and `public.eql_v2_configuration` outside the +`eql_v2` schema so EQL upgrades can drop and recreate `eql_v2` without +cascading into customer columns. + +A transient SQL spike compared three shapes: + +- domain over raw `jsonb` +- domain over `public.eql_v2_encrypted` +- independent composite type with `(data jsonb)` + +The spike showed that domains over `public.eql_v2_encrypted` are ergonomic and +can use existing helpers, but inherit base EQL operators when exact domain +operators are absent. Independent composites avoid inherited behavior, but need +more casts and exact helper/operator wrappers. + +The approved design is simpler: define the high-level types as domains over +raw `jsonb`, then define exact operators for supported and unsupported +operations. This removes the extra `eql_v2_encrypted` layer from the new public +types. + +## Type Model + +Create public domain types over `jsonb`: + +```sql +CREATE DOMAIN public.encrypted_text AS jsonb; +CREATE DOMAIN public.encrypted_jsonb AS jsonb; +CREATE DOMAIN public.encrypted_int4 AS jsonb; +``` + +The payload remains the existing EQL encrypted JSONB payload. The specific +types do not depend on `public.eql_v2_encrypted` for storage or operator +dispatch. + +Because PostgreSQL domains can fall back to base-type behavior, every public +operation in the supported SQL surface must have an exact domain operator: + +- supported operations delegate to fixed index-term helpers; +- unsupported operations raise a type-specific error. + +This prevents accidental fallback to native `jsonb` semantics for common SQL +operators. + +## Prototype Acceptance Criteria + +The prototype must prove these properties: + +- exact domain operators resolve for supported operations; +- exact blocker operators prevent common unsupported operations from falling + through to native `jsonb` behavior; +- supported hot-path operator functions are inlineable SQL functions with no + `SET search_path` clause; +- bare operator predicates use functional indexes and do not require custom + btree or hash operator classes; +- where existing helper signatures are awkward, temporary typed helper wrappers + are small, `LANGUAGE sql`, immutable, strict, parallel-safe, and inlineable + when used in indexed predicates. + +## Operator Surface + +### `encrypted_text` + +Supported: + +- `=` and `<>`, using the `hm` term through `eql_v2.hmac_256(value::jsonb)` +- `~~` and `~~*`, using the `bf` term through `eql_v2.bloom_filter(value::jsonb)` + +Unsupported blockers: + +- `<`, `<=`, `>`, `>=` +- `@>`, `<@` +- `->`, `->>` + +### `encrypted_int4` + +Supported: + +- `=` and `<>`, using the `hm` term through `eql_v2.hmac_256(value::jsonb)` +- `<`, `<=`, `>`, `>=`, using OPE terms by default through an inlineable + expression over `value::jsonb` + +Unsupported blockers: + +- `~~`, `~~*` +- `@>`, `<@` +- `->`, `->>` + +### `encrypted_jsonb` + +Supported: + +- `=` and `<>`, using the `hm` term through `eql_v2.hmac_256(value::jsonb)` +- `@>` and `<@`, using `sv` through inlineable typed STE vector helpers or + wrappers +- `->` and `->>`, using stubbed or adapted encrypted JSON path helpers for the + domain type + +Unsupported blockers: + +- `<`, `<=`, `>`, `>=` +- `~~`, `~~*` + +## Out Of Scope + +Do not add configuration inference in this prototype. The prototype should not +change `eql_v2.add_column`, `eql_v2.add_search_config`, or the configuration +validation functions. + +Do not add automatic registration or event triggers in this prototype. + +Do not add full support for additional encrypted scalar types in this prototype. +The three selected types are enough to test text, scalar range, and JSONB +operator behavior. + +## Error Handling + +Unsupported exact operators should raise clear errors: + +```text +operator < is not supported for encrypted_text +operator ~~ is not supported for encrypted_int4 +operator -> is not supported for encrypted_int4 +``` + +Missing required encrypted index terms should fail through the fixed helper path +with the existing helper errors, such as missing `hm`, `bf`, `opf`, or `sv`. + +Supported hot-path functions should not raise custom errors for missing terms if +an existing helper already provides a precise missing-term error. + +## Testing + +Add focused SQLx coverage for the first three domain types: + +- Domain creation and assignment from valid encrypted JSONB payloads. +- Supported operators for each type. +- Unsupported operators raise the exact type-specific error instead of falling + through to native `jsonb` behavior. +- Functional indexes engage for supported terms: + - `encrypted_text`: `eql_v2.hmac_256(col::jsonb)`, + `eql_v2.bloom_filter(col::jsonb)` + - `encrypted_int4`: `eql_v2.hmac_256(col::jsonb)`, and an OPE order + expression over `col::jsonb` + - `encrypted_jsonb`: `eql_v2.hmac_256(col::jsonb)`, and a typed STE vector + array helper or overload that accepts `encrypted_jsonb` +- `EXPLAIN` plans show index scans for bare operator predicates such as + `col = rhs`, `col ~~ rhs`, `col < rhs`, and `col @> rhs`. +- The same predicates do not require btree/hash operator classes. +- Prepared statements with domain-typed parameters still resolve to exact + domain operators. + +## Implementation Boundary + +Write the first three type surfaces manually. Do not introduce a generator in +the prototype. Manual SQL keeps the spike easy to audit and +lets tests prove the domain-over-`jsonb` approach before expanding to +`encrypted_int2`, `encrypted_int8`, numeric, floating-point, boolean, date, and +timestamp types. + +Supported operator functions and helper wrappers that appear in indexed +predicates must be SQL-language functions intended for planner inlining. +Unsupported blocker functions can use PL/pgSQL because they are not performance +paths. From c43b5ba59afe776d266c807e293323e0cc173549 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 21 May 2026 13:30:46 +1000 Subject: [PATCH 02/93] feat(bench): fixture and benchmark data-generation foundation Adds the encrypted-int4 fixture generator and benchmark dataset generation tooling: tasks/fixtures/ and tasks/bench.toml, the tests/benchmarks/ harness, the CipherStash Proxy docker-compose used to encrypt fixture data, the int4 fixture install migration, and bench-data test coverage. Registers the new task files in mise.toml and ignores local mise credential overrides. --- .gitignore | 4 + mise.toml | 2 +- tasks/bench.toml | 64 ++++++++++++++ tasks/fixtures.toml | 46 ++++++++++ tasks/fixtures/_generate_common.sh | 69 +++++++++++++++ tasks/fixtures/encrypted_int4_schema.sql | 30 +++++++ tasks/fixtures/generate_encrypted_int4.sh | 83 +++++++++++++++++++ tests/benchmarks/.env.example | 7 ++ tests/benchmarks/.gitignore | 6 ++ tests/benchmarks/README.md | 37 +++++++++ tests/benchmarks/docker-compose.yml | 54 ++++++++++++ tests/benchmarks/generate.sh | 77 +++++++++++++++++ tests/benchmarks/reports/.gitkeep | 0 tests/benchmarks/schema.sql | 81 ++++++++++++++++++ tests/docker-compose.proxy.yml | 35 ++++++++ .../009_install_encrypted_int4_fixture.sql | 29 +++++++ tests/sqlx/tests/bench_data_tests.rs | 24 ++++++ 17 files changed, 647 insertions(+), 1 deletion(-) create mode 100644 tasks/bench.toml create mode 100644 tasks/fixtures.toml create mode 100644 tasks/fixtures/_generate_common.sh create mode 100644 tasks/fixtures/encrypted_int4_schema.sql create mode 100755 tasks/fixtures/generate_encrypted_int4.sh create mode 100644 tests/benchmarks/.env.example create mode 100644 tests/benchmarks/.gitignore create mode 100644 tests/benchmarks/README.md create mode 100644 tests/benchmarks/docker-compose.yml create mode 100755 tests/benchmarks/generate.sh create mode 100644 tests/benchmarks/reports/.gitkeep create mode 100644 tests/benchmarks/schema.sql create mode 100644 tests/docker-compose.proxy.yml create mode 100644 tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql diff --git a/.gitignore b/.gitignore index d9a13a2e..7c1b67f2 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,10 @@ web_modules/ .env.local .envrc +# Local mise overrides (CipherStash credentials, etc.) +mise.local.toml +.mise.local.toml + # parcel-bundler cache (https://parceljs.org/) .parcel-cache diff --git a/mise.toml b/mise.toml index fbf499b4..6fd0b7c6 100644 --- a/mise.toml +++ b/mise.toml @@ -14,7 +14,7 @@ "python" = "3.13" [task_config] -includes = ["tasks", "tasks/postgres.toml"] +includes = ["tasks", "tasks/postgres.toml", "tasks/bench.toml", "tasks/fixtures.toml"] [env] POSTGRES_DB = "cipherstash" diff --git a/tasks/bench.toml b/tasks/bench.toml new file mode 100644 index 00000000..72abc487 --- /dev/null +++ b/tasks/bench.toml @@ -0,0 +1,64 @@ +["bench:up"] +description = "Start Postgres + Proxy for benchmark data generation" +dir = "{{config_root}}" +run = """ +if [ ! -f tests/benchmarks/.env ]; then + echo "ERROR: tests/benchmarks/.env missing. Copy .env.example and fill in credentials." >&2 + exit 1 +fi +docker compose --env-file tests/benchmarks/.env -f tests/benchmarks/docker-compose.yml up -d +export PGPASSWORD="password" +echo "Waiting for bench-postgres on localhost:7433..." +for i in $(seq 1 60); do + if psql -U cipherstash -d cipherstash -h localhost -p 7433 -c 'SELECT 1' >/dev/null 2>&1; then + echo "bench-postgres ready." + break + fi + sleep 1 + if [ "$i" -eq 60 ]; then + echo "bench-postgres did not become ready in 60s." + echo + echo '=== bench-postgres logs ===' + docker logs bench-postgres 2>&1 | tail -40 + exit 1 + fi +done + +echo "Waiting for bench-proxy on localhost:6433..." +for i in $(seq 1 60); do + if psql -U cipherstash -d cipherstash -h localhost -p 6433 -c 'SELECT 1' >/dev/null 2>&1; then + echo "bench-proxy ready." + exit 0 + fi + sleep 1 +done +echo "bench-proxy did not become ready in 60s." +echo +echo '=== bench-proxy logs ===' +docker logs bench-proxy 2>&1 | tail -40 +exit 1 +""" + +["bench:down"] +description = "Stop benchmark Postgres + Proxy" +dir = "{{config_root}}" +run = """ +docker compose -f tests/benchmarks/docker-compose.yml down -v +""" + +["bench:generate"] +description = "Generate 100K encrypted bench dataset (requires bench:up first)" +# `build` produces release/cipherstash-encrypt.sql, which generate.sh +# installs into the bench Postgres container before applying schema.sql. +depends = ["build"] +dir = "{{config_root}}" +run = """ +tests/benchmarks/generate.sh 100k +""" + +["bench:full"] +description = "Run committed SQLx bench/regression suite" +dir = "{{config_root}}" +run = """ +mise run --output prefix test:bench +""" diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml new file mode 100644 index 00000000..00279880 --- /dev/null +++ b/tasks/fixtures.toml @@ -0,0 +1,46 @@ +["proxy:up"] +description = "Start CipherStash Proxy connected to existing Postgres" +# Reuses the tests/docker-compose.yml Postgres on POSTGRES_PORT. +# CS_* credentials are read from the shell environment (mise/direnv/profile). +# Readiness is verified from the host (the proxy image lacks busybox nc, so +# the container-internal healthcheck cannot be used). Dumps container logs +# on failure so the user does not need a separate `docker logs` invocation. +dir = "{{config_root}}/tests" +run = """ +docker compose -f docker-compose.proxy.yml up -d +echo "Waiting for proxy on localhost:6432..." +export PGPASSWORD="${POSTGRES_PASSWORD:-password}" +for i in $(seq 1 60); do + if psql -U "${POSTGRES_USER:-cipherstash}" -d "${POSTGRES_DB:-cipherstash}" \ + -h localhost -p 6432 -c 'SELECT 1' >/dev/null 2>&1; then + echo "Proxy ready." + exit 0 + fi + sleep 1 +done +echo "Proxy did not become ready in 60s." +echo +echo '=== cipherstash-proxy logs ===' +docker logs cipherstash-proxy 2>&1 | tail -40 +exit 1 +""" + +["proxy:logs"] +description = "Tail CipherStash Proxy container logs" +dir = "{{config_root}}/tests" +run = "docker logs --tail 100 -f cipherstash-proxy" + +["proxy:down"] +description = "Stop CipherStash Proxy" +dir = "{{config_root}}/tests" +run = "docker compose -f docker-compose.proxy.yml down" + +["fixture:int:generate"] +description = "Generate encrypted_int4 fixture (009) via Proxy" +# Prerequisites: +# - mise run postgres:up (existing Postgres on POSTGRES_PORT) +# - mise run reset (ensures EQL is installed in that Postgres) +# - mise run proxy:up (Proxy on localhost:6432) +depends = ["build"] +dir = "{{config_root}}" +run = "tasks/fixtures/generate_encrypted_int4.sh" diff --git a/tasks/fixtures/_generate_common.sh b/tasks/fixtures/_generate_common.sh new file mode 100644 index 00000000..0d666314 --- /dev/null +++ b/tasks/fixtures/_generate_common.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Common helpers for fixture generators. Sourced — not executed directly. +# Sets PG_URL / PROXY_URL and exposes restart_proxy_and_wait + dump_fixture_table. + +# Resolve Postgres / Proxy connection from mise [env] (POSTGRES_*) with the +# usual defaults. PROXY_PORT comes from tests/docker-compose.proxy.yml. +PG_USER="${POSTGRES_USER:-cipherstash}" +PG_PASSWORD="${POSTGRES_PASSWORD:-password}" +PG_DB="${POSTGRES_DB:-cipherstash}" +PG_HOST="${POSTGRES_HOST:-localhost}" +PG_PORT="${POSTGRES_PORT:-7432}" +PROXY_PORT="${PROXY_PORT:-6432}" + +PG_URL="postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/${PG_DB}" +PROXY_URL="postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PROXY_PORT}/${PG_DB}" + +export PGPASSWORD="$PG_PASSWORD" + +# Proxy caches its encrypt config at connection-handler init time, so any +# add_search_config call applied AFTER Proxy started won't take effect +# until Proxy reconnects. Restart and wait for it to come back. +restart_proxy_and_wait() { + echo "==> Restarting Proxy so it reloads the new encrypt config" + docker restart cipherstash-proxy >/dev/null + + for i in $(seq 1 60); do + if psql "$PROXY_URL" -c 'SELECT 1' >/dev/null 2>&1; then + echo " Proxy ready." + return 0 + fi + sleep 1 + done + + echo "ERROR: Proxy did not come back up after restart" >&2 + docker logs cipherstash-proxy 2>&1 | tail -20 + return 1 +} + +# Render fixture rows as INSERT statements using format(%L). Caller supplies: +# $1 = source table name (e.g. bench_text) +# $2 = destination table name in the migration (e.g. encrypted_text_plaintext) +# $3 = comma-separated source-column projection +# (e.g. "id, plaintext, (encrypted_text).data::text") +# $4 = comma-separated destination column types for format() placeholders +# (e.g. "%L, %L, %L::jsonb") +# $5 = destination column-name tuple +# (e.g. "(id, plaintext, payload)") +# $6 = output path +# +# The migration is written with a DROP / CREATE preamble plus the rendered +# INSERT statements. The CREATE statement must be supplied by the caller via +# stdin BEFORE calling this function; see how each generator pipes it in. +dump_fixture_table() { + local src_table="$1" + local dst_table="$2" + local src_projection="$3" + local fmt_placeholders="$4" + local dst_columns="$5" + local output_path="$6" + + psql "$PG_URL" -v ON_ERROR_STOP=1 -t -A -c " +SELECT format( + 'INSERT INTO ${dst_table} ${dst_columns} VALUES (${fmt_placeholders});', + ${src_projection} +) +FROM ${src_table} +ORDER BY id; +" >> "$output_path" +} diff --git a/tasks/fixtures/encrypted_int4_schema.sql b/tasks/fixtures/encrypted_int4_schema.sql new file mode 100644 index 00000000..5d870590 --- /dev/null +++ b/tasks/fixtures/encrypted_int4_schema.sql @@ -0,0 +1,30 @@ +-- Schema for the encrypted_int4 plaintext-paired fixture. +-- Applied by tasks/fixtures/generate_encrypted_int4.sh; the generator +-- restarts Proxy afterwards so it reloads the new encrypt config. + +DROP TABLE IF EXISTS bench_int4; + +CREATE TABLE bench_int4 ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + plaintext INTEGER NOT NULL, + encrypted_int4 eql_v2_encrypted +); + +-- Idempotency: drop any prior bench_int4 search-config rows so re-running +-- the generator doesn't error with "unique index exists for column". +SELECT eql_v2.remove_search_config('bench_int4', 'encrypted_int4', 'unique') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench_int4,encrypted_int4,indexes,unique}' IS NOT NULL + ); +SELECT eql_v2.remove_search_config('bench_int4', 'encrypted_int4', 'ore') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench_int4,encrypted_int4,indexes,ore}' IS NOT NULL + ); + +-- unique → HMAC (drives =, <>); ore → OPE bytes (drives <, <=, >, >=). +SELECT eql_v2.add_search_config('bench_int4', 'encrypted_int4', 'unique', 'int'); +SELECT eql_v2.add_search_config('bench_int4', 'encrypted_int4', 'ore', 'int'); diff --git a/tasks/fixtures/generate_encrypted_int4.sh b/tasks/fixtures/generate_encrypted_int4.sh new file mode 100755 index 00000000..5662845a --- /dev/null +++ b/tasks/fixtures/generate_encrypted_int4.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Generates an encrypted_int4 fixture by running an integer value set +# through CipherStash Proxy and dumping the resulting (id, plaintext, +# payload jsonb) rows as a SQLx migration. +# +# Prerequisites: +# - mise run postgres:up +# - EQL installed (e.g. via mise run reset + psql -f release/cipherstash-encrypt.sql) +# - mise run proxy:up (Proxy on localhost:6432) +# - mise run build (produces release/cipherstash-encrypt.sql) +# +# Output: +# tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCHEMA_SQL="$SCRIPT_DIR/encrypted_int4_schema.sql" +OUTPUT="$REPO_ROOT/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql" + +# shellcheck source=_generate_common.sh +. "$SCRIPT_DIR/_generate_common.sh" + +if [ ! -f "$SCHEMA_SQL" ]; then + echo "ERROR: $SCHEMA_SQL not found." >&2 + exit 1 +fi + +# 14 values: includes negatives (boundary), small/medium/large/extreme. +# Chosen so range pivots produce distinct cardinalities — see plan in +# docs/superpowers/plans/. +VALUES=(-100 -1 1 2 5 10 17 25 42 50 100 250 1000 9999) +ROW_COUNT=${#VALUES[@]} + +echo "==> Applying fixture schema (drops + recreates bench_int4)" +psql "$PG_URL" -v ON_ERROR_STOP=1 -f "$SCHEMA_SQL" + +restart_proxy_and_wait + +echo "==> Inserting $ROW_COUNT integers through Proxy (encrypts encrypted_int4)" +# Proxy's eql-mapper cannot unify negative integer literals (parsed as +# UnaryOp(Minus, ...)) with the EQL int column when sent via the simple +# query protocol. Send each value over the extended protocol via psql's +# \bind meta-command so the parameter type is communicated as a binary +# int4 instead of being inferred from SQL surface syntax. +INSERT_SQL=$(mktemp) +trap 'rm -f "$INSERT_SQL"' EXIT +for v in "${VALUES[@]}"; do + # Use literal $1/$2 as bind placeholders; \bind supplies their values. + # \g executes the buffered statement; \bind discards bindings after \g. + printf 'INSERT INTO bench_int4 (plaintext, encrypted_int4) VALUES ($1, $2) \\bind %s %s \\g\n' "$v" "$v" >> "$INSERT_SQL" +done +psql "$PROXY_URL" -v ON_ERROR_STOP=1 -f "$INSERT_SQL" >/dev/null + +echo "==> Dumping $ROW_COUNT rows to $OUTPUT" +cat > "$OUTPUT" <
Done. Wrote $ROW_COUNT rows to $OUTPUT" diff --git a/tests/benchmarks/.env.example b/tests/benchmarks/.env.example new file mode 100644 index 00000000..fe41909a --- /dev/null +++ b/tests/benchmarks/.env.example @@ -0,0 +1,7 @@ +# CipherStash Proxy credentials +# Get these from https://dashboard.cipherstash.com +CS_CLIENT_ACCESS_KEY= +CS_DEFAULT_KEYSET_ID= +CS_CLIENT_KEY= +CS_CLIENT_ID= +CS_WORKSPACE_CRN= diff --git a/tests/benchmarks/.gitignore b/tests/benchmarks/.gitignore new file mode 100644 index 00000000..9e7d7623 --- /dev/null +++ b/tests/benchmarks/.gitignore @@ -0,0 +1,6 @@ +# Generated reports (too large for git, regenerated on demand) +reports/* +!reports/.gitkeep + +# Local Proxy credentials +.env diff --git a/tests/benchmarks/README.md b/tests/benchmarks/README.md new file mode 100644 index 00000000..69087bdb --- /dev/null +++ b/tests/benchmarks/README.md @@ -0,0 +1,37 @@ +# Benchmark Utilities + +This directory contains the Dockerized support stack for generating a 100K-row +encrypted benchmark dataset through CipherStash Proxy. + +The committed automated benchmark coverage lives in the SQLx bench/regression +suite (`mise run test:bench`). `mise run bench:full` is a convenience wrapper +around that existing suite; it does not consume the 100K Docker dataset. + +## Local usage + +```bash +# Populate credentials for the Dockerized Proxy +cp tests/benchmarks/.env.example tests/benchmarks/.env +# Edit .env with your CipherStash credentials + +# Start bench-postgres + bench-proxy and wait for host-side readiness checks +mise run bench:up + +# Build EQL and generate the 100K encrypted dataset in bench-postgres +mise run bench:generate + +# Run the committed SQLx bench/regression suite (10K fixture-based) +mise run bench:full + +# Tear down the Dockerized benchmark stack when finished +mise run bench:down +``` + +## What each task does + +- `bench:up` starts `bench-postgres` and `bench-proxy`, then probes them from + the host with `psql`. +- `bench:generate` installs the built EQL SQL into `bench-postgres`, applies + `schema.sql`, and inserts 100K plaintext rows through Proxy on `localhost:6433`. +- `bench:full` delegates to `mise run test:bench`, which runs the committed + SQLx benchmark/regression suite against the normal local test database. diff --git a/tests/benchmarks/docker-compose.yml b/tests/benchmarks/docker-compose.yml new file mode 100644 index 00000000..d67aca7c --- /dev/null +++ b/tests/benchmarks/docker-compose.yml @@ -0,0 +1,54 @@ +services: + postgres: + image: postgres:17 + container_name: bench-postgres + command: > + postgres + -c track_functions=all + -c shared_preload_libraries=pg_stat_statements + -c pg_stat_statements.track=all + -c pg_stat_statements.max=10000 + ports: + - "7433:5432" + environment: + POSTGRES_DB: cipherstash + POSTGRES_USER: cipherstash + POSTGRES_PASSWORD: password + healthcheck: + test: ["CMD-SHELL", "pg_isready -U cipherstash"] + interval: 1s + timeout: 5s + retries: 10 + networks: + - bench + + proxy: + image: cipherstash/proxy:latest + container_name: bench-proxy + ports: + - "6433:6432" + environment: + CS_DATABASE__NAME: cipherstash + CS_DATABASE__USERNAME: cipherstash + CS_DATABASE__PASSWORD: password + CS_DATABASE__HOST: postgres + CS_DATABASE__PORT: 5432 + # EQL install is performed explicitly by generate.sh before schema.sql runs. + # Leaving Proxy's own install off avoids racing against generate.sh. + CS_DATABASE__INSTALL_EQL: "false" + CS_CLIENT_ACCESS_KEY: ${CS_CLIENT_ACCESS_KEY} + CS_DEFAULT_KEYSET_ID: ${CS_DEFAULT_KEYSET_ID} + CS_CLIENT_KEY: ${CS_CLIENT_KEY} + CS_CLIENT_ID: ${CS_CLIENT_ID} + CS_WORKSPACE_CRN: ${CS_WORKSPACE_CRN} + depends_on: + postgres: + condition: service_healthy + networks: + - bench + # No in-container healthcheck: the current cipherstash/proxy image does + # not ship `nc`, so readiness is verified from the host by `bench:up` + # using `psql` against localhost:6433. +networks: + bench: + driver: bridge diff --git a/tests/benchmarks/generate.sh b/tests/benchmarks/generate.sh new file mode 100755 index 00000000..595e8d10 --- /dev/null +++ b/tests/benchmarks/generate.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Generates a 100K-row encrypted bench dataset via CipherStash Proxy. +# No dump is written in v1 — the Tier 2 workflow regenerates fresh each run. +# +# Prerequisites: +# - mise run build (produces release/cipherstash-encrypt.sql) +# - docker compose -f tests/benchmarks/docker-compose.yml up -d --wait +# - tests/benchmarks/.env populated with CipherStash credentials + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +EQL_SQL="$REPO_ROOT/release/cipherstash-encrypt.sql" +SCALE="${1:-100k}" + +case "$SCALE" in + 100k) ROWS=100000 ;; + *) echo "Unsupported scale: $SCALE (only 100k in v1)" >&2; exit 1 ;; +esac + +if [ ! -f "$EQL_SQL" ]; then + echo "ERROR: $EQL_SQL not found. Run 'mise run build' first." >&2 + exit 1 +fi + +PG_URL="postgresql://cipherstash:password@localhost:7433/cipherstash" +PROXY_URL="postgresql://cipherstash:password@localhost:6433/cipherstash" + +echo "==> Installing EQL into bench-postgres" +psql "$PG_URL" -v ON_ERROR_STOP=1 -f "$EQL_SQL" >/dev/null + +echo "==> Applying bench schema and Proxy search configuration" +psql "$PG_URL" -v ON_ERROR_STOP=1 -f "$SCRIPT_DIR/schema.sql" + +# Proxy caches the encrypt config at connection-handler init. add_search_config +# in schema.sql writes the new config but the Proxy will keep running in +# PASSTHROUGH MODE (inserts pass through unencrypted) until it reconnects. +# Restart and wait for it to come back before driving the INSERT. +echo "==> Restarting bench-proxy so it reloads the new encrypt config" +docker restart bench-proxy >/dev/null +for i in $(seq 1 60); do + if psql "$PROXY_URL" -c 'SELECT 1' >/dev/null 2>&1; then + echo " Proxy ready." + break + fi + sleep 1 + if [ "$i" -eq 60 ]; then + echo "ERROR: bench-proxy did not come back up after restart" >&2 + docker logs bench-proxy 2>&1 | tail -20 + exit 1 + fi +done + +echo "==> Inserting $ROWS plaintext rows through Proxy (this encrypts them)" +# generate_series emits plaintext rows; Proxy intercepts and encrypts each +# column per the search config applied in schema.sql. +psql "$PROXY_URL" -v ON_ERROR_STOP=1 -c " +INSERT INTO bench (encrypted_text, encrypted_int, encrypted_bigint) +SELECT + ('text_' || (((gs - 1) % 1000) + 1))::text, + (((gs - 1) % 1000) + 1)::int, + (((gs - 1) % 1000) + 1)::bigint * 1000000000 +FROM generate_series(1, $ROWS) AS gs; +" + +echo "==> Creating indexes and running ANALYZE" +psql "$PG_URL" -v ON_ERROR_STOP=1 -c " +CREATE INDEX IF NOT EXISTS bench_text_hmac_idx ON bench USING hash (eql_v2.hmac_256(encrypted_text)); +CREATE INDEX IF NOT EXISTS bench_text_ore_idx ON bench USING btree (encrypted_text eql_v2.encrypted_operator_class); +CREATE INDEX IF NOT EXISTS bench_int_ore_idx ON bench USING btree (encrypted_int eql_v2.encrypted_operator_class); +CREATE INDEX IF NOT EXISTS bench_bigint_ore_idx ON bench USING btree (encrypted_bigint eql_v2.encrypted_operator_class); +CREATE INDEX IF NOT EXISTS bench_text_bloom_idx ON bench USING gin (eql_v2.bloom_filter(encrypted_text)); +ANALYZE bench; +" + +echo "==> Done. Rows: $ROWS" diff --git a/tests/benchmarks/reports/.gitkeep b/tests/benchmarks/reports/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/benchmarks/schema.sql b/tests/benchmarks/schema.sql new file mode 100644 index 00000000..88bb7be4 --- /dev/null +++ b/tests/benchmarks/schema.sql @@ -0,0 +1,81 @@ +-- Bench schema for Tier 2 benchmarks. +-- Applied against the bench-postgres container AFTER EQL has been explicitly +-- installed by generate.sh (see Task 4 — generate.sh installs +-- release/cipherstash-encrypt.sql directly, not relying on Proxy's async install). + +DROP TABLE IF EXISTS bench; + +CREATE TABLE bench ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + encrypted_text eql_v2_encrypted, + encrypted_int eql_v2_encrypted, + encrypted_bigint eql_v2_encrypted +); + +-- Idempotency: clear any prior bench search-config rows so re-running the +-- generator against the same container doesn't error with "... index exists +-- for column". EQL uninstall drops the schema but not public config rows. +SELECT eql_v2.remove_search_config('bench', 'encrypted_text', 'unique') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench,encrypted_text,indexes,unique}' IS NOT NULL + ); +SELECT eql_v2.remove_search_config('bench', 'encrypted_text', 'match') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench,encrypted_text,indexes,match}' IS NOT NULL + ); +SELECT eql_v2.remove_search_config('bench', 'encrypted_text', 'ore') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench,encrypted_text,indexes,ore}' IS NOT NULL + ); +SELECT eql_v2.remove_search_config('bench', 'encrypted_int', 'unique') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench,encrypted_int,indexes,unique}' IS NOT NULL + ); +SELECT eql_v2.remove_search_config('bench', 'encrypted_int', 'ore') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench,encrypted_int,indexes,ore}' IS NOT NULL + ); +SELECT eql_v2.remove_search_config('bench', 'encrypted_bigint', 'unique') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench,encrypted_bigint,indexes,unique}' IS NOT NULL + ); +SELECT eql_v2.remove_search_config('bench', 'encrypted_bigint', 'ore') + WHERE EXISTS ( + SELECT 1 + FROM public.eql_v2_configuration c + WHERE c.data #> '{tables,bench,encrypted_bigint,indexes,ore}' IS NOT NULL + ); + +-- Proxy search configuration: tells Proxy which index terms to generate +-- for each column when plaintext is inserted. +-- +-- Signature: eql_v2.add_search_config(table, column, index, cast_as) +-- (see src/config/functions.sql). add_search_config calls activate_config +-- internally when migrating=false, so no explicit activate_config call. + +-- text column: equality (hmac), pattern match (bloom), ordering (ore) +SELECT eql_v2.add_search_config('bench', 'encrypted_text', 'unique', 'text'); +SELECT eql_v2.add_search_config('bench', 'encrypted_text', 'match', 'text'); +SELECT eql_v2.add_search_config('bench', 'encrypted_text', 'ore', 'text'); + +-- integer column: equality + ORE range/ordering +SELECT eql_v2.add_search_config('bench', 'encrypted_int', 'unique', 'int'); +SELECT eql_v2.add_search_config('bench', 'encrypted_int', 'ore', 'int'); + +-- bigint column: equality + ORE range/ordering +SELECT eql_v2.add_search_config('bench', 'encrypted_bigint', 'unique', 'big_int'); +SELECT eql_v2.add_search_config('bench', 'encrypted_bigint', 'ore', 'big_int'); + +-- Indexes (created after data load in generate.sh, after ANALYZE) diff --git a/tests/docker-compose.proxy.yml b/tests/docker-compose.proxy.yml new file mode 100644 index 00000000..d64e6a8e --- /dev/null +++ b/tests/docker-compose.proxy.yml @@ -0,0 +1,35 @@ +services: + proxy: + image: cipherstash/proxy:latest + container_name: cipherstash-proxy + ports: + - "6432:6432" + environment: + # Proxy connects to the existing tests/docker-compose.yml Postgres, + # reaching the host via host.docker.internal. POSTGRES_* values come + # from mise.toml [env] block (overridable per shell). + CS_DATABASE__NAME: ${POSTGRES_DB:-cipherstash} + CS_DATABASE__USERNAME: ${POSTGRES_USER:-cipherstash} + CS_DATABASE__PASSWORD: ${POSTGRES_PASSWORD:-password} + CS_DATABASE__HOST: host.docker.internal + CS_DATABASE__PORT: ${POSTGRES_PORT:-7432} + # EQL installation is handled by the existing reset / install flow; the + # Proxy must not race against it. + CS_DATABASE__INSTALL_EQL: "false" + # CipherStash workspace credentials are read from the host shell + # environment (mise / direnv / profile). No .env file is required. + CS_CLIENT_ACCESS_KEY: ${CS_CLIENT_ACCESS_KEY} + CS_DEFAULT_KEYSET_ID: ${CS_DEFAULT_KEYSET_ID} + CS_CLIENT_KEY: ${CS_CLIENT_KEY} + CS_CLIENT_ID: ${CS_CLIENT_ID} + CS_WORKSPACE_CRN: ${CS_WORKSPACE_CRN} + # Optional: pin the CTS region host for workspaces in non-default regions. + CS_CTS_HOST: ${CS_CTS_HOST:-} + CS_ZEROKMS_HOST: ${CS_ZEROKMS_HOST:-} + extra_hosts: + # Linux compatibility; macOS / Windows resolve host.docker.internal natively. + - "host.docker.internal:host-gateway" + # No in-container healthcheck: the current cipherstash/proxy:latest image + # lacks busybox nc, so any TCP probe inside the container fails even when + # the proxy is listening. Readiness is verified from the host by the + # proxy:up task using psql. diff --git a/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql b/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql new file mode 100644 index 00000000..7556d9e6 --- /dev/null +++ b/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql @@ -0,0 +1,29 @@ +-- AUTO-GENERATED by tasks/fixtures/generate_encrypted_int4.sh +-- DO NOT EDIT BY HAND. Re-run the generator to refresh. +-- +-- Source: 14-value integer set defined inline in the generator. +-- Produced via CipherStash Proxy (HMAC + OPE terms). +-- Used by encrypted_int4 domain SQLx fixture tests. + +DROP TABLE IF EXISTS encrypted_int4_plaintext; + +CREATE TABLE encrypted_int4_plaintext ( + id BIGINT PRIMARY KEY, + plaintext INTEGER NOT NULL, + payload JSONB NOT NULL +); + +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('1', '-100', '{"c": "mBbKSl1A>nJLHvy{R_+!y+r237Oz{OOyZusRU1%=^N29ellMsHtFke~AjFeV1#(=nP!7;6lb4dK$exS)cVyEoXZr0(3=TevwytGvV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "c8faf5849bba756007c19df73eb704aa640dea7eec353af533b4502cc354640d", "ob": ["a1a1a1a15481492c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fef2c4558f8c98caf3d95b2a84cfd8d1bf34d597f6b0c7cec23413e27dcb5fea3fa07832f2566f859ed23a16910615e5364d4ac48ae66b56b98836c956171ab4d6cf6eacaa0b1c7b5bae407558a7751d01a3312a765d38eabd8f55815ff310aeab71a3e802ad78088941fb5dbf1867d548b10969184d3a56b71bcbae6f31cec20585879ed7da7174b4bbf9d60080b5ba9ecd38473e20631bbfb4d4883fca75cdf7e4727c85f669e3bc8981fa2d7b6d2309793c86ccdb1fa1fb5e352b4298b138bdee80b603ca95ced5a3a6788048b8922b7f679a524270f36e44ba6006303b494e1e3b6efb9878ce9da67942d1b7c5e1d2ff8d337be041c00f85317c66144171419aa7577d5a717d569c0d5dfe9fa5d52bdf449fa4043a4258c98514eb81abda3b05213f1d096f8e63f7ed72fc7d8f7d88464484bc8d75acc26688770b3f68d699383a92037bc5b015f723aa4a0a7d9073"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('2', '-1', '{"c": "mBbLfH%e(Q?p_s?bNQ{<#{uC8TnV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "f6bde997370f33b54e4ca61473609b197879c18e5197548176518b714b119146", "ob": ["a1a1a1a15481495d65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fef2c4558f8c98caf3d95b2a84cfd8d1bf34d597f6b0c7cec23413e27dcb5fea3fa07832f2566f859ed23a16910615e5365295862e7ec0da68e2a3decc06d1d557b6922aae88582b92f00da5cf7b0e22fef74f1ed895c9164c470aea01637f1d726adaa19b9413d51b2e80f9e6c9fdc1f74a576aabbd6f183d9c6de23082f281d85a5dacf3c5aa719d1ad4a8f22aeb281587ea04465e7d99b73daa382f44af6cc6b300698cdd517f908f618cea3c3733ecccdaf649b4cf9355a9d27bc81c64563e6f177b8755b3aff1bd4549d0e572fe44b752fb8d0e96fd76a5be31a4945ed2ceda86d928aba563485661bcd2ea533396be44c4cd918a5e7eef8dfbb10982ed3eaa49b35036ef673a21ff39e26fe62860d8219ee9aa001e094ce1131ebe5aaef33f8e933a9eb6003c4b9a656043a9cd8723d60174e429f998f37e7317f27a05b68ecbb3c6a4944f062875b85d2d7131da"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('3', '1', '{"c": "mBbLYhRor}6CjJ;AsHY+(?Ng47K{2;`qaGoX)GjDl0Y0;`k@_)eA52JATQHQY^bSRimoI>d;+&&X6|8PG@TT7Dsa8|UCWFmT&`tqV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "e166c0aac72bd6715d32ef1601aa5333531ead4620a5f29da2c1de3488f4c567", "ob": ["a1a1a1a1964545e965cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5af190366875ddb8de5697963484becd5ebb1925d11d89ea7ec7949dc519e7596d9091bf1241fafbc5da30c777b671912ce3e0c372c0ed63fd8499fee2dd5e3934ac2f0f6c4f84a698d76e0fa3a60c19f48dd672705751068bdb2846bd092f5122b1260d9e42cefc95d66b9e2376e6b0a982046952cea11611dbc2a12aac49f2111095cdbe647100a9959bd720d03477ed4fd27e062f65d7b7fdbc9a44d2033563b603a4f3cfe1fcda82cf1730c40f01d92141f4611d6b00dfb08298a41533f7369965867af4a9fba4dbaba1467a2e3b923f970a9c08370b45ae608445f8cab67f1085291ebbf194435b0606b49e8771eb5cfc20cb51c79ff273a77b730fc35fc07c62bb21330260a44e3dc132b19cc7e7874643ffc27ccbb41375abe6c302b009"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('4', '2', '{"c": "mBbM0cqpOq3a!Xis`V0^%ic<-7|Nd29<6#ESv$eRAST6LAHL`!o;_oQUU9dZbi{(cCVQ)i*P2|4WORRB5UyozV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "84507797349e30049201af1f46c5e2e47f4660d18726716a0b56427c0b9a021b", "ob": ["a1a1a1a19645458b65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a513c8a4a70ef54eb8ba541efae0e03dd2fe9264d04a5ef27b7da6eb22e3e902476b6a1bb794b88bae7a12f5341b8c5ea582a5b9d244cd2e6a72e1bc9dca08af01b426d7a918cf1b3f809b957b3083b2d4d93d296c038b17bbd3bdc956a2f97d9791368528ddcc6d87c5b1f8a34f54662ea8f3f8d24ebf67ff186e3ed7ac6e03a94132f7d4e7d7061a53d8013aa36caa74bc98dda9106ad9cf37cfed6a7ff7f8e1270bc9d413251eb1f43ed53fc5a15dfa9d53a8e79478de5a9447c5135d8b8325a5fe397fdb796630bc2fa5e06144fdbd49a576a4833bfd0fc1c89246c9fc807064d10bc3b5d18d3d1f1e07fadb1bd719db0310aa8573a5e7baf1e5fa3d4e791313c225c47cf39089e70d71ee1801c10f278b37eb23afebe904e4f89953d4a64"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('5', '5', '{"c": "mBbKeYS{+(Zso@h#h(k`Pj$@178N4lo-=GaVRpm;j@X|!wb;*>4Co2OARQ#xz%i*M8ke;~Gda*nx5p{`|aV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "fd87f32d954e6b8114710c1cb421b25f9c32ff08cf0b69ba68053ef2a32ec854", "ob": ["a1a1a1a19645459e65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5aa803720971383049abfacef1eae1766a2d5bd879296124ac6930a8cdb4dbe1059aabb4b89fc13888fc4b60fa4498fdecb008eb20c7ae868fa21ba4d55cb69a75a30eaa52f5633c290dffbb6453c35c11fb67fef2a62cabb7a5653e3c3719aa5d805f2fdf80eb05ab66baa6b7f7b3956e71bea6fe97aba914e89e96e4fb6fcf1153f5fd64b8217f18836536df493179b93215ad84e36215c80c2eac0446d6ceb8d1bc26bf325fb597b8bce8192876f2121524d00068bb05b97a7965d982022c61c0fc76ecd90a211587ed344554810dcf102902718a5ed127eacbb43d3030d4ecfa9a6e86541675286dcaa330ae210b3e3d8a5a89f436ac2daa80a1e3f2bd84c4772c417fa71863b43d6a4b8e0e77051240fd73bb3b66eb03a70196ef6f7ffcf8"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('6', '10', '{"c": "mBbJ$2dznX$8+hEVm#p*84AqA7WaqRu6P1z-60C!XlFVZGl#oq!c!{5AkID7t=)F>bIS!jr`cENW5zfF#$D)=RI@5fzL6JWpRQ$YV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "9dfab57faa412c407c8b7a8c6f988afc15f9ab25e604fc6542eb84b00f52e2d9", "ob": ["a1a1a1a19645457265cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a3113f16bb322e59b86500da7574b52d298792c385a5d0355b9ccea9127c7f17fae5686334ec7416bd4da7f31ffe18b8dab571880bd0a11d8dadef2b8210123311a855d8db9c3254af31572e6909f2ec36975aa2e2df540fd7e47398b2dbbb911711c241ad9e2f8471b63e8b74adb6e8c1c10291740faebf8e3c58e2100682e750301eb6807e1723a8a0f228cc3af70860d8b2d7d5efbb8ed2ebad31656f9f31cc20fe49139dd0e97bfeac1d0f599d40e24b6222549b0793be4a19c7f8ebac901021a70eff61a0838eff9acec17d78007237034d10a0672cec37205b095eb80480613da7f4d7139241a274750db8a4e0792e1447f5d52887cbdc935e159c8e13107d26d33dca94518bb4693f7fb6e059c1222561255ccb5f33d4ea9fb187721db"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('7', '17', '{"c": "mBbKs&oax}9^^|SIisV#TLyx}7GwgnjEjg#`hWPogd*#F6}hSJWVEy(B19mLgF*=B&)fehTfyWDL@xFRo>7V{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "0ec321b40a8ae7a37bfd557d8451714996b2986447d32d6a6b3ba91492be0bfe", "ob": ["a1a1a1a1964545ee65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a2b59b0c0132c8308a958946dba0cabf8b5ee08e2c8a7d77f9fe513bf5e8e7da0fecb176102b68aee30552310f7d56af4e973dc54cf43e9d83a72ea3fd8988c7263ec26abd96a3f5afa1b7abfe2c4cee5ed7498c6ea3cc189a1cbac98d9044145dc7598710e35a6ba8bae587c1f115296172695b48c26d4a3497a65cfcb98d63f5e91c34ba1a921883152c1541b85a12bea47bf7de15a9ddb01c8699f93850b42f95cf4f7b5ec56e5fa952235804cd306d5e654da2460a403f81db4aa3b6e667bc5315997e7b69f36acf6b2fc8ba68cfabfb846dbbf124db55cae63f4e38fdbe83d7fc54579b5ba0f2f282847c1a3b3fe4235c5dd1da69e3f1b33eeb6c31fe67ce5f333cd797e2d2aa6d702e9236f7c310b1dc3ee682ff09187c3eb73d11aa72a"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('8', '25', '{"c": "mBbKzuk={LFxu?QubtJ*_sx6=Y5HPpN6zXtgdBlV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "3a8ce9251f3ee4904d625b2e79f9eb930c2b3086ae641f934260c9dd3cf0ecb6", "ob": ["a1a1a1a19645454c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a542bc1720f618d33b43760d45700da56fa52e146601e4e87d97b3a10dd585cb3b0bdc9d13f54e63f4e30326b387b0b4003113da12a722073be23eeb6b6f345e5e0d895ddabc5307b4bee23671ea4793812387c8db666050e8a2c634b5e4bc8b243673485cfc669288dec12bebb53dc5ea0cb079bb0af43b4bac6eb9b52853a7326d9528fe05651c3ee412e855694236d4fcd3192daa776e03bac4852f7310146998dfb0cf39e4e1e6cdb0b329b278472036e751b50a0d43e7b11717a856e93d6f73b4ef447b5ad8f4593f1e8bfbbf6d848cd6887dfd44e03ee79b72b39814071a5f70a5622dea85a6fbeec304a61a5ab4d213e959d2da156323385155c412e94b25b9e7399cb5c249506a17ca65c2a3cd2d49a1ebe99357c7707b5b6e4996f65"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('9', '42', '{"c": "mBbK1bx7W;#5vev=wY1!*a6nW7FhjWI3X9$KbVSRXClSn+{r!8n_e@-AhE3r2#bvbwxh}tD#M--AsG_DqD@Tm2IiPaRrA6Os;*^jV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "231bcb9386ea23eb2630335bc3fa7542b4bec54e5251cec202a10c53453f5b65", "ob": ["a1a1a1a19645450665cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a296cf506a6902dcd1010e8ea6daf751d025252d13f2a5f7ca6f5673aa8bda37cae6d0c2ae66eb602ed1a8922741a8a931a00682f7a84e97267b19e4d3458bd5cc5a54d8fb3a793f0627943eba3318311ea33091db781ccd8c630cb8ff1e5b3edeec7022cfe1a06c987e512f5198825eb659c6206fd6f0197de3e791d52dda77c20adac3d21d3b13acb9fb3742121befe2504ed1c5d150799176b175bf2b82a6ebd5224dab890c323cef7b3e5482eb0efaaf1bc2047afc32345c056e95312c37ece65ba637d452035540e95b338058bad657c2e105b120e30ce76dbbec3a145d491eb5513e69c40dd43a88ddeff386644df2fbd167b7bef640041f9cb57a8174d479954e45e82d3edf5be3637301b5a2c1f633565218618edae2fd75e6048bf0a"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('10', '50', '{"c": "mBbLubhR#eRIBRKP`Za3N&ddX7R78Ur?1=??q*`{-W{g@?u})F3=x0CAOaUdoNrgR-V;Hq{g-NbrRAu#Tm_n~lb?6ctYlh&Os-{aV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "4a040d1a9eb01293059eaf834e84822b97de2dd7c6839d8cdae3cf8e0ef1ab81", "ob": ["a1a1a1a19645458c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a0f4a253e2c8c48870919a382d5b81237d26b63103d7fd84d4077b73f7450239161bbb9cc31c2950d950a1f08897773eff96077f025341ee7985c264c4b9647b7e43c0e17900bdb6d4102d3ccf068ce620d26db623f44d9f5893024538009685c1f178793bfc6b8870cedb33b293a6f5c28f353a4bd0a5bc3979f159399058bb21116b011cad86bb1973be07c3976c2ca262d9fad6fb4903e412eda1839eb30c3439ac51e782e3fab35e517ead7c8b630c490a24aaea97677b4d043f429c0ee4e62a3fe71649c12fecfb7cc8473293ec8f9c9faff8e55c7c5bf4a15c6cbfd4ede19ad2d4f7a29d2c059dbe3f040913ff3c755da18a94182f8d58b94e41770f6c3d8013744e11f8890616e56d380116a7eab2e012364d5a270e8f304b9425ea94d"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('11', '100', '{"c": "mBbL!LwVV+Vl8*@^yEs2*qD*T7PpCgPf=vlV?t2SQ$v0`*^g)EmVCOzAcUF(h2<1yqODlK0-}Arf|cwjcIx+e2jMFj0t9PNsjg*iV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "2fc081842f8545a18689b5eaccca41a935f014ad6d0456a924c4fa8d445833bf", "ob": ["a1a1a1a19645451c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a8fcbebfffe2a0e9c5c05d2eebc34586707605468394a274d4ce84c73300754d90e9904f88788c32137c8207791c44b2752c20812a6ca0201db7f752f944616ab76dbbbfb4924ceae1de65e7a1c38a1b65b0574b5f07ef207a90065c42f4fa65a690e1dca4e037288b70e9a822052953c2a89711f3180c48718689db40a8d8b5715bc16bb5660cbe3484bad5f833b7f4eb57a87310f85299b501ce1efcf26903a448a9187330e2b18f2b0751003c0edf6cf0b99d1d494bf047ac3e204b94ae6aa77a85c9a443de97f7918cb5150a9c59367945d7ae89e18e2a62ad23ecd66f7d32c28ca60728db1c5815293616e4ad1de3c2d74dfdfb839216cb8dc4fc4f49269e39fc1d2f122aff77f0b0e1bfab4ef4432a4e0d3368b4366632b880e52ed89ba"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('12', '250', '{"c": "mBbK#R%P_`%atBydMup-mD_H_7Li%m8A|F;dvA>Y_^QvD>ppyX=lMOvAWh@0st_N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "5f1bd84cff5e65d213b7a5c4005f442d17f169e9e92800598adadf37cff71de6", "ob": ["a1a1a1a19645452e65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5af19c5d189cde4caa7b19cbbb583b33b8dd7bfb4df5f4508c38e1c94e477d128d1873781a3428ec43a7ccc6f55a18d784692f258c2f33bef5cb0997fe486e3b40a5a128db7e5b09db5627ee46a55258e928aa135e0285e764a5ee6aa72311bd810dae9ae2747ce6c4ec2bac140927eaa992cc85d8e9b3c0157cb4a984f58b4fd21ca5e4898cf6a61e7de5bdaa9e045ab546afb65b509f835b023439a2c962312872d38c92357cf83e3475e48450f59197edb6039b25b5abc87e6e58fcbcc42a7b4cdad8d1df6886ba577957207921b53ba1e53830156f116d751ca64138f8936e53a7831ef5fa0819aeb78243579a15383cd55d3d5449b78b01019a35ca9c70d083a2f302f3c3ef7c7ad4ef0a4e8457c552c0d16e43fa7d103ae0171644e00b88"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('13', '1000', '{"c": "mBbKDm$u6vOxUbP-P&WfMw8RT7QXc>s7fP*`bW>P>jPt179vf<5p1l)AiL0!$;tL1C}3Hyy*?tEPN2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "72a100399ebc0cbbf13b13384fbd0a06c7e1c9431ac1204888fc3ee699729f93", "ob": ["a1a1a1a19645d38465cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f6e67f6694308e6798a670a870e41a7dbd333f347fc16f34e9eb2514418afc478cfb9e0893b731832318fe5c01c734eeb58edcb2a49c210e8c80496f45c65ce55bd15fc702bd1a8a50872f2623047bb6138a88291fa548ea837d401d7315be08ae3d725cc9aa0aff3717a6e1254912daa3db212a818b2825055eec4eb6dbc01dc490a727b86e82e51f59cf03c2a76386509d1295d7c76f0474f3216fe77a107a23e88246848bfb86f0494af82b4faf91cabb351db9b568da3eb64ce360d68db724b347da4d844c47439f33585e9f416114ba73973794f2374ed18b0445ed23e6cec6584c23edc1de19e637c4cff55f5203bc104d360ac02b634c11443f110322a1eab498d5ec92abb8c89231f5b3bf10e413fdcbf4ffe41678b66f5a8550c8cce481bbb7e8eae0b6a08ce016f710c4c50"]}'::jsonb); +INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('14', '9999', '{"c": "mBbMDBL-q!6!rQwR5A43Qhsg37M6J*x1=!!pwQ*)It`0cHfasj9oF;2AfOcWDJ#~`f+FVKw-a876;D&NwU@urcv|D7XcB8Ad9Gz{V{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "805123862649caceb6c13fd53c3ea9f9e0888986cdc951cf50a820632aafb2d8", "ob": ["a1a1a1a19645b89065cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f68b1b4ca492400160ebc035ff9744a665420b36ba9eac70df9c96e41e3cc812fd5b754a8970b2f6aab01a2b4285f7aa61290f6aadf52c5c2bd986a2d61af3fe3feba900062db9674416dc29d4d4dd5e7f943a2bf458b9a4baefde3410fd713311c0d1eab7b8039922cb0721969b6fad58986a692110ca39e2293b4dc8022c8518d0bd4615e59897d9776875df6cdb835fbc1cfc69c481d5f48602a4e6da42f953f55add7b1f920d4683747eee2d0f99a9a0ad40827375edfc85ee55cf6acb4bcdc6fc5e2a69a565e7185c86b94a86acdf36be9abd7c843373d0906fd59ec38cf9d2dfd0dd8e0bd85e7525c5c02c8c4d4fb0e6fe5a211b89383a499cbb63820daccd5c37d06f4cf9faa0796eaaef8ae34617ceead72f6ef37cf0c5b5de19234e04bc3e53c92a4f6512ddd0b799fb59839"]}'::jsonb); diff --git a/tests/sqlx/tests/bench_data_tests.rs b/tests/sqlx/tests/bench_data_tests.rs index 0295eaec..c81d6d0d 100644 --- a/tests/sqlx/tests/bench_data_tests.rs +++ b/tests/sqlx/tests/bench_data_tests.rs @@ -20,6 +20,30 @@ async fn fetch_sample_encrypted_text(pool: &PgPool) -> Result { ) } +#[sqlx::test] +async fn benchmark_schema_can_be_reapplied(pool: PgPool) -> Result<()> { + sqlx::query("TRUNCATE TABLE eql_v2_configuration") + .execute(&pool) + .await?; + + let schema = include_str!("../../benchmarks/schema.sql"); + sqlx::raw_sql(schema).execute(&pool).await?; + sqlx::raw_sql(schema).execute(&pool).await?; + + let active_bench_columns: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM eql_v2.config() WHERE state = 'active' AND relation = 'bench'", + ) + .fetch_one(&pool) + .await?; + + assert_eq!( + active_bench_columns, 3, + "reapplying benchmark schema should leave one active config for each bench column" + ); + + Ok(()) +} + // ========== Data Integrity Tests ========== /// Verify fixture seeded exactly 10K rows From 4d0a81c70fb2b36fb5d04312d4e78f002cc36530 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 25 May 2026 10:49:48 +1000 Subject: [PATCH 03/93] feat(fixtures): add typed SQLx fixture generation --- .gitignore | 5 + ...026-05-12-encrypted-domain-types-design.md | 195 --------- mise.toml | 10 +- tasks/bench.toml | 64 --- tasks/fixtures.toml | 27 +- tasks/fixtures/_generate_common.sh | 69 --- tasks/fixtures/encrypted_int4_schema.sql | 30 -- tasks/fixtures/generate_encrypted_int4.sh | 83 ---- tests/benchmarks/.env.example | 7 - tests/benchmarks/.gitignore | 6 - tests/benchmarks/README.md | 37 -- tests/benchmarks/docker-compose.yml | 54 --- tests/benchmarks/generate.sh | 77 ---- tests/benchmarks/reports/.gitkeep | 0 tests/benchmarks/schema.sql | 81 ---- tests/sqlx/Cargo.toml | 6 + tests/sqlx/README.md | 2 +- tests/sqlx/fixtures/FIXTURE_SCHEMA.md | 62 ++- .../{like_data.sql => match_data.sql} | 2 +- .../009_install_encrypted_int4_fixture.sql | 29 -- tests/sqlx/src/fixtures/driver.rs | 406 ++++++++++++++++++ tests/sqlx/src/fixtures/eql_plaintext.rs | 96 +++++ tests/sqlx/src/fixtures/eql_v2_int4.rs | 51 +++ tests/sqlx/src/fixtures/mod.rs | 19 + tests/sqlx/src/fixtures/spec.rs | 364 ++++++++++++++++ tests/sqlx/src/fixtures/validation.rs | 129 ++++++ tests/sqlx/src/lib.rs | 1 + tests/sqlx/tests/bench_data_tests.rs | 24 -- tests/sqlx/tests/eql_v2_int4_fixture_tests.rs | 129 ++++++ tests/sqlx/tests/like_operator_tests.rs | 8 +- 30 files changed, 1301 insertions(+), 772 deletions(-) delete mode 100644 docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md delete mode 100644 tasks/bench.toml delete mode 100644 tasks/fixtures/_generate_common.sh delete mode 100644 tasks/fixtures/encrypted_int4_schema.sql delete mode 100755 tasks/fixtures/generate_encrypted_int4.sh delete mode 100644 tests/benchmarks/.env.example delete mode 100644 tests/benchmarks/.gitignore delete mode 100644 tests/benchmarks/README.md delete mode 100644 tests/benchmarks/docker-compose.yml delete mode 100755 tests/benchmarks/generate.sh delete mode 100644 tests/benchmarks/reports/.gitkeep delete mode 100644 tests/benchmarks/schema.sql rename tests/sqlx/fixtures/{like_data.sql => match_data.sql} (97%) delete mode 100644 tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql create mode 100644 tests/sqlx/src/fixtures/driver.rs create mode 100644 tests/sqlx/src/fixtures/eql_plaintext.rs create mode 100644 tests/sqlx/src/fixtures/eql_v2_int4.rs create mode 100644 tests/sqlx/src/fixtures/mod.rs create mode 100644 tests/sqlx/src/fixtures/spec.rs create mode 100644 tests/sqlx/src/fixtures/validation.rs create mode 100644 tests/sqlx/tests/eql_v2_int4_fixture_tests.rs diff --git a/.gitignore b/.gitignore index 7c1b67f2..68bca65b 100644 --- a/.gitignore +++ b/.gitignore @@ -219,6 +219,10 @@ eql--*.sql # Generated SQLx migration (built from src/, never commit) tests/sqlx/migrations/001_install_eql.sql +# Generated SQLx fixtures (regenerated via `mise run fixture:generate`, +# never commit — stale fixtures hide bugs) +tests/sqlx/fixtures/eql_v2_int4.sql + # Large generated test data files tests/ste_vec_vast.sql tests/ste_vec_*M.sql* @@ -229,6 +233,7 @@ tests/sqlx/target/ # Work files (agent-generated, not for version control) .work/ .serena/ +docs/superpowers/ # Build variants - protect variant deps src/deps-protect.txt diff --git a/docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md b/docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md deleted file mode 100644 index 914f8577..00000000 --- a/docs/superpowers/specs/2026-05-12-encrypted-domain-types-design.md +++ /dev/null @@ -1,195 +0,0 @@ -# High-Level Encrypted Domain Types Prototype Design - -## Context - -EQL currently exposes one public encrypted column type, `public.eql_v2_encrypted`, -implemented as a composite type with a single `jsonb` payload field. Query behavior -is selected dynamically from the encrypted payload terms that are present (`hm`, -`bf`, `ob`, `opf`, `opv`, `sv`, etc.). - -The new goal is to add high-level SQL column types such as `encrypted_text`, -`encrypted_jsonb`, and `encrypted_int4`. These types should make application DDL -clearer and give each plaintext shape a static, predictable SQL operator surface. -They should not rely on the broad dynamic dispatch behavior of -`eql_v2_encrypted`. - -The prototype is intentionally limited to: - -- `public.encrypted_text` -- `public.encrypted_jsonb` -- `public.encrypted_int4` - -Configuration inference, automatic registration, broad type coverage, and -production migration behavior are out of scope for the prototype. The prototype -exists to prove whether `jsonb` domain types can provide a clean client-facing -DDL surface while still producing indexable query plans without operator -classes. - -## History And Spike Findings - -A previous branch tried changing `eql_v2_encrypted` itself from a composite type -to a `jsonb` domain. That PR closed unmerged with failing CI, and there is no -clear written rationale for the failure. Separately, EQL has kept -`public.eql_v2_encrypted` and `public.eql_v2_configuration` outside the -`eql_v2` schema so EQL upgrades can drop and recreate `eql_v2` without -cascading into customer columns. - -A transient SQL spike compared three shapes: - -- domain over raw `jsonb` -- domain over `public.eql_v2_encrypted` -- independent composite type with `(data jsonb)` - -The spike showed that domains over `public.eql_v2_encrypted` are ergonomic and -can use existing helpers, but inherit base EQL operators when exact domain -operators are absent. Independent composites avoid inherited behavior, but need -more casts and exact helper/operator wrappers. - -The approved design is simpler: define the high-level types as domains over -raw `jsonb`, then define exact operators for supported and unsupported -operations. This removes the extra `eql_v2_encrypted` layer from the new public -types. - -## Type Model - -Create public domain types over `jsonb`: - -```sql -CREATE DOMAIN public.encrypted_text AS jsonb; -CREATE DOMAIN public.encrypted_jsonb AS jsonb; -CREATE DOMAIN public.encrypted_int4 AS jsonb; -``` - -The payload remains the existing EQL encrypted JSONB payload. The specific -types do not depend on `public.eql_v2_encrypted` for storage or operator -dispatch. - -Because PostgreSQL domains can fall back to base-type behavior, every public -operation in the supported SQL surface must have an exact domain operator: - -- supported operations delegate to fixed index-term helpers; -- unsupported operations raise a type-specific error. - -This prevents accidental fallback to native `jsonb` semantics for common SQL -operators. - -## Prototype Acceptance Criteria - -The prototype must prove these properties: - -- exact domain operators resolve for supported operations; -- exact blocker operators prevent common unsupported operations from falling - through to native `jsonb` behavior; -- supported hot-path operator functions are inlineable SQL functions with no - `SET search_path` clause; -- bare operator predicates use functional indexes and do not require custom - btree or hash operator classes; -- where existing helper signatures are awkward, temporary typed helper wrappers - are small, `LANGUAGE sql`, immutable, strict, parallel-safe, and inlineable - when used in indexed predicates. - -## Operator Surface - -### `encrypted_text` - -Supported: - -- `=` and `<>`, using the `hm` term through `eql_v2.hmac_256(value::jsonb)` -- `~~` and `~~*`, using the `bf` term through `eql_v2.bloom_filter(value::jsonb)` - -Unsupported blockers: - -- `<`, `<=`, `>`, `>=` -- `@>`, `<@` -- `->`, `->>` - -### `encrypted_int4` - -Supported: - -- `=` and `<>`, using the `hm` term through `eql_v2.hmac_256(value::jsonb)` -- `<`, `<=`, `>`, `>=`, using OPE terms by default through an inlineable - expression over `value::jsonb` - -Unsupported blockers: - -- `~~`, `~~*` -- `@>`, `<@` -- `->`, `->>` - -### `encrypted_jsonb` - -Supported: - -- `=` and `<>`, using the `hm` term through `eql_v2.hmac_256(value::jsonb)` -- `@>` and `<@`, using `sv` through inlineable typed STE vector helpers or - wrappers -- `->` and `->>`, using stubbed or adapted encrypted JSON path helpers for the - domain type - -Unsupported blockers: - -- `<`, `<=`, `>`, `>=` -- `~~`, `~~*` - -## Out Of Scope - -Do not add configuration inference in this prototype. The prototype should not -change `eql_v2.add_column`, `eql_v2.add_search_config`, or the configuration -validation functions. - -Do not add automatic registration or event triggers in this prototype. - -Do not add full support for additional encrypted scalar types in this prototype. -The three selected types are enough to test text, scalar range, and JSONB -operator behavior. - -## Error Handling - -Unsupported exact operators should raise clear errors: - -```text -operator < is not supported for encrypted_text -operator ~~ is not supported for encrypted_int4 -operator -> is not supported for encrypted_int4 -``` - -Missing required encrypted index terms should fail through the fixed helper path -with the existing helper errors, such as missing `hm`, `bf`, `opf`, or `sv`. - -Supported hot-path functions should not raise custom errors for missing terms if -an existing helper already provides a precise missing-term error. - -## Testing - -Add focused SQLx coverage for the first three domain types: - -- Domain creation and assignment from valid encrypted JSONB payloads. -- Supported operators for each type. -- Unsupported operators raise the exact type-specific error instead of falling - through to native `jsonb` behavior. -- Functional indexes engage for supported terms: - - `encrypted_text`: `eql_v2.hmac_256(col::jsonb)`, - `eql_v2.bloom_filter(col::jsonb)` - - `encrypted_int4`: `eql_v2.hmac_256(col::jsonb)`, and an OPE order - expression over `col::jsonb` - - `encrypted_jsonb`: `eql_v2.hmac_256(col::jsonb)`, and a typed STE vector - array helper or overload that accepts `encrypted_jsonb` -- `EXPLAIN` plans show index scans for bare operator predicates such as - `col = rhs`, `col ~~ rhs`, `col < rhs`, and `col @> rhs`. -- The same predicates do not require btree/hash operator classes. -- Prepared statements with domain-typed parameters still resolve to exact - domain operators. - -## Implementation Boundary - -Write the first three type surfaces manually. Do not introduce a generator in -the prototype. Manual SQL keeps the spike easy to audit and -lets tests prove the domain-over-`jsonb` approach before expanding to -`encrypted_int2`, `encrypted_int8`, numeric, floating-point, boolean, date, and -timestamp types. - -Supported operator functions and helper wrappers that appear in indexed -predicates must be SQL-language functions intended for planner inlining. -Unsupported blocker functions can use PL/pgSQL because they are not performance -paths. diff --git a/mise.toml b/mise.toml index 6fd0b7c6..86e30d47 100644 --- a/mise.toml +++ b/mise.toml @@ -14,7 +14,7 @@ "python" = "3.13" [task_config] -includes = ["tasks", "tasks/postgres.toml", "tasks/bench.toml", "tasks/fixtures.toml"] +includes = ["tasks", "tasks/postgres.toml", "tasks/fixtures.toml"] [env] POSTGRES_DB = "cipherstash" @@ -45,7 +45,15 @@ echo "Running SQLx migrations..." cd tests/sqlx sqlx migrate run +# Regenerate fixtures every run — they are not committed (see .gitignore). +# Requires Proxy on PROXY_PORT; brings it up if not already running. +echo "Regenerating SQLx fixtures..." +cd "{{config_root}}" +mise run proxy:up +mise run fixture:generate eql_v2_int4 + echo "Running Rust tests..." +cd tests/sqlx cargo test """ diff --git a/tasks/bench.toml b/tasks/bench.toml deleted file mode 100644 index 72abc487..00000000 --- a/tasks/bench.toml +++ /dev/null @@ -1,64 +0,0 @@ -["bench:up"] -description = "Start Postgres + Proxy for benchmark data generation" -dir = "{{config_root}}" -run = """ -if [ ! -f tests/benchmarks/.env ]; then - echo "ERROR: tests/benchmarks/.env missing. Copy .env.example and fill in credentials." >&2 - exit 1 -fi -docker compose --env-file tests/benchmarks/.env -f tests/benchmarks/docker-compose.yml up -d -export PGPASSWORD="password" -echo "Waiting for bench-postgres on localhost:7433..." -for i in $(seq 1 60); do - if psql -U cipherstash -d cipherstash -h localhost -p 7433 -c 'SELECT 1' >/dev/null 2>&1; then - echo "bench-postgres ready." - break - fi - sleep 1 - if [ "$i" -eq 60 ]; then - echo "bench-postgres did not become ready in 60s." - echo - echo '=== bench-postgres logs ===' - docker logs bench-postgres 2>&1 | tail -40 - exit 1 - fi -done - -echo "Waiting for bench-proxy on localhost:6433..." -for i in $(seq 1 60); do - if psql -U cipherstash -d cipherstash -h localhost -p 6433 -c 'SELECT 1' >/dev/null 2>&1; then - echo "bench-proxy ready." - exit 0 - fi - sleep 1 -done -echo "bench-proxy did not become ready in 60s." -echo -echo '=== bench-proxy logs ===' -docker logs bench-proxy 2>&1 | tail -40 -exit 1 -""" - -["bench:down"] -description = "Stop benchmark Postgres + Proxy" -dir = "{{config_root}}" -run = """ -docker compose -f tests/benchmarks/docker-compose.yml down -v -""" - -["bench:generate"] -description = "Generate 100K encrypted bench dataset (requires bench:up first)" -# `build` produces release/cipherstash-encrypt.sql, which generate.sh -# installs into the bench Postgres container before applying schema.sql. -depends = ["build"] -dir = "{{config_root}}" -run = """ -tests/benchmarks/generate.sh 100k -""" - -["bench:full"] -description = "Run committed SQLx bench/regression suite" -dir = "{{config_root}}" -run = """ -mise run --output prefix test:bench -""" diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml index 00279880..dfac6411 100644 --- a/tasks/fixtures.toml +++ b/tasks/fixtures.toml @@ -35,12 +35,25 @@ description = "Stop CipherStash Proxy" dir = "{{config_root}}/tests" run = "docker compose -f docker-compose.proxy.yml down" -["fixture:int:generate"] -description = "Generate encrypted_int4 fixture (009) via Proxy" +["fixture:generate"] +description = "Generate a SQLx fixture script via CipherStash Proxy" +# Runs the gated generator for the named fixture. Writes +# tests/sqlx/fixtures/.sql. Must run inside the crate — there is no +# root Cargo.toml — matching test:schema / test:sqlx:watch. +# # Prerequisites: -# - mise run postgres:up (existing Postgres on POSTGRES_PORT) -# - mise run reset (ensures EQL is installed in that Postgres) +# - mise run postgres:up (Postgres with EQL installed) # - mise run proxy:up (Proxy on localhost:6432) -depends = ["build"] -dir = "{{config_root}}" -run = "tasks/fixtures/generate_encrypted_int4.sh" +# +# Usage: mise run fixture:generate eql_v2_int4 +dir = "{{config_root}}/tests/sqlx" +run = """ +fixture="{{arg(name="fixture")}}" +case "$fixture" in + (*[!a-z0-9_]*|'') echo "Invalid fixture name: $fixture (expected [a-z0-9_]+)" >&2; exit 1 ;; +esac + +cargo test --features fixture-gen --lib \ + "fixtures::${fixture}::generate" \ + -- --ignored --exact --nocapture +""" diff --git a/tasks/fixtures/_generate_common.sh b/tasks/fixtures/_generate_common.sh deleted file mode 100644 index 0d666314..00000000 --- a/tasks/fixtures/_generate_common.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -# Common helpers for fixture generators. Sourced — not executed directly. -# Sets PG_URL / PROXY_URL and exposes restart_proxy_and_wait + dump_fixture_table. - -# Resolve Postgres / Proxy connection from mise [env] (POSTGRES_*) with the -# usual defaults. PROXY_PORT comes from tests/docker-compose.proxy.yml. -PG_USER="${POSTGRES_USER:-cipherstash}" -PG_PASSWORD="${POSTGRES_PASSWORD:-password}" -PG_DB="${POSTGRES_DB:-cipherstash}" -PG_HOST="${POSTGRES_HOST:-localhost}" -PG_PORT="${POSTGRES_PORT:-7432}" -PROXY_PORT="${PROXY_PORT:-6432}" - -PG_URL="postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/${PG_DB}" -PROXY_URL="postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PROXY_PORT}/${PG_DB}" - -export PGPASSWORD="$PG_PASSWORD" - -# Proxy caches its encrypt config at connection-handler init time, so any -# add_search_config call applied AFTER Proxy started won't take effect -# until Proxy reconnects. Restart and wait for it to come back. -restart_proxy_and_wait() { - echo "==> Restarting Proxy so it reloads the new encrypt config" - docker restart cipherstash-proxy >/dev/null - - for i in $(seq 1 60); do - if psql "$PROXY_URL" -c 'SELECT 1' >/dev/null 2>&1; then - echo " Proxy ready." - return 0 - fi - sleep 1 - done - - echo "ERROR: Proxy did not come back up after restart" >&2 - docker logs cipherstash-proxy 2>&1 | tail -20 - return 1 -} - -# Render fixture rows as INSERT statements using format(%L). Caller supplies: -# $1 = source table name (e.g. bench_text) -# $2 = destination table name in the migration (e.g. encrypted_text_plaintext) -# $3 = comma-separated source-column projection -# (e.g. "id, plaintext, (encrypted_text).data::text") -# $4 = comma-separated destination column types for format() placeholders -# (e.g. "%L, %L, %L::jsonb") -# $5 = destination column-name tuple -# (e.g. "(id, plaintext, payload)") -# $6 = output path -# -# The migration is written with a DROP / CREATE preamble plus the rendered -# INSERT statements. The CREATE statement must be supplied by the caller via -# stdin BEFORE calling this function; see how each generator pipes it in. -dump_fixture_table() { - local src_table="$1" - local dst_table="$2" - local src_projection="$3" - local fmt_placeholders="$4" - local dst_columns="$5" - local output_path="$6" - - psql "$PG_URL" -v ON_ERROR_STOP=1 -t -A -c " -SELECT format( - 'INSERT INTO ${dst_table} ${dst_columns} VALUES (${fmt_placeholders});', - ${src_projection} -) -FROM ${src_table} -ORDER BY id; -" >> "$output_path" -} diff --git a/tasks/fixtures/encrypted_int4_schema.sql b/tasks/fixtures/encrypted_int4_schema.sql deleted file mode 100644 index 5d870590..00000000 --- a/tasks/fixtures/encrypted_int4_schema.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Schema for the encrypted_int4 plaintext-paired fixture. --- Applied by tasks/fixtures/generate_encrypted_int4.sh; the generator --- restarts Proxy afterwards so it reloads the new encrypt config. - -DROP TABLE IF EXISTS bench_int4; - -CREATE TABLE bench_int4 ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - plaintext INTEGER NOT NULL, - encrypted_int4 eql_v2_encrypted -); - --- Idempotency: drop any prior bench_int4 search-config rows so re-running --- the generator doesn't error with "unique index exists for column". -SELECT eql_v2.remove_search_config('bench_int4', 'encrypted_int4', 'unique') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench_int4,encrypted_int4,indexes,unique}' IS NOT NULL - ); -SELECT eql_v2.remove_search_config('bench_int4', 'encrypted_int4', 'ore') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench_int4,encrypted_int4,indexes,ore}' IS NOT NULL - ); - --- unique → HMAC (drives =, <>); ore → OPE bytes (drives <, <=, >, >=). -SELECT eql_v2.add_search_config('bench_int4', 'encrypted_int4', 'unique', 'int'); -SELECT eql_v2.add_search_config('bench_int4', 'encrypted_int4', 'ore', 'int'); diff --git a/tasks/fixtures/generate_encrypted_int4.sh b/tasks/fixtures/generate_encrypted_int4.sh deleted file mode 100755 index 5662845a..00000000 --- a/tasks/fixtures/generate_encrypted_int4.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Generates an encrypted_int4 fixture by running an integer value set -# through CipherStash Proxy and dumping the resulting (id, plaintext, -# payload jsonb) rows as a SQLx migration. -# -# Prerequisites: -# - mise run postgres:up -# - EQL installed (e.g. via mise run reset + psql -f release/cipherstash-encrypt.sql) -# - mise run proxy:up (Proxy on localhost:6432) -# - mise run build (produces release/cipherstash-encrypt.sql) -# -# Output: -# tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SCHEMA_SQL="$SCRIPT_DIR/encrypted_int4_schema.sql" -OUTPUT="$REPO_ROOT/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql" - -# shellcheck source=_generate_common.sh -. "$SCRIPT_DIR/_generate_common.sh" - -if [ ! -f "$SCHEMA_SQL" ]; then - echo "ERROR: $SCHEMA_SQL not found." >&2 - exit 1 -fi - -# 14 values: includes negatives (boundary), small/medium/large/extreme. -# Chosen so range pivots produce distinct cardinalities — see plan in -# docs/superpowers/plans/. -VALUES=(-100 -1 1 2 5 10 17 25 42 50 100 250 1000 9999) -ROW_COUNT=${#VALUES[@]} - -echo "==> Applying fixture schema (drops + recreates bench_int4)" -psql "$PG_URL" -v ON_ERROR_STOP=1 -f "$SCHEMA_SQL" - -restart_proxy_and_wait - -echo "==> Inserting $ROW_COUNT integers through Proxy (encrypts encrypted_int4)" -# Proxy's eql-mapper cannot unify negative integer literals (parsed as -# UnaryOp(Minus, ...)) with the EQL int column when sent via the simple -# query protocol. Send each value over the extended protocol via psql's -# \bind meta-command so the parameter type is communicated as a binary -# int4 instead of being inferred from SQL surface syntax. -INSERT_SQL=$(mktemp) -trap 'rm -f "$INSERT_SQL"' EXIT -for v in "${VALUES[@]}"; do - # Use literal $1/$2 as bind placeholders; \bind supplies their values. - # \g executes the buffered statement; \bind discards bindings after \g. - printf 'INSERT INTO bench_int4 (plaintext, encrypted_int4) VALUES ($1, $2) \\bind %s %s \\g\n' "$v" "$v" >> "$INSERT_SQL" -done -psql "$PROXY_URL" -v ON_ERROR_STOP=1 -f "$INSERT_SQL" >/dev/null - -echo "==> Dumping $ROW_COUNT rows to $OUTPUT" -cat > "$OUTPUT" <
Done. Wrote $ROW_COUNT rows to $OUTPUT" diff --git a/tests/benchmarks/.env.example b/tests/benchmarks/.env.example deleted file mode 100644 index fe41909a..00000000 --- a/tests/benchmarks/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -# CipherStash Proxy credentials -# Get these from https://dashboard.cipherstash.com -CS_CLIENT_ACCESS_KEY= -CS_DEFAULT_KEYSET_ID= -CS_CLIENT_KEY= -CS_CLIENT_ID= -CS_WORKSPACE_CRN= diff --git a/tests/benchmarks/.gitignore b/tests/benchmarks/.gitignore deleted file mode 100644 index 9e7d7623..00000000 --- a/tests/benchmarks/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Generated reports (too large for git, regenerated on demand) -reports/* -!reports/.gitkeep - -# Local Proxy credentials -.env diff --git a/tests/benchmarks/README.md b/tests/benchmarks/README.md deleted file mode 100644 index 69087bdb..00000000 --- a/tests/benchmarks/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Benchmark Utilities - -This directory contains the Dockerized support stack for generating a 100K-row -encrypted benchmark dataset through CipherStash Proxy. - -The committed automated benchmark coverage lives in the SQLx bench/regression -suite (`mise run test:bench`). `mise run bench:full` is a convenience wrapper -around that existing suite; it does not consume the 100K Docker dataset. - -## Local usage - -```bash -# Populate credentials for the Dockerized Proxy -cp tests/benchmarks/.env.example tests/benchmarks/.env -# Edit .env with your CipherStash credentials - -# Start bench-postgres + bench-proxy and wait for host-side readiness checks -mise run bench:up - -# Build EQL and generate the 100K encrypted dataset in bench-postgres -mise run bench:generate - -# Run the committed SQLx bench/regression suite (10K fixture-based) -mise run bench:full - -# Tear down the Dockerized benchmark stack when finished -mise run bench:down -``` - -## What each task does - -- `bench:up` starts `bench-postgres` and `bench-proxy`, then probes them from - the host with `psql`. -- `bench:generate` installs the built EQL SQL into `bench-postgres`, applies - `schema.sql`, and inserts 100K plaintext rows through Proxy on `localhost:6433`. -- `bench:full` delegates to `mise run test:bench`, which runs the committed - SQLx benchmark/regression suite against the normal local test database. diff --git a/tests/benchmarks/docker-compose.yml b/tests/benchmarks/docker-compose.yml deleted file mode 100644 index d67aca7c..00000000 --- a/tests/benchmarks/docker-compose.yml +++ /dev/null @@ -1,54 +0,0 @@ -services: - postgres: - image: postgres:17 - container_name: bench-postgres - command: > - postgres - -c track_functions=all - -c shared_preload_libraries=pg_stat_statements - -c pg_stat_statements.track=all - -c pg_stat_statements.max=10000 - ports: - - "7433:5432" - environment: - POSTGRES_DB: cipherstash - POSTGRES_USER: cipherstash - POSTGRES_PASSWORD: password - healthcheck: - test: ["CMD-SHELL", "pg_isready -U cipherstash"] - interval: 1s - timeout: 5s - retries: 10 - networks: - - bench - - proxy: - image: cipherstash/proxy:latest - container_name: bench-proxy - ports: - - "6433:6432" - environment: - CS_DATABASE__NAME: cipherstash - CS_DATABASE__USERNAME: cipherstash - CS_DATABASE__PASSWORD: password - CS_DATABASE__HOST: postgres - CS_DATABASE__PORT: 5432 - # EQL install is performed explicitly by generate.sh before schema.sql runs. - # Leaving Proxy's own install off avoids racing against generate.sh. - CS_DATABASE__INSTALL_EQL: "false" - CS_CLIENT_ACCESS_KEY: ${CS_CLIENT_ACCESS_KEY} - CS_DEFAULT_KEYSET_ID: ${CS_DEFAULT_KEYSET_ID} - CS_CLIENT_KEY: ${CS_CLIENT_KEY} - CS_CLIENT_ID: ${CS_CLIENT_ID} - CS_WORKSPACE_CRN: ${CS_WORKSPACE_CRN} - depends_on: - postgres: - condition: service_healthy - networks: - - bench - # No in-container healthcheck: the current cipherstash/proxy image does - # not ship `nc`, so readiness is verified from the host by `bench:up` - # using `psql` against localhost:6433. -networks: - bench: - driver: bridge diff --git a/tests/benchmarks/generate.sh b/tests/benchmarks/generate.sh deleted file mode 100755 index 595e8d10..00000000 --- a/tests/benchmarks/generate.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Generates a 100K-row encrypted bench dataset via CipherStash Proxy. -# No dump is written in v1 — the Tier 2 workflow regenerates fresh each run. -# -# Prerequisites: -# - mise run build (produces release/cipherstash-encrypt.sql) -# - docker compose -f tests/benchmarks/docker-compose.yml up -d --wait -# - tests/benchmarks/.env populated with CipherStash credentials - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -EQL_SQL="$REPO_ROOT/release/cipherstash-encrypt.sql" -SCALE="${1:-100k}" - -case "$SCALE" in - 100k) ROWS=100000 ;; - *) echo "Unsupported scale: $SCALE (only 100k in v1)" >&2; exit 1 ;; -esac - -if [ ! -f "$EQL_SQL" ]; then - echo "ERROR: $EQL_SQL not found. Run 'mise run build' first." >&2 - exit 1 -fi - -PG_URL="postgresql://cipherstash:password@localhost:7433/cipherstash" -PROXY_URL="postgresql://cipherstash:password@localhost:6433/cipherstash" - -echo "==> Installing EQL into bench-postgres" -psql "$PG_URL" -v ON_ERROR_STOP=1 -f "$EQL_SQL" >/dev/null - -echo "==> Applying bench schema and Proxy search configuration" -psql "$PG_URL" -v ON_ERROR_STOP=1 -f "$SCRIPT_DIR/schema.sql" - -# Proxy caches the encrypt config at connection-handler init. add_search_config -# in schema.sql writes the new config but the Proxy will keep running in -# PASSTHROUGH MODE (inserts pass through unencrypted) until it reconnects. -# Restart and wait for it to come back before driving the INSERT. -echo "==> Restarting bench-proxy so it reloads the new encrypt config" -docker restart bench-proxy >/dev/null -for i in $(seq 1 60); do - if psql "$PROXY_URL" -c 'SELECT 1' >/dev/null 2>&1; then - echo " Proxy ready." - break - fi - sleep 1 - if [ "$i" -eq 60 ]; then - echo "ERROR: bench-proxy did not come back up after restart" >&2 - docker logs bench-proxy 2>&1 | tail -20 - exit 1 - fi -done - -echo "==> Inserting $ROWS plaintext rows through Proxy (this encrypts them)" -# generate_series emits plaintext rows; Proxy intercepts and encrypts each -# column per the search config applied in schema.sql. -psql "$PROXY_URL" -v ON_ERROR_STOP=1 -c " -INSERT INTO bench (encrypted_text, encrypted_int, encrypted_bigint) -SELECT - ('text_' || (((gs - 1) % 1000) + 1))::text, - (((gs - 1) % 1000) + 1)::int, - (((gs - 1) % 1000) + 1)::bigint * 1000000000 -FROM generate_series(1, $ROWS) AS gs; -" - -echo "==> Creating indexes and running ANALYZE" -psql "$PG_URL" -v ON_ERROR_STOP=1 -c " -CREATE INDEX IF NOT EXISTS bench_text_hmac_idx ON bench USING hash (eql_v2.hmac_256(encrypted_text)); -CREATE INDEX IF NOT EXISTS bench_text_ore_idx ON bench USING btree (encrypted_text eql_v2.encrypted_operator_class); -CREATE INDEX IF NOT EXISTS bench_int_ore_idx ON bench USING btree (encrypted_int eql_v2.encrypted_operator_class); -CREATE INDEX IF NOT EXISTS bench_bigint_ore_idx ON bench USING btree (encrypted_bigint eql_v2.encrypted_operator_class); -CREATE INDEX IF NOT EXISTS bench_text_bloom_idx ON bench USING gin (eql_v2.bloom_filter(encrypted_text)); -ANALYZE bench; -" - -echo "==> Done. Rows: $ROWS" diff --git a/tests/benchmarks/reports/.gitkeep b/tests/benchmarks/reports/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/benchmarks/schema.sql b/tests/benchmarks/schema.sql deleted file mode 100644 index 88bb7be4..00000000 --- a/tests/benchmarks/schema.sql +++ /dev/null @@ -1,81 +0,0 @@ --- Bench schema for Tier 2 benchmarks. --- Applied against the bench-postgres container AFTER EQL has been explicitly --- installed by generate.sh (see Task 4 — generate.sh installs --- release/cipherstash-encrypt.sql directly, not relying on Proxy's async install). - -DROP TABLE IF EXISTS bench; - -CREATE TABLE bench ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - encrypted_text eql_v2_encrypted, - encrypted_int eql_v2_encrypted, - encrypted_bigint eql_v2_encrypted -); - --- Idempotency: clear any prior bench search-config rows so re-running the --- generator against the same container doesn't error with "... index exists --- for column". EQL uninstall drops the schema but not public config rows. -SELECT eql_v2.remove_search_config('bench', 'encrypted_text', 'unique') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench,encrypted_text,indexes,unique}' IS NOT NULL - ); -SELECT eql_v2.remove_search_config('bench', 'encrypted_text', 'match') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench,encrypted_text,indexes,match}' IS NOT NULL - ); -SELECT eql_v2.remove_search_config('bench', 'encrypted_text', 'ore') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench,encrypted_text,indexes,ore}' IS NOT NULL - ); -SELECT eql_v2.remove_search_config('bench', 'encrypted_int', 'unique') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench,encrypted_int,indexes,unique}' IS NOT NULL - ); -SELECT eql_v2.remove_search_config('bench', 'encrypted_int', 'ore') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench,encrypted_int,indexes,ore}' IS NOT NULL - ); -SELECT eql_v2.remove_search_config('bench', 'encrypted_bigint', 'unique') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench,encrypted_bigint,indexes,unique}' IS NOT NULL - ); -SELECT eql_v2.remove_search_config('bench', 'encrypted_bigint', 'ore') - WHERE EXISTS ( - SELECT 1 - FROM public.eql_v2_configuration c - WHERE c.data #> '{tables,bench,encrypted_bigint,indexes,ore}' IS NOT NULL - ); - --- Proxy search configuration: tells Proxy which index terms to generate --- for each column when plaintext is inserted. --- --- Signature: eql_v2.add_search_config(table, column, index, cast_as) --- (see src/config/functions.sql). add_search_config calls activate_config --- internally when migrating=false, so no explicit activate_config call. - --- text column: equality (hmac), pattern match (bloom), ordering (ore) -SELECT eql_v2.add_search_config('bench', 'encrypted_text', 'unique', 'text'); -SELECT eql_v2.add_search_config('bench', 'encrypted_text', 'match', 'text'); -SELECT eql_v2.add_search_config('bench', 'encrypted_text', 'ore', 'text'); - --- integer column: equality + ORE range/ordering -SELECT eql_v2.add_search_config('bench', 'encrypted_int', 'unique', 'int'); -SELECT eql_v2.add_search_config('bench', 'encrypted_int', 'ore', 'int'); - --- bigint column: equality + ORE range/ordering -SELECT eql_v2.add_search_config('bench', 'encrypted_bigint', 'unique', 'big_int'); -SELECT eql_v2.add_search_config('bench', 'encrypted_bigint', 'ore', 'big_int'); - --- Indexes (created after data load in generate.sh, after ANALYZE) diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index 875383cf..95fb86a2 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -22,3 +22,9 @@ default = [] # it on push to main and on a nightly schedule. Run locally with: # mise run test:bench bench = [] +# Opt-in to compiling the fixture generators. Without this feature the +# `#[cfg(feature = "fixture-gen")]` generator tests do not exist, so +# `cargo test` and CI never see them. Generators need a live Postgres and +# CipherStash Proxy; run one with: +# mise run fixture:generate +fixture-gen = [] diff --git a/tests/sqlx/README.md b/tests/sqlx/README.md index ec4e92eb..a1dc8252 100644 --- a/tests/sqlx/README.md +++ b/tests/sqlx/README.md @@ -70,7 +70,7 @@ cargo test -- --nocapture **encryptindex_tables.sql**: Tables for encryption workflow tests - Table: `users` with plaintext columns for encryption testing -**like_data.sql**: Test data for LIKE operator tests +**match_data.sql**: Test data for LIKE operator tests - 3 encrypted records with bloom filter indexes diff --git a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md index 8814dff0..e4ac7856 100644 --- a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md +++ b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md @@ -7,13 +7,21 @@ This document defines the structure and dependencies of test fixtures used in th ``` EQL Extension (via migrations) ├── encrypted_json.sql - ├── array_data.sql + │ └── array_data.sql (extends `encrypted` table from encrypted_json) + ├── match_data.sql + ├── aggregate_minmax_data.sql + ├── config_tables.sql + ├── constraint_tables.sql + ├── encryptindex_tables.sql + ├── drop_operator_classes.sql (Supabase-simulation; drops opclasses + ORE operators) ├── order_by_null_data.sql (depends on ore migration) ├── ore table (migration 002 — not a fixture) └── bench_data.sql + bench_setup.sql (depend on migration 007) + +eql_v2_int4.sql (no EQL dependency — generated, plain jsonb, not committed) ``` -All fixtures depend on the EQL extension being installed via SQLx migrations. +All fixtures except the generated `eql_v2_int4.sql` depend on the EQL extension being installed via SQLx migrations. --- @@ -182,6 +190,56 @@ CREATE TABLE bench ( --- +## eql_v2_int4.sql + +**Purpose:** 14 encrypted integers for verifying encrypted-integer fixture +structure. Unlike its neighbours, this is a **generated** fixture — produced by +`mise run fixture:generate eql_v2_int4` (the Rust fixture framework in +`tests/sqlx/src/fixtures/`) and **not committed** (see `.gitignore`). It is +plain SQL with **no EQL dependency**: `payload` is `jsonb`, so the script +applies standalone. + +**Regenerated every test run.** `mise run test:sqlx` invokes the generator +before `cargo test`, so a stale committed fixture cannot mask a payload-shape +regression. The generator needs a live Postgres with EQL and a running +CipherStash Proxy — `test:sqlx` brings the Proxy up automatically via +`mise run proxy:up`. Do not hand-edit the generated file; it is overwritten in +place on every run. + +**Schema:** Table lives in the dedicated `fixtures` SQL schema (kept out of the +`public` type/domain namespace so a downstream `public.eql_v2_int4` domain can +coexist): +```sql +CREATE SCHEMA IF NOT EXISTS fixtures; +CREATE TABLE fixtures.eql_v2_int4 ( + id BIGINT PRIMARY KEY, + plaintext integer NOT NULL, + payload jsonb NOT NULL +); +``` + +**Data:** +- 14 rows, ids 1-14; `id = N` is the Nth generated value. +- `plaintext` values: `-100, -1, 1, 2, 5, 10, 17, 25, 42, 50, 100, 250, 1000, 9999` + — a negative boundary plus small/medium/large/extreme magnitudes. +- `plaintext` is the **in-table oracle**: consuming tests filter + `WHERE plaintext = N` directly, so no Rust value constant is shared. +- Each `payload` is a Proxy-encrypted JSONB object carrying `c` (ciphertext), + `hm` (HMAC equality term), `ob` (ORE block ordering term), and an inert `i` + metadata object. + +**Used By:** +- eql_v2_int4_fixture_tests.rs (structural verification) +- (#225) the `eql_v2_int4` domain operator tests, via per-query `payload` casts + +**Opt-in:** Not a migration — a SQLx fixture script. Each consuming test opts +in explicitly: +```rust +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +``` + +--- + ## Validation Tests Each fixture should have a validation test to ensure correct structure: diff --git a/tests/sqlx/fixtures/like_data.sql b/tests/sqlx/fixtures/match_data.sql similarity index 97% rename from tests/sqlx/fixtures/like_data.sql rename to tests/sqlx/fixtures/match_data.sql index 8f5b2adc..bd7a49c5 100644 --- a/tests/sqlx/fixtures/like_data.sql +++ b/tests/sqlx/fixtures/match_data.sql @@ -1,4 +1,4 @@ --- Fixture: like_data.sql +-- Fixture: match_data.sql -- -- Creates test data for LIKE operator tests (~~ and ~~* operators) -- Tests encrypted-to-encrypted matching using bloom filter indexes diff --git a/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql b/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql deleted file mode 100644 index 7556d9e6..00000000 --- a/tests/sqlx/migrations/009_install_encrypted_int4_fixture.sql +++ /dev/null @@ -1,29 +0,0 @@ --- AUTO-GENERATED by tasks/fixtures/generate_encrypted_int4.sh --- DO NOT EDIT BY HAND. Re-run the generator to refresh. --- --- Source: 14-value integer set defined inline in the generator. --- Produced via CipherStash Proxy (HMAC + OPE terms). --- Used by encrypted_int4 domain SQLx fixture tests. - -DROP TABLE IF EXISTS encrypted_int4_plaintext; - -CREATE TABLE encrypted_int4_plaintext ( - id BIGINT PRIMARY KEY, - plaintext INTEGER NOT NULL, - payload JSONB NOT NULL -); - -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('1', '-100', '{"c": "mBbKSl1A>nJLHvy{R_+!y+r237Oz{OOyZusRU1%=^N29ellMsHtFke~AjFeV1#(=nP!7;6lb4dK$exS)cVyEoXZr0(3=TevwytGvV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "c8faf5849bba756007c19df73eb704aa640dea7eec353af533b4502cc354640d", "ob": ["a1a1a1a15481492c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fef2c4558f8c98caf3d95b2a84cfd8d1bf34d597f6b0c7cec23413e27dcb5fea3fa07832f2566f859ed23a16910615e5364d4ac48ae66b56b98836c956171ab4d6cf6eacaa0b1c7b5bae407558a7751d01a3312a765d38eabd8f55815ff310aeab71a3e802ad78088941fb5dbf1867d548b10969184d3a56b71bcbae6f31cec20585879ed7da7174b4bbf9d60080b5ba9ecd38473e20631bbfb4d4883fca75cdf7e4727c85f669e3bc8981fa2d7b6d2309793c86ccdb1fa1fb5e352b4298b138bdee80b603ca95ced5a3a6788048b8922b7f679a524270f36e44ba6006303b494e1e3b6efb9878ce9da67942d1b7c5e1d2ff8d337be041c00f85317c66144171419aa7577d5a717d569c0d5dfe9fa5d52bdf449fa4043a4258c98514eb81abda3b05213f1d096f8e63f7ed72fc7d8f7d88464484bc8d75acc26688770b3f68d699383a92037bc5b015f723aa4a0a7d9073"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('2', '-1', '{"c": "mBbLfH%e(Q?p_s?bNQ{<#{uC8TnV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "f6bde997370f33b54e4ca61473609b197879c18e5197548176518b714b119146", "ob": ["a1a1a1a15481495d65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fef2c4558f8c98caf3d95b2a84cfd8d1bf34d597f6b0c7cec23413e27dcb5fea3fa07832f2566f859ed23a16910615e5365295862e7ec0da68e2a3decc06d1d557b6922aae88582b92f00da5cf7b0e22fef74f1ed895c9164c470aea01637f1d726adaa19b9413d51b2e80f9e6c9fdc1f74a576aabbd6f183d9c6de23082f281d85a5dacf3c5aa719d1ad4a8f22aeb281587ea04465e7d99b73daa382f44af6cc6b300698cdd517f908f618cea3c3733ecccdaf649b4cf9355a9d27bc81c64563e6f177b8755b3aff1bd4549d0e572fe44b752fb8d0e96fd76a5be31a4945ed2ceda86d928aba563485661bcd2ea533396be44c4cd918a5e7eef8dfbb10982ed3eaa49b35036ef673a21ff39e26fe62860d8219ee9aa001e094ce1131ebe5aaef33f8e933a9eb6003c4b9a656043a9cd8723d60174e429f998f37e7317f27a05b68ecbb3c6a4944f062875b85d2d7131da"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('3', '1', '{"c": "mBbLYhRor}6CjJ;AsHY+(?Ng47K{2;`qaGoX)GjDl0Y0;`k@_)eA52JATQHQY^bSRimoI>d;+&&X6|8PG@TT7Dsa8|UCWFmT&`tqV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "e166c0aac72bd6715d32ef1601aa5333531ead4620a5f29da2c1de3488f4c567", "ob": ["a1a1a1a1964545e965cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5af190366875ddb8de5697963484becd5ebb1925d11d89ea7ec7949dc519e7596d9091bf1241fafbc5da30c777b671912ce3e0c372c0ed63fd8499fee2dd5e3934ac2f0f6c4f84a698d76e0fa3a60c19f48dd672705751068bdb2846bd092f5122b1260d9e42cefc95d66b9e2376e6b0a982046952cea11611dbc2a12aac49f2111095cdbe647100a9959bd720d03477ed4fd27e062f65d7b7fdbc9a44d2033563b603a4f3cfe1fcda82cf1730c40f01d92141f4611d6b00dfb08298a41533f7369965867af4a9fba4dbaba1467a2e3b923f970a9c08370b45ae608445f8cab67f1085291ebbf194435b0606b49e8771eb5cfc20cb51c79ff273a77b730fc35fc07c62bb21330260a44e3dc132b19cc7e7874643ffc27ccbb41375abe6c302b009"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('4', '2', '{"c": "mBbM0cqpOq3a!Xis`V0^%ic<-7|Nd29<6#ESv$eRAST6LAHL`!o;_oQUU9dZbi{(cCVQ)i*P2|4WORRB5UyozV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "84507797349e30049201af1f46c5e2e47f4660d18726716a0b56427c0b9a021b", "ob": ["a1a1a1a19645458b65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a513c8a4a70ef54eb8ba541efae0e03dd2fe9264d04a5ef27b7da6eb22e3e902476b6a1bb794b88bae7a12f5341b8c5ea582a5b9d244cd2e6a72e1bc9dca08af01b426d7a918cf1b3f809b957b3083b2d4d93d296c038b17bbd3bdc956a2f97d9791368528ddcc6d87c5b1f8a34f54662ea8f3f8d24ebf67ff186e3ed7ac6e03a94132f7d4e7d7061a53d8013aa36caa74bc98dda9106ad9cf37cfed6a7ff7f8e1270bc9d413251eb1f43ed53fc5a15dfa9d53a8e79478de5a9447c5135d8b8325a5fe397fdb796630bc2fa5e06144fdbd49a576a4833bfd0fc1c89246c9fc807064d10bc3b5d18d3d1f1e07fadb1bd719db0310aa8573a5e7baf1e5fa3d4e791313c225c47cf39089e70d71ee1801c10f278b37eb23afebe904e4f89953d4a64"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('5', '5', '{"c": "mBbKeYS{+(Zso@h#h(k`Pj$@178N4lo-=GaVRpm;j@X|!wb;*>4Co2OARQ#xz%i*M8ke;~Gda*nx5p{`|aV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "fd87f32d954e6b8114710c1cb421b25f9c32ff08cf0b69ba68053ef2a32ec854", "ob": ["a1a1a1a19645459e65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5aa803720971383049abfacef1eae1766a2d5bd879296124ac6930a8cdb4dbe1059aabb4b89fc13888fc4b60fa4498fdecb008eb20c7ae868fa21ba4d55cb69a75a30eaa52f5633c290dffbb6453c35c11fb67fef2a62cabb7a5653e3c3719aa5d805f2fdf80eb05ab66baa6b7f7b3956e71bea6fe97aba914e89e96e4fb6fcf1153f5fd64b8217f18836536df493179b93215ad84e36215c80c2eac0446d6ceb8d1bc26bf325fb597b8bce8192876f2121524d00068bb05b97a7965d982022c61c0fc76ecd90a211587ed344554810dcf102902718a5ed127eacbb43d3030d4ecfa9a6e86541675286dcaa330ae210b3e3d8a5a89f436ac2daa80a1e3f2bd84c4772c417fa71863b43d6a4b8e0e77051240fd73bb3b66eb03a70196ef6f7ffcf8"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('6', '10', '{"c": "mBbJ$2dznX$8+hEVm#p*84AqA7WaqRu6P1z-60C!XlFVZGl#oq!c!{5AkID7t=)F>bIS!jr`cENW5zfF#$D)=RI@5fzL6JWpRQ$YV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "9dfab57faa412c407c8b7a8c6f988afc15f9ab25e604fc6542eb84b00f52e2d9", "ob": ["a1a1a1a19645457265cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a3113f16bb322e59b86500da7574b52d298792c385a5d0355b9ccea9127c7f17fae5686334ec7416bd4da7f31ffe18b8dab571880bd0a11d8dadef2b8210123311a855d8db9c3254af31572e6909f2ec36975aa2e2df540fd7e47398b2dbbb911711c241ad9e2f8471b63e8b74adb6e8c1c10291740faebf8e3c58e2100682e750301eb6807e1723a8a0f228cc3af70860d8b2d7d5efbb8ed2ebad31656f9f31cc20fe49139dd0e97bfeac1d0f599d40e24b6222549b0793be4a19c7f8ebac901021a70eff61a0838eff9acec17d78007237034d10a0672cec37205b095eb80480613da7f4d7139241a274750db8a4e0792e1447f5d52887cbdc935e159c8e13107d26d33dca94518bb4693f7fb6e059c1222561255ccb5f33d4ea9fb187721db"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('7', '17', '{"c": "mBbKs&oax}9^^|SIisV#TLyx}7GwgnjEjg#`hWPogd*#F6}hSJWVEy(B19mLgF*=B&)fehTfyWDL@xFRo>7V{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "0ec321b40a8ae7a37bfd557d8451714996b2986447d32d6a6b3ba91492be0bfe", "ob": ["a1a1a1a1964545ee65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a2b59b0c0132c8308a958946dba0cabf8b5ee08e2c8a7d77f9fe513bf5e8e7da0fecb176102b68aee30552310f7d56af4e973dc54cf43e9d83a72ea3fd8988c7263ec26abd96a3f5afa1b7abfe2c4cee5ed7498c6ea3cc189a1cbac98d9044145dc7598710e35a6ba8bae587c1f115296172695b48c26d4a3497a65cfcb98d63f5e91c34ba1a921883152c1541b85a12bea47bf7de15a9ddb01c8699f93850b42f95cf4f7b5ec56e5fa952235804cd306d5e654da2460a403f81db4aa3b6e667bc5315997e7b69f36acf6b2fc8ba68cfabfb846dbbf124db55cae63f4e38fdbe83d7fc54579b5ba0f2f282847c1a3b3fe4235c5dd1da69e3f1b33eeb6c31fe67ce5f333cd797e2d2aa6d702e9236f7c310b1dc3ee682ff09187c3eb73d11aa72a"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('8', '25', '{"c": "mBbKzuk={LFxu?QubtJ*_sx6=Y5HPpN6zXtgdBlV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "3a8ce9251f3ee4904d625b2e79f9eb930c2b3086ae641f934260c9dd3cf0ecb6", "ob": ["a1a1a1a19645454c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a542bc1720f618d33b43760d45700da56fa52e146601e4e87d97b3a10dd585cb3b0bdc9d13f54e63f4e30326b387b0b4003113da12a722073be23eeb6b6f345e5e0d895ddabc5307b4bee23671ea4793812387c8db666050e8a2c634b5e4bc8b243673485cfc669288dec12bebb53dc5ea0cb079bb0af43b4bac6eb9b52853a7326d9528fe05651c3ee412e855694236d4fcd3192daa776e03bac4852f7310146998dfb0cf39e4e1e6cdb0b329b278472036e751b50a0d43e7b11717a856e93d6f73b4ef447b5ad8f4593f1e8bfbbf6d848cd6887dfd44e03ee79b72b39814071a5f70a5622dea85a6fbeec304a61a5ab4d213e959d2da156323385155c412e94b25b9e7399cb5c249506a17ca65c2a3cd2d49a1ebe99357c7707b5b6e4996f65"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('9', '42', '{"c": "mBbK1bx7W;#5vev=wY1!*a6nW7FhjWI3X9$KbVSRXClSn+{r!8n_e@-AhE3r2#bvbwxh}tD#M--AsG_DqD@Tm2IiPaRrA6Os;*^jV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "231bcb9386ea23eb2630335bc3fa7542b4bec54e5251cec202a10c53453f5b65", "ob": ["a1a1a1a19645450665cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a296cf506a6902dcd1010e8ea6daf751d025252d13f2a5f7ca6f5673aa8bda37cae6d0c2ae66eb602ed1a8922741a8a931a00682f7a84e97267b19e4d3458bd5cc5a54d8fb3a793f0627943eba3318311ea33091db781ccd8c630cb8ff1e5b3edeec7022cfe1a06c987e512f5198825eb659c6206fd6f0197de3e791d52dda77c20adac3d21d3b13acb9fb3742121befe2504ed1c5d150799176b175bf2b82a6ebd5224dab890c323cef7b3e5482eb0efaaf1bc2047afc32345c056e95312c37ece65ba637d452035540e95b338058bad657c2e105b120e30ce76dbbec3a145d491eb5513e69c40dd43a88ddeff386644df2fbd167b7bef640041f9cb57a8174d479954e45e82d3edf5be3637301b5a2c1f633565218618edae2fd75e6048bf0a"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('10', '50', '{"c": "mBbLubhR#eRIBRKP`Za3N&ddX7R78Ur?1=??q*`{-W{g@?u})F3=x0CAOaUdoNrgR-V;Hq{g-NbrRAu#Tm_n~lb?6ctYlh&Os-{aV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "4a040d1a9eb01293059eaf834e84822b97de2dd7c6839d8cdae3cf8e0ef1ab81", "ob": ["a1a1a1a19645458c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a0f4a253e2c8c48870919a382d5b81237d26b63103d7fd84d4077b73f7450239161bbb9cc31c2950d950a1f08897773eff96077f025341ee7985c264c4b9647b7e43c0e17900bdb6d4102d3ccf068ce620d26db623f44d9f5893024538009685c1f178793bfc6b8870cedb33b293a6f5c28f353a4bd0a5bc3979f159399058bb21116b011cad86bb1973be07c3976c2ca262d9fad6fb4903e412eda1839eb30c3439ac51e782e3fab35e517ead7c8b630c490a24aaea97677b4d043f429c0ee4e62a3fe71649c12fecfb7cc8473293ec8f9c9faff8e55c7c5bf4a15c6cbfd4ede19ad2d4f7a29d2c059dbe3f040913ff3c755da18a94182f8d58b94e41770f6c3d8013744e11f8890616e56d380116a7eab2e012364d5a270e8f304b9425ea94d"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('11', '100', '{"c": "mBbL!LwVV+Vl8*@^yEs2*qD*T7PpCgPf=vlV?t2SQ$v0`*^g)EmVCOzAcUF(h2<1yqODlK0-}Arf|cwjcIx+e2jMFj0t9PNsjg*iV{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "2fc081842f8545a18689b5eaccca41a935f014ad6d0456a924c4fa8d445833bf", "ob": ["a1a1a1a19645451c65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5a8fcbebfffe2a0e9c5c05d2eebc34586707605468394a274d4ce84c73300754d90e9904f88788c32137c8207791c44b2752c20812a6ca0201db7f752f944616ab76dbbbfb4924ceae1de65e7a1c38a1b65b0574b5f07ef207a90065c42f4fa65a690e1dca4e037288b70e9a822052953c2a89711f3180c48718689db40a8d8b5715bc16bb5660cbe3484bad5f833b7f4eb57a87310f85299b501ce1efcf26903a448a9187330e2b18f2b0751003c0edf6cf0b99d1d494bf047ac3e204b94ae6aa77a85c9a443de97f7918cb5150a9c59367945d7ae89e18e2a62ad23ecd66f7d32c28ca60728db1c5815293616e4ad1de3c2d74dfdfb839216cb8dc4fc4f49269e39fc1d2f122aff77f0b0e1bfab4ef4432a4e0d3368b4366632b880e52ed89ba"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('12', '250', '{"c": "mBbK#R%P_`%atBydMup-mD_H_7Li%m8A|F;dvA>Y_^QvD>ppyX=lMOvAWh@0st_N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "5f1bd84cff5e65d213b7a5c4005f442d17f169e9e92800598adadf37cff71de6", "ob": ["a1a1a1a19645452e65cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f1888b615f7526dbcd21f213f1891df5af19c5d189cde4caa7b19cbbb583b33b8dd7bfb4df5f4508c38e1c94e477d128d1873781a3428ec43a7ccc6f55a18d784692f258c2f33bef5cb0997fe486e3b40a5a128db7e5b09db5627ee46a55258e928aa135e0285e764a5ee6aa72311bd810dae9ae2747ce6c4ec2bac140927eaa992cc85d8e9b3c0157cb4a984f58b4fd21ca5e4898cf6a61e7de5bdaa9e045ab546afb65b509f835b023439a2c962312872d38c92357cf83e3475e48450f59197edb6039b25b5abc87e6e58fcbcc42a7b4cdad8d1df6886ba577957207921b53ba1e53830156f116d751ca64138f8936e53a7831ef5fa0819aeb78243579a15383cd55d3d5449b78b01019a35ca9c70d083a2f302f3c3ef7c7ad4ef0a4e8457c552c0d16e43fa7d103ae0171644e00b88"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('13', '1000', '{"c": "mBbKDm$u6vOxUbP-P&WfMw8RT7QXc>s7fP*`bW>P>jPt179vf<5p1l)AiL0!$;tL1C}3Hyy*?tEPN2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "72a100399ebc0cbbf13b13384fbd0a06c7e1c9431ac1204888fc3ee699729f93", "ob": ["a1a1a1a19645d38465cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f6e67f6694308e6798a670a870e41a7dbd333f347fc16f34e9eb2514418afc478cfb9e0893b731832318fe5c01c734eeb58edcb2a49c210e8c80496f45c65ce55bd15fc702bd1a8a50872f2623047bb6138a88291fa548ea837d401d7315be08ae3d725cc9aa0aff3717a6e1254912daa3db212a818b2825055eec4eb6dbc01dc490a727b86e82e51f59cf03c2a76386509d1295d7c76f0474f3216fe77a107a23e88246848bfb86f0494af82b4faf91cabb351db9b568da3eb64ce360d68db724b347da4d844c47439f33585e9f416114ba73973794f2374ed18b0445ed23e6cec6584c23edc1de19e637c4cff55f5203bc104d360ac02b634c11443f110322a1eab498d5ec92abb8c89231f5b3bf10e413fdcbf4ffe41678b66f5a8550c8cce481bbb7e8eae0b6a08ce016f710c4c50"]}'::jsonb); -INSERT INTO encrypted_int4_plaintext (id, plaintext, payload) VALUES ('14', '9999', '{"c": "mBbMDBL-q!6!rQwR5A43Qhsg37M6J*x1=!!pwQ*)It`0cHfasj9oF;2AfOcWDJ#~`f+FVKw-a876;D&NwU@urcv|D7XcB8Ad9Gz{V{&N2h#1KJ4(b;4eKp?Aiw4IQd+|>", "i": {"c": "encrypted_int4", "t": "bench_int4"}, "v": 2, "hm": "805123862649caceb6c13fd53c3ea9f9e0888986cdc951cf50a820632aafb2d8", "ob": ["a1a1a1a19645b89065cecfeb3421313bec09225b7928c0122f3a5d81152b1289903ac6485ca9843c2de97fa9663be406a80b7d54a06ab01a16abf5fdcab6b6f5a2a48ba6fd34c0fe6bc8785683b327c03f13eb22dad68591e08a40c49634b09f96c8ac98661a5d2f68b1b4ca492400160ebc035ff9744a665420b36ba9eac70df9c96e41e3cc812fd5b754a8970b2f6aab01a2b4285f7aa61290f6aadf52c5c2bd986a2d61af3fe3feba900062db9674416dc29d4d4dd5e7f943a2bf458b9a4baefde3410fd713311c0d1eab7b8039922cb0721969b6fad58986a692110ca39e2293b4dc8022c8518d0bd4615e59897d9776875df6cdb835fbc1cfc69c481d5f48602a4e6da42f953f55add7b1f920d4683747eee2d0f99a9a0ad40827375edfc85ee55cf6acb4bcdc6fc5e2a69a565e7185c86b94a86acdf36be9abd7c843373d0906fd59ec38cf9d2dfd0dd8e0bd85e7525c5c02c8c4d4fb0e6fe5a211b89383a499cbb63820daccd5c37d06f4cf9faa0796eaaef8ae34617ceead72f6ef37cf0c5b5de19234e04bc3e53c92a4f6512ddd0b799fb59839"]}'::jsonb); diff --git a/tests/sqlx/src/fixtures/driver.rs b/tests/sqlx/src/fixtures/driver.rs new file mode 100644 index 00000000..1b973bfa --- /dev/null +++ b/tests/sqlx/src/fixtures/driver.rs @@ -0,0 +1,406 @@ +//! `FixtureSpec::run()` — the generation driver. +//! +//! mise owns the containers; this owns the data. The driver assumes +//! `mise run proxy:up` has started `cipherstash-proxy` and that the +//! generation Postgres has EQL installed. Errors are `anyhow` with +//! `.context(...)` — a generator is a developer tool; a clear crash beats a +//! partial fixture. +//! +//! The `public._fixture_` working table is transient plumbing: `.run()` +//! creates it, encrypts into it, renders the committed rows from it, then +//! drops it before returning. The drop runs unconditionally once the table +//! exists — on success *and* on any returned error: `run` captures the +//! post-schema result, drops the table, and only then propagates a failure. +//! So the table never outlives a *returned* run; only a hard crash (panic / +//! `kill`) can leak it, and the next run's start-of-schema +//! `DROP TABLE IF EXISTS` reclaims that case. + +use std::path::PathBuf; +use std::process::Command; +use std::time::Duration; + +use anyhow::{anyhow, Context, Result}; +use sqlx::postgres::PgConnectOptions; +use sqlx::{ConnectOptions, Connection, PgConnection, Row}; + +use super::eql_plaintext::EqlPlaintext; +use super::spec::FixtureSpec; + +/// `cipherstash-proxy` — the fixed container_name from +/// `tests/docker-compose.proxy.yml`. +const PROXY_CONTAINER: &str = "cipherstash-proxy"; + +/// Bag of Rust-type bounds required of a fixture's plaintext value `T`. +/// Collapses the long `where` clause on `impl FixtureSpec<'a, T>` to a single +/// alias; the blanket impl below makes it auto-applied to any `T` that +/// already satisfies the bounds. +pub trait FixtureValue: + EqlPlaintext + + Copy + + Send + + Sync + + for<'q> sqlx::Encode<'q, sqlx::Postgres> + + sqlx::Type +{ +} + +impl FixtureValue for T where + T: EqlPlaintext + + Copy + + Send + + Sync + + for<'q> sqlx::Encode<'q, sqlx::Postgres> + + sqlx::Type +{ +} + +/// Driver connection options, parsed once from the environment at the start +/// of `run`. `direct` is the unmediated Postgres connection (DDL + +/// rendering); `proxy` is the Proxy-mediated connection (encrypted inserts). +struct DriverConfig { + direct: PgConnectOptions, + proxy: PgConnectOptions, +} + +impl DriverConfig { + /// Build connection options from env vars, defaulting to the + /// `mise.toml` `[env]` values. Port parses are strict — a malformed + /// `POSTGRES_PORT` or `PROXY_PORT` surfaces as an `anyhow::Error` with + /// the offending value, matching the rest of the driver's error story. + fn from_env() -> Result { + let host = env_or("POSTGRES_HOST", "localhost"); + let user = env_or("POSTGRES_USER", "cipherstash"); + let password = env_or("POSTGRES_PASSWORD", "password"); + let database = env_or("POSTGRES_DB", "cipherstash"); + let port = parse_port_env("POSTGRES_PORT", 7432)?; + let proxy_port = parse_port_env("PROXY_PORT", 6432)?; + + let direct = PgConnectOptions::new() + .host(&host) + .port(port) + .username(&user) + .password(&password) + .database(&database); + + // Proxy runs on the host at PROXY_PORT (default 6432); same credentials. + let proxy = direct.clone().port(proxy_port); + + Ok(Self { direct, proxy }) + } +} + +fn env_or(key: &str, default: &str) -> String { + std::env::var(key).unwrap_or_else(|_| default.to_string()) +} + +fn parse_port_env(key: &str, default: u16) -> Result { + match std::env::var(key) { + Ok(value) => value + .parse::() + .with_context(|| format!("{key}={value:?} must be a valid u16")), + Err(_) => Ok(default), + } +} + +/// Restart Proxy (so it reloads the new encrypt config) and poll until it +/// accepts a connection. On timeout, dump `docker logs` and fail. +async fn restart_proxy_and_wait(proxy_options: &PgConnectOptions) -> Result<()> { + let status = Command::new("docker") + .args(["restart", PROXY_CONTAINER]) + .status() + .context("failed to spawn `docker restart`")?; + if !status.success() { + anyhow::bail!("`docker restart {PROXY_CONTAINER}` exited non-zero"); + } + + for _ in 0..60 { + if let Ok(mut conn) = proxy_options.clone().connect().await { + if sqlx::query("SELECT 1").execute(&mut conn).await.is_ok() { + let _ = conn.close().await; + return Ok(()); + } + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + + // `docker logs` sends the container's stdout to our stdout and its stderr + // to our stderr; capture both so the diagnostic is non-empty regardless of + // which stream the Proxy logs to. + let logs = Command::new("docker") + .args(["logs", "--tail", "40", PROXY_CONTAINER]) + .output() + .map(|o| { + format!( + "{}{}", + String::from_utf8_lossy(&o.stdout), + String::from_utf8_lossy(&o.stderr), + ) + }) + .unwrap_or_default(); + Err(anyhow!( + "Proxy did not become ready within 60s after restart\n\ + === {PROXY_CONTAINER} logs ===\n{logs}" + )) +} + +/// Absolute path to `tests/sqlx/fixtures/.sql`. Resolved from +/// `CARGO_MANIFEST_DIR` (the `tests/sqlx` crate root) so the path is correct +/// regardless of the process working directory. +fn fixture_script_path(filename: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("fixtures") + .join(filename) +} + +impl<'a, T> FixtureSpec<'a, T> +where + T: FixtureValue, +{ + /// Generate and write `tests/sqlx/fixtures/.sql`. + /// + /// The production entry point. Parses the env-driven `DriverConfig` + /// once, opens a direct Postgres connection, then delegates the + /// schema + teardown orchestration to `run_with`, supplying + /// `insert_through_proxy` as the closure. After `run_with` returns the + /// rendered INSERT lines, this method composes them with + /// `fixture_script_preamble` and writes the committed script to disk. + pub async fn run(&self) -> Result<()> { + let config = DriverConfig::from_env()?; + + let mut direct = config + .direct + .clone() + .connect() + .await + .context("connecting to Postgres (direct)")?; + + let lines = self + .run_with(&mut direct, || self.insert_through_proxy(&config.proxy)) + .await?; + + let _ = direct.close().await; + + let mut script = self.fixture_script_preamble(); + for line in &lines { + script.push_str(line); + script.push('\n'); + } + + let path = fixture_script_path(&self.script_filename()); + std::fs::write(&path, script) + .with_context(|| format!("writing fixture script {}", path.display()))?; + println!("wrote {} ({} rows)", path.display(), self.values().len()); + Ok(()) + } + + /// Restart Proxy so it picks up the new `add_search_config`, then open a + /// Proxy connection and insert each plaintext value into the working + /// table. Proxy intercepts each insert and writes the encrypted JSONB + /// composite into `payload`. The production insert step extracted from + /// `run`'s closure for skim-ability. + async fn insert_through_proxy(&self, proxy_options: &PgConnectOptions) -> Result<()> { + restart_proxy_and_wait(proxy_options).await?; + + let mut proxy = proxy_options + .clone() + .connect() + .await + .context("connecting to Proxy")?; + let working = self.working_table(); + for (i, value) in self.values().iter().enumerate() { + let id = (i as i64) + 1; + let insert = + format!("INSERT INTO {working} (id, plaintext, payload) VALUES ($1, $2, $3)"); + sqlx::query(&insert) + .bind(id) + .bind(*value) + .bind(*value) + .execute(&mut proxy) + .await + .with_context(|| format!("inserting value #{id} through Proxy"))?; + } + let _ = proxy.close().await; + Ok(()) + } + + /// Orchestrates the schema-apply / insert / render / teardown pipeline + /// against a caller-supplied `direct` connection, with the insert step + /// pluggable via `insert_rows`. The pipeline is: + /// + /// 1. Check the spec is complete. + /// 2. Apply `working_schema_sql` on `direct`. After this succeeds the + /// `public._fixture_` table exists and MUST be dropped before + /// return, whatever happens next. + /// 3. Run `insert_rows()`. Its result is captured (not `?`-propagated) + /// so the drop in step 5 always runs. + /// 4. If the inserter succeeded, render the committed rows via + /// `render_rows_sql` on `direct`. Skipped on inserter error. + /// 5. Drop the working table on `direct` unconditionally. + /// 6. Propagate failures in causal order: inserter error first + /// (root cause), then render, then drop. + /// + /// `run()` calls this with `insert_through_proxy`. Tests call it with + /// closures that insert hand-crafted `eql_v2_encrypted` composite + /// literals directly (no Proxy required), or with closures that return + /// `Err` to exercise the teardown contract. + /// + /// Private by design: this is a test seam, not a public API. Other + /// fixtures must go through `run`. + async fn run_with( + &self, + direct: &mut PgConnection, + insert_rows: F, + ) -> Result> + where + F: FnOnce() -> Fut + Send, + Fut: std::future::Future> + Send, + { + self.check_complete().context("invalid FixtureSpec")?; + + sqlx::raw_sql(&self.working_schema_sql()) + .execute(&mut *direct) + .await + .context("applying working-table schema")?; + + let insert_result = insert_rows().await; + let render_result = if insert_result.is_ok() { + sqlx::query(&self.render_rows_sql()) + .fetch_all(&mut *direct) + .await + .context("rendering fixture rows") + } else { + // Empty placeholder — never observed; `insert_result?` below short-circuits. + Ok(Vec::new()) + }; + + let working = self.working_table(); + let drop_result = sqlx::raw_sql(&format!("DROP TABLE IF EXISTS public.{working};")) + .execute(&mut *direct) + .await; + + insert_result?; + let rows = render_result?; + drop_result.context("dropping the working table")?; + + rows.iter() + .map(|r| r.try_get::(0).context("reading rendered INSERT")) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sqlx::PgPool; + + /// A small int4 spec for driver tests. Three values keeps the test fast; + /// the driver's orchestration is independent of value count. + fn small_spec(name: &'static str) -> FixtureSpec<'static, i32> { + const VALUES: &[i32] = &[-1, 1, 42]; + FixtureSpec::new(name) + .with_index("unique") + .with_index("ore") + .with_column_type("jsonb") + .with_values(VALUES) + } + + #[sqlx::test] + async fn run_with_renders_committed_rows_and_drops_working_table(pool: PgPool) -> Result<()> { + let spec = small_spec("driver_test_a"); + let working = spec.working_table(); + let working_for_closure = working.clone(); + let pool_for_closure = pool.clone(); + + let mut conn = pool.acquire().await?; + + let lines = spec + .run_with(&mut *conn, move || async move { + // Working table should exist while the closure runs. + let mut c = pool_for_closure.acquire().await?; + let exists: Option = sqlx::query_scalar(&format!( + "SELECT to_regclass('public.{working_for_closure}')::text" + )) + .fetch_one(&mut *c) + .await?; + assert!( + exists.is_some(), + "working table should exist inside the closure" + ); + + for (i, value) in [-1i32, 1, 42].iter().enumerate() { + let id = (i as i64) + 1; + let insert = format!( + "INSERT INTO public.{working_for_closure} \ + (id, plaintext, payload) \ + VALUES ($1, $2, ROW($3::jsonb)::public.eql_v2_encrypted)" + ); + sqlx::query(&insert) + .bind(id) + .bind(*value) + .bind( + r#"{"v":2,"c":"x","i":{"t":"_fixture_driver_test_a","c":"payload"},"hm":"x","ob":["1"]}"#, + ) + .execute(&mut *c) + .await?; + } + Ok(()) + }) + .await?; + + assert_eq!(lines.len(), 3, "one rendered INSERT per inserted row"); + for line in &lines { + assert!( + line.starts_with( + "INSERT INTO fixtures.driver_test_a (id, plaintext, payload) VALUES (" + ), + "rendered line should target the committed table: {line}" + ); + } + + let after: Option = + sqlx::query_scalar(&format!("SELECT to_regclass('public.{working}')::text")) + .fetch_one(&pool) + .await?; + assert!( + after.is_none(), + "working table should be dropped after run_with returns" + ); + + Ok(()) + } + + #[sqlx::test] + async fn run_with_drops_working_table_on_inserter_error(pool: PgPool) -> Result<()> { + let spec = small_spec("driver_test_b"); + let working = spec.working_table(); + + let mut conn = pool.acquire().await?; + + let result = spec + .run_with(&mut *conn, || async { + anyhow::bail!("forced failure for test") + }) + .await; + + assert!( + result.is_err(), + "run_with should propagate the inserter error" + ); + let err_msg = format!("{:#}", result.unwrap_err()); + assert!( + err_msg.contains("forced failure for test"), + "error chain should contain the forced failure: {err_msg}" + ); + + let after: Option = + sqlx::query_scalar(&format!("SELECT to_regclass('public.{working}')::text")) + .fetch_one(&pool) + .await?; + assert!( + after.is_none(), + "working table should be dropped even on inserter error" + ); + + Ok(()) + } +} diff --git a/tests/sqlx/src/fixtures/eql_plaintext.rs b/tests/sqlx/src/fixtures/eql_plaintext.rs new file mode 100644 index 00000000..e8c17a22 --- /dev/null +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -0,0 +1,96 @@ +//! Maps a Rust plaintext type `T` to its EQL search-config cast and the SQL +//! type of the `plaintext` column. +//! +//! `Cast` and `PlaintextSqlType` are newtypes with private fields; the only +//! way to obtain one is via the predeclared constants on each type. That +//! makes the EQL allowlist structural — a `T::CAST` is, by construction, a +//! value EQL accepts. The trait is sealed so external crates cannot add +//! impls that bypass this guarantee. + +use std::fmt; + +/// The `cast_as` argument for `eql_v2.add_search_config`. The field is +/// private so the allowlist is the set of `pub const`s below. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Cast(&'static str); + +impl Cast { + pub const TEXT: Cast = Cast("text"); + pub const INT: Cast = Cast("int"); + pub const SMALL_INT: Cast = Cast("small_int"); + pub const BIG_INT: Cast = Cast("big_int"); + pub const REAL: Cast = Cast("real"); + pub const DOUBLE: Cast = Cast("double"); + pub const BOOLEAN: Cast = Cast("boolean"); + pub const DATE: Cast = Cast("date"); + pub const JSONB: Cast = Cast("jsonb"); + pub const JSON: Cast = Cast("json"); + pub const FLOAT: Cast = Cast("float"); + pub const DECIMAL: Cast = Cast("decimal"); + pub const TIMESTAMP: Cast = Cast("timestamp"); + + pub fn as_str(&self) -> &'static str { + self.0 + } +} + +impl fmt::Display for Cast { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.0) + } +} + +/// The SQL type for the `plaintext` oracle column. As with `Cast`, the only +/// way to construct one is via the predeclared constants below. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PlaintextSqlType(&'static str); + +impl PlaintextSqlType { + pub const INTEGER: PlaintextSqlType = PlaintextSqlType("integer"); + + pub fn as_str(&self) -> &'static str { + self.0 + } +} + +impl fmt::Display for PlaintextSqlType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.0) + } +} + +mod sealed { + pub trait Sealed {} + impl Sealed for i32 {} +} + +/// A Rust type usable as a fixture `plaintext` value, carrying its EQL cast +/// and the SQL type of the `plaintext` column. Sealed; only this crate may +/// add impls. +pub trait EqlPlaintext: sealed::Sealed { + const CAST: Cast; + const PLAINTEXT_SQL_TYPE: PlaintextSqlType; +} + +impl EqlPlaintext for i32 { + const CAST: Cast = Cast::INT; + const PLAINTEXT_SQL_TYPE: PlaintextSqlType = PlaintextSqlType::INTEGER; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn i32_casts_to_int() { + assert_eq!(::CAST.as_str(), "int"); + } + + #[test] + fn i32_plaintext_sql_type_is_integer() { + assert_eq!( + ::PLAINTEXT_SQL_TYPE.as_str(), + "integer" + ); + } +} diff --git a/tests/sqlx/src/fixtures/eql_v2_int4.rs b/tests/sqlx/src/fixtures/eql_v2_int4.rs new file mode 100644 index 00000000..f32e93a5 --- /dev/null +++ b/tests/sqlx/src/fixtures/eql_v2_int4.rs @@ -0,0 +1,51 @@ +//! The `eql_v2_int4` fixture — the framework's reference example and proof. +//! +//! 14 integers spanning a negative boundary and small/medium/large/extreme +//! magnitudes. The generated `tests/sqlx/fixtures/eql_v2_int4.sql` is a plain +//! `jsonb`-payload table with no EQL dependency; #225 layers the `eql_v2_int4` +//! domain on top by casting `payload` per query. + +use super::spec::FixtureSpec; + +/// 14 values: a negative boundary plus small/medium/large/extreme magnitudes, +/// chosen so range pivots produce distinct cardinalities. +const VALUES: &[i32] = &[-100, -1, 1, 2, 5, 10, 17, 25, 42, 50, 100, 250, 1000, 9999]; + +/// The complete fixture definition. `.with_index("unique")` drives `=` / `<>` +/// (HMAC); `.with_index("ore")` drives `<` `<=` `>` `>=` (ORE block terms). +pub fn spec() -> FixtureSpec<'static, i32> { + FixtureSpec::new("eql_v2_int4") + .with_index("unique") + .with_index("ore") + .with_column_type("jsonb") + .with_values(VALUES) +} + +/// The generator. Gated by `fixture-gen` so `cargo test` never compiles it; +/// `#[ignore]` is a second guard. Run via `mise run fixture:generate eql_v2_int4`. +#[cfg(feature = "fixture-gen")] +#[tokio::test] +#[ignore = "generator — run via `mise run fixture:generate`"] +async fn generate() -> anyhow::Result<()> { + spec().run().await +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn spec_is_complete() { + assert!(spec().check_complete().is_ok()); + } + + #[test] + fn spec_has_14_values() { + assert_eq!(spec().values().len(), 14); + } + + #[test] + fn spec_includes_negative_values() { + assert!(spec().values().iter().any(|&v| v < 0)); + } +} diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs new file mode 100644 index 00000000..38504ce5 --- /dev/null +++ b/tests/sqlx/src/fixtures/mod.rs @@ -0,0 +1,19 @@ +//! Type-checked fixture generation framework. +//! +//! A fixture is one Rust file under `src/fixtures/` declaring a `FixtureSpec`. +//! `FixtureSpec::run()` generates the committed SQLx fixture script +//! `tests/sqlx/fixtures/.sql`. + +pub mod validation; + +pub mod eql_plaintext; + +pub use eql_plaintext::EqlPlaintext; + +pub mod spec; + +pub use spec::FixtureSpec; + +pub mod driver; + +pub mod eql_v2_int4; diff --git a/tests/sqlx/src/fixtures/spec.rs b/tests/sqlx/src/fixtures/spec.rs new file mode 100644 index 00000000..0f1ea41c --- /dev/null +++ b/tests/sqlx/src/fixtures/spec.rs @@ -0,0 +1,364 @@ +//! `FixtureSpec` — the type-checked fixture plug-in contract. +//! +//! `T` is the Rust plaintext type, inferred from `.with_values()`. Everything +//! not derivable — the indexes, the committed `payload` column type, the +//! data — is explicit. The fixture name drives every path by convention: +//! - table `fixtures.` +//! - working table `public._fixture_` +//! - script `tests/sqlx/fixtures/.sql` +//! - SQLx ref `scripts("")` +//! +//! Token-safety is enforced **at construction**: `new`, `.with_index`, and +//! `.with_column_type` each validate via the newtype `TryFrom` and **panic** +//! on a violation, so the builder stays a fluent chain (no `Result`, no +//! `?`). Because the spec stores validated newtypes (`FixtureIdentifier`, +//! `ColumnType`) and uses them via `Display` in the SQL renderers, an +//! unvalidated `&str` cannot reach a generated SQL string. `T::CAST` / +//! `T::PLAINTEXT_SQL_TYPE` are typed const newtypes (`Cast`, +//! `PlaintextSqlType`) — their allowlists are structural, so no runtime +//! check is needed. `check_complete()` covers only the completeness checks +//! (non-empty indexes/values) that the builder cannot make until the chain +//! is finished. + +use super::eql_plaintext::EqlPlaintext; +use super::validation::{ColumnType, FixtureIdentifier}; + +/// A fully specified fixture, ready to `.run()`. +pub struct FixtureSpec<'a, T> { + name: FixtureIdentifier, + indexes: Vec, + column_type: ColumnType, + values: &'a [T], +} + +impl<'a, T> FixtureSpec<'a, T> { + /// Start a spec. `name` must match `^[a-z][a-z0-9_]*$` — it becomes a SQL + /// identifier and a filename. Other fields take defaults until set: + /// `column_type` defaults to `"jsonb"`, `indexes`/`values` to empty. + /// + /// # Panics + /// Panics if `name` is not a valid identifier. + pub fn new(name: &str) -> Self { + let name = FixtureIdentifier::try_from(name).unwrap_or_else(|e| panic!("fixture name: {e}")); + let column_type = ColumnType::try_from("jsonb") + .expect("default column type \"jsonb\" must be in the allowlist"); + Self { + name, + indexes: Vec::new(), + column_type, + values: &[], + } + } + + /// Add a search index (`"unique"`, `"ore"`, ...). Chainable. + /// + /// # Panics + /// Panics if `index_name` is not a valid identifier. + pub fn with_index(mut self, index_name: &str) -> Self { + let id = FixtureIdentifier::try_from(index_name).unwrap_or_else(|e| panic!("index name: {e}")); + self.indexes.push(id); + self + } + + /// Set the committed `payload` column SQL type. Defaults to `"jsonb"`. + /// + /// # Panics + /// Panics if `column_type` is not in `validation::ALLOWED_COLUMN_TYPES`. + pub fn with_column_type(mut self, column_type: &str) -> Self { + self.column_type = + ColumnType::try_from(column_type).unwrap_or_else(|e| panic!("column type: {e}")); + self + } + + /// Set the plaintext value list. `T` is inferred and bound here, so this + /// is where `T::CAST` and `T::PLAINTEXT_SQL_TYPE` become known. Their + /// allowlists are structural (typed-const newtypes), so no runtime + /// validation is needed at this point. + pub fn with_values(mut self, values: &'a [T]) -> Self + where + T: EqlPlaintext, + { + self.values = values; + self + } + + // ----- accessors used by SQL rendering / the driver ----- + + pub fn name(&self) -> &str { + self.name.as_str() + } + + pub fn indexes(&self) -> &[FixtureIdentifier] { + &self.indexes + } + + pub fn column_type(&self) -> &ColumnType { + &self.column_type + } + + /// The plaintext value slice. + pub fn values(&self) -> &[T] { + self.values + } + + /// `fixtures.` — the committed fixture table. + pub fn fixture_table(&self) -> String { + format!("fixtures.{}", self.name) + } + + /// `_fixture_` — the transient working table (unqualified `public`). + pub fn working_table(&self) -> String { + format!("_fixture_{}", self.name) + } + + /// `.sql` — the generated script filename (relative to fixtures dir). + pub fn script_filename(&self) -> String { + format!("{}.sql", self.name) + } + + /// SQL for the transient working table on the generation database. + /// `id BIGINT PRIMARY KEY`, `plaintext` as the SQL type for `T`, and + /// `payload eql_v2_encrypted` so Proxy encrypts inserts. Per index: + /// an idempotent `remove_search_config` guarded by `WHERE EXISTS`, then + /// `add_search_config`. Every `add_search_config` argument is a quoted + /// string literal — the table/column names are fixed literals, and the + /// index name and cast are validated tokens (`FixtureIdentifier` / `Cast`). + /// + /// The leading `DROP TABLE IF EXISTS` is belt-and-suspenders: a normal run + /// drops the working table itself at the end of `run()`, so this only + /// matters when a prior run crashed before its own teardown. + pub fn working_schema_sql(&self) -> String + where + T: EqlPlaintext, + { + let working = self.working_table(); + let mut sql = format!( + "DROP TABLE IF EXISTS public.{working};\n\ + CREATE TABLE public.{working} (\n \ + id BIGINT PRIMARY KEY,\n \ + plaintext {plaintext_type} NOT NULL,\n \ + payload eql_v2_encrypted\n);\n", + plaintext_type = T::PLAINTEXT_SQL_TYPE, + ); + for ix in &self.indexes { + sql.push_str(&format!( + "SELECT eql_v2.remove_search_config('{working}', 'payload', '{ix}')\n \ + WHERE EXISTS (\n \ + SELECT 1 FROM public.eql_v2_configuration c\n \ + WHERE c.data #> '{{tables,{working},payload,indexes,{ix}}}' IS NOT NULL\n );\n", + )); + sql.push_str(&format!( + "SELECT eql_v2.add_search_config('{working}', 'payload', '{ix}', '{cast}');\n", + cast = T::CAST, + )); + } + sql + } + + /// The committed fixture script's header + schema + DDL, up to (not + /// including) the rendered INSERT rows. The driver appends the INSERTs. + /// `payload` uses the committed `column_type` (`jsonb` for #224), not + /// `eql_v2_encrypted`; `plaintext` uses the SQL type for `T`. + pub fn fixture_script_preamble(&self) -> String + where + T: EqlPlaintext, + { + format!( + "-- AUTO-GENERATED by `mise run fixture:generate {name}`.\n\ + -- DO NOT EDIT BY HAND. Re-run the generator to refresh.\n\ + --\n\ + -- Encrypted via CipherStash Proxy (HMAC + ORE block terms).\n\ + -- A SQLx fixture script: opt in with\n\ + -- #[sqlx::test(fixtures(path = \"../fixtures\", scripts(\"{name}\")))]\n\ + \n\ + CREATE SCHEMA IF NOT EXISTS fixtures;\n\ + DROP TABLE IF EXISTS {table};\n\ + CREATE TABLE {table} (\n \ + id BIGINT PRIMARY KEY,\n \ + plaintext {plaintext_type} NOT NULL,\n \ + payload {column_type} NOT NULL\n);\n\n", + name = self.name, + table = self.fixture_table(), + plaintext_type = T::PLAINTEXT_SQL_TYPE, + column_type = self.column_type, + ) + } + + /// SQL run on the *direct* connection to render each working-table row as + /// a committed INSERT. `format('%L', ...)` does server-side literal + /// escaping; row values never pass through Rust string interpolation. + /// `(payload).data::text` unwraps the `eql_v2_encrypted` composite to the + /// JSONB text that the committed `jsonb` column stores. + pub fn render_rows_sql(&self) -> String { + format!( + "SELECT format(\n \ + 'INSERT INTO {table} (id, plaintext, payload) VALUES (%L, %L, %L::{column_type});',\n \ + id, plaintext, (payload).data::text\n) \ + FROM public.{working} ORDER BY id", + table = self.fixture_table(), + column_type = self.column_type, + working = self.working_table(), + ) + } + + /// Check the spec is *complete*: it has at least one index and at least + /// one value. These cannot be checked at construction — the builder does + /// not know when the chain is finished — so the driver calls this before + /// generating any SQL. Token safety is already guaranteed by the + /// `FixtureIdentifier`/`ColumnType` newtypes; this method covers only what + /// construction cannot. + pub fn check_complete(&self) -> anyhow::Result<()> { + if self.indexes.is_empty() { + anyhow::bail!("fixture {:?} declares no indexes", self.name.as_str()); + } + if self.values.is_empty() { + anyhow::bail!("fixture {:?} has no values", self.name.as_str()); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn int4_spec() -> FixtureSpec<'static, i32> { + const VALUES: &[i32] = &[-1, 1, 42]; + FixtureSpec::new("eql_v2_int4") + .with_index("unique") + .with_index("ore") + .with_column_type("jsonb") + .with_values(VALUES) + } + + #[test] + fn derives_paths_from_the_name() { + let s = int4_spec(); + assert_eq!(s.fixture_table(), "fixtures.eql_v2_int4"); + assert_eq!(s.working_table(), "_fixture_eql_v2_int4"); + assert_eq!(s.script_filename(), "eql_v2_int4.sql"); + } + + #[test] + fn records_indexes_in_order() { + let s = int4_spec(); + let names: Vec<&str> = s.indexes().iter().map(FixtureIdentifier::as_str).collect(); + assert_eq!(names, vec!["unique", "ore"]); + } + + #[test] + fn column_type_defaults_to_jsonb() { + const V: &[i32] = &[1]; + let s = FixtureSpec::new("x").with_index("unique").with_values(V); + assert_eq!(s.column_type().as_str(), "jsonb"); + } + + #[test] + fn valid_spec_passes_completeness_check() { + assert!(int4_spec().check_complete().is_ok()); + } + + #[test] + #[should_panic(expected = "is not a valid identifier")] + fn validation_rejects_a_bad_name() { + // A bad name panics at construction, before the chain continues. + let _ = FixtureSpec::<'static, i32>::new("Bad-Name"); + } + + #[test] + #[should_panic(expected = "is not in the allowlist")] + fn validation_rejects_a_non_allowlisted_column_type() { + // A non-allowlisted column type panics in `.with_column_type()`. + let _ = FixtureSpec::<'static, i32>::new("x").with_column_type("text"); + } + + #[test] + #[should_panic(expected = "is not a valid identifier")] + fn validation_rejects_a_bad_index_name() { + // A bad index name panics in `.with_index()`. + let _ = FixtureSpec::<'static, i32>::new("x").with_index("BAD IX"); + } + + #[test] + fn completeness_rejects_a_spec_with_no_indexes() { + const V: &[i32] = &[1]; + let s = FixtureSpec::new("x").with_values(V); + assert!(s.check_complete().is_err()); + } + + #[test] + fn completeness_rejects_a_spec_with_no_values() { + const V: &[i32] = &[]; + let s = FixtureSpec::new("x").with_index("unique").with_values(V); + assert!(s.check_complete().is_err()); + } + + #[test] + fn working_schema_sql_drops_and_creates_the_working_table() { + let sql = int4_spec().working_schema_sql(); + assert!(sql.contains("DROP TABLE IF EXISTS public._fixture_eql_v2_int4;")); + assert!(sql.contains("CREATE TABLE public._fixture_eql_v2_int4 (")); + assert!(sql.contains("id BIGINT PRIMARY KEY")); + assert!(sql.contains("plaintext integer NOT NULL")); + // The working table's payload is eql_v2_encrypted so Proxy encrypts inserts. + assert!(sql.contains("payload eql_v2_encrypted")); + } + + #[test] + fn working_schema_sql_configures_each_index_idempotently() { + let sql = int4_spec().working_schema_sql(); + // remove first (idempotent), then add, for both indexes. + assert!(sql + .contains("eql_v2.remove_search_config('_fixture_eql_v2_int4', 'payload', 'unique')")); + assert!(sql.contains( + "eql_v2.add_search_config('_fixture_eql_v2_int4', 'payload', 'unique', 'int')" + )); + assert!( + sql.contains("eql_v2.remove_search_config('_fixture_eql_v2_int4', 'payload', 'ore')") + ); + assert!(sql + .contains("eql_v2.add_search_config('_fixture_eql_v2_int4', 'payload', 'ore', 'int')")); + } + + #[test] + fn working_schema_sql_uses_the_t_cast_not_the_column_type() { + // payload column-type is jsonb, but the EQL cast is i32::CAST = "int". + let sql = int4_spec().working_schema_sql(); + assert!(sql.contains("'int')")); // cast_as argument + assert!(!sql.contains("'jsonb')")); // jsonb is the committed type, not the cast + } + + #[test] + fn fixture_script_preamble_renders_the_committed_table() { + let preamble = int4_spec().fixture_script_preamble(); + // header + assert!(preamble.contains("AUTO-GENERATED")); + assert!(preamble.contains("DO NOT EDIT BY HAND")); + assert!(preamble.contains("mise run fixture:generate eql_v2_int4")); + assert!(preamble.contains("HMAC + ORE block terms")); + // schema + table in the fixtures schema, jsonb payload + assert!(preamble.contains("CREATE SCHEMA IF NOT EXISTS fixtures;")); + assert!(preamble.contains("DROP TABLE IF EXISTS fixtures.eql_v2_int4;")); + assert!(preamble.contains("CREATE TABLE fixtures.eql_v2_int4 (")); + assert!(preamble.contains("id BIGINT PRIMARY KEY")); + assert!(preamble.contains("plaintext integer NOT NULL")); + assert!(preamble.contains("payload jsonb NOT NULL")); + } + + #[test] + fn fixture_script_preamble_uses_the_committed_column_type() { + // The committed table uses .with_column_type(), NOT eql_v2_encrypted. + let preamble = int4_spec().fixture_script_preamble(); + assert!(!preamble.contains("eql_v2_encrypted")); + } + + #[test] + fn render_rows_sql_projects_format_l_over_the_working_table() { + let sql = int4_spec().render_rows_sql(); + assert!(sql.contains("INSERT INTO fixtures.eql_v2_int4 (id, plaintext, payload) VALUES")); + assert!(sql.contains("%L, %L, %L::jsonb")); + assert!(sql.contains("FROM public._fixture_eql_v2_int4")); + assert!(sql.contains("(payload).data::text")); + assert!(sql.contains("ORDER BY id")); + } +} diff --git a/tests/sqlx/src/fixtures/validation.rs b/tests/sqlx/src/fixtures/validation.rs new file mode 100644 index 00000000..e641a726 --- /dev/null +++ b/tests/sqlx/src/fixtures/validation.rs @@ -0,0 +1,129 @@ +//! Pure SQL-token validators. Validated tokens are wrapped in newtypes +//! (`FixtureIdentifier`, `ColumnType`) so a renderer that accepts the newtype +//! receives type-level proof of validation — an unvalidated `&str` cannot +//! reach the renderer's format strings. + +use std::fmt; + +/// Lowercase snake-case identifier, must start with a letter: `^[a-z][a-z0-9_]*$`. +fn is_valid_identifier(s: &str) -> bool { + let mut chars = s.chars(); + match chars.next() { + Some(c) if c.is_ascii_lowercase() => {} + _ => return false, + } + chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_') +} + +/// Allowlist of committed `payload` column types. `{ jsonb }` for #224 — no +/// domain types exist yet. Extending to domain-typed fixtures means extending +/// this list with validated, optionally schema-qualified type tokens. +pub const ALLOWED_COLUMN_TYPES: &[&str] = &["jsonb"]; + +fn is_valid_column_type(s: &str) -> bool { + ALLOWED_COLUMN_TYPES.contains(&s) +} + +/// A validated SQL identifier. Construction proves the string matches +/// `^[a-z][a-z0-9_]*$`. Renderers interpolate via `Display`, so the bare +/// `&str` cannot reach generated SQL once it has been validated into this type. +#[derive(Debug, Clone)] +pub struct FixtureIdentifier(String); + +impl FixtureIdentifier { + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl TryFrom<&str> for FixtureIdentifier { + type Error = String; + fn try_from(s: &str) -> Result { + if is_valid_identifier(s) { + Ok(Self(s.to_string())) + } else { + Err(format!( + "{s:?} is not a valid identifier (^[a-z][a-z0-9_]*$)" + )) + } + } +} + +impl fmt::Display for FixtureIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +/// A validated committed-payload column type token. Construction proves the +/// string is in `ALLOWED_COLUMN_TYPES`. +#[derive(Debug, Clone)] +pub struct ColumnType(String); + +impl ColumnType { + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl TryFrom<&str> for ColumnType { + type Error = String; + fn try_from(s: &str) -> Result { + if is_valid_column_type(s) { + Ok(Self(s.to_string())) + } else { + Err(format!( + "{s:?} is not in the allowlist {ALLOWED_COLUMN_TYPES:?}" + )) + } + } +} + +impl fmt::Display for ColumnType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn accepts_valid_identifiers() { + assert!(FixtureIdentifier::try_from("eql_v2_int4").is_ok()); + assert!(FixtureIdentifier::try_from("a").is_ok()); + assert!(FixtureIdentifier::try_from("x9_y").is_ok()); + } + + #[test] + fn rejects_invalid_identifiers() { + assert!(FixtureIdentifier::try_from("").is_err()); + assert!(FixtureIdentifier::try_from("9abc").is_err()); // leading digit + assert!(FixtureIdentifier::try_from("_abc").is_err()); // leading underscore + assert!(FixtureIdentifier::try_from("Abc").is_err()); // uppercase + assert!(FixtureIdentifier::try_from("a-b").is_err()); // hyphen + assert!(FixtureIdentifier::try_from("a b").is_err()); // space + assert!(FixtureIdentifier::try_from("a;DROP").is_err()); // injection attempt + } + + #[test] + fn identifier_renders_via_display() { + let id = FixtureIdentifier::try_from("eql_v2_int4").unwrap(); + assert_eq!(format!("{id}"), "eql_v2_int4"); + } + + #[test] + fn column_type_accepts_jsonb_only() { + assert!(ColumnType::try_from("jsonb").is_ok()); + assert!(ColumnType::try_from("text").is_err()); + assert!(ColumnType::try_from("eql_v2_int4").is_err()); + assert!(ColumnType::try_from("jsonb; DROP TABLE x").is_err()); + } + + #[test] + fn column_type_renders_via_display() { + let ct = ColumnType::try_from("jsonb").unwrap(); + assert_eq!(format!("{ct}"), "jsonb"); + } +} diff --git a/tests/sqlx/src/lib.rs b/tests/sqlx/src/lib.rs index 911264c3..2f915e37 100644 --- a/tests/sqlx/src/lib.rs +++ b/tests/sqlx/src/lib.rs @@ -5,6 +5,7 @@ use sqlx::PgPool; pub mod assertions; +pub mod fixtures; pub mod helpers; pub mod index_types; pub mod selectors; diff --git a/tests/sqlx/tests/bench_data_tests.rs b/tests/sqlx/tests/bench_data_tests.rs index c81d6d0d..0295eaec 100644 --- a/tests/sqlx/tests/bench_data_tests.rs +++ b/tests/sqlx/tests/bench_data_tests.rs @@ -20,30 +20,6 @@ async fn fetch_sample_encrypted_text(pool: &PgPool) -> Result { ) } -#[sqlx::test] -async fn benchmark_schema_can_be_reapplied(pool: PgPool) -> Result<()> { - sqlx::query("TRUNCATE TABLE eql_v2_configuration") - .execute(&pool) - .await?; - - let schema = include_str!("../../benchmarks/schema.sql"); - sqlx::raw_sql(schema).execute(&pool).await?; - sqlx::raw_sql(schema).execute(&pool).await?; - - let active_bench_columns: i64 = sqlx::query_scalar( - "SELECT COUNT(*) FROM eql_v2.config() WHERE state = 'active' AND relation = 'bench'", - ) - .fetch_one(&pool) - .await?; - - assert_eq!( - active_bench_columns, 3, - "reapplying benchmark schema should leave one active config for each bench column" - ); - - Ok(()) -} - // ========== Data Integrity Tests ========== /// Verify fixture seeded exactly 10K rows diff --git a/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs b/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs new file mode 100644 index 00000000..eb842b58 --- /dev/null +++ b/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs @@ -0,0 +1,129 @@ +//! Structural verification of the generated `eql_v2_int4` fixture. +//! +//! Vanilla SQL over `fixtures.eql_v2_int4` — `payload` is plain `jsonb`, no +//! domain type required. The `plaintext` column is the in-table oracle; no +//! Rust value constant is shared with the generator. #224 verifies the +//! fixture is well-formed; #225 verifies the domain operators on it. + +use anyhow::Result; +use sqlx::PgPool; + +/// The 14 values from `src/fixtures/eql_v2_int4.rs`, in id order. Kept here +/// only to assert the in-table `plaintext` oracle matches what was generated. +/// If `plaintext_column_matches_the_generated_values` fails, the generator's +/// `VALUES` and this constant have drifted — re-run +/// `mise run fixture:generate eql_v2_int4` and update this list to match. +const EXPECTED_PLAINTEXTS: &[i32] = &[-100, -1, 1, 2, 5, 10, 17, 25, 42, 50, 100, 250, 1000, 9999]; + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn fixture_has_fourteen_rows(pool: PgPool) -> Result<()> { + let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM fixtures.eql_v2_int4") + .fetch_one(&pool) + .await?; + assert_eq!(count, 14, "eql_v2_int4 fixture should have 14 rows"); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn ids_are_sequential_one_to_fourteen(pool: PgPool) -> Result<()> { + let ids: Vec = sqlx::query_scalar("SELECT id FROM fixtures.eql_v2_int4 ORDER BY id") + .fetch_all(&pool) + .await?; + assert_eq!(ids, (1..=14).collect::>()); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn plaintext_column_matches_the_generated_values(pool: PgPool) -> Result<()> { + let plaintexts: Vec = + sqlx::query_scalar("SELECT plaintext FROM fixtures.eql_v2_int4 ORDER BY id") + .fetch_all(&pool) + .await?; + assert_eq!(plaintexts, EXPECTED_PLAINTEXTS); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn every_payload_carries_the_hmac_equality_term(pool: PgPool) -> Result<()> { + // `hm` drives equality. Every row's payload must carry an `hm` string term. + let missing: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM fixtures.eql_v2_int4 + WHERE payload->'hm' IS NULL OR jsonb_typeof(payload->'hm') <> 'string'", + ) + .fetch_one(&pool) + .await?; + assert_eq!(missing, 0, "every payload must carry an `hm` string term"); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn every_payload_carries_the_ore_block_term(pool: PgPool) -> Result<()> { + // `ob` drives ordering. Every row's payload must carry a non-null ob array. + let missing: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM fixtures.eql_v2_int4 + WHERE payload->'ob' IS NULL OR jsonb_typeof(payload->'ob') <> 'array'", + ) + .fetch_one(&pool) + .await?; + assert_eq!(missing, 0, "every payload must carry an `ob` array term"); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn every_payload_carries_a_ciphertext(pool: PgPool) -> Result<()> { + // `c` is the ciphertext. Every row's payload must carry a `c` string. + let missing: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM fixtures.eql_v2_int4 + WHERE payload->'c' IS NULL OR jsonb_typeof(payload->'c') <> 'string'", + ) + .fetch_one(&pool) + .await?; + assert_eq!( + missing, 0, + "every payload must carry a `c` ciphertext string" + ); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn plaintext_oracle_supports_value_filtering(pool: PgPool) -> Result<()> { + // The in-table `plaintext` oracle: a consuming test can filter on it + // directly. Exactly one row has plaintext = 42. + let ids: Vec = + sqlx::query_scalar("SELECT id FROM fixtures.eql_v2_int4 WHERE plaintext = 42 ORDER BY id") + .fetch_all(&pool) + .await?; + assert_eq!(ids, vec![9], "expected exactly one row with plaintext = 42 at id 9"); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn hmac_equality_terms_are_distinct_for_distinct_values(pool: PgPool) -> Result<()> { + // All 14 plaintext values are distinct, so all 14 `hm` terms must be too. + let distinct_hm: i64 = + sqlx::query_scalar("SELECT COUNT(DISTINCT payload->>'hm') FROM fixtures.eql_v2_int4") + .fetch_one(&pool) + .await?; + assert_eq!( + distinct_hm, 14, + "14 distinct values -> 14 distinct hm terms" + ); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] +async fn every_payload_declares_eql_payload_version_v2(pool: PgPool) -> Result<()> { + // The EQL `v` payload-format field is checked server-side against `'2'` + // when an `eql_v2_encrypted` value is inserted. Asserting equality here + // (not just presence) means a future bump to `v=3` fails this test + // loudly, forcing the maintainer to regenerate the fixture and audit + // consumers for v2→v3 semantic changes. + let mismatched: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM fixtures.eql_v2_int4 + WHERE payload->'v' IS NULL OR payload->>'v' <> '2'", + ) + .fetch_one(&pool) + .await?; + assert_eq!(mismatched, 0, "every payload must declare v = '2'"); + Ok(()) +} diff --git a/tests/sqlx/tests/like_operator_tests.rs b/tests/sqlx/tests/like_operator_tests.rs index ad062727..679ee81d 100644 --- a/tests/sqlx/tests/like_operator_tests.rs +++ b/tests/sqlx/tests/like_operator_tests.rs @@ -53,7 +53,7 @@ async fn create_encrypted_json_with_index( }) } -#[sqlx::test(fixtures(path = "../fixtures", scripts("like_data")))] +#[sqlx::test(fixtures(path = "../fixtures", scripts("match_data")))] async fn like_operator_matches_pattern(pool: PgPool) -> Result<()> { // Test: ~~ operator (LIKE) matches encrypted values // Tests both ~~ operator and LIKE operator (they're equivalent) @@ -89,7 +89,7 @@ async fn like_operator_matches_pattern(pool: PgPool) -> Result<()> { Ok(()) } -#[sqlx::test(fixtures(path = "../fixtures", scripts("like_data")))] +#[sqlx::test(fixtures(path = "../fixtures", scripts("match_data")))] async fn like_operator_no_match(pool: PgPool) -> Result<()> { // Test: ~~ operator returns empty for non-matching pattern // This test verifies that LIKE operations correctly return no results @@ -109,7 +109,7 @@ async fn like_operator_no_match(pool: PgPool) -> Result<()> { Ok(()) } -#[sqlx::test(fixtures(path = "../fixtures", scripts("like_data")))] +#[sqlx::test(fixtures(path = "../fixtures", scripts("match_data")))] async fn like_function_matches_pattern(pool: PgPool) -> Result<()> { // Test: eql_v2.like() function // Tests the eql_v2.like() function which wraps bloom filter matching @@ -129,7 +129,7 @@ async fn like_function_matches_pattern(pool: PgPool) -> Result<()> { Ok(()) } -#[sqlx::test(fixtures(path = "../fixtures", scripts("like_data")))] +#[sqlx::test(fixtures(path = "../fixtures", scripts("match_data")))] async fn ilike_operator_case_insensitive_matches(pool: PgPool) -> Result<()> { // Test: ~~* operator (ILIKE) matches encrypted values (case-insensitive) // Tests both ~~* operator and ILIKE operator (they're equivalent) From 457b9d198223985896798d402e3def2f85fee6aa Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 25 May 2026 14:22:19 +1000 Subject: [PATCH 04/93] refactor(fixtures): replace Proxy with direct cipherstash-client Generator no longer shells out to a CipherStash Proxy container to encrypt fixture values. `cipherstash-client` 0.35 is now a direct dependency of the SQLx test crate and the new `fixtures::cipherstash` module owns ZeroKMS bootstrap (cached in a OnceCell), the index-name -> ColumnConfig mapping, and the per-value encrypt helper. The Rust fixture owns configuration and encryption; the DB only owns table structure. Removes: `tests/docker-compose.proxy.yml`, the `proxy:up`/`proxy:logs`/ `proxy:down` mise tasks, the `mise run proxy:up` step from `test:sqlx`, and the `restart_proxy_and_wait` / `insert_through_proxy` / `PROXY_PORT` plumbing in `driver.rs`. The working-table `payload` column is now plain `jsonb` (no `eql_v2_encrypted` composite) and no `add_search_config` rows are written to `eql_v2_configuration` during generation. `mise run test:sqlx` now needs `CS_CLIENT_ACCESS_KEY` (or `CS_CLIENT_ID` + `CS_CLIENT_KEY`) and `CS_WORKSPACE_CRN` in the process env so `AutoStrategy::detect()` / `EnvKeyProvider` can pick them up. --- mise.toml | 5 +- tasks/fixtures.toml | 44 +- tests/docker-compose.proxy.yml | 35 - tests/sqlx/Cargo.lock | 4506 +++++++++++++++++++--- tests/sqlx/Cargo.toml | 5 +- tests/sqlx/README.md | 18 + tests/sqlx/fixtures/FIXTURE_SCHEMA.md | 16 +- tests/sqlx/src/fixtures/cipherstash.rs | 253 ++ tests/sqlx/src/fixtures/driver.rs | 137 +- tests/sqlx/src/fixtures/eql_plaintext.rs | 26 + tests/sqlx/src/fixtures/mod.rs | 2 + tests/sqlx/src/fixtures/spec.rs | 94 +- 12 files changed, 4281 insertions(+), 860 deletions(-) delete mode 100644 tests/docker-compose.proxy.yml create mode 100644 tests/sqlx/src/fixtures/cipherstash.rs diff --git a/mise.toml b/mise.toml index 86e30d47..87934676 100644 --- a/mise.toml +++ b/mise.toml @@ -46,10 +46,11 @@ cd tests/sqlx sqlx migrate run # Regenerate fixtures every run — they are not committed (see .gitignore). -# Requires Proxy on PROXY_PORT; brings it up if not already running. +# Generator encrypts via cipherstash-client directly; CS_* credentials must +# be present in the shell environment (CS_CLIENT_ACCESS_KEY + +# CS_WORKSPACE_CRN, or the legacy CS_CLIENT_ID/CS_CLIENT_KEY pair). echo "Regenerating SQLx fixtures..." cd "{{config_root}}" -mise run proxy:up mise run fixture:generate eql_v2_int4 echo "Running Rust tests..." diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml index dfac6411..808200cd 100644 --- a/tasks/fixtures.toml +++ b/tasks/fixtures.toml @@ -1,49 +1,15 @@ -["proxy:up"] -description = "Start CipherStash Proxy connected to existing Postgres" -# Reuses the tests/docker-compose.yml Postgres on POSTGRES_PORT. -# CS_* credentials are read from the shell environment (mise/direnv/profile). -# Readiness is verified from the host (the proxy image lacks busybox nc, so -# the container-internal healthcheck cannot be used). Dumps container logs -# on failure so the user does not need a separate `docker logs` invocation. -dir = "{{config_root}}/tests" -run = """ -docker compose -f docker-compose.proxy.yml up -d -echo "Waiting for proxy on localhost:6432..." -export PGPASSWORD="${POSTGRES_PASSWORD:-password}" -for i in $(seq 1 60); do - if psql -U "${POSTGRES_USER:-cipherstash}" -d "${POSTGRES_DB:-cipherstash}" \ - -h localhost -p 6432 -c 'SELECT 1' >/dev/null 2>&1; then - echo "Proxy ready." - exit 0 - fi - sleep 1 -done -echo "Proxy did not become ready in 60s." -echo -echo '=== cipherstash-proxy logs ===' -docker logs cipherstash-proxy 2>&1 | tail -40 -exit 1 -""" - -["proxy:logs"] -description = "Tail CipherStash Proxy container logs" -dir = "{{config_root}}/tests" -run = "docker logs --tail 100 -f cipherstash-proxy" - -["proxy:down"] -description = "Stop CipherStash Proxy" -dir = "{{config_root}}/tests" -run = "docker compose -f docker-compose.proxy.yml down" - ["fixture:generate"] -description = "Generate a SQLx fixture script via CipherStash Proxy" +description = "Generate a SQLx fixture script via cipherstash-client" # Runs the gated generator for the named fixture. Writes # tests/sqlx/fixtures/.sql. Must run inside the crate — there is no # root Cargo.toml — matching test:schema / test:sqlx:watch. # # Prerequisites: # - mise run postgres:up (Postgres with EQL installed) -# - mise run proxy:up (Proxy on localhost:6432) +# - CS_* credentials in the shell environment (auto-loaded by +# cipherstash-client's AutoStrategy / EnvKeyProvider): +# CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN (preferred), OR +# CS_CLIENT_ID + CS_CLIENT_KEY (legacy pair) # # Usage: mise run fixture:generate eql_v2_int4 dir = "{{config_root}}/tests/sqlx" diff --git a/tests/docker-compose.proxy.yml b/tests/docker-compose.proxy.yml deleted file mode 100644 index d64e6a8e..00000000 --- a/tests/docker-compose.proxy.yml +++ /dev/null @@ -1,35 +0,0 @@ -services: - proxy: - image: cipherstash/proxy:latest - container_name: cipherstash-proxy - ports: - - "6432:6432" - environment: - # Proxy connects to the existing tests/docker-compose.yml Postgres, - # reaching the host via host.docker.internal. POSTGRES_* values come - # from mise.toml [env] block (overridable per shell). - CS_DATABASE__NAME: ${POSTGRES_DB:-cipherstash} - CS_DATABASE__USERNAME: ${POSTGRES_USER:-cipherstash} - CS_DATABASE__PASSWORD: ${POSTGRES_PASSWORD:-password} - CS_DATABASE__HOST: host.docker.internal - CS_DATABASE__PORT: ${POSTGRES_PORT:-7432} - # EQL installation is handled by the existing reset / install flow; the - # Proxy must not race against it. - CS_DATABASE__INSTALL_EQL: "false" - # CipherStash workspace credentials are read from the host shell - # environment (mise / direnv / profile). No .env file is required. - CS_CLIENT_ACCESS_KEY: ${CS_CLIENT_ACCESS_KEY} - CS_DEFAULT_KEYSET_ID: ${CS_DEFAULT_KEYSET_ID} - CS_CLIENT_KEY: ${CS_CLIENT_KEY} - CS_CLIENT_ID: ${CS_CLIENT_ID} - CS_WORKSPACE_CRN: ${CS_WORKSPACE_CRN} - # Optional: pin the CTS region host for workspaces in non-default regions. - CS_CTS_HOST: ${CS_CTS_HOST:-} - CS_ZEROKMS_HOST: ${CS_ZEROKMS_HOST:-} - extra_hosts: - # Linux compatibility; macOS / Windows resolve host.docker.internal natively. - - "host.docker.internal:host-gateway" - # No in-container healthcheck: the current cipherstash/proxy:latest image - # lacks busybox nc, so any TCP probe inside the container fails even when - # the proxy is listening. Readiness is verified from the host by the - # proxy:up task using psql. diff --git a/tests/sqlx/Cargo.lock b/tests/sqlx/Cargo.lock index db86aeba..e39e030b 100644 --- a/tests/sqlx/Cargo.lock +++ b/tests/sqlx/Cargo.lock @@ -2,6 +2,84 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.6", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", + "zeroize", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -25,18 +103,104 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "async-compression" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-mutex" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73112ce9e1059d8604242af62c7ec8e5975ac58ac251686c8403b45e8a6fe778" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "atoi" version = "2.0.0" @@ -46,12 +210,86 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + [[package]] name = "base64" version = "0.22.1" @@ -64,6 +302,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "base85" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36915bbaca237c626689b5bd14d02f2ba7a5a359d30a2a08be697392e3718079" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -81,13 +328,40 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", + "zeroize", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -97,24 +371,112 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-modes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2211b0817f061502a8dd9f11a37e879e79763e3c698d2418cf824d8cb2f21e" + [[package]] name = "borrow-or-share" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytecount" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -126,1036 +488,2802 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "cached" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "9718806c4a2fe9e8a56fd736f97b340dd10ed1be8ed733ed50449f351dc33cae" +dependencies = [ + "ahash 0.8.12", + "cached_proc_macro", + "cached_proc_macro_types", + "hashbrown 0.14.5", + "once_cell", + "thiserror 1.0.69", + "web-time", +] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "cached_proc_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa" dependencies = [ - "crossbeam-utils", + "darling", + "proc-macro2", + "quote", + "syn 2.0.108", ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "cached_proc_macro_types" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "cc" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ + "find-msvc-tools", + "jobserver", "libc", + "shlex", ] [[package]] -name = "crc" -version = "3.3.0" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" -dependencies = [ - "crc-catalog", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "crc-catalog" -version = "2.4.0" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "crossbeam-queue" -version = "0.3.12" +name = "chacha20" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ - "crossbeam-utils", + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "chrono" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ - "generic-array", - "typenum", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", ] [[package]] -name = "data-encoding" -version = "2.11.0" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.6", + "inout", +] [[package]] -name = "der" -version = "0.7.10" +name = "cipherstash-client" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "0257cff25a25a706af6e190e5209865aae4ddf0b36f81febee7de014b22bcc40" dependencies = [ - "const-oid", - "pem-rfc7468", + "aes-gcm-siv", + "anyhow", + "async-mutex", + "async-trait", + "base16ct", + "base64", + "base85", + "blake3", + "chrono", + "cipherstash-config", + "cipherstash-core", + "cllw-ore", + "cts-common", + "derive_more 1.0.0", + "dirs", + "futures", + "hex", + "hmac", + "itertools 0.12.1", + "lazy_static", + "log", + "miette", + "opaque-debug", + "orderable-bytes", + "ore-rs", + "percent-encoding", + "rand 0.8.6", + "recipher", + "reqwest", + "rmp-serde", + "rust-stemmers", + "rust_decimal", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "serdect", + "sha2", + "stack-auth", + "stack-profile", + "static_assertions", + "thiserror 1.0.69", + "tokio", + "toml", + "tracing", + "url", + "uuid", + "vitaminc", + "vitaminc-protected", + "winnow 0.6.26", "zeroize", + "zerokms-protocol", ] [[package]] -name = "digest" -version = "0.10.7" +name = "cipherstash-config" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "d376d237e368e77de53b07bb7309812f45809299063c80b6cc3132f7d8494aed" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "bitflags", + "serde", + "serde_json", + "thiserror 1.0.69", ] [[package]] -name = "displaydoc" -version = "0.2.5" +name = "cipherstash-core" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "ca84ffd8a7b2f0c8c6b04eba600738fea115f5a4ed825035a2f13aa1524085a7" dependencies = [ - "proc-macro2", - "quote", - "syn", + "getrandom 0.2.16", + "hmac", + "lazy_static", + "num-bigint", + "rand 0.8.6", + "regex", + "sha2", + "thiserror 1.0.69", ] [[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "either" -version = "1.15.0" +name = "cllw-ore" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "4f73a23cbc15404d9b314c03b16a888f798dbc681bceeb2e18674f602f9da02d" dependencies = [ - "serde", + "blake3", + "chrono", + "hex", + "orderable-bytes", + "rust_decimal", + "subtle", + "thiserror 1.0.69", + "unicode-normalization", ] [[package]] -name = "email_address" -version = "0.2.9" +name = "cmac" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" dependencies = [ - "serde", + "cipher", + "dbl", + "digest 0.10.7", ] [[package]] -name = "eql_tests" -version = "0.1.0" +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "anyhow", - "hex", - "jsonschema", - "serde", - "serde_json", - "sqlx", - "tokio", + "cc", ] [[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "etcetera" -version = "0.8.0" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", + "bytes", + "memchr", ] [[package]] -name = "event-listener" -version = "5.4.1" +name = "compression-codecs" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", + "brotli", + "compression-core", + "flate2", + "memchr", ] [[package]] -name = "fancy-regex" -version = "0.18.0" +name = "compression-core" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" -dependencies = [ - "bit-set", - "regex-automata", - "regex-syntax", -] +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] -name = "fluent-uri" -version = "0.4.1" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "borrow-or-share", - "ref-cast", - "serde", + "crossbeam-utils", ] [[package]] -name = "flume" -version = "0.11.1" +name = "const-hex" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" dependencies = [ - "futures-core", - "futures-sink", - "spin", + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", ] [[package]] -name = "foldhash" -version = "0.1.5" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "foldhash" -version = "0.2.0" +name = "constant_time_eq" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] -name = "form_urlencoded" -version = "1.2.2" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ - "percent-encoding", + "unicode-segmentation", ] [[package]] -name = "fraction" -version = "0.15.4" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "lazy_static", - "num", + "core-foundation-sys", + "libc", ] [[package]] -name = "futures-channel" -version = "0.3.31" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "futures-core" -version = "0.3.31" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] [[package]] -name = "futures-executor" -version = "0.3.31" +name = "cpufeatures" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "libc", ] [[package]] -name = "futures-intrusive" -version = "0.5.0" +name = "crc" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ - "futures-core", - "lock_api", - "parking_lot", + "crc-catalog", ] [[package]] -name = "futures-io" -version = "0.3.31" +name = "crc-catalog" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "futures-sink" -version = "0.3.31" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] [[package]] -name = "futures-task" -version = "0.3.31" +name = "critical-section" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] -name = "futures-util" -version = "0.3.31" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "crossbeam-utils", ] [[package]] -name = "generic-array" -version = "0.14.9" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "typenum", - "version_check", + "crossbeam-utils", ] [[package]] -name = "getrandom" -version = "0.2.16" +name = "crossbeam-queue" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ - "cfg-if", - "libc", - "wasi", + "crossbeam-utils", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", + "generic-array", + "rand_core 0.6.4", + "typenum", ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "crypto-common" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.1.5", + "hybrid-array", ] [[package]] -name = "hashbrown" -version = "0.16.0" +name = "ctr" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", + "cipher", ] [[package]] -name = "hashlink" -version = "0.10.0" +name = "cts-common" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "ae6508011dee61bc36e16615e43cb26a8014c49ebb832e713602f3b0dc15af29" dependencies = [ - "hashbrown 0.15.5", + "arrayvec", + "base32", + "cached", + "chrono", + "derive_more 2.1.1", + "either", + "getrandom 0.4.2", + "miette", + "nom", + "regex", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", + "url", + "utoipa", + "uuid", + "vitaminc", ] [[package]] -name = "heck" -version = "0.5.0" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] [[package]] -name = "hex" -version = "0.4.3" +name = "darling_core" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.108", +] [[package]] -name = "hkdf" -version = "0.12.4" +name = "darling_macro" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "hmac", + "darling_core", + "quote", + "syn 2.0.108", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "data-encoding" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" dependencies = [ - "digest", + "generic-array", ] [[package]] -name = "home" -version = "0.5.11" +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "windows-sys 0.59.0", + "const-oid", + "pem-rfc7468", + "zeroize", ] [[package]] -name = "icu_collections" -version = "2.0.0" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", + "powerfmt", ] [[package]] -name = "icu_locale_core" -version = "2.0.0" +name = "derive_more" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "derive_more-impl 1.0.0", ] [[package]] -name = "icu_normalizer" -version = "2.0.0" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", + "derive_more-impl 2.1.1", ] [[package]] -name = "icu_normalizer_data" -version = "2.0.0" +name = "derive_more-impl" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "unicode-xid", +] [[package]] -name = "icu_properties" -version = "2.0.1" +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.108", + "unicode-xid", ] [[package]] -name = "icu_properties_data" -version = "2.0.1" +name = "deunicode" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] -name = "icu_provider" -version = "2.0.0" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.6", + "subtle", ] [[package]] -name = "idna" -version = "1.1.0" +name = "digest" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "block-buffer 0.12.0", + "crypto-common 0.2.2", ] [[package]] -name = "idna_adapter" -version = "1.2.1" +name = "dirs" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "icu_normalizer", - "icu_properties", + "dirs-sys", ] [[package]] -name = "indexmap" -version = "2.12.0" +name = "dirs-sys" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ - "equivalent", - "hashbrown 0.16.0", + "libc", + "redox_users", + "winapi", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] [[package]] -name = "js-sys" -version = "0.3.98" +name = "dotenvy" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dummy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac124e13ae9aa56acc4241f8c8207501d93afdd8d8e62f0c1f2e12f6508c65" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen", + "darling", + "proc-macro2", + "quote", + "syn 2.0.108", ] [[package]] -name = "jsonschema" -version = "0.46.4" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ - "ahash", - "bytecount", - "data-encoding", - "email_address", - "fancy-regex", - "fraction", - "getrandom 0.3.4", - "idna", - "itoa", - "num-cmp", - "num-traits", - "percent-encoding", - "referencing", - "regex", - "regex-syntax", "serde", - "serde_json", - "unicode-general-category", - "uuid-simd", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "email_address" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" dependencies = [ - "spin", + "serde", ] [[package]] -name = "libc" -version = "0.2.177" +name = "enum-as-inner" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.108", +] [[package]] -name = "libm" -version = "0.2.15" +name = "eql_tests" +version = "0.1.0" +dependencies = [ + "anyhow", + "cipherstash-client", + "hex", + "jsonschema", + "serde", + "serde_json", + "sqlx", + "tokio", +] + +[[package]] +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "libredox" -version = "0.1.10" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "bitflags", "libc", - "redox_syscall", + "windows-sys 0.61.2", ] [[package]] -name = "libsqlite3-sys" -version = "0.30.1" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "pkg-config", - "vcpkg", + "cfg-if", + "home", + "windows-sys 0.48.0", ] [[package]] -name = "litemap" -version = "0.8.0" +name = "event-listener" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] -name = "lock_api" -version = "0.4.14" +name = "event-listener" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ - "scopeguard", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "log" -version = "0.4.28" +name = "fake" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "2d391ba4af7f1d93f01fcf7b2f29e2bc9348e109dfdbf4dcbdc51dfa38dab0b6" +dependencies = [ + "deunicode", + "dummy", + "rand 0.8.6", + "uuid", +] + +[[package]] +name = "fancy-regex" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fraction" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" +dependencies = [ + "rustix 0.38.44", + "windows-targets 0.52.6", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.4", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.4", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.108", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.108", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonschema" +version = "0.46.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f" +dependencies = [ + "ahash 0.8.12", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex", + "fraction", + "getrandom 0.3.4", + "idna", + "itoa", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "regex-syntax", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "open" +version = "5.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "orderable-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adba0469737879cabc3bdcaa6a7beb4ff7f5bb4aa5d624816c8bb9d425a7d5df" +dependencies = [ + "chrono", + "rust_decimal", +] + +[[package]] +name = "ore-rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77fb65718f1aba7bb7f568db90a2b17a48871d83cd9d1101d19fce449dd8dea" +dependencies = [ + "aes", + "block-modes", + "byteorder", + "chrono", + "hex", + "lazy_static", + "num", + "orderable-bytes", + "rand 0.8.6", + "rand_chacha 0.3.1", + "rust_decimal", + "subtle-ng", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", - "digest", + "libc", + "redox_syscall", + "smallvec", + "windows-link", ] [[package]] -name = "memchr" -version = "2.7.6" +name = "pathdiff" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] -name = "micromap" -version = "0.3.0" +name = "pem" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] [[package]] -name = "mio" -version = "1.1.0" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "base64ct", ] [[package]] -name = "num" -version = "0.4.3" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.108", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.6+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "proc-macro2" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags", "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "unarray", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] -name = "num-bigint" -version = "0.4.6" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "num-integer", - "num-traits", + "getrandom 0.2.16", ] [[package]] -name = "num-bigint-dig" -version = "0.8.6" +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", + "getrandom 0.3.4", ] [[package]] -name = "num-cmp" -version = "0.1.0" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] -name = "num-complex" -version = "0.4.6" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "num-traits", + "rand_core 0.9.5", ] [[package]] -name = "num-integer" -version = "0.1.46" +name = "recipher" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "9398dce78ddfce08f93e9d9a3ac64d9b0a4fed478c0a82003c6e4c90dc245125" dependencies = [ - "num-traits", + "aes", + "cmac", + "getrandom 0.2.16", + "hex", + "hex-literal", + "opaque-debug", + "rand 0.8.6", + "rand_chacha 0.3.1", + "serde", + "serde_cbor", + "sha2", + "thiserror 1.0.69", + "zeroize", ] [[package]] -name = "num-iter" -version = "0.1.45" +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "bitflags", ] [[package]] -name = "num-rational" -version = "0.4.2" +name = "redox_users" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "num-bigint", - "num-integer", - "num-traits", + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "ref-cast" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ - "autocfg", - "libm", + "ref-cast-impl", ] [[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "outref" -version = "0.5.2" +name = "ref-cast-impl" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] [[package]] -name = "parking" -version = "2.2.1" +name = "referencing" +version = "0.46.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730" +dependencies = [ + "ahash 0.8.12", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.0", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] [[package]] -name = "parking_lot" -version = "0.12.5" +name = "regex" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ - "lock_api", - "parking_lot_core", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "regex-automata" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "pem-rfc7468" -version = "0.7.0" +name = "regex-syntax" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "rend" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "reqwest" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "hickory-resolver", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "resolv-conf" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] -name = "pkcs1" -version = "0.7.5" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "der", - "pkcs8", - "spki", + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "rkyv" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" dependencies = [ - "der", - "spki", + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", ] [[package]] -name = "pkg-config" -version = "0.3.32" +name = "rkyv_derive" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "potential_utf" -version = "0.1.3" +name = "rmp" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "zerovec", + "num-traits", ] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "rmp-serde" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" dependencies = [ - "zerocopy", + "rmp", + "serde", ] [[package]] -name = "proc-macro2" -version = "1.0.102" +name = "rsa" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "unicode-ident", + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", ] [[package]] -name = "quote" -version = "1.0.41" +name = "rust-stemmers" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" dependencies = [ - "proc-macro2", + "serde", + "serde_derive", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "rust_decimal" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "0c5108e3d4d903e21aac27f12ba5377b6b34f9f44b325e4894c7924169d06995" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.6", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", +] [[package]] -name = "rand" -version = "0.8.6" +name = "rustc-demangle" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rustc-hash" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] -name = "rand_core" -version = "0.6.4" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "getrandom 0.2.16", + "semver", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "rustix" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] -name = "ref-cast" -version = "1.0.25" +name = "rustix" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "ref-cast-impl", + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", ] [[package]] -name = "ref-cast-impl" -version = "1.0.25" +name = "rustls" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "referencing" -version = "0.46.4" +name = "rustls-native-certs" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "ahash", - "fluent-uri", - "getrandom 0.3.4", - "hashbrown 0.16.0", - "itoa", - "micromap", - "parking_lot", - "percent-encoding", - "serde_json", + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] -name = "regex" -version = "1.12.3" +name = "rustls-pki-types" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "web-time", + "zeroize", ] [[package]] -name = "regex-automata" -version = "0.4.14" +name = "rustls-platform-verifier" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] [[package]] -name = "regex-syntax" -version = "0.8.10" +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] -name = "rsa" -version = "0.9.10" +name = "rustls-webpki" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", - "subtle", - "zeroize", + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", ] [[package]] @@ -1170,12 +3298,65 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -1186,6 +3367,26 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1203,7 +3404,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] @@ -1219,6 +3420,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1231,6 +3441,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +dependencies = [ + "base16ct", + "serde", + "zeroize", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1238,10 +3459,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -1249,10 +3476,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -1268,8 +3501,42 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", - "rand_core", + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", ] [[package]] @@ -1340,7 +3607,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", @@ -1356,7 +3623,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -1373,7 +3640,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.108", ] [[package]] @@ -1396,7 +3663,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.108", "tokio", "url", ] @@ -1413,7 +3680,7 @@ dependencies = [ "byteorder", "bytes", "crc", - "digest", + "digest 0.10.7", "dotenvy", "either", "futures-channel", @@ -1430,7 +3697,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.6", "rsa", "serde", "sha1", @@ -1438,7 +3705,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -1468,14 +3735,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.6", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -1499,74 +3766,258 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stack-auth" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c823cc3d88e15478d51694ef56037400bd4ae3e1a9e046071e01d251168cc466" +dependencies = [ + "aquamarine", + "base64", + "cts-common", + "jsonwebtoken", + "miette", + "open", + "reqwest", + "serde", + "serde_json", + "stack-profile", + "thiserror 1.0.69", + "tokio", "tracing", "url", + "uuid", + "vitaminc", + "vitaminc-protected", + "web-time", + "zeroize", + "zerokms-protocol", +] + +[[package]] +name = "stack-profile" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1be30119d59260f3e932f78fbbfbe3674c151d39e7fcca24d0439e7e31ba8" +dependencies = [ + "dirs", + "gethostname", + "serde", + "serde_json", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix 1.1.4", + "windows-sys 0.61.2", ] [[package]] -name = "stable_deref_trait" -version = "1.2.1" +name = "textwrap" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] [[package]] -name = "stringprep" -version = "0.1.5" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", + "thiserror-impl 1.0.69", ] [[package]] -name = "subtle" -version = "2.6.1" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] [[package]] -name = "syn" -version = "2.0.108" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.108", ] [[package]] -name = "synstructure" -version = "0.13.2" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] -name = "thiserror" -version = "2.0.17" +name = "time" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ - "thiserror-impl", + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", ] [[package]] -name = "thiserror-impl" -version = "2.0.17" +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ - "proc-macro2", - "quote", - "syn", + "num-conv", + "time-core", ] [[package]] @@ -1619,7 +4070,17 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", ] [[package]] @@ -1633,6 +4094,140 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db3bae107c9522f86d361697dee1d7386a2ddcf659d5aea5159819a21a3c4a7" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -1653,7 +4248,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] @@ -1665,11 +4260,23 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unarray" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" @@ -1689,6 +4296,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.24" @@ -1704,6 +4317,52 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -1722,27 +4381,204 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "url", + "uuid", +] + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "atomic", + "getrandom 0.4.2", + "js-sys", + "md-5", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + [[package]] name = "uuid-simd" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ - "outref", - "vsimd", + "outref", + "vsimd", +] + +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vitaminc" +version = "0.2.0-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9807fc6a29a9ab4a9cd24f103573661d7e04d48651ce8c752d32e73a686446" +dependencies = [ + "vitaminc-aead", + "vitaminc-encrypt", + "vitaminc-protected", + "vitaminc-random", + "vitaminc-traits", +] + +[[package]] +name = "vitaminc-aead" +version = "0.2.0-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3933759550ab4841536e8d019fc73cd77ec2e43dcdaefa3218936534eefabb8d" +dependencies = [ + "bytes", + "serde", + "vitaminc-protected", + "vitaminc-random", + "zeroize", +] + +[[package]] +name = "vitaminc-encrypt" +version = "0.2.0-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdbcfd5e9054ce2a3f0cfac67c3e3164d65f38f3a6d593bc4d5d943e53aaf44" +dependencies = [ + "aes-gcm", + "aws-lc-rs", + "vitaminc-aead", + "vitaminc-protected", + "vitaminc-random", + "zeroize", +] + +[[package]] +name = "vitaminc-protected" +version = "0.2.0-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c639c70c23871680d0cedabb2fb8cfd0ff91eb98d90dd27e9bdebf6eac1fd18" +dependencies = [ + "bitvec", + "digest 0.11.3", + "serde", + "serde_bytes", + "subtle", + "vitaminc-protected-derive", + "zeroize", +] + +[[package]] +name = "vitaminc-protected-derive" +version = "0.2.0-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659afc33e7252768204f44bd9f377a120a35dc586aa3538b66a3f4cc54a88653" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "vitaminc-random" +version = "0.2.0-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac67bf5f90acc53c87deb5f612ad1beefd0e0e7f5452ab3443d9fa4b0e23f9c5" +dependencies = [ + "getrandom 0.4.2", + "rand 0.10.1", + "thiserror 2.0.18", + "vitaminc-protected", + "vitaminc-random-derives", + "zeroize", ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "vitaminc-random-derives" +version = "0.2.0-pre" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "efeeb4347ca86ff167228cfbd655c7a6e8a5bd6f032330ac881d6c4c0441768b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] [[package]] -name = "version_check" -version = "0.9.5" +name = "vitaminc-traits" +version = "0.2.0-pre" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "5aeb6ef24c094d225d00753134f082eba59ea2ba8d5d0b5ba761c038ca9375b3" +dependencies = [ + "anyhow", + "bytes", + "rmp-serde", + "serde", + "thiserror 2.0.18", + "vitaminc-protected", + "vitaminc-random", + "zeroize", +] [[package]] name = "vsimd" @@ -1750,6 +4586,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1762,7 +4617,16 @@ version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -1780,10 +4644,21 @@ dependencies = [ "cfg-if", "once_cell", "rustversion", + "serde", "wasm-bindgen-macro", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.121" @@ -1803,7 +4678,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.108", "wasm-bindgen-shared", ] @@ -1816,6 +4691,82 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.6.1" @@ -1826,12 +4777,113 @@ dependencies = [ "wasite", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1841,6 +4893,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -2054,18 +5115,142 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.108", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.108", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yoke" version = "0.8.0" @@ -2086,7 +5271,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", "synstructure", ] @@ -2107,7 +5292,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] @@ -2127,7 +5312,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", "synstructure", ] @@ -2136,6 +5321,43 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "zerokms-protocol" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a9411f442b247e7d00c6a07cfbc6d56c12d485cfaf4f7effa001bfb1615296" +dependencies = [ + "base64", + "cipherstash-config", + "const-hex", + "cts-common", + "fake", + "getrandom 0.2.16", + "opaque-debug", + "rand 0.8.6", + "serde", + "static_assertions", + "thiserror 1.0.69", + "utoipa", + "uuid", + "validator", + "zeroize", +] [[package]] name = "zerotrie" @@ -2167,5 +5389,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index 95fb86a2..153aebf3 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -11,6 +11,7 @@ serde_json = "1" anyhow = "1" hex = "0.4" jsonschema = { version = "0.46.4", default-features = false } +cipherstash-client = { version = "0.35", features = ["tokio"] } [dev-dependencies] # None needed - tests live in this crate @@ -25,6 +26,8 @@ bench = [] # Opt-in to compiling the fixture generators. Without this feature the # `#[cfg(feature = "fixture-gen")]` generator tests do not exist, so # `cargo test` and CI never see them. Generators need a live Postgres and -# CipherStash Proxy; run one with: +# CipherStash workspace credentials in the process env +# (`CS_CLIENT_ACCESS_KEY` + `CS_WORKSPACE_CRN`, or the legacy +# `CS_CLIENT_ID`/`CS_CLIENT_KEY` pair). Run one with: # mise run fixture:generate fixture-gen = [] diff --git a/tests/sqlx/README.md b/tests/sqlx/README.md index a1dc8252..978ad1f1 100644 --- a/tests/sqlx/README.md +++ b/tests/sqlx/README.md @@ -48,6 +48,24 @@ cargo test equality cargo test -- --nocapture ``` +### Generator credentials + +`mise run test:sqlx` regenerates the `eql_v2_int4` fixture before running the +suite via `mise run fixture:generate eql_v2_int4`. The generator encrypts +plaintexts in-process using `cipherstash-client` (no Proxy / no Docker +sidecar), so the following CipherStash workspace credentials must be +present in the shell environment when you run it locally or in CI: + +- `CS_CLIENT_ACCESS_KEY` + `CS_WORKSPACE_CRN` — preferred, single-token + bearer credential, picked up by `AutoStrategy::detect()`. +- `CS_CLIENT_ID` + `CS_CLIENT_KEY` — the client-key pair used by + `EnvKeyProvider` to derive the per-call data keys. + +If either set is missing the first call into the generator fails fast +during ZeroKMS handshake with a clear `anyhow` chain naming the missing +variable. Other SQLx tests do not need the `CS_*` variables — only the +fixture-regeneration step does. + ## Test Data ### Fixtures diff --git a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md index e4ac7856..9ac804b7 100644 --- a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md +++ b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md @@ -201,10 +201,11 @@ applies standalone. **Regenerated every test run.** `mise run test:sqlx` invokes the generator before `cargo test`, so a stale committed fixture cannot mask a payload-shape -regression. The generator needs a live Postgres with EQL and a running -CipherStash Proxy — `test:sqlx` brings the Proxy up automatically via -`mise run proxy:up`. Do not hand-edit the generated file; it is overwritten in -place on every run. +regression. The generator encrypts in-process via `cipherstash-client`; it +needs a live Postgres plus CipherStash workspace credentials +(`CS_CLIENT_ACCESS_KEY` + `CS_WORKSPACE_CRN`, or the legacy +`CS_CLIENT_ID`/`CS_CLIENT_KEY` pair) in the shell environment. Do not +hand-edit the generated file; it is overwritten in place on every run. **Schema:** Table lives in the dedicated `fixtures` SQL schema (kept out of the `public` type/domain namespace so a downstream `public.eql_v2_int4` domain can @@ -224,9 +225,10 @@ CREATE TABLE fixtures.eql_v2_int4 ( — a negative boundary plus small/medium/large/extreme magnitudes. - `plaintext` is the **in-table oracle**: consuming tests filter `WHERE plaintext = N` directly, so no Rust value constant is shared. -- Each `payload` is a Proxy-encrypted JSONB object carrying `c` (ciphertext), - `hm` (HMAC equality term), `ob` (ORE block ordering term), and an inert `i` - metadata object. +- Each `payload` is a cipherstash-client-encrypted JSONB object carrying + `c` (ciphertext), `hm` (HMAC equality term), `ob` (ORE block ordering + term), an inert `i` metadata object, and the EQL v2 root discriminator + (`k = "ct"`, `v = 2`). **Used By:** - eql_v2_int4_fixture_tests.rs (structural verification) diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs new file mode 100644 index 00000000..c8dd6c5b --- /dev/null +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -0,0 +1,253 @@ +//! Direct `cipherstash-client` integration — the encryption oracle for the +//! SQLx fixture generator. +//! +//! Earlier revisions of the generator started a CipherStash Proxy container, +//! wrote `add_search_config` rows so Proxy knew which columns to encrypt, +//! restarted the container so it reloaded that config, then INSERTed +//! plaintexts through a Proxy-mediated Postgres connection. That whole loop +//! existed only because the Proxy was the encryption oracle. +//! +//! `cipherstash-client` 0.35 exposes the same surface natively. This module +//! owns the bootstrap — `cipher()` lazily builds a process-wide +//! `ScopedCipher` — and the per-value helper +//! `encrypt_store()` that wraps `eql::encrypt_eql` and returns the resulting +//! EQL ciphertext as a `serde_json::Value` ready to bind into a `jsonb` +//! column. +//! +//! `column_config_for` is the bridge between the fixture spec's string-typed +//! index names (`"unique"`, `"ore"`, …) and the typed `IndexType` enum +//! cipherstash-config uses. Unknown names raise immediately so a typo at +//! spec construction fails fast. + +use std::borrow::Cow; +use std::sync::Arc; + +use anyhow::{anyhow, Context, Result}; +use cipherstash_client::encryption::ScopedCipher; +use cipherstash_client::eql::{ + encrypt_eql, EqlCiphertext, EqlEncryptOpts, EqlOperation, EqlOutput, Identifier, + PreparedPlaintext, +}; +use cipherstash_client::schema::column::{Index, IndexType}; +use cipherstash_client::schema::{ColumnConfig, ColumnType}; +use cipherstash_client::zerokms::{EnvKeyProvider, ZeroKMSBuilder}; +use cipherstash_client::AutoStrategy; +use tokio::sync::OnceCell; + +use super::eql_plaintext::{Cast, EqlPlaintext}; +use super::validation::FixtureIdentifier; + +/// Process-wide `ScopedCipher`. Built on first use and held for the lifetime +/// of the test binary — `ScopedCipher` is documented as +/// "initialise once per process, hold an `Arc` for the process lifetime" +/// (see the upstream doc comment in `scoped_cipher.rs`). Re-initialising it +/// per call discards the warm reqwest pool and the cached auth token, and +/// makes the generator slower for no benefit. +static CIPHER: OnceCell>> = OnceCell::const_new(); + +/// Lazily initialise the process-wide cipher. On the first call this performs +/// the AutoStrategy detection, the ZeroKMS handshake, and the keyset load — +/// each subsequent call is an `Arc` clone. +/// +/// Errors surface as `anyhow::Error` with `.context(...)` naming the step +/// that failed (credential detection vs ZeroKMS connect vs keyset load). +pub async fn cipher() -> Result>> { + CIPHER + .get_or_try_init(|| async { + let zerokms = ZeroKMSBuilder::auto() + .context( + "building ZeroKMSBuilder via AutoStrategy::detect() — check \ + CS_CLIENT_ACCESS_KEY or CS_WORKSPACE_CRN env vars", + )? + .with_key_provider(EnvKeyProvider) + .build() + .await + .context( + "building ZeroKMS client — check CS_CLIENT_ID + CS_CLIENT_KEY \ + env vars (loaded by EnvKeyProvider)", + )?; + + let cipher = ScopedCipher::init_default(Arc::new(zerokms)) + .await + .context("initialising ScopedCipher for the default keyset")?; + + Ok::<_, anyhow::Error>(Arc::new(cipher)) + }) + .await + .cloned() +} + +/// Build a `ColumnConfig` from the fixture spec's index list + cast. +/// +/// The fixture spec uses EQL's string-typed index identifiers (`"unique"`, +/// `"ore"`, `"match"`, `"ste_vec"`); cipherstash-config uses the typed +/// `IndexType` enum. The mapping here is the single point of contact +/// between the two — extending fixture coverage to a new index means one +/// new arm here plus the corresponding `EqlPlaintext::CAST` constant. +/// +/// Unknown identifiers raise immediately with the offending name in the +/// error so a typo at spec-construction surfaces at run time (the +/// `FixtureIdentifier` newtype only proves the string is a valid SQL +/// identifier, not that it names a real index type). +pub fn column_config_for( + spec_indexes: &[FixtureIdentifier], + cast: Cast, +) -> Result { + let column_type = cast_to_column_type(cast)?; + let mut config = ColumnConfig::build("payload").casts_as(column_type); + + for ix in spec_indexes { + let index_type = index_type_for(ix.as_str())?; + config = config.add_index(Index::new(index_type)); + } + + Ok(config) +} + +/// Map an `EqlPlaintext::Cast` onto cipherstash-config's `ColumnType`. The +/// `Cast` newtype's allowlist is structural, so the only failure mode is +/// "we extended `EqlPlaintext` with a new variant but forgot to extend +/// this mapping" — explicit error rather than a `_ => unreachable!()` +/// gives the maintainer a clear breadcrumb. +fn cast_to_column_type(cast: Cast) -> Result { + match cast.as_str() { + "int" => Ok(ColumnType::Int), + "small_int" => Ok(ColumnType::SmallInt), + "big_int" => Ok(ColumnType::BigInt), + "boolean" => Ok(ColumnType::Boolean), + "date" => Ok(ColumnType::Date), + "decimal" => Ok(ColumnType::Decimal), + "float" | "real" | "double" => Ok(ColumnType::Float), + "text" => Ok(ColumnType::Text), + "jsonb" | "json" => Ok(ColumnType::Json), + "timestamp" => Ok(ColumnType::Timestamp), + other => Err(anyhow!( + "no cipherstash-config ColumnType mapping for cast {other:?} — \ + extend cipherstash::cast_to_column_type when adding a new \ + EqlPlaintext variant" + )), + } +} + +/// Map the fixture spec's string-typed index identifier onto a typed +/// `IndexType`. Reuses the canonical constructors on `Index` +/// (`Index::new_unique`, etc.) so the defaults stay in sync with whatever +/// cipherstash-config considers the canonical shape for each index. +fn index_type_for(name: &str) -> Result { + match name { + "unique" => Ok(Index::new_unique().index_type), + "ore" => Ok(IndexType::Ore), + "match" => Ok(Index::new_match().index_type), + other => Err(anyhow!( + "unknown EQL index identifier {other:?} — supported: \ + unique, ore, match" + )), + } +} + +/// Encrypt a single plaintext value for storage and return the resulting +/// EQL ciphertext as a `serde_json::Value` ready to bind into a `jsonb` +/// column. +/// +/// Uses `EqlOperation::Store`, which yields a full storage payload +/// (`{"k": "ct", "v": 2, "i": …, "c": …, "hm": …, "ob": …}`) — the same +/// shape Proxy produced for the working table. `EqlEncryptOpts::default()` +/// uses the cipher's default keyset, no lock context, no service token, no +/// index filter — the same defaults Proxy uses for column-config-driven +/// inserts. +pub async fn encrypt_store( + table: &str, + column: &str, + value: T, + config: &ColumnConfig, +) -> Result { + let cipher = cipher().await?; + + let prepared = PreparedPlaintext::new( + Cow::Borrowed(config), + Identifier::new(table, column), + value.to_plaintext(), + EqlOperation::Store, + ); + + let opts = EqlEncryptOpts::default(); + let mut outputs = encrypt_eql(cipher, vec![prepared], &opts) + .await + .with_context(|| format!("encrypting value for {table}.{column}"))?; + + let output = outputs + .pop() + .ok_or_else(|| anyhow!("encrypt_eql returned no outputs"))?; + + let ciphertext: EqlCiphertext = match output { + EqlOutput::Store(ct) => ct, + EqlOutput::Query(_) => { + // EqlOperation::Store always yields EqlOutput::Store; treating + // the other arm as unreachable would hide a future API drift. + return Err(anyhow!( + "encrypt_eql returned a Query output for an EqlOperation::Store input" + )); + } + }; + + serde_json::to_value(&ciphertext).context("serialising EqlCiphertext to JSON") +} + +#[cfg(test)] +mod tests { + use super::*; + + fn ident(s: &str) -> FixtureIdentifier { + FixtureIdentifier::try_from(s).unwrap() + } + + #[test] + fn column_config_for_int_with_unique_and_ore_builds_a_two_index_config() { + let indexes = [ident("unique"), ident("ore")]; + let config = column_config_for(&indexes, Cast::INT).unwrap(); + + assert_eq!(config.name, "payload"); + assert!(matches!(config.cast_type, ColumnType::Int)); + assert_eq!(config.indexes.len(), 2); + assert!(config.indexes.iter().any(|i| i.is_unique())); + assert!(config.indexes.iter().any(|i| i.is_ore())); + } + + #[test] + fn column_config_for_rejects_an_unknown_index_name() { + let indexes = [ident("bogus")]; + let err = column_config_for(&indexes, Cast::INT).unwrap_err(); + assert!( + format!("{err:#}").contains("unknown EQL index identifier"), + "error should name the unknown identifier: {err:#}" + ); + } + + #[test] + fn cast_to_column_type_covers_every_eql_plaintext_cast_constant() { + // Every Cast constant on EqlPlaintext must round-trip into a + // ColumnType — otherwise a freshly-added EqlPlaintext variant + // would crash the generator at run time instead of failing the + // build. Listed explicitly so a new `pub const` on Cast forces an + // update here. + for cast in [ + Cast::TEXT, + Cast::INT, + Cast::SMALL_INT, + Cast::BIG_INT, + Cast::REAL, + Cast::DOUBLE, + Cast::BOOLEAN, + Cast::DATE, + Cast::JSONB, + Cast::JSON, + Cast::FLOAT, + Cast::DECIMAL, + Cast::TIMESTAMP, + ] { + cast_to_column_type(cast).unwrap_or_else(|e| { + panic!("Cast::{} has no ColumnType mapping: {e}", cast.as_str()) + }); + } + } +} diff --git a/tests/sqlx/src/fixtures/driver.rs b/tests/sqlx/src/fixtures/driver.rs index 1b973bfa..0691ab5d 100644 --- a/tests/sqlx/src/fixtures/driver.rs +++ b/tests/sqlx/src/fixtures/driver.rs @@ -1,10 +1,11 @@ //! `FixtureSpec::run()` — the generation driver. //! -//! mise owns the containers; this owns the data. The driver assumes -//! `mise run proxy:up` has started `cipherstash-proxy` and that the -//! generation Postgres has EQL installed. Errors are `anyhow` with -//! `.context(...)` — a generator is a developer tool; a clear crash beats a -//! partial fixture. +//! mise owns the containers; this owns the data. The driver opens a direct +//! Postgres connection and encrypts each plaintext value via +//! `cipherstash-client` (see the sibling `cipherstash` module) before +//! inserting the result into a transient working table. Errors are `anyhow` +//! with `.context(...)` — a generator is a developer tool; a clear crash +//! beats a partial fixture. //! //! The `public._fixture_` working table is transient plumbing: `.run()` //! creates it, encrypts into it, renders the committed rows from it, then @@ -16,20 +17,15 @@ //! `DROP TABLE IF EXISTS` reclaims that case. use std::path::PathBuf; -use std::process::Command; -use std::time::Duration; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use sqlx::postgres::PgConnectOptions; use sqlx::{ConnectOptions, Connection, PgConnection, Row}; +use super::cipherstash; use super::eql_plaintext::EqlPlaintext; use super::spec::FixtureSpec; -/// `cipherstash-proxy` — the fixed container_name from -/// `tests/docker-compose.proxy.yml`. -const PROXY_CONTAINER: &str = "cipherstash-proxy"; - /// Bag of Rust-type bounds required of a fixture's plaintext value `T`. /// Collapses the long `where` clause on `impl FixtureSpec<'a, T>` to a single /// alias; the blanket impl below makes it auto-applied to any `T` that @@ -55,25 +51,24 @@ impl FixtureValue for T where } /// Driver connection options, parsed once from the environment at the start -/// of `run`. `direct` is the unmediated Postgres connection (DDL + -/// rendering); `proxy` is the Proxy-mediated connection (encrypted inserts). +/// of `run`. Only the unmediated Postgres connection is needed: DDL, +/// inserts, and the render step all run against it. Encryption happens in +/// Rust (cipherstash-client), so there is no second connection. struct DriverConfig { direct: PgConnectOptions, - proxy: PgConnectOptions, } impl DriverConfig { /// Build connection options from env vars, defaulting to the /// `mise.toml` `[env]` values. Port parses are strict — a malformed - /// `POSTGRES_PORT` or `PROXY_PORT` surfaces as an `anyhow::Error` with - /// the offending value, matching the rest of the driver's error story. + /// `POSTGRES_PORT` surfaces as an `anyhow::Error` with the offending + /// value, matching the rest of the driver's error story. fn from_env() -> Result { let host = env_or("POSTGRES_HOST", "localhost"); let user = env_or("POSTGRES_USER", "cipherstash"); let password = env_or("POSTGRES_PASSWORD", "password"); let database = env_or("POSTGRES_DB", "cipherstash"); let port = parse_port_env("POSTGRES_PORT", 7432)?; - let proxy_port = parse_port_env("PROXY_PORT", 6432)?; let direct = PgConnectOptions::new() .host(&host) @@ -82,10 +77,7 @@ impl DriverConfig { .password(&password) .database(&database); - // Proxy runs on the host at PROXY_PORT (default 6432); same credentials. - let proxy = direct.clone().port(proxy_port); - - Ok(Self { direct, proxy }) + Ok(Self { direct }) } } @@ -102,47 +94,6 @@ fn parse_port_env(key: &str, default: u16) -> Result { } } -/// Restart Proxy (so it reloads the new encrypt config) and poll until it -/// accepts a connection. On timeout, dump `docker logs` and fail. -async fn restart_proxy_and_wait(proxy_options: &PgConnectOptions) -> Result<()> { - let status = Command::new("docker") - .args(["restart", PROXY_CONTAINER]) - .status() - .context("failed to spawn `docker restart`")?; - if !status.success() { - anyhow::bail!("`docker restart {PROXY_CONTAINER}` exited non-zero"); - } - - for _ in 0..60 { - if let Ok(mut conn) = proxy_options.clone().connect().await { - if sqlx::query("SELECT 1").execute(&mut conn).await.is_ok() { - let _ = conn.close().await; - return Ok(()); - } - } - tokio::time::sleep(Duration::from_secs(1)).await; - } - - // `docker logs` sends the container's stdout to our stdout and its stderr - // to our stderr; capture both so the diagnostic is non-empty regardless of - // which stream the Proxy logs to. - let logs = Command::new("docker") - .args(["logs", "--tail", "40", PROXY_CONTAINER]) - .output() - .map(|o| { - format!( - "{}{}", - String::from_utf8_lossy(&o.stdout), - String::from_utf8_lossy(&o.stderr), - ) - }) - .unwrap_or_default(); - Err(anyhow!( - "Proxy did not become ready within 60s after restart\n\ - === {PROXY_CONTAINER} logs ===\n{logs}" - )) -} - /// Absolute path to `tests/sqlx/fixtures/.sql`. Resolved from /// `CARGO_MANIFEST_DIR` (the `tests/sqlx` crate root) so the path is correct /// regardless of the process working directory. @@ -161,7 +112,7 @@ where /// The production entry point. Parses the env-driven `DriverConfig` /// once, opens a direct Postgres connection, then delegates the /// schema + teardown orchestration to `run_with`, supplying - /// `insert_through_proxy` as the closure. After `run_with` returns the + /// `insert_direct` as the closure. After `run_with` returns the /// rendered INSERT lines, this method composes them with /// `fixture_script_preamble` and writes the committed script to disk. pub async fn run(&self) -> Result<()> { @@ -174,10 +125,21 @@ where .await .context("connecting to Postgres (direct)")?; + // Second direct connection for the inserter closure. `run_with` + // borrows the first connection mutably for the duration of the + // pipeline, so the inserter must hold its own. + let mut inserter_conn = config + .direct + .clone() + .connect() + .await + .context("connecting to Postgres (direct inserter)")?; + let lines = self - .run_with(&mut direct, || self.insert_through_proxy(&config.proxy)) + .run_with(&mut direct, || self.insert_direct(&mut inserter_conn)) .await?; + let _ = inserter_conn.close().await; let _ = direct.close().await; let mut script = self.fixture_script_preamble(); @@ -193,33 +155,34 @@ where Ok(()) } - /// Restart Proxy so it picks up the new `add_search_config`, then open a - /// Proxy connection and insert each plaintext value into the working - /// table. Proxy intercepts each insert and writes the encrypted JSONB - /// composite into `payload`. The production insert step extracted from - /// `run`'s closure for skim-ability. - async fn insert_through_proxy(&self, proxy_options: &PgConnectOptions) -> Result<()> { - restart_proxy_and_wait(proxy_options).await?; + /// Encrypt each plaintext value via cipherstash-client and INSERT it + /// into the working table as plain JSONB. The committed + /// `ColumnConfig` is built once from the spec's indexes + cast — the + /// fixture name is fed as the table identifier so the resulting + /// payload's `i.t` field matches the working table, preserving the + /// shape Proxy used to emit. + async fn insert_direct(&self, direct: &mut PgConnection) -> Result<()> { + let config = cipherstash::column_config_for(self.indexes(), T::CAST) + .context("building ColumnConfig from FixtureSpec indexes")?; - let mut proxy = proxy_options - .clone() - .connect() - .await - .context("connecting to Proxy")?; let working = self.working_table(); for (i, value) in self.values().iter().enumerate() { let id = (i as i64) + 1; + let payload = + cipherstash::encrypt_store(&working, "payload", *value, &config) + .await + .with_context(|| format!("encrypting value #{id}"))?; + let insert = - format!("INSERT INTO {working} (id, plaintext, payload) VALUES ($1, $2, $3)"); + format!("INSERT INTO public.{working} (id, plaintext, payload) VALUES ($1, $2, $3)"); sqlx::query(&insert) .bind(id) .bind(*value) - .bind(*value) - .execute(&mut proxy) + .bind(sqlx::types::Json(payload)) + .execute(&mut *direct) .await - .with_context(|| format!("inserting value #{id} through Proxy"))?; + .with_context(|| format!("inserting value #{id}"))?; } - let _ = proxy.close().await; Ok(()) } @@ -239,10 +202,10 @@ where /// 6. Propagate failures in causal order: inserter error first /// (root cause), then render, then drop. /// - /// `run()` calls this with `insert_through_proxy`. Tests call it with - /// closures that insert hand-crafted `eql_v2_encrypted` composite - /// literals directly (no Proxy required), or with closures that return - /// `Err` to exercise the teardown contract. + /// `run()` calls this with `insert_direct`. Tests call it with + /// closures that insert hand-crafted JSONB payloads directly (no + /// cipherstash-client required), or with closures that return `Err` + /// to exercise the teardown contract. /// /// Private by design: this is a test seam, not a public API. Other /// fixtures must go through `run`. @@ -332,7 +295,7 @@ mod tests { let insert = format!( "INSERT INTO public.{working_for_closure} \ (id, plaintext, payload) \ - VALUES ($1, $2, ROW($3::jsonb)::public.eql_v2_encrypted)" + VALUES ($1, $2, $3::jsonb)" ); sqlx::query(&insert) .bind(id) diff --git a/tests/sqlx/src/fixtures/eql_plaintext.rs b/tests/sqlx/src/fixtures/eql_plaintext.rs index e8c17a22..4cdc807d 100644 --- a/tests/sqlx/src/fixtures/eql_plaintext.rs +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -6,9 +6,15 @@ //! makes the EQL allowlist structural — a `T::CAST` is, by construction, a //! value EQL accepts. The trait is sealed so external crates cannot add //! impls that bypass this guarantee. +//! +//! `to_plaintext` lifts the value into the cipherstash-client +//! `encryption::Plaintext` enum so the fixture generator can encrypt directly +//! via `eql::encrypt_eql` (no Proxy round trip). use std::fmt; +use cipherstash_client::encryption::Plaintext; + /// The `cast_as` argument for `eql_v2.add_search_config`. The field is /// private so the allowlist is the set of `pub const`s below. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -70,11 +76,21 @@ mod sealed { pub trait EqlPlaintext: sealed::Sealed { const CAST: Cast; const PLAINTEXT_SQL_TYPE: PlaintextSqlType; + + /// Lift the Rust value into the cipherstash-client `Plaintext` enum the + /// EQL encryption pipeline consumes. The mapping is total — every + /// `EqlPlaintext` impl maps cleanly onto a `Plaintext::*(Some(_))` + /// variant. + fn to_plaintext(self) -> Plaintext; } impl EqlPlaintext for i32 { const CAST: Cast = Cast::INT; const PLAINTEXT_SQL_TYPE: PlaintextSqlType = PlaintextSqlType::INTEGER; + + fn to_plaintext(self) -> Plaintext { + Plaintext::Int(Some(self)) + } } #[cfg(test)] @@ -93,4 +109,14 @@ mod tests { "integer" ); } + + #[test] + fn i32_to_plaintext_wraps_in_int_variant() { + // The trait must lift the raw i32 into the EQL pipeline's Plaintext + // enum so the fixture driver can hand it to `eql::encrypt_eql`. + match (42_i32).to_plaintext() { + Plaintext::Int(Some(value)) => assert_eq!(value, 42), + other => panic!("expected Plaintext::Int(Some(42)), got {other:?}"), + } + } } diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index 38504ce5..d2c90c3f 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -14,6 +14,8 @@ pub mod spec; pub use spec::FixtureSpec; +pub mod cipherstash; + pub mod driver; pub mod eql_v2_int4; diff --git a/tests/sqlx/src/fixtures/spec.rs b/tests/sqlx/src/fixtures/spec.rs index 0f1ea41c..9ab0c1f2 100644 --- a/tests/sqlx/src/fixtures/spec.rs +++ b/tests/sqlx/src/fixtures/spec.rs @@ -117,12 +117,13 @@ impl<'a, T> FixtureSpec<'a, T> { } /// SQL for the transient working table on the generation database. - /// `id BIGINT PRIMARY KEY`, `plaintext` as the SQL type for `T`, and - /// `payload eql_v2_encrypted` so Proxy encrypts inserts. Per index: - /// an idempotent `remove_search_config` guarded by `WHERE EXISTS`, then - /// `add_search_config`. Every `add_search_config` argument is a quoted - /// string literal — the table/column names are fixed literals, and the - /// index name and cast are validated tokens (`FixtureIdentifier` / `Cast`). + /// `id BIGINT PRIMARY KEY`, `plaintext` as the SQL type for `T`, and a + /// plain `payload jsonb` staging column. The fixture driver encrypts in + /// Rust via `cipherstash-client` and inserts the resulting JSONB directly + /// — the working table is a values buffer that exists only so the render + /// step can use Postgres `format('%L', …)` for SQL literal escaping. No + /// `eql_v2_configuration` writes, no EQL types — the working table has + /// no EQL dependency at all. /// /// The leading `DROP TABLE IF EXISTS` is belt-and-suspenders: a normal run /// drops the working table itself at the end of `run()`, so this only @@ -132,27 +133,14 @@ impl<'a, T> FixtureSpec<'a, T> { T: EqlPlaintext, { let working = self.working_table(); - let mut sql = format!( + format!( "DROP TABLE IF EXISTS public.{working};\n\ CREATE TABLE public.{working} (\n \ id BIGINT PRIMARY KEY,\n \ plaintext {plaintext_type} NOT NULL,\n \ - payload eql_v2_encrypted\n);\n", + payload jsonb\n);\n", plaintext_type = T::PLAINTEXT_SQL_TYPE, - ); - for ix in &self.indexes { - sql.push_str(&format!( - "SELECT eql_v2.remove_search_config('{working}', 'payload', '{ix}')\n \ - WHERE EXISTS (\n \ - SELECT 1 FROM public.eql_v2_configuration c\n \ - WHERE c.data #> '{{tables,{working},payload,indexes,{ix}}}' IS NOT NULL\n );\n", - )); - sql.push_str(&format!( - "SELECT eql_v2.add_search_config('{working}', 'payload', '{ix}', '{cast}');\n", - cast = T::CAST, - )); - } - sql + ) } /// The committed fixture script's header + schema + DDL, up to (not @@ -167,7 +155,7 @@ impl<'a, T> FixtureSpec<'a, T> { "-- AUTO-GENERATED by `mise run fixture:generate {name}`.\n\ -- DO NOT EDIT BY HAND. Re-run the generator to refresh.\n\ --\n\ - -- Encrypted via CipherStash Proxy (HMAC + ORE block terms).\n\ + -- Encrypted via cipherstash-client (HMAC + ORE block terms).\n\ -- A SQLx fixture script: opt in with\n\ -- #[sqlx::test(fixtures(path = \"../fixtures\", scripts(\"{name}\")))]\n\ \n\ @@ -187,13 +175,14 @@ impl<'a, T> FixtureSpec<'a, T> { /// SQL run on the *direct* connection to render each working-table row as /// a committed INSERT. `format('%L', ...)` does server-side literal /// escaping; row values never pass through Rust string interpolation. - /// `(payload).data::text` unwraps the `eql_v2_encrypted` composite to the - /// JSONB text that the committed `jsonb` column stores. + /// `payload::text` projects the already-encrypted JSONB straight through + /// — the working table stores the cipherstash-client-encrypted payload as + /// plain `jsonb`, so no composite unwrap is needed. pub fn render_rows_sql(&self) -> String { format!( "SELECT format(\n \ 'INSERT INTO {table} (id, plaintext, payload) VALUES (%L, %L, %L::{column_type});',\n \ - id, plaintext, (payload).data::text\n) \ + id, plaintext, payload::text\n) \ FROM public.{working} ORDER BY id", table = self.fixture_table(), column_type = self.column_type, @@ -300,32 +289,25 @@ mod tests { assert!(sql.contains("CREATE TABLE public._fixture_eql_v2_int4 (")); assert!(sql.contains("id BIGINT PRIMARY KEY")); assert!(sql.contains("plaintext integer NOT NULL")); - // The working table's payload is eql_v2_encrypted so Proxy encrypts inserts. - assert!(sql.contains("payload eql_v2_encrypted")); - } - - #[test] - fn working_schema_sql_configures_each_index_idempotently() { - let sql = int4_spec().working_schema_sql(); - // remove first (idempotent), then add, for both indexes. - assert!(sql - .contains("eql_v2.remove_search_config('_fixture_eql_v2_int4', 'payload', 'unique')")); - assert!(sql.contains( - "eql_v2.add_search_config('_fixture_eql_v2_int4', 'payload', 'unique', 'int')" - )); + // The working table's payload is plain jsonb — encryption happens in + // Rust via cipherstash-client, not via a Proxy round trip. + assert!(sql.contains("payload jsonb")); assert!( - sql.contains("eql_v2.remove_search_config('_fixture_eql_v2_int4', 'payload', 'ore')") + !sql.contains("eql_v2_encrypted"), + "working table should not depend on the eql_v2_encrypted type" ); - assert!(sql - .contains("eql_v2.add_search_config('_fixture_eql_v2_int4', 'payload', 'ore', 'int')")); } #[test] - fn working_schema_sql_uses_the_t_cast_not_the_column_type() { - // payload column-type is jsonb, but the EQL cast is i32::CAST = "int". + fn working_schema_sql_does_not_touch_eql_configuration() { + // The cipherstash-client path does NOT write to eql_v2_configuration: + // ColumnConfig lives entirely in Rust, no add_search_config / + // remove_search_config calls are emitted, and the working table has + // no EQL dependency. let sql = int4_spec().working_schema_sql(); - assert!(sql.contains("'int')")); // cast_as argument - assert!(!sql.contains("'jsonb')")); // jsonb is the committed type, not the cast + assert!(!sql.contains("add_search_config")); + assert!(!sql.contains("remove_search_config")); + assert!(!sql.contains("eql_v2_configuration")); } #[test] @@ -345,6 +327,18 @@ mod tests { assert!(preamble.contains("payload jsonb NOT NULL")); } + #[test] + fn fixture_script_preamble_attributes_encryption_to_cipherstash_client() { + // The preamble must record the encryption path so a reader of the + // committed SQL can trace it back to the generator. + let preamble = int4_spec().fixture_script_preamble(); + assert!(preamble.contains("cipherstash-client")); + assert!( + !preamble.contains("CipherStash Proxy"), + "preamble must not credit the Proxy — encryption is direct now" + ); + } + #[test] fn fixture_script_preamble_uses_the_committed_column_type() { // The committed table uses .with_column_type(), NOT eql_v2_encrypted. @@ -358,7 +352,13 @@ mod tests { assert!(sql.contains("INSERT INTO fixtures.eql_v2_int4 (id, plaintext, payload) VALUES")); assert!(sql.contains("%L, %L, %L::jsonb")); assert!(sql.contains("FROM public._fixture_eql_v2_int4")); - assert!(sql.contains("(payload).data::text")); + // payload is already encrypted JSONB in the working table; no + // composite to unwrap. + assert!(sql.contains("payload::text")); + assert!( + !sql.contains("(payload).data"), + "render must not unwrap a composite — payload is plain jsonb" + ); assert!(sql.contains("ORDER BY id")); } } From f8dfd92cd73f67b487897abfbe114ff3f98a4762 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 25 May 2026 15:17:29 +1000 Subject: [PATCH 05/93] Add CipherStash secrets to EQL test workflow --- .github/workflows/test-eql.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 73e5ab37..844764b6 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -64,6 +64,8 @@ jobs: env: POSTGRES_VERSION: ${{ matrix.postgres-version }} + CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }} + CS_WORKSPACE_CRN: ${{ secrets.CS_WORKSPACE_CRN }} steps: - uses: actions/checkout@v6 @@ -126,4 +128,3 @@ jobs: mise run --output prefix test:splinter --postgres ${POSTGRES_VERSION} - From ded50dfadd487c7123c183f20112857f4582a792 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 25 May 2026 15:39:36 +1000 Subject: [PATCH 06/93] perf(fixtures): batch encrypt_store and add cipherstash test coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cipherstash::encrypt_store` now takes a slice and returns a `Vec`, so a fixture run issues one `encrypt_eql` call regardless of value count instead of one per value. The 14-row `eql_v2_int4` fixture drops from 14 ZeroKMS round trips to one; the planned bench fixture (~10k rows) gets the same treatment. Empty input short-circuits before `cipher()` so a caller with nothing to encrypt does not pay the bootstrap cost. `driver::insert_direct` now does one batched encrypt then a per-row INSERT loop — the INSERT is invariant across rows so the SQL string is lifted out of the loop. The working table is local Postgres and the per-row execute cost is in microseconds, so batching the INSERTs is not worth the dynamic-SQL complexity. Test coverage closes the gap flagged in the PR review: - Cheap unit tests (no live ZeroKMS): `index_type_for` mapping for unique/ore/match plus the unknown-name error path; an empty-batch short-circuit test that proves `cipher()` is not reached when there is nothing to encrypt (visible because the test runs without CS_* env vars). - Live tests gated by `fixture-gen` + `#[ignore]`: single-value round trip, batch length + per-payload identifier check, and a distinct-plaintexts → distinct `hm` assertion that mirrors the fixture-tests' equality term check at the unit-test layer. --- tests/sqlx/src/fixtures/cipherstash.rs | 269 +++++++++++++++--- tests/sqlx/src/fixtures/driver.rs | 32 ++- tests/sqlx/src/fixtures/spec.rs | 6 +- tests/sqlx/tests/eql_v2_int4_fixture_tests.rs | 6 +- 4 files changed, 262 insertions(+), 51 deletions(-) diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs index c8dd6c5b..eea51f44 100644 --- a/tests/sqlx/src/fixtures/cipherstash.rs +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -89,10 +89,7 @@ pub async fn cipher() -> Result>> { /// error so a typo at spec-construction surfaces at run time (the /// `FixtureIdentifier` newtype only proves the string is a valid SQL /// identifier, not that it names a real index type). -pub fn column_config_for( - spec_indexes: &[FixtureIdentifier], - cast: Cast, -) -> Result { +pub fn column_config_for(spec_indexes: &[FixtureIdentifier], cast: Cast) -> Result { let column_type = cast_to_column_type(cast)?; let mut config = ColumnConfig::build("payload").casts_as(column_type); @@ -145,9 +142,14 @@ fn index_type_for(name: &str) -> Result { } } -/// Encrypt a single plaintext value for storage and return the resulting -/// EQL ciphertext as a `serde_json::Value` ready to bind into a `jsonb` -/// column. +/// Encrypt a batch of plaintext values for storage and return one EQL +/// ciphertext per input as a `serde_json::Value` ready to bind into a +/// `jsonb` column. +/// +/// One `encrypt_eql` call regardless of `values.len()` — ZeroKMS does the +/// round trip once, not N times. The per-value field in each +/// `PreparedPlaintext` is `value.to_plaintext()`; the config, identifier, +/// and `EqlOperation::Store` are shared across the batch. /// /// Uses `EqlOperation::Store`, which yields a full storage payload /// (`{"k": "ct", "v": 2, "i": …, "c": …, "hm": …, "ob": …}`) — the same @@ -155,42 +157,71 @@ fn index_type_for(name: &str) -> Result { /// uses the cipher's default keyset, no lock context, no service token, no /// index filter — the same defaults Proxy uses for column-config-driven /// inserts. -pub async fn encrypt_store( +/// +/// An empty `values` slice short-circuits before `cipher()` so a caller +/// with nothing to encrypt does not pay the ZeroKMS bootstrap cost. +pub async fn encrypt_store( table: &str, column: &str, - value: T, + values: &[T], config: &ColumnConfig, -) -> Result { +) -> Result> { + if values.is_empty() { + return Ok(Vec::new()); + } + let cipher = cipher().await?; - let prepared = PreparedPlaintext::new( - Cow::Borrowed(config), - Identifier::new(table, column), - value.to_plaintext(), - EqlOperation::Store, - ); + // `Identifier::new` does two `String` allocations per call — cheap + // enough that constructing per-iteration is preferred over assuming + // the upstream type implements `Clone`. + let prepared: Vec = values + .iter() + .map(|value| { + PreparedPlaintext::new( + Cow::Borrowed(config), + Identifier::new(table, column), + value.to_plaintext(), + EqlOperation::Store, + ) + }) + .collect(); let opts = EqlEncryptOpts::default(); - let mut outputs = encrypt_eql(cipher, vec![prepared], &opts) + let outputs = encrypt_eql(cipher, prepared, &opts) .await - .with_context(|| format!("encrypting value for {table}.{column}"))?; - - let output = outputs - .pop() - .ok_or_else(|| anyhow!("encrypt_eql returned no outputs"))?; - - let ciphertext: EqlCiphertext = match output { - EqlOutput::Store(ct) => ct, - EqlOutput::Query(_) => { - // EqlOperation::Store always yields EqlOutput::Store; treating - // the other arm as unreachable would hide a future API drift. - return Err(anyhow!( - "encrypt_eql returned a Query output for an EqlOperation::Store input" - )); - } - }; + .with_context(|| { + format!( + "encrypting batch of {} values for {table}.{column}", + values.len() + ) + })?; - serde_json::to_value(&ciphertext).context("serialising EqlCiphertext to JSON") + if outputs.len() != values.len() { + return Err(anyhow!( + "encrypt_eql returned {} outputs for {} inputs", + outputs.len(), + values.len() + )); + } + + outputs + .into_iter() + .map(|output| { + let ciphertext: EqlCiphertext = match output { + EqlOutput::Store(ct) => ct, + EqlOutput::Query(_) => { + // EqlOperation::Store always yields EqlOutput::Store; + // treating the other arm as unreachable would hide a + // future API drift. + return Err(anyhow!( + "encrypt_eql returned a Query output for an EqlOperation::Store input" + )); + } + }; + serde_json::to_value(&ciphertext).context("serialising EqlCiphertext to JSON") + }) + .collect() } #[cfg(test)] @@ -223,6 +254,45 @@ mod tests { ); } + #[test] + fn index_type_for_maps_known_names_to_their_canonical_index_type() { + // The named EQL index identifiers each round-trip into the + // `IndexType` cipherstash-config considers canonical for that + // name. Compared via the public `Index` surface (`is_unique`, + // `is_ore`, `is_match`) so the assertion does not depend on the + // shape of the non-exhaustive `IndexType` enum. + let unique = Index::new(index_type_for("unique").unwrap()); + assert!(unique.is_unique(), "'unique' must map to the unique index"); + + let ore = Index::new(index_type_for("ore").unwrap()); + assert!(ore.is_ore(), "'ore' must map to the ORE index"); + + let m = Index::new(index_type_for("match").unwrap()); + assert!(m.is_match(), "'match' must map to the match (bloom) index"); + } + + #[test] + fn index_type_for_rejects_an_unknown_index_name() { + let err = index_type_for("bogus").unwrap_err(); + let msg = format!("{err:#}"); + assert!( + msg.contains("unknown EQL index identifier") && msg.contains("bogus"), + "error should name the offending identifier: {msg}" + ); + } + + #[tokio::test] + async fn encrypt_store_with_empty_values_returns_an_empty_vec_without_calling_cipher() { + // Empty input short-circuits before `cipher()` so a caller with + // nothing to encrypt does not pay the ZeroKMS bootstrap cost. + // Running this test under `cargo test` (no `fixture-gen` feature, + // no CS_* env vars) proves the short-circuit: if `cipher()` were + // reached, the missing credentials would surface as an error. + let config = column_config_for(&[ident("unique")], Cast::INT).unwrap(); + let out = encrypt_store::("t", "c", &[], &config).await.unwrap(); + assert!(out.is_empty(), "empty input must yield empty output"); + } + #[test] fn cast_to_column_type_covers_every_eql_plaintext_cast_constant() { // Every Cast constant on EqlPlaintext must round-trip into a @@ -251,3 +321,134 @@ mod tests { } } } + +/// Live `encrypt_store` round-trips against a real ZeroKMS keyset. Gated +/// by `fixture-gen` so default `cargo test` runs do not require +/// `CS_CLIENT_ACCESS_KEY` / `CS_WORKSPACE_CRN`. Each test is +/// `#[ignore]` so it only runs under +/// `cargo test --features fixture-gen -- --ignored --test-threads=1`, +/// mirroring the `generate` test in `eql_v2_int4.rs`. +/// +/// **Must run serially (`--test-threads=1`).** The process-wide +/// `CIPHER` `OnceCell` caches a `ScopedCipher` whose reqwest connection +/// pool is bound to the tokio runtime that initialised it. Each +/// `#[tokio::test]` builds its own runtime, so under parallel +/// execution the second test's calls go through a pool whose +/// dispatcher has been dropped — failing with +/// "SendRequest: dispatch task is gone". Production fixture runs (one +/// `#[tokio::main]` runtime) are unaffected. +/// +/// These complement the structural fixture-tests in +/// `tests/sqlx/tests/eql_v2_int4_fixture_tests.rs`: those assert over the +/// regenerated SQL file end-to-end; these isolate the +/// `encrypt_store` call so an SDK API drift surfaces here before the +/// whole fixture pipeline fails. +#[cfg(all(test, feature = "fixture-gen"))] +mod live_tests { + use super::*; + use serde_json::Value; + + fn ident(s: &str) -> FixtureIdentifier { + FixtureIdentifier::try_from(s).unwrap() + } + + /// Config used by every live test — `unique` drives the `hm` term, + /// `ore` drives the `ob` term, so the returned payloads carry both. + fn int_config_with_hm_and_ob() -> ColumnConfig { + column_config_for(&[ident("unique"), ident("ore")], Cast::INT).unwrap() + } + + /// Assert the well-formed Store shape: the payload is a JSON object + /// with non-null `v`, `c`, `hm`, `ob`, and `i` fields. Mirrors the + /// per-key assertions in `eql_v2_int4_fixture_tests.rs`. + fn assert_store_shape(payload: &Value) { + let obj = payload + .as_object() + .expect("payload must be a JSON object"); + for key in ["v", "c", "hm", "ob", "i"] { + assert!( + obj.get(key).is_some_and(|v| !v.is_null()), + "payload must carry a non-null `{key}` field; got {payload}" + ); + } + // `v` is the EQL payload-format version. The cipherstash-client + // JSON encodes it as the integer 2; the existing fixture tests + // check `payload->>'v' = '2'` via Postgres's text-cast operator. + // Asserting the number here matches the source format directly. + assert_eq!( + obj.get("v").and_then(Value::as_i64), + Some(2), + "payload must declare v = 2; got {payload}" + ); + } + + #[tokio::test] + #[ignore = "live ZeroKMS — run via `cargo test --features fixture-gen -- --ignored`"] + async fn encrypt_store_single_value_returns_one_eql_payload() { + let config = int_config_with_hm_and_ob(); + let out = encrypt_store("live_one", "payload", &[42_i32], &config) + .await + .expect("encrypt_store should succeed against live ZeroKMS"); + assert_eq!(out.len(), 1, "single input should produce single output"); + assert_store_shape(&out[0]); + } + + #[tokio::test] + #[ignore = "live ZeroKMS — run via `cargo test --features fixture-gen -- --ignored`"] + async fn encrypt_store_batch_returns_one_payload_per_input_in_input_order() { + let config = int_config_with_hm_and_ob(); + let values = [-1_i32, 1, 42]; + let out = encrypt_store("live_batch", "payload", &values, &config) + .await + .expect("encrypt_store should succeed against live ZeroKMS"); + assert_eq!( + out.len(), + values.len(), + "batch length must equal input length" + ); + for (i, payload) in out.iter().enumerate() { + assert_store_shape(payload); + // Each payload's `i.t` should match the table identifier we + // supplied — that's the field consuming code uses to bind a + // payload to its source column. + let identifier_t = payload + .get("i") + .and_then(Value::as_object) + .and_then(|o| o.get("t")) + .and_then(Value::as_str); + assert_eq!( + identifier_t, + Some("live_batch"), + "payload[{i}].i.t must match the table argument; got {payload}" + ); + } + } + + #[tokio::test] + #[ignore = "live ZeroKMS — run via `cargo test --features fixture-gen -- --ignored`"] + async fn encrypt_store_batch_distinct_plaintexts_yield_distinct_hm() { + // HMAC is the equality term — three distinct plaintexts must + // yield three distinct `hm` strings. Mirrors + // `hmac_equality_terms_are_distinct_for_distinct_values` in the + // fixture-tests but at the unit-test layer. + let config = int_config_with_hm_and_ob(); + let out = encrypt_store("live_distinct", "payload", &[-1_i32, 1, 42], &config) + .await + .expect("encrypt_store should succeed against live ZeroKMS"); + + let hms: Vec<&str> = out + .iter() + .map(|p| { + p.get("hm") + .and_then(Value::as_str) + .expect("payload must carry a string `hm` term") + }) + .collect(); + let unique: std::collections::HashSet<&&str> = hms.iter().collect(); + assert_eq!( + unique.len(), + hms.len(), + "distinct plaintexts must yield distinct hm terms; got {hms:?}" + ); + } +} diff --git a/tests/sqlx/src/fixtures/driver.rs b/tests/sqlx/src/fixtures/driver.rs index 0691ab5d..90060a5f 100644 --- a/tests/sqlx/src/fixtures/driver.rs +++ b/tests/sqlx/src/fixtures/driver.rs @@ -155,26 +155,30 @@ where Ok(()) } - /// Encrypt each plaintext value via cipherstash-client and INSERT it - /// into the working table as plain JSONB. The committed - /// `ColumnConfig` is built once from the spec's indexes + cast — the - /// fixture name is fed as the table identifier so the resulting - /// payload's `i.t` field matches the working table, preserving the - /// shape Proxy used to emit. + /// Encrypt every plaintext value via cipherstash-client in **one + /// batched call**, then INSERT each ciphertext into the working + /// table as plain JSONB. The committed `ColumnConfig` is built once + /// from the spec's indexes + cast — the fixture name is fed as the + /// table identifier so the resulting payload's `i.t` field matches + /// the working table, preserving the shape Proxy used to emit. + /// + /// Batching means one ZeroKMS round trip per fixture run regardless + /// of value count; the INSERT loop is per-row because the working + /// table is local Postgres and the per-row execute cost is in + /// microseconds. async fn insert_direct(&self, direct: &mut PgConnection) -> Result<()> { let config = cipherstash::column_config_for(self.indexes(), T::CAST) .context("building ColumnConfig from FixtureSpec indexes")?; let working = self.working_table(); - for (i, value) in self.values().iter().enumerate() { - let id = (i as i64) + 1; - let payload = - cipherstash::encrypt_store(&working, "payload", *value, &config) - .await - .with_context(|| format!("encrypting value #{id}"))?; + let payloads = cipherstash::encrypt_store(&working, "payload", self.values(), &config) + .await + .context("encrypting fixture values")?; - let insert = - format!("INSERT INTO public.{working} (id, plaintext, payload) VALUES ($1, $2, $3)"); + let insert = + format!("INSERT INTO public.{working} (id, plaintext, payload) VALUES ($1, $2, $3)"); + for (i, (value, payload)) in self.values().iter().zip(payloads).enumerate() { + let id = (i as i64) + 1; sqlx::query(&insert) .bind(id) .bind(*value) diff --git a/tests/sqlx/src/fixtures/spec.rs b/tests/sqlx/src/fixtures/spec.rs index 9ab0c1f2..35e82615 100644 --- a/tests/sqlx/src/fixtures/spec.rs +++ b/tests/sqlx/src/fixtures/spec.rs @@ -39,7 +39,8 @@ impl<'a, T> FixtureSpec<'a, T> { /// # Panics /// Panics if `name` is not a valid identifier. pub fn new(name: &str) -> Self { - let name = FixtureIdentifier::try_from(name).unwrap_or_else(|e| panic!("fixture name: {e}")); + let name = + FixtureIdentifier::try_from(name).unwrap_or_else(|e| panic!("fixture name: {e}")); let column_type = ColumnType::try_from("jsonb") .expect("default column type \"jsonb\" must be in the allowlist"); Self { @@ -55,7 +56,8 @@ impl<'a, T> FixtureSpec<'a, T> { /// # Panics /// Panics if `index_name` is not a valid identifier. pub fn with_index(mut self, index_name: &str) -> Self { - let id = FixtureIdentifier::try_from(index_name).unwrap_or_else(|e| panic!("index name: {e}")); + let id = + FixtureIdentifier::try_from(index_name).unwrap_or_else(|e| panic!("index name: {e}")); self.indexes.push(id); self } diff --git a/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs b/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs index eb842b58..3cf73225 100644 --- a/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs +++ b/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs @@ -93,7 +93,11 @@ async fn plaintext_oracle_supports_value_filtering(pool: PgPool) -> Result<()> { sqlx::query_scalar("SELECT id FROM fixtures.eql_v2_int4 WHERE plaintext = 42 ORDER BY id") .fetch_all(&pool) .await?; - assert_eq!(ids, vec![9], "expected exactly one row with plaintext = 42 at id 9"); + assert_eq!( + ids, + vec![9], + "expected exactly one row with plaintext = 42 at id 9" + ); Ok(()) } From ecacbe3a7e50cd87f480d1a2a93f7c121ee523b3 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 25 May 2026 17:15:38 +1000 Subject: [PATCH 07/93] chore(mise): make test:sqlx depend on build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `test:sqlx` copies `release/cipherstash-encrypt.sql` into `tests/sqlx/migrations/001_install_eql.sql` so the EQL extension is applied to each per-test database. Without an explicit build dep a stale release artifact silently ships an old EQL extension — visible when regression-guard migrations (e.g. 003_install_ste_vec_data.sql) fail on a `_encrypted_check_c` shape the current source has already fixed. Declaring `depends = ["build"]` makes the release artifact fresh on every direct invocation of `test:sqlx`; the top-level `test.sh` already builds, so CI behaviour is unchanged. --- mise.toml | 5 +++++ tests/sqlx/src/fixtures/cipherstash.rs | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mise.toml b/mise.toml index 87934676..a5ead126 100644 --- a/mise.toml +++ b/mise.toml @@ -34,6 +34,11 @@ run = """ [tasks."test:sqlx"] description = "Run SQLx tests with hybrid migration approach" +# `build` produces release/cipherstash-encrypt.sql, which is then cp'd into +# tests/sqlx/migrations/001_install_eql.sql below. Without this dep, a stale +# release artifact silently ships an old EQL extension into the test DB and +# regression-guard migrations (e.g. 003_install_ste_vec_data.sql) fail. +depends = ["build"] dir = "{{config_root}}" run = """ # Copy built SQL to SQLx migrations (EQL install is generated, not static) diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs index eea51f44..bb3a69bc 100644 --- a/tests/sqlx/src/fixtures/cipherstash.rs +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -362,9 +362,7 @@ mod live_tests { /// with non-null `v`, `c`, `hm`, `ob`, and `i` fields. Mirrors the /// per-key assertions in `eql_v2_int4_fixture_tests.rs`. fn assert_store_shape(payload: &Value) { - let obj = payload - .as_object() - .expect("payload must be a JSON object"); + let obj = payload.as_object().expect("payload must be a JSON object"); for key in ["v", "c", "hm", "ob", "i"] { assert!( obj.get(key).is_some_and(|v| !v.is_null()), From d1718ad0557347deef8dbd006604194d422393b4 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 28 May 2026 15:22:58 +1000 Subject: [PATCH 08/93] fix(ci): supply client-key creds to fixture generator in test workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The eql_v2_int4 fixture generator encrypts via cipherstash-client, whose EnvKeyProvider requires CS_CLIENT_ID + CS_CLIENT_KEY to load the client key. test:sqlx regenerates fixtures every run, so CI needs that pair — but f8dfd92 only wired CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN (ZeroKMS auth), leaving the generator failing with "CS_CLIENT_ID environment variable not set". Add the client-key pair to the test job env. Also correct comments in mise.toml, tasks/fixtures.toml, Cargo.toml, and FIXTURE_SCHEMA.md that framed the two credential pairs as alternatives: auth (access key + workspace CRN) and key material (client id + key) are distinct roles and both are required. --- .github/workflows/test-eql.yml | 2 ++ mise.toml | 8 +++++--- tasks/fixtures.toml | 8 ++++---- tests/sqlx/Cargo.toml | 9 +++++---- tests/sqlx/fixtures/FIXTURE_SCHEMA.md | 9 +++++---- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 844764b6..9527faa8 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -66,6 +66,8 @@ jobs: POSTGRES_VERSION: ${{ matrix.postgres-version }} CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }} CS_WORKSPACE_CRN: ${{ secrets.CS_WORKSPACE_CRN }} + CS_CLIENT_ID: ${{ secrets.CS_CLIENT_ID }} + CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }} steps: - uses: actions/checkout@v6 diff --git a/mise.toml b/mise.toml index a5ead126..1cd33881 100644 --- a/mise.toml +++ b/mise.toml @@ -51,9 +51,11 @@ cd tests/sqlx sqlx migrate run # Regenerate fixtures every run — they are not committed (see .gitignore). -# Generator encrypts via cipherstash-client directly; CS_* credentials must -# be present in the shell environment (CS_CLIENT_ACCESS_KEY + -# CS_WORKSPACE_CRN, or the legacy CS_CLIENT_ID/CS_CLIENT_KEY pair). +# Generator encrypts via cipherstash-client directly, which needs BOTH a +# ZeroKMS auth credential (CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN, via +# AutoStrategy) AND a client key (CS_CLIENT_ID + CS_CLIENT_KEY, via +# EnvKeyProvider) in the shell environment. Auth and key material are +# separate roles — the two pairs are not alternatives. echo "Regenerating SQLx fixtures..." cd "{{config_root}}" mise run fixture:generate eql_v2_int4 diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml index 808200cd..be0d6fd1 100644 --- a/tasks/fixtures.toml +++ b/tasks/fixtures.toml @@ -6,10 +6,10 @@ description = "Generate a SQLx fixture script via cipherstash-client" # # Prerequisites: # - mise run postgres:up (Postgres with EQL installed) -# - CS_* credentials in the shell environment (auto-loaded by -# cipherstash-client's AutoStrategy / EnvKeyProvider): -# CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN (preferred), OR -# CS_CLIENT_ID + CS_CLIENT_KEY (legacy pair) +# - CS_* credentials in the shell environment. The generator needs BOTH +# pairs — they are not alternatives: +# CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN ZeroKMS auth (AutoStrategy) +# CS_CLIENT_ID + CS_CLIENT_KEY client key (EnvKeyProvider) # # Usage: mise run fixture:generate eql_v2_int4 dir = "{{config_root}}/tests/sqlx" diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index 153aebf3..12273e6c 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -25,9 +25,10 @@ default = [] bench = [] # Opt-in to compiling the fixture generators. Without this feature the # `#[cfg(feature = "fixture-gen")]` generator tests do not exist, so -# `cargo test` and CI never see them. Generators need a live Postgres and -# CipherStash workspace credentials in the process env -# (`CS_CLIENT_ACCESS_KEY` + `CS_WORKSPACE_CRN`, or the legacy -# `CS_CLIENT_ID`/`CS_CLIENT_KEY` pair). Run one with: +# `cargo test` and CI never see them. Generators need a live Postgres and, +# in the process env, BOTH a ZeroKMS auth credential (`CS_CLIENT_ACCESS_KEY` +# + `CS_WORKSPACE_CRN`, via AutoStrategy) AND a client key (`CS_CLIENT_ID` + +# `CS_CLIENT_KEY`, via EnvKeyProvider) — the two pairs are not alternatives. +# Run one with: # mise run fixture:generate fixture-gen = [] diff --git a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md index 9ac804b7..58ecd742 100644 --- a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md +++ b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md @@ -202,10 +202,11 @@ applies standalone. **Regenerated every test run.** `mise run test:sqlx` invokes the generator before `cargo test`, so a stale committed fixture cannot mask a payload-shape regression. The generator encrypts in-process via `cipherstash-client`; it -needs a live Postgres plus CipherStash workspace credentials -(`CS_CLIENT_ACCESS_KEY` + `CS_WORKSPACE_CRN`, or the legacy -`CS_CLIENT_ID`/`CS_CLIENT_KEY` pair) in the shell environment. Do not -hand-edit the generated file; it is overwritten in place on every run. +needs a live Postgres plus **both** CipherStash credential pairs in the shell +environment (they are not alternatives): `CS_CLIENT_ACCESS_KEY` + +`CS_WORKSPACE_CRN` for ZeroKMS auth (AutoStrategy) **and** `CS_CLIENT_ID` + +`CS_CLIENT_KEY` for the client key (EnvKeyProvider). Do not hand-edit the +generated file; it is overwritten in place on every run. **Schema:** Table lives in the dedicated `fixtures` SQL schema (kept out of the `public` type/domain namespace so a downstream `public.eql_v2_int4` domain can From 2aba09ea2cf2e0aa0932416bd087a005a7cff10e Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 12:32:37 +1000 Subject: [PATCH 09/93] feat(codegen): scalar encrypted-domain SQL generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render eql_v2_ jsonb-backed domain families (types, functions, operators, aggregates) from a minimal TOML manifest. Term capabilities are fixed in terms.py (hm -> equality, ore -> equality+ordering); output is byte-identical and headed AUTO-GENERATED — DO NOT EDIT. Hardening: _sql_str() quote-doubler at every SQL interpolation boundary, distinct-fixture-value validation in spec.py, SQL-identifier validation of manifest domain names, and codegen:domain / codegen:domain:all mise tasks. mise run build regenerates all domains every build. Part of PR #239 (eql_v2_int4 variant family). --- .gitignore | 10 +- mise.toml | 38 ++ tasks/build.sh | 22 +- tasks/codegen/__init__.py | 17 + tasks/codegen/conftest.py | 6 + tasks/codegen/domain.sh | 15 + tasks/codegen/generate.py | 332 ++++++++++++++++ tasks/codegen/operator_surface.py | 72 ++++ tasks/codegen/scalars.py | 93 +++++ tasks/codegen/spec.py | 141 +++++++ tasks/codegen/templates.py | 490 +++++++++++++++++++++++ tasks/codegen/terms.py | 107 +++++ tasks/codegen/test_against_reference.py | 117 ++++++ tasks/codegen/test_generate.py | 351 +++++++++++++++++ tasks/codegen/test_operator_surface.py | 126 ++++++ tasks/codegen/test_scalars.py | 64 +++ tasks/codegen/test_spec.py | 208 ++++++++++ tasks/codegen/test_templates.py | 499 ++++++++++++++++++++++++ tasks/codegen/test_terms.py | 96 +++++ tasks/codegen/test_writer.py | 157 ++++++++ tasks/codegen/types/.gitkeep | 0 tasks/codegen/types/int4.toml | 19 + tasks/codegen/writer.py | 89 +++++ 23 files changed, 3067 insertions(+), 2 deletions(-) create mode 100644 tasks/codegen/__init__.py create mode 100644 tasks/codegen/conftest.py create mode 100755 tasks/codegen/domain.sh create mode 100644 tasks/codegen/generate.py create mode 100644 tasks/codegen/operator_surface.py create mode 100644 tasks/codegen/scalars.py create mode 100644 tasks/codegen/spec.py create mode 100644 tasks/codegen/templates.py create mode 100644 tasks/codegen/terms.py create mode 100644 tasks/codegen/test_against_reference.py create mode 100644 tasks/codegen/test_generate.py create mode 100644 tasks/codegen/test_operator_surface.py create mode 100644 tasks/codegen/test_scalars.py create mode 100644 tasks/codegen/test_spec.py create mode 100644 tasks/codegen/test_templates.py create mode 100644 tasks/codegen/test_terms.py create mode 100644 tasks/codegen/test_writer.py create mode 100644 tasks/codegen/types/.gitkeep create mode 100644 tasks/codegen/types/int4.toml create mode 100644 tasks/codegen/writer.py diff --git a/.gitignore b/.gitignore index 68bca65b..05b0ae00 100644 --- a/.gitignore +++ b/.gitignore @@ -221,7 +221,15 @@ tests/sqlx/migrations/001_install_eql.sql # Generated SQLx fixtures (regenerated via `mise run fixture:generate`, # never commit — stale fixtures hide bugs) -tests/sqlx/fixtures/eql_v2_int4.sql +tests/sqlx/fixtures/eql_v2* + +# Generated encrypted-domain SQL — regenerated by `tasks/build.sh` from +# tasks/codegen/types/.toml on every build (or `mise run codegen:domain +# ` to refresh manually). Hand-written *_extensions.sql stays committed. +src/encrypted_domain/*/*_types.sql +src/encrypted_domain/*/*_functions.sql +src/encrypted_domain/*/*_operators.sql +src/encrypted_domain/*/*_aggregates.sql # Large generated test data files tests/ste_vec_vast.sql diff --git a/mise.toml b/mise.toml index a5ead126..33c96519 100644 --- a/mise.toml +++ b/mise.toml @@ -76,3 +76,41 @@ dir = "{{config_root}}/tests/sqlx" run = """ cargo test --test payload_schema_tests """ + +[tasks."codegen:domain:all"] +description = "Regenerate every encrypted-domain type from its TOML manifest" +dir = "{{config_root}}" +run = """ +mise exec python -- python -m tasks.codegen.generate --all +""" + +[tasks."test:codegen"] +description = "Run the encrypted-domain codegen generator tests (no database required)" +dir = "{{config_root}}" +run = """ +# pytest is the only non-stdlib dependency; the install is a fast no-op once satisfied. +mise exec python -- python -m pip install --quiet --disable-pip-version-check pytest +mise exec python -- python -m pytest tasks/codegen -q +""" + +[tasks."test:matrix:inventory"] +description = "Regenerate the int4 matrix test-name inventory snapshot (no database required)" +dir = "{{config_root}}/tests/sqlx" +run = """ +# Pin an explicit feature set so the inventory is deterministic regardless of +# the caller's local flags. `--no-default-features` keeps the `scale` arm +# (`#[cfg(feature = "scale")]`) excluded — its add/delete is a known blind spot +# of this default-feature inventory, covered instead by the scale gate + the +# family::mutations negative controls. `--list` enumerates the whole +# encrypted_domain binary (family::support, family::inlinability, +# family::mutations, scalars::int4); `grep '^scalars::int4'` scopes the +# snapshot to the matrix only, so landing other family tests never dirties it. +# `LC_ALL=C sort` makes ordering byte-stable across locales (a bare `sort` is +# locale-dependent and yields spurious CI diffs). +set -euo pipefail +mkdir -p snapshots +cargo test --no-default-features --test encrypted_domain -- --list | + sed -n 's/: test$//p' | + grep '^scalars::int4' | + LC_ALL=C sort > snapshots/int4_matrix_tests.txt +""" diff --git a/tasks/build.sh b/tasks/build.sh index 0768dd34..cef25521 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash #MISE description="Build SQL into single release file" #MISE alias="b" -#MISE sources=["src/**/*.sql", "tasks/pin_search_path.sql", "tasks/uninstall.sql", "tasks/uninstall-protect.sql"] +#MISE sources=["src/**/*.sql", "tasks/pin_search_path.sql", "tasks/uninstall.sql", "tasks/uninstall-protect.sql", "tasks/codegen/types/*.toml", "tasks/codegen/*.py"] #MISE outputs=["release/cipherstash-encrypt.sql","release/cipherstash-encrypt-uninstall.sql","release/cipherstash-encrypt-protect.sql","release/cipherstash-encrypt-protect-uninstall.sql"] #USAGE flag "--version " help="Specify release version of EQL" default="DEV" @@ -9,6 +9,26 @@ set -euo pipefail +# Regenerate encrypted-domain SQL from TOML specs before building. +# Generated files (src/encrypted_domain//_*.sql) are gitignored; the +# manifest at tasks/codegen/types/.toml is the source of truth. +# +# Nuke every generated file first so a deleted or renamed manifest can't +# leave orphans in src/ that the `src/**/*.sql` build glob would silently +# pick up. writer.py cleans within a directory it's regenerating, but it +# never runs for a type whose manifest no longer exists. Hand-written +# *_extensions.sql is preserved by the name patterns; -mindepth 2 keeps +# the type-agnostic src/encrypted_domain/functions.sql safe. +find src/encrypted_domain -mindepth 2 -type f \ + \( -name '*_types.sql' -o -name '*_functions.sql' -o -name '*_operators.sql' \ + -o -name '*_aggregates.sql' \) \ + -delete 2>/dev/null || true + +# Regenerate every type — single source of truth for the enumeration lives in +# tasks/codegen/generate.py (sorted, deterministic, aggregate exit code). The +# orphan sweep above still handles the manifest-deleted case --all cannot. +mise exec python -- python -m tasks.codegen.generate --all + # Fail loudly if any file referenced in a tsorted dep list doesn't exist. # Without this, `xargs cat` would print `cat: foo.sql: No such file or directory` # and continue — silently producing an incomplete release artefact. diff --git a/tasks/codegen/__init__.py b/tasks/codegen/__init__.py new file mode 100644 index 00000000..4443af87 --- /dev/null +++ b/tasks/codegen/__init__.py @@ -0,0 +1,17 @@ +"""Encrypted-domain SQL code generator for EQL scalar domain families.""" + +from .generate import generate_type, main +from .spec import DomainSpec, SpecError, TypeSpec, load_spec +from .terms import TERM_CATALOG, Term, TermError + +__all__ = [ + "DomainSpec", + "SpecError", + "TERM_CATALOG", + "Term", + "TermError", + "TypeSpec", + "generate_type", + "load_spec", + "main", +] diff --git a/tasks/codegen/conftest.py b/tasks/codegen/conftest.py new file mode 100644 index 00000000..f03b0e8b --- /dev/null +++ b/tasks/codegen/conftest.py @@ -0,0 +1,6 @@ +"""pytest discovery anchor for the codegen package. + +Tests import via `from tasks.codegen. import ...`; pytest runs +from the repo root (where `tasks/__init__.py` exists), so no `sys.path` +manipulation is needed. +""" diff --git a/tasks/codegen/domain.sh b/tasks/codegen/domain.sh new file mode 100755 index 00000000..ae279a12 --- /dev/null +++ b/tasks/codegen/domain.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +#MISE description="Regenerate an encrypted-domain type from its TOML spec" +#USAGE arg "type" help="Type token, e.g. int4 (matches tasks/codegen/types/.toml)" + +set -euo pipefail + +TYPE=${usage_type:?type argument required} + +echo "Regenerating encrypted-domain type: ${TYPE}" +mise exec python -- python -m tasks.codegen.generate "${TYPE}" +echo "" +echo "✓ Regenerated src/encrypted_domain/${TYPE}/ (gitignored)" +echo " Note: 'mise run build' regenerates every type automatically;" +echo " this task is for refreshing one type while iterating on its manifest." +echo " When ready, run 'mise run clean && mise run build' then 'mise run test'." diff --git a/tasks/codegen/generate.py b/tasks/codegen/generate.py new file mode 100644 index 00000000..cf30c598 --- /dev/null +++ b/tasks/codegen/generate.py @@ -0,0 +1,332 @@ +"""Top-level scalar encrypted-domain materializer.""" + +import sys +from collections.abc import Iterator +from pathlib import Path + +from .operator_surface import ( + BLOCKER_ONLY_OPERATORS, + PATH_OPERATORS, + SYMMETRIC_OPERATORS, + backing_function, +) +from .spec import DomainSpec, SpecError, TypeSpec, load_spec +from .templates import ( + AGGREGATE_OPS, + domain_name, + extractor_for_operator, + is_ord_capable, + render_aggregate, + render_blocker_bool, + render_blocker_native, + render_blocker_path, + render_domain_block, + render_extractor, + render_fixture_values_rs, + render_operator, + render_wrapper, + role_phrase, + supported_operators, +) +from .terms import TERM_CATALOG, Term, term_requires +from .writer import ( + clean_generated_files, + ensure_generated_paths_writable, + write_generated_file, + write_generated_rs, +) + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _symmetric_shapes(dom: str) -> list[tuple[str, str]]: + return [(dom, dom), (dom, "jsonb"), ("jsonb", dom)] + + +def _path_shapes(dom: str) -> list[tuple[str, str]]: + return [(dom, "text"), (dom, "integer"), ("jsonb", dom)] + + +def _blocker_only_shapes(dom: str, op: str) -> list[tuple[str, str, str]]: + if op in {"?", "?|", "?&"}: + rhs = "text[]" if op in {"?|", "?&"} else "text" + return [(dom, rhs, "boolean")] + if op in {"@?", "@@"}: + return [(dom, "jsonpath", "boolean")] + if op == "#>": + return [(dom, "text[]", "jsonb")] + if op == "#>>": + return [(dom, "text[]", "text")] + if op == "-": + return [(dom, "text", "jsonb"), (dom, "integer", "jsonb"), (dom, "text[]", "jsonb")] + if op == "#-": + return [(dom, "text[]", "jsonb")] + if op == "||": + return [(dom, dom, "jsonb"), (dom, "jsonb", "jsonb"), ("jsonb", dom, "jsonb")] + raise ValueError(f"unhandled blocker-only operator: {op}") + + +def _types_path(token: str) -> str: + return f"src/encrypted_domain/{token}/{token}_types.sql" + + +def fixture_values_rs_path(out_root: Path, token: str) -> Path: + """Committed Rust fixture-value const for a type. Outside the gitignored + src/encrypted_domain/ SQL tree because it is consumed (and committed) by + the Rust test crate.""" + return ( + out_root / "tests" / "sqlx" / "src" / "fixtures" / f"{token}_values.rs" + ) + + +def render_types_file(spec: TypeSpec) -> str: + """Body for _types.sql: every domain in one idempotent DO block. + + Iteration order follows the manifest's declared order — the TOML file is + the source of truth for emit order. + """ + blocks = [render_domain_block(domain, spec.token) for domain in spec.domains] + return ( + "-- REQUIRE: src/schema.sql\n\n" + f"--! @file encrypted_domain/{spec.token}/{spec.token}_types.sql\n" + f"--! @brief Encrypted-domain type family for {spec.token}.\n\n" + "DO $$\nBEGIN\n" + + "\n".join(blocks) + + "END\n$$;\n" + ) + + +def _functions_requires(spec: TypeSpec, domain: DomainSpec) -> list[str]: + reqs = [ + "src/schema.sql", + _types_path(spec.token), + "src/encrypted_domain/functions.sql", + ] + for extra in term_requires(domain.terms): + if extra not in reqs: + reqs.append(extra) + return reqs + + +def _extractor_terms(domain: DomainSpec) -> Iterator[Term]: + seen: set[str] = set() + for term_name in domain.terms: + term = TERM_CATALOG[term_name] + if term.extractor not in seen: + seen.add(term.extractor) + yield term + + +def render_functions_file(spec: TypeSpec, domain: DomainSpec) -> str: + """Body for a domain's _functions.sql.""" + dom = domain_name(domain.name) + supported = set(supported_operators(domain)) + parts: list[str] = [] + + for term in _extractor_terms(domain): + parts.append(render_extractor(domain, term)) + + for op in SYMMETRIC_OPERATORS: + extractor = extractor_for_operator(domain, op) + for arg_a, arg_b in _symmetric_shapes(dom): + if op in supported and extractor is not None: + parts.append(render_wrapper(domain, op, arg_a, arg_b, extractor)) + else: + parts.append(render_blocker_bool(domain, op, arg_a, arg_b)) + + for op in PATH_OPERATORS: + for arg_a, arg_b in _path_shapes(dom): + parts.append(render_blocker_path(domain, op, arg_a, arg_b)) + + for op in BLOCKER_ONLY_OPERATORS: + for arg_a, arg_b, returns in _blocker_only_shapes(dom, op): + parts.append(render_blocker_native(domain, op, arg_a, arg_b, returns)) + + requires = "\n".join(f"-- REQUIRE: {r}" for r in _functions_requires(spec, domain)) + header = ( + requires + "\n\n" + f"--! @file encrypted_domain/{spec.token}/{domain.name}_functions.sql\n" + f"--! @brief {role_phrase(domain.terms)} domain of the {spec.token} " + f"encrypted-domain family — comparison/path functions.\n\n" + ) + return header + "\n".join(parts) + + +def render_operators_file(spec: TypeSpec, domain: DomainSpec) -> str: + """Body for a domain's _operators.sql: 44 CREATE OPERATOR statements.""" + dom = domain_name(domain.name) + supported = set(supported_operators(domain)) + parts: list[str] = [] + + for op in SYMMETRIC_OPERATORS: + backing = backing_function(op) + for leftarg, rightarg in _symmetric_shapes(dom): + parts.append( + render_operator( + op, backing, leftarg, rightarg, + supported=op in supported, + ) + ) + for op in PATH_OPERATORS: + backing = backing_function(op) + for leftarg, rightarg in _path_shapes(dom): + parts.append( + render_operator(op, backing, leftarg, rightarg, supported=False) + ) + for op in BLOCKER_ONLY_OPERATORS: + backing = backing_function(op) + for leftarg, rightarg, _returns in _blocker_only_shapes(dom, op): + parts.append( + render_operator(op, backing, leftarg, rightarg, supported=False) + ) + + requires = ( + "-- REQUIRE: src/schema.sql\n" + f"-- REQUIRE: {_types_path(spec.token)}\n" + f"-- REQUIRE: src/encrypted_domain/{spec.token}/" + f"{domain.name}_functions.sql\n" + ) + header = ( + requires + "\n" + f"--! @file encrypted_domain/{spec.token}/{domain.name}_operators.sql\n" + f"--! @brief {role_phrase(domain.terms)} domain of the {spec.token} " + f"encrypted-domain family — operator declarations.\n\n" + ) + return header + "\n".join(parts) + + +def render_aggregates_file(spec: TypeSpec, domain: DomainSpec) -> str | None: + """Body for a domain's _aggregates.sql, or None if the domain has no + ordering comparator (storage/eq variants have no MIN/MAX semantics).""" + if not is_ord_capable(domain): + return None + parts = [render_aggregate(domain, AGGREGATE_OPS[name]) for name in ("min", "max")] + requires = ( + "-- REQUIRE: src/schema.sql\n" + f"-- REQUIRE: {_types_path(spec.token)}\n" + f"-- REQUIRE: src/encrypted_domain/{spec.token}/" + f"{domain.name}_functions.sql\n" + f"-- REQUIRE: src/encrypted_domain/{spec.token}/" + f"{domain.name}_operators.sql\n" + ) + header = ( + requires + "\n" + f"--! @file encrypted_domain/{spec.token}/{domain.name}_aggregates.sql\n" + f"--! @brief {role_phrase(domain.terms)} domain of the {spec.token} " + f"encrypted-domain family — MIN/MAX aggregates.\n\n" + ) + return header + "\n".join(parts) + + +def generate_type(spec: TypeSpec, out_dir: Path) -> list[Path]: + """Regenerate every generated file for a type.""" + out_dir = Path(out_dir) + target_paths = [out_dir / f"{spec.token}_types.sql"] + for domain in spec.domains: + target_paths.append(out_dir / f"{domain.name}_functions.sql") + target_paths.append(out_dir / f"{domain.name}_operators.sql") + if is_ord_capable(domain): + target_paths.append(out_dir / f"{domain.name}_aggregates.sql") + ensure_generated_paths_writable(target_paths) + clean_generated_files(out_dir) + + written: list[Path] = [] + + types_path = out_dir / f"{spec.token}_types.sql" + write_generated_file(types_path, render_types_file(spec)) + written.append(types_path) + + for domain in spec.domains: + fn_path = out_dir / f"{domain.name}_functions.sql" + write_generated_file(fn_path, render_functions_file(spec, domain)) + written.append(fn_path) + + op_path = out_dir / f"{domain.name}_operators.sql" + write_generated_file(op_path, render_operators_file(spec, domain)) + written.append(op_path) + + agg_body = render_aggregates_file(spec, domain) + if agg_body is not None: + agg_path = out_dir / f"{domain.name}_aggregates.sql" + write_generated_file(agg_path, agg_body) + written.append(agg_path) + + return written + + +DEFAULT_TYPES_DIR = Path(__file__).parent / "types" + + +def generate_one(token: str, *, types_dir: Path, out_root: Path) -> int: + """Regenerate one type from types_dir/.toml. + + Returns 0 on success, 1 when the manifest is missing or its inferred token + does not match. A malformed manifest raises SpecError — the caller decides + whether to surface it (single-type CLI) or aggregate it (--all).""" + toml_path = types_dir / f"{token}.toml" + if not toml_path.is_file(): + print(f"error: no manifest at {toml_path}", file=sys.stderr) + return 1 + spec = load_spec(toml_path) + if spec.token != token: + print( + f"error: manifest token '{spec.token}' does not match '{token}'", + file=sys.stderr, + ) + return 1 + out_dir = out_root / "src" / "encrypted_domain" / token + written = generate_type(spec, out_dir) + + if spec.fixture_values is not None: + rs_path = fixture_values_rs_path(out_root, token) + write_generated_rs(rs_path, render_fixture_values_rs(spec)) + written.append(rs_path) + + for path in written: + print(f"generated {path.relative_to(out_root)}") + print(f"generated {len(written)} files for {token}") + return 0 + + +def generate_all(*, types_dir: Path, out_root: Path) -> int: + """Regenerate every type whose manifest lives in types_dir. + + Iterates sorted(types_dir.glob('*.toml')) for deterministic order and + aggregates return codes: a missing/mismatched/malformed manifest is + reported and counted as a failure without aborting the remaining types.""" + tokens = [p.stem for p in sorted(types_dir.glob("*.toml"))] + if not tokens: + print(f"error: no manifests found in {types_dir}", file=sys.stderr) + return 1 + rc = 0 + for token in tokens: + try: + if generate_one(token, types_dir=types_dir, out_root=out_root) != 0: + rc = 1 + except SpecError as exc: + print(f"error: {token}: {exc}", file=sys.stderr) + rc = 1 + status = "ok" if rc == 0 else "FAILED" + print(f"codegen --all: {status} ({len(tokens)} types: {', '.join(tokens)})") + return rc + + +def main( + argv: list[str], + *, + types_dir: Path | None = None, + out_root: Path | None = None, +) -> int: + """CLI entrypoint: generate , or --all for every manifest.""" + types_dir = types_dir or DEFAULT_TYPES_DIR + out_root = out_root or REPO_ROOT + if len(argv) == 2 and argv[1] == "--all": + return generate_all(types_dir=types_dir, out_root=out_root) + if len(argv) != 2: + print("Usage: generate.py | generate.py --all", file=sys.stderr) + return 2 + return generate_one(argv[1], types_dir=types_dir, out_root=out_root) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tasks/codegen/operator_surface.py b/tasks/codegen/operator_surface.py new file mode 100644 index 00000000..355e5751 --- /dev/null +++ b/tasks/codegen/operator_surface.py @@ -0,0 +1,72 @@ +"""The generated operator surface for a scalar encrypted-domain type. + +Supported comparison operators route to inlinable wrappers when the domain +has the required term. Unsupported comparisons, path operators, and native +jsonb fallback operators route to blockers. +""" + +from dataclasses import dataclass +from typing import Literal + + +@dataclass(frozen=True) +class Operator: + """One operator in the generated surface.""" + + symbol: str + backing: str # eql_v2 backing function name (bare or quoted) + kind: Literal["symmetric", "path", "blocker_only"] + restrict: str | None # selectivity estimator, symmetric ops only + join: str | None # join selectivity estimator, symmetric ops only + commutator: str | None + negator: str | None + + +SYMMETRIC_OPERATORS = ["=", "<>", "<", "<=", ">", ">=", "@>", "<@"] +PATH_OPERATORS = ["->", "->>"] +BLOCKER_ONLY_OPERATORS = ["?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||"] + + +OPERATORS: dict[str, Operator] = { + "=": Operator("=", "eq", "symmetric", "eqsel", "eqjoinsel", "=", "<>"), + "<>": Operator("<>", "neq", "symmetric", "neqsel", "neqjoinsel", "<>", "="), + "<": Operator("<", "lt", "symmetric", "scalarltsel", "scalarltjoinsel", ">", ">="), + "<=": Operator("<=", "lte", "symmetric", "scalarlesel", "scalarlejoinsel", ">=", ">"), + ">": Operator(">", "gt", "symmetric", "scalargtsel", "scalargtjoinsel", "<", "<="), + ">=": Operator(">=", "gte", "symmetric", "scalargesel", "scalargejoinsel", "<=", "<"), + "@>": Operator("@>", "contains", "symmetric", None, None, None, None), + "<@": Operator("<@", "contained_by", "symmetric", None, None, None, None), + "->": Operator("->", '"->"', "path", None, None, None, None), + "->>": Operator("->>", '"->>"', "path", None, None, None, None), + "?": Operator("?", '"?"', "blocker_only", None, None, None, None), + "?|": Operator("?|", '"?|"', "blocker_only", None, None, None, None), + "?&": Operator("?&", '"?&"', "blocker_only", None, None, None, None), + "@?": Operator("@?", '"@?"', "blocker_only", None, None, None, None), + "@@": Operator("@@", '"@@"', "blocker_only", None, None, None, None), + "#>": Operator("#>", '"#>"', "blocker_only", None, None, None, None), + "#>>": Operator("#>>", '"#>>"', "blocker_only", None, None, None, None), + "-": Operator("-", '"-"', "blocker_only", None, None, None, None), + "#-": Operator("#-", '"#-"', "blocker_only", None, None, None, None), + "||": Operator("||", '"||"', "blocker_only", None, None, None, None), +} + + +def backing_function(symbol: str) -> str: + """Return the eql_v2 backing function name for an operator symbol.""" + return OPERATORS[symbol].backing + + +# The full union of operator symbols the generator knows about: supported +# wrappers, path operators, and explicit blockers. Together these are exactly +# the native jsonb operator surface for PG 14-17, so this set is the basis of +# the storage-only "every native jsonb operator is blocked" guarantee. +# +# A live-DB structural guard (tests/sqlx/.../family/jsonb_operator_surface.rs) +# queries pg_operator for every operator with a jsonb argument and asserts the +# set is a subset of this union — if a future PG version adds a jsonb operator +# not enumerated here, that test fails rather than silently letting native +# plaintext-jsonb semantics through on an encrypted column. Keep that test's +# hardcoded expectation in sync with this set. +KNOWN_JSONB_OPERATORS: frozenset[str] = frozenset( + SYMMETRIC_OPERATORS + PATH_OPERATORS + BLOCKER_ONLY_OPERATORS +) diff --git a/tasks/codegen/scalars.py b/tasks/codegen/scalars.py new file mode 100644 index 00000000..a93df905 --- /dev/null +++ b/tasks/codegen/scalars.py @@ -0,0 +1,93 @@ +"""Fixed scalar-kind catalog for fixture-value emission. + +A `ScalarKind` knows how to turn a manifest fixture-value token into a Rust +literal of the type's native Rust scalar, and how to resolve it to a numeric +value for the MIN/MAX/zero invariant check. The manifest carries only the +list of value tokens; the per-type behaviour lives here (mirroring terms.py), +not in free-form TOML fields. + +Recognised sentinels are ``MIN`` / ``MAX`` / ``ZERO``; every other token is a +numeric literal validated against the type's representable range. +""" + +from dataclasses import dataclass + + +class ScalarError(Exception): + """Raised for an unknown scalar token or an invalid fixture value.""" + + +_SENTINELS = ("MIN", "MAX", "ZERO") + + +@dataclass(frozen=True) +class ScalarKind: + """One scalar type's Rust rendering rules for fixture values.""" + + token: str + rust_type: str + min_symbol: str + max_symbol: str + zero_symbol: str + min_value: int + max_value: int + + def _parse(self, value: str) -> int: + if value == "MIN": + return self.min_value + if value == "MAX": + return self.max_value + if value == "ZERO": + return 0 + try: + n = int(value) + except ValueError as exc: + raise ScalarError( + f"{self.token}: {value!r} is not a valid {self.rust_type} " + f"literal or sentinel ({'/'.join(_SENTINELS)})" + ) from exc + if not (self.min_value <= n <= self.max_value): + raise ScalarError( + f"{self.token}: {value!r} out of range for {self.rust_type} " + f"[{self.min_value}, {self.max_value}]" + ) + return n + + def numeric_value(self, value: str) -> int: + """Resolve a fixture token to its numeric value (validates range).""" + return self._parse(value) + + def render_literal(self, value: str) -> str: + """Render a fixture token as a Rust literal of this scalar type.""" + symbols = { + "MIN": self.min_symbol, + "MAX": self.max_symbol, + "ZERO": self.zero_symbol, + } + if value in symbols: + return symbols[value] + return str(self._parse(value)) + + +SCALAR_KINDS: dict[str, ScalarKind] = { + "int4": ScalarKind( + token="int4", + rust_type="i32", + min_symbol="i32::MIN", + max_symbol="i32::MAX", + zero_symbol="0", + min_value=-2147483648, + max_value=2147483647, + ), +} + + +def require_scalar(token: str) -> ScalarKind: + """Return the catalog kind for `token`, or raise ScalarError.""" + try: + return SCALAR_KINDS[token] + except KeyError as exc: + raise ScalarError( + f"unknown scalar token '{token}' " + f"(expected one of {sorted(SCALAR_KINDS)})" + ) from exc diff --git a/tasks/codegen/spec.py b/tasks/codegen/spec.py new file mode 100644 index 00000000..40e28cac --- /dev/null +++ b/tasks/codegen/spec.py @@ -0,0 +1,141 @@ +"""Minimal TOML manifest loader for scalar encrypted-domain codegen.""" + +import re +import tomllib +from dataclasses import dataclass +from pathlib import Path + +from .scalars import ScalarError, require_scalar +from .terms import TermError, require_terms + + +_SQL_IDENTIFIER = re.compile(r"^[a-z][a-z0-9_]*$") + + +class SpecError(Exception): + """Raised when a TOML manifest is missing or invalid.""" + + +@dataclass(frozen=True) +class DomainSpec: + """One generated public domain and the fixed terms it carries.""" + + name: str + terms: list[str] + + +@dataclass(frozen=True) +class TypeSpec: + """A scalar encrypted-domain manifest loaded from one TOML file.""" + + token: str + domains: list[DomainSpec] + fixture_values: list[str] | None = None + + +def _load_fixture_values(raw: dict, token: str) -> list[str] | None: + """Parse and validate the optional [fixture] table. + + Returns the ordered list of value tokens, or None when no [fixture] table + is present. The tokens are the manifest source of truth for the generated + Rust fixture-value const; the scalar kind validates each one and the set + must include MIN, MAX, and zero (the matrix comparison pivots).""" + if "fixture" not in raw: + return None + + fixture_table = raw["fixture"] + if not isinstance(fixture_table, dict) or "values" not in fixture_table: + raise SpecError("[fixture]: missing required key 'values'") + + values = fixture_table["values"] + if not isinstance(values, list): + raise SpecError("[fixture] values: must be a list of value tokens") + if not values: + raise SpecError("[fixture] values: must not be empty") + if any(not isinstance(v, str) for v in values): + raise SpecError("[fixture] values: must be strings") + + try: + kind = require_scalar(token) + resolved = [(v, kind.numeric_value(v)) for v in values] + for v in values: + kind.render_literal(v) + except ScalarError as exc: + raise SpecError(f"[fixture] values: {exc}") from exc + + # Distinct-plaintext contract: the matrix oracle treats each fixture value + # as a distinct plaintext, and the generated Rust const must not repeat a + # literal. Detect duplicates against the *resolved numeric* value so that + # both copy-paste token dups ("1", "1") and sentinel/literal aliases + # (e.g. "MIN" alongside the same number as a literal) are rejected. + seen: dict[int, str] = {} + duplicates: list[str] = [] + for token_value, number in resolved: + if number in seen: + duplicates.append( + f"{token_value!r} duplicates {seen[number]!r} (both resolve to {number})" + if token_value != seen[number] + else f"{token_value!r}" + ) + else: + seen[number] = token_value + if duplicates: + raise SpecError( + "[fixture] values: must be distinct, but found duplicate values: " + + ", ".join(duplicates) + ) + + numbers = set(seen) + if not ({kind.min_value, kind.max_value, 0} <= numbers): + raise SpecError( + "[fixture] values: must include MIN, MAX, and zero " + "(the matrix comparison pivots)" + ) + + return list(values) + + +def load_spec(path: Path | str) -> TypeSpec: + """Load and validate a per-type scalar-domain manifest.""" + path = Path(path) + with path.open("rb") as fh: + raw = tomllib.load(fh) + + if "domain" not in raw: + raise SpecError("spec: missing required table '[domain]'") + + domain_table = raw["domain"] + if not isinstance(domain_table, dict) or not domain_table: + raise SpecError("[domain]: at least one domain is required") + + token = path.stem + if not _SQL_IDENTIFIER.match(token): + raise SpecError( + f"spec: token {token!r} must match {_SQL_IDENTIFIER.pattern}" + ) + domains: list[DomainSpec] = [] + for name, terms in domain_table.items(): + if not isinstance(name, str) or not _SQL_IDENTIFIER.match(name): + raise SpecError( + f"[domain] {name}: domain name {name!r} must match " + f"{_SQL_IDENTIFIER.pattern}" + ) + if name != token and not name.startswith(f"{token}_"): + raise SpecError( + f"[domain] {name}: domain name must start with '{token}'" + ) + if not isinstance(terms, list): + raise SpecError( + f"[domain] {name}: value must be a list of term names" + ) + if any(not isinstance(term, str) for term in terms): + raise SpecError(f"[domain] {name}: term names must be strings") + try: + require_terms(list(terms)) + except TermError as exc: + raise SpecError(f"[domain] {name}: {exc}") from exc + domains.append(DomainSpec(name=name, terms=list(terms))) + + fixture_values = _load_fixture_values(raw, token) + + return TypeSpec(token=token, domains=domains, fixture_values=fixture_values) diff --git a/tasks/codegen/templates.py b/tasks/codegen/templates.py new file mode 100644 index 00000000..60833495 --- /dev/null +++ b/tasks/codegen/templates.py @@ -0,0 +1,490 @@ +"""Per-construct SQL template functions for scalar encrypted-domain codegen.""" + +from dataclasses import dataclass + +from .operator_surface import OPERATORS +from .scalars import require_scalar +from .spec import DomainSpec, TypeSpec +from .terms import ( + Term, + extractor_for_operator as _catalog_extractor_for_operator, + operators_for_terms, + role_for_terms, + term_json_keys, +) + +AUTO_GENERATED_HEADER = ( + "-- AUTO-GENERATED — DO NOT EDIT.\n" + "-- Regenerated automatically by `mise run build`; " + "also `mise run codegen:domain ` to refresh one type.\n" + "-- Source of truth: tasks/codegen/types/.toml\n" + "-- This file is gitignored; never commit it.\n" +) + +# Rust counterpart of AUTO_GENERATED_HEADER. Unlike the gitignored SQL surface, +# the fixture-value const IS committed and verified by the CI staleness guard, +# so the wording differs deliberately. +AUTO_GENERATED_HEADER_RS = ( + "// AUTO-GENERATED — DO NOT EDIT.\n" + "// Regenerated by `mise run build` " + "(or `mise run codegen:domain `).\n" + "// Source of truth: tasks/codegen/types/.toml `[fixture] values`.\n" + "// This file IS committed and verified in CI (git diff --exit-code).\n" +) + +ENVELOPE_KEYS = ["v", "i"] +CIPHERTEXT_KEY = "c" +# EQL payload-format version. The domain CHECK pins the 'v' envelope key to +# this value, matching EQL's repo-wide rule (eql_v2._encrypted_check_v, +# src/encrypted/constraints.sql). Presence of 'v' is enforced via +# ENVELOPE_KEYS; this pins its value so a stale/foreign-version payload is +# rejected on insert or cast rather than surfacing later at query time. +VERSION_KEY = "v" +ENVELOPE_VERSION = 2 + + +def _sql_str(s: str) -> str: + """Escape a Python string for use *inside* a single-quoted SQL string + literal by doubling embedded single quotes. + + Use this at every `'{...}'` interpolation boundary in the render_* + helpers — payload keys, operator symbols, domain names rendered into + RAISE messages, etc. + + Today every catalog string (term keys, operator symbols) is quote-free, + so this is a no-op on real input and output stays byte-identical. It + exists so a future quote-bearing catalog string can never break out of + its SQL literal — nothing else enforces the quote-free invariant.""" + return s.replace("'", "''") + + +def render_fixture_values_rs(spec: TypeSpec) -> str: + """Body for tests/sqlx/src/fixtures/_values.rs. + + Emits one `pub const VALUES: &[]` from the manifest's + `[fixture] values`, preserving declaration order. The writer prepends the + AUTO-GENERATED Rust header, so the body carries none.""" + kind = require_scalar(spec.token) + values = spec.fixture_values or [] + literals = "".join(f" {kind.render_literal(v)},\n" for v in values) + return ( + f"//! Fixture plaintext values for the {spec.token} " + "encrypted-domain family.\n" + "//!\n" + f"//! Generated from tasks/codegen/types/{spec.token}.toml " + "`[fixture] values` —\n" + "//! the single source of truth shared by the fixture generator\n" + f"//! (`fixtures::eql_v2_{spec.token}`) and the matrix oracle\n" + "//! (`ScalarType::FIXTURE_VALUES`).\n\n" + f"/// Distinct plaintext values present in the `eql_v2_{spec.token}` " + "fixture.\n" + f"pub const VALUES: &[{kind.rust_type}] = &[\n" + f"{literals}" + "];\n" + ) + +OPERATOR_PHRASES: dict[str, str] = { + "=": "Equality", + "<>": "Inequality", + "<": "Less-than", + "<=": "Less-than-or-equal", + ">": "Greater-than", + ">=": "Greater-than-or-equal", + "@>": "Contains", + "<@": "Contained-by", +} + +DOMAIN_ROLE_PHRASES: dict[str, str] = { + "storage": "Storage-only", + "eq": "Equality-only", + "ord": "Ordered", +} + + +def role_phrase(terms: list[str]) -> str: + """Proper-cased prose label for a domain with these terms — the single + source of truth for role → human prose. Every renderer that wants to + describe a domain's role in @brief lines reaches for this, so a rename + in DOMAIN_ROLE_PHRASES propagates to every generated file.""" + return DOMAIN_ROLE_PHRASES[role_for_terms(terms)] + + +def _scheme_suffix(name: str, token: str, role: str) -> str | None: + """The scheme tag of a domain name, or None for the converged name. + + The naming convention is ``_`` for the recommended converged + domain and ``__`` for a scheme-explicit twin that + pins the same role to one concrete index scheme. ``storage`` has no role + segment, so its converged name is the bare ````. + + Generic by construction: it reads ``token`` and ``role`` rather than any + hard-coded type or scheme string, so it works for int8/date/etc. and for + schemes other than ``ore``. Returns the scheme segment (e.g. ``"ore"``) + for a twin, or None when ``name`` is the converged name (or doesn't match + the convention at all).""" + converged = token if role == "storage" else f"{token}_{role}" + if name == converged: + return None + prefix = converged + "_" + if name.startswith(prefix): + scheme = name[len(prefix):] + if scheme: + return scheme + return None + + +# Roles that come in converged + scheme-explicit-twin pairs and therefore need +# a disambiguating @brief clause. Ordered domains are the case the reviewer +# flagged: int4_ord and int4_ord_ore carry identical terms (["ore"]) and would +# otherwise render an identical brief. Driven by role (generic across int8, +# date, etc.), never by a literal type/scheme name. eq and storage have a +# single name each, so no disambiguation is needed (or wanted — it'd be noise). +_TWINNABLE_ROLES = frozenset({"ord"}) + + +def brief_role_clause(domain: DomainSpec, token: str) -> str: + """The trailing clause distinguishing the recommended converged domain + from a scheme-explicit twin, for use in a per-domain @brief. + + Two domains that carry identical terms (e.g. ``int4_ord`` and + ``int4_ord_ore``, both ``["ore"]``) would otherwise render an identical + brief. The converged name is the recommended one to reach for; the twin + names the concrete scheme explicitly. Returns "" for roles that don't come + in converged/twin pairs (eq, storage) and for names that match no pattern. + + Generic by construction: keyed on the term-derived role and the + ``_[_]`` name shape, never on a literal type or scheme + string, so int8/date/etc. and non-ore schemes work unchanged.""" + role = role_for_terms(domain.terms) + if role not in _TWINNABLE_ROLES: + return "" + scheme = _scheme_suffix(domain.name, token, role) + if scheme is not None: + return ( + f" Scheme-explicit twin pinning the {scheme} scheme; " + f"prefer the converged {token}_{role} name." + ) + if domain.name == f"{token}_{role}": + return " Recommended converged name for this role." + return "" + + +def domain_name(domain: str) -> str: + """The public SQL domain type name.""" + return f"eql_v2_{domain}" + + +def _arg_label(dom: str, arg_type: str) -> str: + """Doxygen brief shape qualifier for one operand: 'domain' if it's + the encrypted-domain type, otherwise the literal SQL type.""" + return "domain" if arg_type == dom else arg_type + + +def _shape_qualifier(dom: str, arg_a: str, arg_b: str) -> str: + """Doxygen brief parenthetical. Empty for the canonical (dom, dom) shape.""" + if arg_a == dom and arg_b == dom: + return "" + return f" ({_arg_label(dom, arg_a)}, {_arg_label(dom, arg_b)})" + + +def render_domain_block(domain: DomainSpec, token: str) -> str: + """One idempotent IF NOT EXISTS CREATE DOMAIN block, prefixed by a + per-domain --! @brief derived from role + token.""" + dom = domain_name(domain.name) + keys = ENVELOPE_KEYS + [CIPHERTEXT_KEY] + term_json_keys(domain.terms) + presence = "\n AND ".join(f"VALUE ? '{_sql_str(key)}'" for key in keys) + checks = ( + presence + + f"\n AND VALUE->>'{_sql_str(VERSION_KEY)}' = '{ENVELOPE_VERSION}'" + ) + phrase = role_phrase(domain.terms) + clause = brief_role_clause(domain, token) + return ( + f" --! @brief {phrase} encrypted {token} domain.{clause}\n" + f" IF NOT EXISTS (\n" + f" SELECT 1 FROM pg_type\n" + f" WHERE typname = '{_sql_str(dom)}' " + f"AND typnamespace = 'public'::regnamespace\n" + f" ) THEN\n" + f" CREATE DOMAIN public.{dom} AS jsonb\n" + f" CHECK (\n" + f" jsonb_typeof(VALUE) = 'object'\n" + f" AND {checks}\n" + f" );\n" + f" END IF;\n" + ) + + +def render_extractor(domain: DomainSpec, term: Term) -> str: + """The inlinable index-term extractor for a domain term.""" + dom = domain_name(domain.name) + doxy = ( + f"--! @brief Index extractor for the {dom} variant.\n" + f"--! @param a {dom}\n" + f"--! @return {term.returns}\n" + ) + return doxy + ( + f"CREATE FUNCTION eql_v2.{term.extractor}(a {dom})\n" + f"RETURNS {term.returns}\n" + f"LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" + f"AS $$ SELECT eql_v2.{term.ctor}(a::jsonb) $$;\n" + ) + + +def _extract_arg(arg_type: str, extractor: str, domain: str, arg: str) -> str: + """The extractor-call SQL for one operand, casting jsonb to the domain first.""" + if arg_type == "jsonb": + return f"eql_v2.{extractor}({arg}::{domain})" + return f"eql_v2.{extractor}({arg})" + + +def render_wrapper( + domain: DomainSpec, op: str, arg_a: str, arg_b: str, extractor: str +) -> str: + """An inlinable comparison wrapper for a supported operator.""" + dom = domain_name(domain.name) + backing = OPERATORS[op].backing + call_a = _extract_arg(arg_a, extractor, dom, "a") + call_b = _extract_arg(arg_b, extractor, dom, "b") + doxy = ( + f"--! @brief {OPERATOR_PHRASES[op]} wrapper for {dom}" + f"{_shape_qualifier(dom, arg_a, arg_b)}.\n" + f"--! @param a {arg_a}\n" + f"--! @param b {arg_b}\n" + f"--! @return boolean\n" + ) + return doxy + ( + f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, b {arg_b})\n" + f"RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" + f"AS $$ SELECT {call_a} {op} {call_b} $$;\n" + ) + + +def render_blocker_bool( + domain: DomainSpec, op: str, arg_a: str, arg_b: str +) -> str: + """A boolean-returning blocker. NEVER STRICT, ALWAYS LANGUAGE plpgsql + so the RAISE survives inlining and planner-time elision; see CLAUDE.md + footguns and the encrypted-domain spec §4.""" + dom = domain_name(domain.name) + backing = OPERATORS[op].backing + doxy = ( + f"--! @brief Blocker for {op} on {dom}" + f"{_shape_qualifier(dom, arg_a, arg_b)}.\n" + f"--! @param a {arg_a}\n" + f"--! @param b {arg_b}\n" + f"--! @return boolean (never returns; always raises)\n" + ) + return doxy + ( + f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, b {arg_b})\n" + f"RETURNS boolean IMMUTABLE PARALLEL SAFE\n" + f"AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool(" + f"'{_sql_str(dom)}', '{_sql_str(op)}'); END; $$\n" + f"LANGUAGE plpgsql;\n" + ) + + +def render_blocker_path( + domain: DomainSpec, op: str, arg_a: str, arg_b: str +) -> str: + """A path-operator blocker. NEVER STRICT, ALWAYS LANGUAGE plpgsql + so the RAISE survives inlining and planner-time elision; see CLAUDE.md + footguns and the encrypted-domain spec §4.""" + dom = domain_name(domain.name) + backing = OPERATORS[op].backing + returns = "text" if op == "->>" else dom + doxy = ( + f"--! @brief Blocker for {op} on {dom} " + f"({_arg_label(dom, arg_a)}, {_arg_label(dom, arg_b)}).\n" + f"--! @param a {arg_a}\n" + f"--! @param selector {arg_b}\n" + f"--! @return {returns} (never returns; always raises)\n" + ) + return doxy + ( + f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, selector {arg_b})\n" + f"RETURNS {returns} IMMUTABLE PARALLEL SAFE\n" + f"AS $$ BEGIN RAISE EXCEPTION " + f"'operator % is not supported for %', '{_sql_str(op)}', " + f"'{_sql_str(dom)}'; END; $$\n" + f"LANGUAGE plpgsql;\n" + ) + + +def render_blocker_native( + domain: DomainSpec, op: str, arg_a: str, arg_b: str, returns: str +) -> str: + """A blocker for a native jsonb fallback operator. NEVER STRICT, ALWAYS + LANGUAGE plpgsql. Boolean blockers delegate to the shared helper so lint + recognition and messages stay uniform; other return types raise directly. + """ + dom = domain_name(domain.name) + backing = OPERATORS[op].backing + doxy = ( + f"--! @brief Blocker for {op} on {dom}" + f"{_shape_qualifier(dom, arg_a, arg_b)}.\n" + f"--! @param a {arg_a}\n" + f"--! @param b {arg_b}\n" + f"--! @return {returns} (never returns; always raises)\n" + ) + if returns == "boolean": + body = ( + "BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool(" + f"'{_sql_str(dom)}', '{_sql_str(op)}'); END;" + ) + else: + body = ( + "BEGIN RAISE EXCEPTION " + f"'operator % is not supported for %', '{_sql_str(op)}', " + f"'{_sql_str(dom)}'; END;" + ) + return doxy + ( + f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, b {arg_b})\n" + f"RETURNS {returns} IMMUTABLE PARALLEL SAFE\n" + f"AS $$ {body} $$\n" + f"LANGUAGE plpgsql;\n" + ) + + +def extractor_for_operator(domain: DomainSpec, op: str) -> str | None: + """Return the catalog extractor that supports op for this domain.""" + return _catalog_extractor_for_operator(domain.terms, op) + + +def supported_operators(domain: DomainSpec) -> list[str]: + """Supported operators for this domain.""" + return operators_for_terms(domain.terms) + + +@dataclass(frozen=True) +class AggregateOp: + """One aggregate operator definition (min or max).""" + + name: str # public function name, e.g. "min" + sfunc_name: str # state function name, e.g. "min_sfunc" + comparator: str # SQL comparator used to choose the new state: "<" or ">" + phrase: str # short prose label used in --! @brief lines + + +AGGREGATE_OPS: dict[str, AggregateOp] = { + "min": AggregateOp("min", "min_sfunc", "<", "minimum"), + "max": AggregateOp("max", "max_sfunc", ">", "maximum"), +} + + +def is_ord_capable(domain: DomainSpec) -> bool: + """True if the domain carries a comparator term (i.e. supports `<`).""" + return role_for_terms(domain.terms) == "ord" + + +def render_aggregate(domain: DomainSpec, op: AggregateOp) -> str: + """Render state function + CREATE AGGREGATE for one aggregate op on one + domain. The ord-capability gate lives at the file-level renderer + (`render_aggregates_file`); callers may legitimately render a single + aggregate without re-asserting that precondition. MIN/MAX on a non-ord + domain is structurally well-formed text but semantically meaningless — + the file-level gate is what stops it ever reaching disk.""" + dom = domain_name(domain.name) + sfunc_doxy = ( + f"--! @brief State function for {op.name} aggregate on {dom}.\n" + f"--! @internal\n" + f"--!\n" + f"--! @param state {dom} running extremum\n" + f"--! @param value {dom} next non-NULL value\n" + f"--! @return {dom} the {op.phrase} of state and value\n" + ) + # plpgsql + STRICT: PG seeds the state with the first non-NULL value and + # skips NULL inputs. plpgsql (not sql) because aggregate state functions + # aren't index expressions — opacity to the planner is fine — and a + # multi-statement BEGIN/IF/END body is the natural shape. + # + # The same rationale is mirrored into the emitted SQL below so a reader of + # the generated file (who never sees this Python) understands why it isn't + # an inlinable LANGUAGE sql CASE. + sfunc_rationale = ( + "-- LANGUAGE plpgsql, not sql: aggregate state functions are not index\n" + "-- expressions, so opacity to the planner is fine, and a multi-statement\n" + "-- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would\n" + "-- also work, but the procedural form mirrors the blocker convention.)\n" + ) + sfunc = sfunc_rationale + ( + f"CREATE FUNCTION eql_v2.{op.sfunc_name}(state {dom}, value {dom})\n" + f"RETURNS {dom}\n" + f"LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE\n" + f"SET search_path = pg_catalog, extensions, public\n" + f"AS $$\n" + f"BEGIN\n" + f" IF value {op.comparator} state THEN\n" + f" RETURN value;\n" + f" END IF;\n" + f" RETURN state;\n" + f"END;\n" + f"$$;\n" + ) + agg_doxy = ( + f"--! @brief Find the {op.phrase} encrypted value in a group of " + f"{dom} values.\n" + f"--!\n" + f"--! Comparison routes through the domain's `{op.comparator}` " + f"operator, which uses the ORE block term — no decryption.\n" + f"--!\n" + f"--! @param input {dom} encrypted values to aggregate\n" + f"--! @return {dom} {op.phrase} of the group, or NULL if all " + f"inputs are NULL\n" + ) + # min/max are associative, so the state function doubles as the combine + # function: merging two partial extrema is the same comparison. With a + # PARALLEL SAFE sfunc/combinefunc and `parallel = safe`, PG can use partial + # and parallel aggregation on the large GROUP BY workloads these ORE + # aggregates exist to serve — still with no decryption. The combinefunc is + # STRICT (it is the sfunc), so PG carries a null partial state through as + # "no value yet", matching the serial seed-and-skip semantics. + aggregate = ( + "-- combinefunc = sfunc: min/max are associative, so merging two partial\n" + "-- extrema is the same comparison. PARALLEL SAFE enables partial and\n" + "-- parallel aggregation on large GROUP BY workloads, with no decryption.\n" + f"CREATE AGGREGATE eql_v2.{op.name}({dom}) (\n" + f" sfunc = eql_v2.{op.sfunc_name},\n" + f" stype = {dom},\n" + f" combinefunc = eql_v2.{op.sfunc_name},\n" + f" parallel = safe\n" + f");\n" + ) + return sfunc_doxy + sfunc + "\n" + agg_doxy + aggregate + + +def render_operator( + op: str, backing: str, leftarg: str, rightarg: str, supported: bool +) -> str: + """A CREATE OPERATOR declaration. + + Unsupported operators are still declared, but their backing function is a + blocker that always raises. We emit them so the operator resolves on the + domain (rather than silently falling through to a native jsonb operator), + and a leading SQL comment explains the placeholder to future readers.""" + meta = OPERATORS[op] + lines = [] + if not supported: + lines.append( + f"-- Placeholder: this domain's term set does not support {op}; " + f"the backing function always raises." + ) + lines += [ + f"CREATE OPERATOR {op} (", + f" FUNCTION = eql_v2.{backing},", + f" LEFTARG = {leftarg}, RIGHTARG = {rightarg}", + ] + if supported and meta.kind == "symmetric": + extras = [] + if meta.commutator: + extras.append(f"COMMUTATOR = {meta.commutator}") + if meta.negator: + extras.append(f"NEGATOR = {meta.negator}") + if meta.restrict: + extras.append(f"RESTRICT = {meta.restrict}") + if meta.join: + extras.append(f"JOIN = {meta.join}") + if extras: + lines[-1] += "," + lines.append(" " + ", ".join(extras)) + lines.append(");") + return "\n".join(lines) + "\n" diff --git a/tasks/codegen/terms.py b/tasks/codegen/terms.py new file mode 100644 index 00000000..32a7c788 --- /dev/null +++ b/tasks/codegen/terms.py @@ -0,0 +1,107 @@ +"""Fixed index-term catalog for scalar encrypted-domain codegen.""" + +from collections.abc import Iterable +from dataclasses import dataclass + + +class TermError(Exception): + """Raised when a manifest references an unknown term.""" + + +@dataclass(frozen=True) +class Term: + """One fixed index term known to the scalar materializer.""" + + name: str + json_key: str + extractor: str + returns: str + ctor: str + role: str + operators: tuple[str, ...] + requires: tuple[str, ...] + + +TERM_CATALOG: dict[str, Term] = { + "hm": Term( + name="hm", + json_key="hm", + extractor="eq_term", + returns="eql_v2.hmac_256", + ctor="hmac_256", + role="eq", + operators=("=", "<>"), + requires=("src/hmac_256/functions.sql",), + ), + "ore": Term( + name="ore", + json_key="ob", + extractor="ord_term", + returns="eql_v2.ore_block_u64_8_256", + ctor="ore_block_u64_8_256", + role="ord", + operators=("=", "<>", "<", "<=", ">", ">="), + requires=( + "src/ore_block_u64_8_256/functions.sql", + "src/ore_block_u64_8_256/operators.sql", + ), + ), +} + + +def _dedupe_preserving_order(values: Iterable[str]) -> list[str]: + """Stable dedupe — first occurrence wins. `dict.fromkeys` preserves insert order.""" + return list(dict.fromkeys(values)) + + +def require_terms(names: list[str]) -> list[Term]: + """Return catalog terms for manifest names, preserving input order.""" + terms: list[Term] = [] + for name in names: + try: + terms.append(TERM_CATALOG[name]) + except KeyError as exc: + raise TermError( + f"unknown term '{name}' (expected one of {sorted(TERM_CATALOG)})" + ) from exc + return terms + + +def operators_for_terms(names: list[str]) -> list[str]: + """Supported operators for the union of a domain's terms.""" + return _dedupe_preserving_order( + op for term in require_terms(names) for op in term.operators + ) + + +def term_json_keys(names: list[str]) -> list[str]: + """JSON payload keys required by these terms.""" + return _dedupe_preserving_order( + term.json_key for term in require_terms(names) + ) + + +def term_requires(names: list[str]) -> list[str]: + """SQL REQUIRE edges needed by these terms.""" + return _dedupe_preserving_order( + req for term in require_terms(names) for req in term.requires + ) + + +def extractor_for_operator(names: list[str], op: str) -> str | None: + """The catalog extractor that supports `op` for a domain carrying `names`.""" + for term in require_terms(names): + if op in term.operators: + return term.extractor + return None + + +def role_for_terms(names: list[str]) -> str: + """Generated-file role label for a domain with these terms. + + A domain with no terms is `storage`; otherwise the role comes from + the first term's catalog role (e.g. `hm` -> `eq`, `ore` -> `ord`). + """ + if not names: + return "storage" + return require_terms(names)[0].role diff --git a/tasks/codegen/test_against_reference.py b/tasks/codegen/test_against_reference.py new file mode 100644 index 00000000..e7ea62e9 --- /dev/null +++ b/tasks/codegen/test_against_reference.py @@ -0,0 +1,117 @@ +"""Identity guard: the generator must reproduce the frozen manual +reference under tests/codegen/reference// byte-for-byte. + +The reference is the reviewed manual implementation. If the generator's +output diverges from the reference, either the generator regressed (fix +it) or the reference is being deliberately updated (commit the new +reference in this PR). + +Compares in-memory `render_*_file` output directly against the reference, +so it runs anywhere regardless of whether the build has materialised +src/encrypted_domain// (those files are gitignored — `tasks/build.sh` +regenerates them on each build). +""" +from pathlib import Path + +import pytest + +from tasks.codegen.generate import ( + REPO_ROOT, + render_aggregates_file, + render_functions_file, + render_operators_file, + render_types_file, +) +from tasks.codegen.spec import load_spec +from tasks.codegen.templates import render_fixture_values_rs + +_REFERENCE_ROOT = REPO_ROOT / "tests" / "codegen" / "reference" +_TYPES_DIR = REPO_ROOT / "tasks" / "codegen" / "types" + + +def _strip_reference_marker(text: str) -> str: + """Drop any leading `-- REFERENCE:` / `// REFERENCE:` lines. They label the + file as the parity baseline (see tests/codegen/reference/README.md) and are + not part of the generator's output. Both comment styles are recognised so + the same helper serves SQL and Rust reference files.""" + lines = text.splitlines(keepends=True) + while lines and lines[0].startswith(("-- REFERENCE:", "// REFERENCE:")): + lines.pop(0) + return "".join(lines) + + +def _reference_files() -> list[Path]: + """Every SQL file under tests/codegen/reference//.""" + if not _REFERENCE_ROOT.is_dir(): + return [] + return sorted(_REFERENCE_ROOT.glob("*/*.sql")) + + +def _render(reference_path: Path) -> str: + """Render the corresponding generator output for a reference file.""" + token = reference_path.parent.name + name = reference_path.name + spec = load_spec(_TYPES_DIR / f"{token}.toml") + + if name == f"{token}_types.sql": + return render_types_file(spec) + + for domain in spec.domains: + if name == f"{domain.name}_functions.sql": + return render_functions_file(spec, domain) + if name == f"{domain.name}_operators.sql": + return render_operators_file(spec, domain) + if name == f"{domain.name}_aggregates.sql": + body = render_aggregates_file(spec, domain) + if body is None: + pytest.fail( + f"reference {reference_path.relative_to(REPO_ROOT)} exists " + f"but the generator skipped this variant (not ord-capable). " + f"Remove the reference file or update the manifest." + ) + return body + + pytest.fail(f"unrecognised reference filename: {name}") + + +@pytest.mark.parametrize( + "reference_path", + _reference_files(), + ids=lambda p: f"{p.parent.name}/{p.name}", +) +def test_generator_matches_manual_reference(reference_path: Path): + """Generator render output must equal the reviewed reference.""" + token = reference_path.parent.name + fix = ( + f"either the generator regressed (fix tasks/codegen/) or the " + f"manual reference is being updated deliberately — commit the " + f"new reference at {reference_path.relative_to(REPO_ROOT)} in " + f"this PR. Regenerate via: mise run codegen:domain {token}" + ) + + expected = _strip_reference_marker(reference_path.read_text(encoding="utf-8")) + actual = _render(reference_path) + + assert actual == expected, f"{reference_path.name}: {fix}" + + +def test_generator_matches_rust_fixture_values_reference(): + """The generated Rust fixture-value const must match the reviewed reference. + + Guards the committed tests/sqlx/src/fixtures/int4_values.rs against drift + from the manifest (the same property the CI staleness guard enforces, but + runnable without a checkout diff).""" + reference_path = _REFERENCE_ROOT / "int4" / "int4_values.rs" + spec = load_spec(_TYPES_DIR / "int4.toml") + + expected = _strip_reference_marker( + reference_path.read_text(encoding="utf-8") + ) + actual = render_fixture_values_rs(spec) + + assert actual == expected, ( + "int4_values.rs: either the generator regressed (fix tasks/codegen/) " + "or the reference is being updated deliberately — commit the new " + f"reference at {reference_path.relative_to(REPO_ROOT)} in this PR. " + "Regenerate via: mise run codegen:domain int4" + ) diff --git a/tasks/codegen/test_generate.py b/tasks/codegen/test_generate.py new file mode 100644 index 00000000..e92e2f2f --- /dev/null +++ b/tasks/codegen/test_generate.py @@ -0,0 +1,351 @@ +"""Tests for composing scalar encrypted-domain files from a manifest.""" + +import textwrap + +import pytest + +from tasks.codegen.generate import ( + generate_type, + main, + render_aggregates_file, + render_functions_file, + render_operators_file, + render_types_file, +) +from tasks.codegen.spec import load_spec +from tasks.codegen.templates import AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS +from tasks.codegen.writer import OwnershipError + + +INT4_TOML = textwrap.dedent(""" + [domain] + int4 = [] + int4_eq = ["hm"] + int4_ord_ore = ["ore"] + int4_ord = ["ore"] +""") + +INT4_FIXTURE_TOML = INT4_TOML + textwrap.dedent(""" + [fixture] + values = ["MIN", "-1", "ZERO", "1", "MAX"] +""") + +# A second, synthetic type for multi-type (--all) coverage. No [fixture] table, +# so it never touches scalars.py (which only registers int4) — it exercises the +# enumeration, not fixture rendering. +INT4X_TOML = textwrap.dedent(""" + [domain] + int4x = [] + int4x_eq = ["hm"] + int4x_ord = ["ore"] +""") + + +def _fixture_values_rs(out_root): + return out_root / "tests" / "sqlx" / "src" / "fixtures" / "int4_values.rs" + + +def load(tmp_path): + p = tmp_path / "int4.toml" + p.write_text(INT4_TOML) + return load_spec(p) + + +def test_types_file_has_all_four_domains(tmp_path): + spec = load(tmp_path) + sql = render_types_file(spec) + assert "-- REQUIRE: src/schema.sql" in sql + for dom in ("eql_v2_int4", "eql_v2_int4_eq", + "eql_v2_int4_ord", "eql_v2_int4_ord_ore"): + assert f"CREATE DOMAIN public.{dom} AS jsonb" in sql + + +def test_storage_functions_file_is_all_blockers(tmp_path): + spec = load(tmp_path) + storage = next(d for d in spec.domains if d.name == "int4") + sql = render_functions_file(spec, storage) + assert sql.count("CREATE FUNCTION") == 44 + assert "SET search_path" not in sql + assert sql.count("LANGUAGE plpgsql") == 44 + assert sql.count("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") == 0 + + +def test_eq_functions_file_counts_and_extractor(tmp_path): + spec = load(tmp_path) + eq = next(d for d in spec.domains if d.name == "int4_eq") + sql = render_functions_file(spec, eq) + assert sql.count("CREATE FUNCTION") == 45 + assert "CREATE FUNCTION eql_v2.eq_term(a eql_v2_int4_eq)" in sql + assert "RETURNS eql_v2.hmac_256" in sql + # 1 extractor + 6 wrappers (=, <> across 3 arg-shapes) inlined as SQL; + # 38 blockers across the remaining native jsonb surface as plpgsql. + assert sql.count("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") == 7 + assert sql.count("LANGUAGE plpgsql") == 38 + assert "SET search_path" not in sql + + +def test_ore_functions_file_counts_and_extractor(tmp_path): + spec = load(tmp_path) + ordered = next(d for d in spec.domains if d.name == "int4_ord") + sql = render_functions_file(spec, ordered) + assert sql.count("CREATE FUNCTION") == 45 + assert "CREATE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord)" in sql + assert "RETURNS eql_v2.ore_block_u64_8_256" in sql + # 1 extractor + 18 wrappers (=, <>, <, <=, >, >= across 3 shapes); + # 26 blockers across containment/path/native-jsonb fallback ops. + assert sql.count("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") == 19 + assert sql.count("LANGUAGE plpgsql") == 26 + assert "SET search_path" not in sql + + +def test_operators_file_has_forty_four(tmp_path): + spec = load(tmp_path) + eq = next(d for d in spec.domains if d.name == "int4_eq") + sql = render_operators_file(spec, eq) + assert sql.count("CREATE OPERATOR") == 44 + + +def test_generate_type_writes_expected_files(tmp_path): + spec = load(tmp_path) + out_dir = tmp_path / "int4" + written = generate_type(spec, out_dir) + names = {p.name for p in written} + assert "int4_types.sql" in names + for domain in ("int4", "int4_eq", "int4_ord", "int4_ord_ore"): + assert f"{domain}_functions.sql" in names + assert f"{domain}_operators.sql" in names + # Aggregates only emitted for ord-capable variants — storage and eq skip. + assert "int4_aggregates.sql" not in names + assert "int4_eq_aggregates.sql" not in names + assert "int4_ord_aggregates.sql" in names + assert "int4_ord_ore_aggregates.sql" in names + # 1 types + 4 functions + 4 operators + 2 aggregates = 11 + assert len(written) == 11 + for p in written: + assert p.read_text().startswith(AUTO_GENERATED_HEADER) + + +def test_generate_type_cleans_stale_files(tmp_path): + spec = load(tmp_path) + out_dir = tmp_path / "int4" + out_dir.mkdir() + stale = out_dir / "int4_removed_functions.sql" + stale.write_text(AUTO_GENERATED_HEADER + "-- orphan\n") + generate_type(spec, out_dir) + assert not stale.exists() + + +def test_generate_type_preserves_hand_written_extension_file(tmp_path): + spec = load(tmp_path) + out_dir = tmp_path / "int4" + out_dir.mkdir() + extension = out_dir / "int4_extensions.sql" + body = ( + "-- REQUIRE: src/encrypted_domain/int4/int4_types.sql\n" + "-- hand-written extension SQL\n" + ) + extension.write_text(body) + generate_type(spec, out_dir) + assert extension.read_text() == body + + +def test_generate_type_preflights_hand_written_target_before_cleanup(tmp_path): + spec = load(tmp_path) + out_dir = tmp_path / "int4" + out_dir.mkdir() + generated = out_dir / "int4_types.sql" + protected = out_dir / "int4_eq_functions.sql" + original_generated = AUTO_GENERATED_HEADER + "-- old generated\n" + original_protected = "-- REQUIRE: src/schema.sql\n-- hand-written\n" + generated.write_text(original_generated) + protected.write_text(original_protected) + + with pytest.raises(OwnershipError, match="hand-written"): + generate_type(spec, out_dir) + + assert generated.read_text() == original_generated + assert protected.read_text() == original_protected + assert not (out_dir / "int4_eq_operators.sql").exists() + + +def _seed_types_dir(tmp_path, name: str = "int4.toml", body: str = INT4_TOML): + types_dir = tmp_path / "types" + types_dir.mkdir() + (types_dir / name).write_text(body) + return types_dir + + +def test_main_rejects_wrong_argv_length(capsys): + rc = main(["generate.py"]) + assert rc == 2 + err = capsys.readouterr().err + assert "Usage: generate.py " in err + + +def test_main_errors_on_missing_manifest(tmp_path, capsys): + types_dir = tmp_path / "types" + types_dir.mkdir() + rc = main( + ["generate.py", "int4"], + types_dir=types_dir, + out_root=tmp_path, + ) + assert rc == 1 + err = capsys.readouterr().err + assert "no manifest at" in err + assert "int4.toml" in err + + +def test_main_errors_on_token_mismatch(tmp_path, capsys): + """Manifest stem must equal argv token — guards against a copy/rename.""" + types_dir = _seed_types_dir(tmp_path, name="int4.toml") + rc = main( + ["generate.py", "int8"], + types_dir=types_dir, + out_root=tmp_path, + ) + # int8.toml doesn't exist — first failure is missing manifest, not mismatch. + # To exercise the mismatch branch we need a manifest at int8.toml that + # declares int4 domains (impossible — the loader infers token from stem). + # The branch is therefore unreachable via the normal types/.toml + # convention; the assertion below just confirms the missing-manifest + # error path fires when the names diverge. + assert rc == 1 + err = capsys.readouterr().err + assert "no manifest at" in err + assert "int8.toml" in err + + +def test_main_happy_path_writes_files(tmp_path, capsys): + types_dir = _seed_types_dir(tmp_path) + rc = main( + ["generate.py", "int4"], + types_dir=types_dir, + out_root=tmp_path, + ) + assert rc == 0 + out_dir = tmp_path / "src" / "encrypted_domain" / "int4" + assert (out_dir / "int4_types.sql").is_file() + assert (out_dir / "int4_eq_functions.sql").is_file() + assert (out_dir / "int4_ord_operators.sql").is_file() + assert (out_dir / "int4_ord_aggregates.sql").is_file() + assert (out_dir / "int4_ord_ore_aggregates.sql").is_file() + assert not (out_dir / "int4_aggregates.sql").exists() + assert not (out_dir / "int4_eq_aggregates.sql").exists() + stdout = capsys.readouterr().out + assert "generated 11 files for int4" in stdout + + +def test_main_emits_fixture_values_rs_when_manifest_has_fixture(tmp_path, capsys): + types_dir = _seed_types_dir(tmp_path, body=INT4_FIXTURE_TOML) + rc = main(["generate.py", "int4"], types_dir=types_dir, out_root=tmp_path) + assert rc == 0 + rs = _fixture_values_rs(tmp_path) + assert rs.is_file() + text = rs.read_text() + assert text.startswith(AUTO_GENERATED_HEADER_RS) + assert "pub const VALUES: &[i32] = &[" in text + assert "i32::MIN," in text and "i32::MAX," in text + stdout = capsys.readouterr().out + assert "int4_values.rs" in stdout + + +def test_main_omits_fixture_values_rs_when_no_fixture_table(tmp_path, capsys): + types_dir = _seed_types_dir(tmp_path, body=INT4_TOML) + rc = main(["generate.py", "int4"], types_dir=types_dir, out_root=tmp_path) + assert rc == 0 + assert not _fixture_values_rs(tmp_path).exists() + + +def _seed_two_types(tmp_path): + types_dir = _seed_types_dir(tmp_path, name="int4.toml", body=INT4_TOML) + (types_dir / "int4x.toml").write_text(INT4X_TOML) + return types_dir + + +def test_main_all_generates_every_type(tmp_path, capsys): + types_dir = _seed_two_types(tmp_path) + rc = main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) + assert rc == 0 + assert (tmp_path / "src/encrypted_domain/int4/int4_types.sql").is_file() + assert (tmp_path / "src/encrypted_domain/int4x/int4x_types.sql").is_file() + out = capsys.readouterr().out + assert "generated 11 files for int4" in out + assert "codegen --all: ok (2 types: int4, int4x)" in out + + +def test_main_all_generates_in_sorted_order(tmp_path, capsys): + types_dir = _seed_two_types(tmp_path) + main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) + out = capsys.readouterr().out + assert out.index("for int4\n") < out.index("for int4x\n") + + +def test_main_all_errors_when_no_manifests(tmp_path, capsys): + types_dir = tmp_path / "types" + types_dir.mkdir() + rc = main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) + assert rc == 1 + assert "no manifests found" in capsys.readouterr().err + + +def test_main_all_aggregates_nonzero_on_bad_manifest(tmp_path, capsys): + types_dir = _seed_types_dir(tmp_path, name="int4.toml", body=INT4_TOML) + # 'broken' sorts before 'int4', so it is processed first; its domain name + # does not start with the token, so load_spec raises SpecError. + (types_dir / "broken.toml").write_text("[domain]\nwrongprefix = []\n") + rc = main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) + assert rc == 1 + captured = capsys.readouterr() + assert "broken" in captured.err + assert "codegen --all: FAILED" in captured.out + # The good type still generated despite the broken sibling. + assert (tmp_path / "src/encrypted_domain/int4/int4_types.sql").is_file() + + +def test_ordered_files_are_byte_identical_modulo_typename(tmp_path): + spec = load(tmp_path) + ord_domain = next(d for d in spec.domains if d.name == "int4_ord") + ore_domain = next(d for d in spec.domains if d.name == "int4_ord_ore") + + for renderer in (render_functions_file, render_operators_file, render_aggregates_file): + ord_sql = renderer(spec, ord_domain) + ore_sql = renderer(spec, ore_domain) + normalised_ord = ord_sql.replace("int4_ord_ore", "T").replace( + "int4_ord", "T" + ) + normalised_ore = ore_sql.replace("int4_ord_ore", "T").replace( + "int4_ord", "T" + ) + assert normalised_ord == normalised_ore, ( + f"{renderer.__name__}: int4_ord and int4_ord_ore must produce " + f"byte-identical SQL modulo their typenames" + ) + + +def test_render_aggregates_file_only_for_ord_variants(tmp_path): + spec = load(tmp_path) + storage = next(d for d in spec.domains if d.name == "int4") + eq = next(d for d in spec.domains if d.name == "int4_eq") + ordered = next(d for d in spec.domains if d.name == "int4_ord") + ore = next(d for d in spec.domains if d.name == "int4_ord_ore") + + assert render_aggregates_file(spec, storage) is None + assert render_aggregates_file(spec, eq) is None + assert render_aggregates_file(spec, ordered) is not None + assert render_aggregates_file(spec, ore) is not None + + +def test_render_aggregates_file_carries_both_min_and_max(tmp_path): + spec = load(tmp_path) + ordered = next(d for d in spec.domains if d.name == "int4_ord") + sql = render_aggregates_file(spec, ordered) + assert sql is not None + assert sql.count("CREATE FUNCTION") == 2 + assert sql.count("CREATE AGGREGATE") == 2 + assert "eql_v2.min_sfunc" in sql + assert "eql_v2.max_sfunc" in sql + # REQUIRE edges: types + functions + operators must all be declared. + assert "-- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql" in sql + assert "-- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql" in sql + assert "-- REQUIRE: src/encrypted_domain/int4/int4_types.sql" in sql diff --git a/tasks/codegen/test_operator_surface.py b/tasks/codegen/test_operator_surface.py new file mode 100644 index 00000000..a513ce82 --- /dev/null +++ b/tasks/codegen/test_operator_surface.py @@ -0,0 +1,126 @@ +"""Tests for the scalar operator surface definition.""" +from tasks.codegen.operator_surface import ( + BLOCKER_ONLY_OPERATORS, + KNOWN_JSONB_OPERATORS, + OPERATORS, + PATH_OPERATORS, + SYMMETRIC_OPERATORS, + backing_function, +) + + +def test_twenty_operators_total(): + """The surface covers supported wrappers plus native jsonb fallbacks.""" + assert len(OPERATORS) == 20 + + +def test_eight_symmetric_operators(): + """8 symmetric boolean operators.""" + assert SYMMETRIC_OPERATORS == ["=", "<>", "<", "<=", ">", ">=", "@>", "<@"] + + +def test_two_path_operators(): + """2 path operators.""" + assert PATH_OPERATORS == ["->", "->>"] + + +def test_ten_blocker_only_jsonb_fallback_operators(): + """Native jsonb operators not otherwise supported are blocker-only.""" + assert BLOCKER_ONLY_OPERATORS == [ + "?", + "?|", + "?&", + "@?", + "@@", + "#>", + "#>>", + "-", + "#-", + "||", + ] + + +def test_no_like_operators(): + """The surface excludes ~~ and ~~* (int4 has no LIKE support).""" + assert "~~" not in OPERATORS + assert "~~*" not in OPERATORS + + +def test_backing_function_names(): + """Each operator maps to its eql_v2 backing function name.""" + assert backing_function("=") == "eq" + assert backing_function("<>") == "neq" + assert backing_function("<") == "lt" + assert backing_function("<=") == "lte" + assert backing_function(">") == "gt" + assert backing_function(">=") == "gte" + assert backing_function("@>") == "contains" + assert backing_function("<@") == "contained_by" + assert backing_function("->") == '"->"' + assert backing_function("->>") == '"->>"' + assert backing_function("?") == '"?"' + assert backing_function("?|") == '"?|"' + assert backing_function("?&") == '"?&"' + assert backing_function("@?") == '"@?"' + assert backing_function("@@") == '"@@"' + assert backing_function("#>") == '"#>"' + assert backing_function("#>>") == '"#>>"' + assert backing_function("-") == '"-"' + assert backing_function("#-") == '"#-"' + assert backing_function("||") == '"||"' + + +def test_selectivity_estimators(): + """Symmetric ops carry RESTRICT/JOIN selectivity estimators.""" + assert OPERATORS["="].restrict == "eqsel" + assert OPERATORS["="].join == "eqjoinsel" + assert OPERATORS["<>"].restrict == "neqsel" + assert OPERATORS["<"].restrict == "scalarltsel" + assert OPERATORS["<="].restrict == "scalarlesel" + assert OPERATORS[">"].restrict == "scalargtsel" + assert OPERATORS[">="].restrict == "scalargesel" + + +def test_negators_and_commutators(): + """= / <> are negators; range ops commute as documented.""" + assert OPERATORS["="].negator == "<>" + assert OPERATORS["<>"].negator == "=" + assert OPERATORS["<"].commutator == ">" + assert OPERATORS["<"].negator == ">=" + assert OPERATORS[">="].commutator == "<=" + + +def test_known_jsonb_operators_is_union_of_the_three_lists(): + """The exported union is exactly the three enumerated lists, deduped.""" + assert KNOWN_JSONB_OPERATORS == frozenset( + SYMMETRIC_OPERATORS + PATH_OPERATORS + BLOCKER_ONLY_OPERATORS + ) + + +def test_known_jsonb_operators_matches_operators_keys(): + """The union must stay in lockstep with the OPERATORS table itself, so a + new operator added to one but not the other is caught here rather than + leaving a hole in the storage-only blocker guarantee.""" + assert KNOWN_JSONB_OPERATORS == frozenset(OPERATORS) + + +def test_known_jsonb_operators_full_native_surface(): + """Pin the full native jsonb operator surface for PG 14-17. This is the + source-of-truth the live-DB structural guard + (tests/sqlx/.../family/jsonb_operator_surface.rs) asserts pg_operator is a + subset of. If PG adds a jsonb operator, that DB test fails; if this list is + edited, both must move together. The three lists are disjoint, so the union + size equals their combined length.""" + assert KNOWN_JSONB_OPERATORS == frozenset( + { + # symmetric (supported wrappers) + "=", "<>", "<", "<=", ">", ">=", "@>", "<@", + # path + "->", "->>", + # blocker-only native jsonb fallbacks + "?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||", + } + ) + assert len(KNOWN_JSONB_OPERATORS) == ( + len(SYMMETRIC_OPERATORS) + len(PATH_OPERATORS) + len(BLOCKER_ONLY_OPERATORS) + ) diff --git a/tasks/codegen/test_scalars.py b/tasks/codegen/test_scalars.py new file mode 100644 index 00000000..3ef7d0f0 --- /dev/null +++ b/tasks/codegen/test_scalars.py @@ -0,0 +1,64 @@ +"""Tests for the scalar-kind catalog driving fixture-value emission.""" + +import pytest + +from tasks.codegen.scalars import ( + ScalarError, + require_scalar, + SCALAR_KINDS, +) + + +def test_int4_kind_fields(): + kind = require_scalar("int4") + assert kind.token == "int4" + assert kind.rust_type == "i32" + assert kind.min_symbol == "i32::MIN" + assert kind.max_symbol == "i32::MAX" + assert kind.zero_symbol == "0" + assert kind.min_value == -2147483648 + assert kind.max_value == 2147483647 + + +def test_render_literal_maps_sentinels(): + kind = require_scalar("int4") + assert kind.render_literal("MIN") == "i32::MIN" + assert kind.render_literal("MAX") == "i32::MAX" + assert kind.render_literal("ZERO") == "0" + + +def test_render_literal_passes_through_numeric(): + kind = require_scalar("int4") + assert kind.render_literal("-100") == "-100" + assert kind.render_literal("0") == "0" + assert kind.render_literal("9999") == "9999" + + +def test_render_literal_rejects_non_numeric(): + kind = require_scalar("int4") + with pytest.raises(ScalarError, match="not a valid i32 literal or sentinel"): + kind.render_literal("oops") + + +def test_render_literal_rejects_out_of_range(): + kind = require_scalar("int4") + with pytest.raises(ScalarError, match="out of range"): + kind.render_literal("2147483648") # i32::MAX + 1 + + +def test_numeric_value_resolves_sentinels_and_literals(): + kind = require_scalar("int4") + assert kind.numeric_value("MIN") == -2147483648 + assert kind.numeric_value("MAX") == 2147483647 + assert kind.numeric_value("ZERO") == 0 + assert kind.numeric_value("42") == 42 + assert kind.numeric_value("-1") == -1 + + +def test_require_scalar_unknown_raises(): + with pytest.raises(ScalarError, match="unknown scalar token 'bogus'"): + require_scalar("bogus") + + +def test_int4_registered_in_catalog(): + assert "int4" in SCALAR_KINDS diff --git a/tasks/codegen/test_spec.py b/tasks/codegen/test_spec.py new file mode 100644 index 00000000..151a03cb --- /dev/null +++ b/tasks/codegen/test_spec.py @@ -0,0 +1,208 @@ +"""Tests for the scalar-domain manifest loader.""" + +import textwrap + +import pytest + +from tasks.codegen.spec import DomainSpec, SpecError, TypeSpec, load_spec + + +VALID_TOML = textwrap.dedent(""" + [domain] + int4 = [] + int4_eq = ["hm"] + int4_ord_ore = ["ore"] + int4_ord = ["ore"] +""") + + +def write(tmp_path, name, text): + p = tmp_path / name + p.write_text(text) + return p + + +def test_loads_valid_manifest_and_infers_token_from_filename(tmp_path): + spec = load_spec(write(tmp_path, "int4.toml", VALID_TOML)) + assert isinstance(spec, TypeSpec) + assert spec.token == "int4" + assert spec.domains == [ + DomainSpec(name="int4", terms=[]), + DomainSpec(name="int4_eq", terms=["hm"]), + DomainSpec(name="int4_ord_ore", terms=["ore"]), + DomainSpec(name="int4_ord", terms=["ore"]), + ] + + +def test_missing_domain_table_raises(tmp_path): + with pytest.raises(SpecError, match="missing required table '\\[domain\\]'"): + load_spec(write(tmp_path, "int4.toml", "")) + + +def test_empty_domain_table_raises(tmp_path): + with pytest.raises(SpecError, match="at least one domain"): + load_spec(write(tmp_path, "int4.toml", "[domain]\n")) + + +def test_domain_value_must_be_list(tmp_path): + bad = textwrap.dedent(""" + [domain] + int4_eq = "hm" + """) + with pytest.raises(SpecError, match="must be a list of term names"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_domain_term_must_be_string(tmp_path): + bad = textwrap.dedent(""" + [domain] + int4_eq = [1] + """) + with pytest.raises(SpecError, match="term names must be strings"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_unknown_term_raises_with_domain_context(tmp_path): + bad = textwrap.dedent(""" + [domain] + int4_eq = ["bogus"] + """) + with pytest.raises(SpecError, match="\\[domain\\] int4_eq: unknown term 'bogus'"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_domain_name_must_start_with_type_token(tmp_path): + bad = textwrap.dedent(""" + [domain] + text = [] + """) + with pytest.raises(SpecError, match="domain name must start with 'int4'"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_domain_name_must_be_token_or_token_underscore(tmp_path): + bad = textwrap.dedent(""" + [domain] + int4xfoo = [] + """) + with pytest.raises(SpecError, match="domain name must start with 'int4'"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +@pytest.mark.parametrize("filename", [ + "Int4.toml", + "int-4.toml", + "int 4.toml", + "4int.toml", + "int4;drop.toml", +]) +def test_token_must_be_sql_identifier(tmp_path, filename): + with pytest.raises(SpecError, match=r"token .* must match"): + load_spec(write(tmp_path, filename, VALID_TOML)) + + +@pytest.mark.parametrize("bad_name", [ + "int4-eq", + "int4 eq", + "INT4_eq", + "int4;drop", +]) +def test_domain_name_must_be_sql_identifier(tmp_path, bad_name): + bad = textwrap.dedent(f""" + [domain] + "{bad_name}" = [] + """) + with pytest.raises(SpecError, match=r"domain name .* must match"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +FIXTURE_TOML = VALID_TOML + textwrap.dedent(""" + [fixture] + values = ["MIN", "-100", "-1", "ZERO", "1", "9999", "MAX"] +""") + + +def test_fixture_values_default_to_none_when_absent(tmp_path): + spec = load_spec(write(tmp_path, "int4.toml", VALID_TOML)) + assert spec.fixture_values is None + + +def test_loads_fixture_values_when_present(tmp_path): + spec = load_spec(write(tmp_path, "int4.toml", FIXTURE_TOML)) + assert spec.fixture_values == [ + "MIN", "-100", "-1", "ZERO", "1", "9999", "MAX", + ] + + +def test_fixture_values_must_be_a_list(tmp_path): + bad = VALID_TOML + '\n[fixture]\nvalues = "MIN"\n' + with pytest.raises(SpecError, match=r"\[fixture\] values: must be a list"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_table_requires_values_key(tmp_path): + bad = VALID_TOML + "\n[fixture]\nother = 1\n" + with pytest.raises(SpecError, match=r"\[fixture\]: missing required key 'values'"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_values_must_be_non_empty(tmp_path): + bad = VALID_TOML + "\n[fixture]\nvalues = []\n" + with pytest.raises(SpecError, match=r"\[fixture\] values: must not be empty"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_values_must_be_strings(tmp_path): + bad = VALID_TOML + "\n[fixture]\nvalues = [1, 2]\n" + with pytest.raises(SpecError, match=r"\[fixture\] values: must be strings"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_values_reject_invalid_literal(tmp_path): + bad = VALID_TOML + '\n[fixture]\nvalues = ["MIN", "oops", "ZERO", "MAX"]\n' + with pytest.raises(SpecError, match="not a valid i32 literal"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_values_require_min_max_zero(tmp_path): + bad = VALID_TOML + '\n[fixture]\nvalues = ["1", "2", "3"]\n' + with pytest.raises(SpecError, match="must include MIN, MAX, and zero"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_values_require_max_even_if_min_and_zero_present(tmp_path): + bad = VALID_TOML + '\n[fixture]\nvalues = ["MIN", "ZERO", "1"]\n' + with pytest.raises(SpecError, match="must include MIN, MAX, and zero"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_values_reject_duplicate_literal(tmp_path): + bad = VALID_TOML + '\n[fixture]\nvalues = ["MIN", "1", "ZERO", "1", "MAX"]\n' + with pytest.raises(SpecError, match=r"must be distinct.*duplicate values.*'1'"): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_values_reject_sentinel_literal_alias(tmp_path): + # "MIN" and the i32::MIN literal resolve to the same plaintext value; + # the distinct-plaintext contract must reject the pair. + bad = ( + VALID_TOML + + '\n[fixture]\nvalues = ["MIN", "-2147483648", "ZERO", "MAX"]\n' + ) + with pytest.raises( + SpecError, + match=r"must be distinct.*'-2147483648' duplicates 'MIN' \(both resolve to -2147483648\)", + ): + load_spec(write(tmp_path, "int4.toml", bad)) + + +def test_fixture_for_unknown_scalar_token_raises(tmp_path): + bad = textwrap.dedent(""" + [domain] + int8 = [] + + [fixture] + values = ["1"] + """) + with pytest.raises(SpecError, match="unknown scalar token 'int8'"): + load_spec(write(tmp_path, "int8.toml", bad)) diff --git a/tasks/codegen/test_templates.py b/tasks/codegen/test_templates.py new file mode 100644 index 00000000..ba1e3b7d --- /dev/null +++ b/tasks/codegen/test_templates.py @@ -0,0 +1,499 @@ +"""Tests for per-construct SQL template functions.""" + +from tasks.codegen.spec import DomainSpec, TypeSpec +from tasks.codegen.templates import ( + AGGREGATE_OPS, + AUTO_GENERATED_HEADER, + AUTO_GENERATED_HEADER_RS, + _sql_str, + brief_role_clause, + domain_name, + extractor_for_operator, + is_ord_capable, + render_aggregate, + render_blocker_bool, + render_blocker_native, + render_blocker_path, + render_domain_block, + render_extractor, + render_fixture_values_rs, + render_operator, + render_wrapper, +) +from tasks.codegen.terms import TERM_CATALOG + + +def test_auto_generated_header_present(): + assert "AUTO-GENERATED" in AUTO_GENERATED_HEADER + assert "DO NOT EDIT" in AUTO_GENERATED_HEADER + + +def test_rust_header_is_comment_and_marks_committed(): + # Rust uses // comments, not SQL's --, and unlike the gitignored SQL + # surface this file is committed and CI-verified. + assert AUTO_GENERATED_HEADER_RS.startswith("// AUTO-GENERATED") + assert "DO NOT EDIT" in AUTO_GENERATED_HEADER_RS + assert "committed" in AUTO_GENERATED_HEADER_RS + # No line is an SQL-style (`--`) comment — this is Rust, not SQL. + assert not any( + line.startswith("--") for line in AUTO_GENERATED_HEADER_RS.splitlines() + ) + + +def test_render_fixture_values_rs_emits_typed_const(): + spec = TypeSpec( + token="int4", + domains=[], + fixture_values=["MIN", "-1", "ZERO", "1", "MAX"], + ) + body = render_fixture_values_rs(spec) + assert "pub const VALUES: &[i32] = &[" in body + assert "tasks/codegen/types/int4.toml" in body + # Sentinels map to named consts; numeric tokens pass through. + assert "i32::MIN," in body + assert "i32::MAX," in body + assert " -1,\n" in body + assert " 0,\n" in body # ZERO and "1" both literal + assert " 1,\n" in body + # No AUTO-GENERATED header in the body — the writer prepends it. + assert "AUTO-GENERATED" not in body + + +def test_render_fixture_values_rs_preserves_manifest_order(): + spec = TypeSpec( + token="int4", + domains=[], + fixture_values=["MIN", "ZERO", "MAX"], + ) + body = render_fixture_values_rs(spec) + assert body.index("i32::MIN") < body.index("0,") < body.index("i32::MAX") + + +def test_domain_block_storage_uses_fixed_envelope_only(): + domain = DomainSpec(name="int4", terms=[]) + sql = render_domain_block(domain, "int4") + assert "CREATE DOMAIN public.eql_v2_int4 AS jsonb" in sql + assert "VALUE ? 'v'" in sql + assert "VALUE ? 'i'" in sql + assert "VALUE ? 'c'" in sql + assert "VALUE ? 'hm'" not in sql + assert "VALUE ? 'ob'" not in sql + + +def test_domain_block_uses_catalog_json_keys(): + domain = DomainSpec(name="int4_ord", terms=["ore"]) + sql = render_domain_block(domain, "int4") + assert "CREATE DOMAIN public.eql_v2_int4_ord AS jsonb" in sql + assert "VALUE ? 'ob'" in sql + assert "VALUE ? 'ore'" not in sql + + +def test_domain_block_check_pins_envelope_version(): + """Thread D: the CHECK both verifies the envelope `v` key is PRESENT and + pins its value to the EQL payload-format version (2), matching the + repo-wide eql_v2._encrypted_check_v rule. The v=1 payloads in + tests/sqlx/fixtures/aggregate_minmax_data.sql belong to the separate + composite-type (eql_v2_encrypted) aggregate stream, not these domains, so + pinning the value here rejects stale/foreign-version payloads without + affecting that fixture.""" + for domain in ( + DomainSpec(name="int4", terms=[]), + DomainSpec(name="int4_eq", terms=["hm"]), + DomainSpec(name="int4_ord", terms=["ore"]), + ): + sql = render_domain_block(domain, "int4") + assert "VALUE ? 'v'" in sql # presence checked + assert "VALUE->>'v' = '2'" in sql # value pinned to version 2 + + +def test_extractor_is_catalog_derived_and_inlinable(): + domain = DomainSpec(name="int4_eq", terms=["hm"]) + sql = render_extractor(domain, TERM_CATALOG["hm"]) + assert "CREATE FUNCTION eql_v2.eq_term(a eql_v2_int4_eq)" in sql + assert "RETURNS eql_v2.hmac_256" in sql + assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" in sql + assert "SELECT eql_v2.hmac_256(a::jsonb)" in sql + assert "SET search_path" not in sql + + +def test_wrapper_uses_term_extractor_for_supported_operator(): + domain = DomainSpec(name="int4_ord", terms=["ore"]) + sql = render_wrapper( + domain, + op="<", + arg_a="eql_v2_int4_ord", + arg_b="jsonb", + extractor="ord_term", + ) + assert "CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b jsonb)" in sql + assert "SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b::eql_v2_int4_ord)" in sql + + +def test_wrapper_is_inlinable_sql(): + """Wrappers must be single-statement LANGUAGE sql with no search_path pin.""" + domain = DomainSpec(name="int4_eq", terms=["hm"]) + sql = render_wrapper( + domain, + op="=", + arg_a="eql_v2_int4_eq", + arg_b="eql_v2_int4_eq", + extractor="eq_term", + ) + assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" in sql + assert "SET search_path" not in sql + assert "LANGUAGE plpgsql" not in sql + + +def test_extractor_for_operator_selects_catalog_term(): + domain = DomainSpec(name="int4_ord", terms=["ore"]) + assert extractor_for_operator(domain, "=") == "ord_term" + assert extractor_for_operator(domain, "<") == "ord_term" + + +def test_extractor_for_operator_returns_none_for_unsupported_operator(): + domain = DomainSpec(name="int4_eq", terms=["hm"]) + assert extractor_for_operator(domain, "<") is None + + +def test_blocker_bool_is_not_strict(): + """Footgun: a STRICT blocker lets Postgres skip the body on NULL input, + silently bypassing the 'operator not supported' raise. Assert the exact + attribute line so any future refactor that re-adds STRICT fails loudly.""" + domain = DomainSpec(name="int4", terms=[]) + sql = render_blocker_bool( + domain, op="<", arg_a="eql_v2_int4", arg_b="eql_v2_int4", + ) + assert "CREATE FUNCTION eql_v2.lt(a eql_v2_int4, b eql_v2_int4)" in sql + assert "encrypted_domain_unsupported_bool('eql_v2_int4', '<')" in sql + assert "RETURNS boolean IMMUTABLE PARALLEL SAFE\n" in sql + assert "LANGUAGE plpgsql" in sql + assert "STRICT" not in sql + + +def test_blocker_path_is_not_strict(): + """Mirror of test_blocker_bool_is_not_strict for path blockers.""" + domain = DomainSpec(name="int4", terms=[]) + sql = render_blocker_path( + domain, op="->", arg_a="eql_v2_int4", arg_b="text", + ) + assert "RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE\n" in sql + assert "LANGUAGE plpgsql" in sql + assert "STRICT" not in sql + + +def test_blocker_path_returns_domain_or_text(): + domain = DomainSpec(name="int4", terms=[]) + arrow = render_blocker_path( + domain, op="->", arg_a="eql_v2_int4", arg_b="text", + ) + assert 'CREATE FUNCTION eql_v2."->"(a eql_v2_int4, selector text)' in arrow + assert "RETURNS eql_v2_int4" in arrow + arrow2 = render_blocker_path( + domain, op="->>", arg_a="eql_v2_int4", arg_b="text", + ) + assert "RETURNS text" in arrow2 + + +def test_blocker_path_for_jsonb_left_arg_returns_domain(): + """The (jsonb, dom) shape from _path_shapes still routes to the domain + return type for `->` (only `->>` returns text).""" + domain = DomainSpec(name="int4", terms=[]) + sql = render_blocker_path( + domain, op="->", arg_a="jsonb", arg_b="eql_v2_int4", + ) + assert 'CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4)' in sql + assert "RETURNS eql_v2_int4" in sql + + +def test_blocker_native_bool_uses_helper_and_is_not_strict(): + domain = DomainSpec(name="int4", terms=[]) + sql = render_blocker_native( + domain, op="?", arg_a="eql_v2_int4", arg_b="text", returns="boolean", + ) + assert 'CREATE FUNCTION eql_v2."?"(a eql_v2_int4, b text)' in sql + assert "encrypted_domain_unsupported_bool('eql_v2_int4', '?')" in sql + assert "RETURNS boolean IMMUTABLE PARALLEL SAFE\n" in sql + assert "LANGUAGE plpgsql" in sql + assert "STRICT" not in sql + + +def test_blocker_native_jsonb_result_raises_and_is_not_strict(): + domain = DomainSpec(name="int4", terms=[]) + sql = render_blocker_native( + domain, op="#>", arg_a="eql_v2_int4", arg_b="text[]", returns="jsonb", + ) + assert 'CREATE FUNCTION eql_v2."#>"(a eql_v2_int4, b text[])' in sql + assert "RETURNS jsonb IMMUTABLE PARALLEL SAFE\n" in sql + assert "RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4'" in sql + assert "LANGUAGE plpgsql" in sql + assert "STRICT" not in sql + + +def test_blocker_native_text_result_raises_and_is_not_strict(): + domain = DomainSpec(name="int4", terms=[]) + sql = render_blocker_native( + domain, op="#>>", arg_a="eql_v2_int4", arg_b="text[]", returns="text", + ) + assert 'CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4, b text[])' in sql + assert "RETURNS text IMMUTABLE PARALLEL SAFE\n" in sql + assert "LANGUAGE plpgsql" in sql + assert "STRICT" not in sql + + +def test_blocker_native_concat_cross_shape(): + domain = DomainSpec(name="int4", terms=[]) + sql = render_blocker_native( + domain, op="||", arg_a="jsonb", arg_b="eql_v2_int4", returns="jsonb", + ) + assert 'CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4)' in sql + assert "RETURNS jsonb" in sql + + +def test_operator_symmetric_metadata(): + sql = render_operator( + op="=", backing="eq", + leftarg="eql_v2_int4_eq", rightarg="eql_v2_int4_eq", + supported=True, + ) + assert "CREATE OPERATOR = (" in sql + assert "FUNCTION = eql_v2.eq" in sql + assert "LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq" in sql + assert "NEGATOR = <>" in sql + assert "RESTRICT = eqsel" in sql + + +def test_render_operator_unsupported_emits_only_function_and_args(): + """Unsupported routing must not emit NEGATOR / RESTRICT / JOIN / COMMUTATOR + (those would lie about selectivity for a function that always raises).""" + sql = render_operator( + op="=", backing="eq", + leftarg="eql_v2_int4", rightarg="eql_v2_int4", + supported=False, + ) + assert "CREATE OPERATOR = (" in sql + assert "FUNCTION = eql_v2.eq" in sql + assert "LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4" in sql + assert "NEGATOR" not in sql + assert "RESTRICT" not in sql + assert "JOIN" not in sql + assert "COMMUTATOR" not in sql + + +def test_render_aggregate_min_int4_ord_emits_state_function_and_aggregate(): + """Pin the rendered shape for the canonical (int4_ord, min) case.""" + domain = DomainSpec(name="int4_ord", terms=["ore"]) + sql = render_aggregate(domain, AGGREGATE_OPS["min"]) + assert "CREATE FUNCTION eql_v2.min_sfunc(state eql_v2_int4_ord, value eql_v2_int4_ord)" in sql + assert "RETURNS eql_v2_int4_ord" in sql + assert "LANGUAGE plpgsql IMMUTABLE STRICT" in sql + assert "SET search_path = pg_catalog, extensions, public" in sql + assert "IF value < state THEN" in sql + assert "CREATE AGGREGATE eql_v2.min(eql_v2_int4_ord) (" in sql + assert "sfunc = eql_v2.min_sfunc" in sql + assert "stype = eql_v2_int4_ord" in sql + + +def test_render_aggregate_max_uses_greater_than_comparator(): + """Symmetric pin: max uses `>` not `<`.""" + domain = DomainSpec(name="int4_ord_ore", terms=["ore"]) + sql = render_aggregate(domain, AGGREGATE_OPS["max"]) + assert "CREATE FUNCTION eql_v2.max_sfunc(state eql_v2_int4_ord_ore, value eql_v2_int4_ord_ore)" in sql + assert "IF value > state THEN" in sql + assert "CREATE AGGREGATE eql_v2.max(eql_v2_int4_ord_ore) (" in sql + + +def test_render_aggregate_state_function_is_not_inlinable(): + """Footgun mirror: blockers must be LANGUAGE plpgsql; the state function + deliberately is too, so the planner can't elide an IMMUTABLE STRICT + aggregate state call away. STRICT + plpgsql + SET search_path together.""" + domain = DomainSpec(name="int4_ord", terms=["ore"]) + sql = render_aggregate(domain, AGGREGATE_OPS["min"]) + assert "LANGUAGE plpgsql" in sql + assert "STRICT" in sql + # Inlinable-SQL shape — explicitly absent. + assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" not in sql + + +def test_is_ord_capable_matches_role(): + assert is_ord_capable(DomainSpec(name="int4_ord", terms=["ore"])) is True + assert is_ord_capable(DomainSpec(name="int4_ord_ore", terms=["ore"])) is True + assert is_ord_capable(DomainSpec(name="int4_eq", terms=["hm"])) is False + assert is_ord_capable(DomainSpec(name="int4", terms=[])) is False + + +def test_render_operator_for_containment_omits_commutator(): + """@> has no commutator / negator / selectivity in OPERATORS; supported=True + must still omit those clauses.""" + sql = render_operator( + op="@>", backing="contains", + leftarg="eql_v2_int4_ord", rightarg="eql_v2_int4_ord", + supported=True, + ) + assert "CREATE OPERATOR @> (" in sql + assert "FUNCTION = eql_v2.contains" in sql + assert "COMMUTATOR" not in sql + assert "NEGATOR" not in sql + assert "RESTRICT" not in sql + assert "JOIN" not in sql + + +# --- ITEM A: placeholder/blocker operator comment ------------------------- + + +def test_render_operator_unsupported_emits_placeholder_comment(): + """Thread A: a blocker-backed (unsupported) operator must carry a leading + SQL comment explaining it is a placeholder that raises, so a future + reviewer doesn't wonder why an ordering op is declared on an eq-only + domain.""" + sql = render_operator( + op="<", backing="lt", + leftarg="eql_v2_int4_eq", rightarg="eql_v2_int4_eq", + supported=False, + ) + assert sql.startswith("-- Placeholder:") + assert "does not support <" in sql + assert "always raises" in sql + # The comment precedes the CREATE OPERATOR. + assert sql.index("-- Placeholder:") < sql.index("CREATE OPERATOR") + + +def test_render_operator_supported_has_no_placeholder_comment(): + """Supported operators route to real wrappers — no placeholder comment.""" + sql = render_operator( + op="=", backing="eq", + leftarg="eql_v2_int4_eq", rightarg="eql_v2_int4_eq", + supported=True, + ) + assert "Placeholder" not in sql + + +# --- ITEM B & J: aggregate SQL rationale comments ------------------------- + + +def test_render_aggregate_state_function_emits_plpgsql_rationale_comment(): + """Thread B: the plpgsql rationale must appear in the emitted SQL (not just + as a Python comment) so a SQL reader sees why it isn't an inlinable + LANGUAGE sql CASE.""" + domain = DomainSpec(name="int4_ord", terms=["ore"]) + sql = render_aggregate(domain, AGGREGATE_OPS["min"]) + assert "-- LANGUAGE plpgsql, not sql:" in sql + assert "not index" in sql + # The rationale precedes the state-function definition. + assert sql.index("-- LANGUAGE plpgsql, not sql:") < sql.index( + "CREATE FUNCTION eql_v2.min_sfunc" + ) + + +def test_render_aggregate_enables_parallel_and_combinefunc(): + """Thread #22: MIN/MAX aggregates declare a combine function (the state + function itself — min/max are associative) and PARALLEL = SAFE, so PG can + use partial/parallel aggregation on the large GROUP BY workloads these ORE + aggregates exist to serve. The sfunc is likewise PARALLEL SAFE.""" + for op_name, sfunc in (("min", "min_sfunc"), ("max", "max_sfunc")): + domain = DomainSpec(name="int4_ord", terms=["ore"]) + sql = render_aggregate(domain, AGGREGATE_OPS[op_name]) + # The state function must be parallel-safe... + assert "LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE" in sql + # ...and the aggregate must declare the combinefunc + parallel safety + # inside the CREATE AGGREGATE option list (not merely in prose). + aggregate_body = sql[sql.index(f"CREATE AGGREGATE eql_v2.{op_name}"):] + assert f"combinefunc = eql_v2.{sfunc}" in aggregate_body + assert "parallel = safe" in aggregate_body + # The stale "intentionally disabled" omission note must be gone. + assert "intentionally disabled" not in sql + assert "-- No COMBINEFUNC" not in sql + + +# --- ITEM K: differentiated @brief for converged vs scheme-explicit ------- + + +def test_domain_brief_distinguishes_converged_from_scheme_twin(): + """Thread K: int4_ord (converged) and int4_ord_ore (scheme twin) carry the + same terms but must render distinct, sensible briefs.""" + ord_dom = DomainSpec(name="int4_ord", terms=["ore"]) + ore_dom = DomainSpec(name="int4_ord_ore", terms=["ore"]) + ord_sql = render_domain_block(ord_dom, "int4") + ore_sql = render_domain_block(ore_dom, "int4") + + ord_brief = next( + line for line in ord_sql.splitlines() if "@brief" in line + ) + ore_brief = next( + line for line in ore_sql.splitlines() if "@brief" in line + ) + # Both still lead with the role phrase... + assert "Ordered encrypted int4 domain." in ord_brief + assert "Ordered encrypted int4 domain." in ore_brief + # ...but the trailing clause differs and reads sensibly. + assert ord_brief != ore_brief + assert "Recommended converged name" in ord_brief + assert "Scheme-explicit twin" in ore_brief + assert "ore scheme" in ore_brief + assert "int4_ord" in ore_brief # points back at the converged name + + +def test_brief_role_clause_is_generic_over_token_and_scheme(): + """The disambiguation reads token/role/scheme from the name, not a + hard-coded literal — so it works for other types (int8) and schemes.""" + # Converged ordered name for a different token. + assert "Recommended converged name" in brief_role_clause( + DomainSpec(name="int8_ord", terms=["ore"]), "int8" + ) + # Scheme-explicit twin with a hypothetical non-ore scheme label. + clause = brief_role_clause( + DomainSpec(name="date_ord_lex", terms=["ore"]), "date" + ) + assert "Scheme-explicit twin" in clause + assert "lex scheme" in clause + assert "date_ord" in clause + + +def test_brief_role_clause_empty_for_storage_and_eq(): + """Storage and eq domains have no converged/twin ambiguity (only one name + each), so they get no disambiguating clause — brief stays unchanged.""" + assert brief_role_clause(DomainSpec(name="int4", terms=[]), "int4") == "" + assert brief_role_clause( + DomainSpec(name="int4_eq", terms=["hm"]), "int4" + ) == "" + + +# --- THREAD 1: SQL-string interpolation hardening ------------------------- + + +def test_sql_str_doubles_single_quotes(): + """_sql_str doubles embedded single quotes so a value can't break out of + its SQL string literal.""" + assert _sql_str("o'brien") == "o''brien" + assert _sql_str("a'b'c") == "a''b''c" + # Quote-free input is unchanged — current catalog strings stay byte-stable. + assert _sql_str("int4_eq") == "int4_eq" + assert _sql_str("<=") == "<=" + + +def test_blocker_escapes_quote_bearing_domain_in_rendered_sql(): + """A hypothetical quote-bearing domain name must be doubled inside the + helper-call string literal in the rendered blocker, not interpolated raw. + + (op can't carry a quote in practice — it's looked up in the operator + catalog — so the domain name is the live escaping path through the blocker + string literals.)""" + domain = DomainSpec(name="o'dom", terms=[]) + sql = render_blocker_bool( + domain, op="<", arg_a="eql_v2_o'dom", arg_b="eql_v2_o'dom", + ) + # The dom flows into encrypted_domain_unsupported_bool('', '') + # as a single-quoted literal — the quote must be doubled. + assert "encrypted_domain_unsupported_bool('eql_v2_o''dom', '<')" in sql + # The raw, unescaped single-quoted form must not appear. + assert "'eql_v2_o'dom'" not in sql + + +def test_domain_block_escapes_quote_bearing_key_in_check(): + """A hypothetical quote-bearing payload key must be doubled inside the + VALUE ? '' check rather than interpolated raw.""" + # A term-free domain whose name carries a quote exercises the typname + # literal escaping in the IF NOT EXISTS guard. + quoted = DomainSpec(name="we'ird", terms=[]) + sql = render_domain_block(quoted, "int4") + assert "typname = 'eql_v2_we''ird'" in sql + assert "typname = 'eql_v2_we'ird'" not in sql diff --git a/tasks/codegen/test_terms.py b/tasks/codegen/test_terms.py new file mode 100644 index 00000000..8ac7aa4b --- /dev/null +++ b/tasks/codegen/test_terms.py @@ -0,0 +1,96 @@ +"""Tests for the fixed scalar-domain term catalog.""" + +import pytest + +from tasks.codegen.terms import ( + TermError, + extractor_for_operator, + operators_for_terms, + require_terms, + role_for_terms, + term_json_keys, + term_requires, +) + + +def test_hm_term_provides_equality(): + terms = require_terms(["hm"]) + hm = terms[0] + assert hm.name == "hm" + assert hm.json_key == "hm" + assert hm.extractor == "eq_term" + assert hm.returns == "eql_v2.hmac_256" + assert hm.ctor == "hmac_256" + assert hm.role == "eq" + assert hm.operators == ("=", "<>") + assert hm.requires == ("src/hmac_256/functions.sql",) + + +def test_ore_term_preserves_existing_int4_sql_contract(): + terms = require_terms(["ore"]) + ore = terms[0] + assert ore.name == "ore" + assert ore.json_key == "ob" + assert ore.extractor == "ord_term" + assert ore.returns == "eql_v2.ore_block_u64_8_256" + assert ore.ctor == "ore_block_u64_8_256" + assert ore.role == "ord" + assert ore.operators == ("=", "<>", "<", "<=", ">", ">=") + assert ore.requires == ( + "src/ore_block_u64_8_256/functions.sql", + "src/ore_block_u64_8_256/operators.sql", + ) + + +def test_unknown_term_raises(): + with pytest.raises(TermError, match="unknown term 'bogus'"): + require_terms(["bogus"]) + + +def test_operators_are_union_in_catalog_order(): + assert operators_for_terms(["ore", "hm"]) == [ + "=", "<>", "<", "<=", ">", ">=", + ] + + +def test_json_keys_come_from_catalog_not_manifest_names(): + assert term_json_keys(["hm", "ore"]) == ["hm", "ob"] + + +def test_term_requires_are_deduplicated(): + assert term_requires(["ore", "ore", "hm"]) == [ + "src/ore_block_u64_8_256/functions.sql", + "src/ore_block_u64_8_256/operators.sql", + "src/hmac_256/functions.sql", + ] + + +def test_role_for_terms_handles_storage_eq_ord(): + assert role_for_terms([]) == "storage" + assert role_for_terms(["hm"]) == "eq" + assert role_for_terms(["ore"]) == "ord" + + +def test_operators_for_terms_handles_empty_list(): + assert operators_for_terms([]) == [] + + +def test_term_json_keys_handles_empty_list(): + assert term_json_keys([]) == [] + + +def test_term_requires_handles_empty_list(): + assert term_requires([]) == [] + + +def test_extractor_for_operator_picks_first_term_supporting_op(): + assert extractor_for_operator(["hm"], "=") == "eq_term" + assert extractor_for_operator(["ore"], "<") == "ord_term" + # Multi-term domains: first supporting term wins. + assert extractor_for_operator(["hm", "ore"], "=") == "eq_term" + assert extractor_for_operator(["hm", "ore"], "<") == "ord_term" + + +def test_extractor_for_operator_returns_none_when_no_term_supports_op(): + assert extractor_for_operator(["hm"], "<") is None + assert extractor_for_operator([], "=") is None diff --git a/tasks/codegen/test_writer.py b/tasks/codegen/test_writer.py new file mode 100644 index 00000000..81805cb1 --- /dev/null +++ b/tasks/codegen/test_writer.py @@ -0,0 +1,157 @@ +"""Tests for the ownership / overwrite-refusal / stale-cleanup rules.""" +import pytest +from tasks.codegen.generate import REPO_ROOT +from tasks.codegen.templates import AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS +from tasks.codegen.writer import ( + _MARKER, + OwnershipError, + is_generated, + is_generated_rs, + clean_generated_files, + ensure_generated_paths_writable, + write_generated_file, + write_generated_rs, +) + + +_EXPECTED_SUFFIXES = ( + "_types.sql", + "_functions.sql", + "_operators.sql", + "_aggregates.sql", + "_extensions.sql", +) + + +def test_is_generated_true_for_header(tmp_path): + p = tmp_path / "x.sql" + p.write_text(AUTO_GENERATED_HEADER + "SELECT 1;\n") + assert is_generated(p) is True + + +def test_is_generated_false_for_handwritten(tmp_path): + p = tmp_path / "x.sql" + p.write_text("-- REQUIRE: src/schema.sql\nSELECT 1;\n") + assert is_generated(p) is False + + +def test_is_generated_true_for_crlf_header(tmp_path): + p = tmp_path / "x.sql" + p.write_bytes((_MARKER + "\r\n" + "SELECT 1;\n").encode("utf-8")) + assert is_generated(p) is True + + +def test_write_generated_file_creates_with_header(tmp_path): + p = tmp_path / "int4_types.sql" + write_generated_file(p, "DO $$ BEGIN END $$;\n") + text = p.read_text() + assert text.startswith(AUTO_GENERATED_HEADER) + assert "DO $$ BEGIN END $$;" in text + + +def test_write_refuses_to_overwrite_handwritten(tmp_path): + """Refuse to clobber a hand-written file at a generated path.""" + p = tmp_path / "int4_types.sql" + p.write_text("-- REQUIRE: src/schema.sql\n-- hand-written\n") + with pytest.raises(OwnershipError, match="hand-written"): + write_generated_file(p, "DO $$ BEGIN END $$;\n") + + +def test_preflight_refuses_handwritten_target_before_cleanup(tmp_path): + generated = tmp_path / "int4_types.sql" + hand = tmp_path / "int4_eq_functions.sql" + generated.write_text(AUTO_GENERATED_HEADER + "-- old generated\n") + hand.write_text("-- REQUIRE: src/schema.sql\n-- hand-written\n") + + with pytest.raises(OwnershipError, match=r"int4_eq_functions\.sql"): + ensure_generated_paths_writable([generated, hand]) + + assert generated.exists() + assert hand.exists() + + +def test_write_overwrites_existing_generated_file(tmp_path): + """A file that already carries the header may be overwritten.""" + p = tmp_path / "int4_types.sql" + p.write_text(AUTO_GENERATED_HEADER + "-- old content\n") + write_generated_file(p, "-- new content\n") + text = p.read_text() + assert "-- new content" in text + assert "-- old content" not in text + + +def test_clean_removes_only_generated_files(tmp_path): + """Clean deletes every generated file, keeps the rest.""" + gen1 = tmp_path / "int4_eq_functions.sql" + gen2 = tmp_path / "int4_old_domain_functions.sql" # stale orphan + hand = tmp_path / "int4_jsonb_extra.sql" + gen1.write_text(AUTO_GENERATED_HEADER + "SELECT 1;\n") + gen2.write_text(AUTO_GENERATED_HEADER + "SELECT 2;\n") + hand.write_text("-- REQUIRE: src/schema.sql\n-- hand-written\n") + + removed = clean_generated_files(tmp_path) + + assert not gen1.exists() + assert not gen2.exists() # stale orphan cleaned up + assert hand.exists() # hand-written file untouched + assert set(removed) == {gen1, gen2} + + +def test_clean_on_empty_directory(tmp_path): + """Clean on a greenfield directory removes nothing and does not error.""" + removed = clean_generated_files(tmp_path) + assert removed == [] + + +def test_write_generated_rs_creates_with_rust_header(tmp_path): + p = tmp_path / "int4_values.rs" + write_generated_rs(p, "pub const VALUES: &[i32] = &[];\n") + text = p.read_text() + assert text.startswith(AUTO_GENERATED_HEADER_RS) + assert "pub const VALUES" in text + + +def test_is_generated_rs_true_for_rust_header(tmp_path): + p = tmp_path / "int4_values.rs" + p.write_text(AUTO_GENERATED_HEADER_RS + "pub const VALUES: &[i32] = &[];\n") + assert is_generated_rs(p) is True + + +def test_is_generated_rs_false_for_handwritten(tmp_path): + p = tmp_path / "int4_values.rs" + p.write_text("//! hand-written\npub const VALUES: &[i32] = &[];\n") + assert is_generated_rs(p) is False + + +def test_write_generated_rs_refuses_to_overwrite_handwritten(tmp_path): + p = tmp_path / "int4_values.rs" + p.write_text("//! hand-written\n") + with pytest.raises(OwnershipError, match="hand-written"): + write_generated_rs(p, "pub const VALUES: &[i32] = &[];\n") + + +def test_write_generated_rs_overwrites_existing_generated(tmp_path): + p = tmp_path / "int4_values.rs" + p.write_text(AUTO_GENERATED_HEADER_RS + "// old\n") + write_generated_rs(p, "// new\n") + text = p.read_text() + assert "// new" in text + assert "// old" not in text + + +def test_no_misnamed_sql_files_in_generated_dirs(): + """Files under src/encrypted_domain// must end in one of the four + documented suffixes — catches mistakes like `int4_extension.sql` + (singular), which the build would silently include despite violating + the documented convention.""" + root = REPO_ROOT / "src" / "encrypted_domain" + misnamed = [ + path.relative_to(REPO_ROOT) + for type_dir in root.iterdir() if type_dir.is_dir() + for path in sorted(type_dir.glob("*.sql")) + if not path.name.endswith(_EXPECTED_SUFFIXES) + ] if root.is_dir() else [] + assert not misnamed, ( + f"misnamed SQL files in src/encrypted_domain/ — expected suffix in " + f"{_EXPECTED_SUFFIXES}: {misnamed}" + ) diff --git a/tasks/codegen/types/.gitkeep b/tasks/codegen/types/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tasks/codegen/types/int4.toml b/tasks/codegen/types/int4.toml new file mode 100644 index 00000000..606d80ee --- /dev/null +++ b/tasks/codegen/types/int4.toml @@ -0,0 +1,19 @@ +# Encrypted-domain scalar manifest for int4. +# The filename supplies the type token. Each domain lists the index terms +# it carries; term capabilities are fixed in tasks/codegen/terms.py. + +[domain] +int4 = [] +int4_eq = ["hm"] +int4_ord_ore = ["ore"] +int4_ord = ["ore"] + +# Single source of truth for the int4 fixture plaintext list. Drives the +# generated tests/sqlx/src/fixtures/int4_values.rs const, shared by the fixture +# generator and the matrix oracle. Sentinels MIN/MAX/ZERO map to i32 named +# consts; the set MUST include MIN, MAX, and zero (matrix comparison pivots). +[fixture] +values = [ + "MIN", "-100", "-1", "ZERO", "1", "2", "5", "10", "17", "25", + "42", "50", "100", "250", "1000", "9999", "MAX", +] diff --git a/tasks/codegen/writer.py b/tasks/codegen/writer.py new file mode 100644 index 00000000..aa0cdd99 --- /dev/null +++ b/tasks/codegen/writer.py @@ -0,0 +1,89 @@ +"""File writer enforcing the AUTO-GENERATED-header ownership rule. + +The generator owns only files carrying the AUTO-GENERATED header. It +preflights expected output paths, deletes generated files to clear stale +orphans, and refuses to overwrite a hand-written file at a generated path. +""" + +from pathlib import Path + +from .templates import AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS + +# The first line of each header is the ownership marker. +_MARKER = AUTO_GENERATED_HEADER.splitlines()[0] +_RS_MARKER = AUTO_GENERATED_HEADER_RS.splitlines()[0] + + +class OwnershipError(Exception): + """Raised when the generator would clobber a hand-written file.""" + + +def _first_line(path: Path) -> str: + with path.open("r", encoding="utf-8") as fh: + return fh.readline().rstrip("\r\n") + + +def is_generated(path: Path) -> bool: + """True if the file at `path` carries the SQL AUTO-GENERATED marker.""" + if not path.is_file(): + return False + return _first_line(path) == _MARKER + + +def is_generated_rs(path: Path) -> bool: + """True if the file at `path` carries the Rust AUTO-GENERATED marker.""" + if not path.is_file(): + return False + return _first_line(path) == _RS_MARKER + + +def clean_generated_files(directory: Path) -> list[Path]: + """Delete every generated .sql file in `directory`. Returns the list + of removed paths. Hand-written files are left untouched. A no-op on a + directory that does not exist or holds no generated files.""" + directory = Path(directory) + if not directory.is_dir(): + return [] + removed: list[Path] = [] + for path in sorted(directory.glob("*.sql")): + if is_generated(path): + path.unlink() + removed.append(path) + return removed + + +def ensure_generated_paths_writable(paths: list[Path]) -> None: + """Refuse a generation run before cleanup if any target is hand-written.""" + for path in paths: + path = Path(path) + if path.exists() and not is_generated(path): + raise OwnershipError( + f"refusing to overwrite hand-written file: {path} " + f"(no AUTO-GENERATED header). Remove it by hand if it is a " + f"one-time generator-adoption target." + ) + + +def write_generated_file(path: Path, body: str) -> None: + """Write `body` to `path`, prefixed with the SQL AUTO-GENERATED header. + + Refuses (OwnershipError) if `path` exists and is hand-written — a file + at a generated path that lacks the header is never clobbered.""" + path = Path(path) + ensure_generated_paths_writable([path]) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(AUTO_GENERATED_HEADER + body, encoding="utf-8") + + +def write_generated_rs(path: Path, body: str) -> None: + """Write `body` to a Rust file, prefixed with the Rust AUTO-GENERATED + header. Unlike the SQL surface this file is committed; the header still + guards against clobbering a hand-written file at the same path.""" + path = Path(path) + if path.exists() and not is_generated_rs(path): + raise OwnershipError( + f"refusing to overwrite hand-written file: {path} " + f"(no AUTO-GENERATED header)." + ) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(AUTO_GENERATED_HEADER_RS + body, encoding="utf-8") From b0d716dd1c137336b17e0eeb1e11cce6ca4bcb28 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 12:32:37 +1000 Subject: [PATCH 10/93] feat(encrypted-domain): eql_v2_int4 variant family Four jsonb-backed domains for encrypted int4: storage-only eql_v2_int4 (every operator blocked), eql_v2_int4_eq (hm; =, <>), and the ordered pair eql_v2_int4_ord / eql_v2_int4_ord_ore (ore; = <> < <= > >=). Domain CHECK pins the envelope version (VALUE->>'v' = '2'). Unsupported operators are error-throwing placeholders carrying an explanatory comment. Uniform extractors eql_v2.eq_term/ord_term keep functional indexes engaging without ::jsonb casts. Committed reference baselines guard byte parity. Part of PR #239. --- src/encrypted_domain/functions.sql | 26 ++ src/ore_block_u64_8_256/operators.sql | 11 + tests/codegen/reference/README.md | 5 + .../reference/int4/int4_eq_functions.sql | 406 ++++++++++++++++++ .../reference/int4/int4_eq_operators.sql | 271 ++++++++++++ .../codegen/reference/int4/int4_functions.sql | 403 +++++++++++++++++ .../codegen/reference/int4/int4_operators.sql | 271 ++++++++++++ .../reference/int4/int4_ord_functions.sql | 395 +++++++++++++++++ .../reference/int4/int4_ord_operators.sql | 271 ++++++++++++ .../reference/int4/int4_ord_ore_functions.sql | 395 +++++++++++++++++ .../reference/int4/int4_ord_ore_operators.sql | 271 ++++++++++++ tests/codegen/reference/int4/int4_types.sql | 72 ++++ 12 files changed, 2797 insertions(+) create mode 100644 src/encrypted_domain/functions.sql create mode 100644 tests/codegen/reference/README.md create mode 100644 tests/codegen/reference/int4/int4_eq_functions.sql create mode 100644 tests/codegen/reference/int4/int4_eq_operators.sql create mode 100644 tests/codegen/reference/int4/int4_functions.sql create mode 100644 tests/codegen/reference/int4/int4_operators.sql create mode 100644 tests/codegen/reference/int4/int4_ord_functions.sql create mode 100644 tests/codegen/reference/int4/int4_ord_operators.sql create mode 100644 tests/codegen/reference/int4/int4_ord_ore_functions.sql create mode 100644 tests/codegen/reference/int4/int4_ord_ore_operators.sql create mode 100644 tests/codegen/reference/int4/int4_types.sql diff --git a/src/encrypted_domain/functions.sql b/src/encrypted_domain/functions.sql new file mode 100644 index 00000000..24b75145 --- /dev/null +++ b/src/encrypted_domain/functions.sql @@ -0,0 +1,26 @@ +-- REQUIRE: src/schema.sql + +--! @file encrypted_domain/functions.sql +--! @brief Shared blocker helper for the eql_v2_int4 domain family. +--! +--! Per-domain wrapper functions live in src/encrypted_domain/int4/. +--! Blockers in those files delegate to encrypted_domain_unsupported_bool +--! so every domain raises a uniform domain-specific error rather than +--! letting an unsupported operator fall through to native jsonb +--! behaviour. + +--! @brief Shared blocker helper. Raises 'operator X is not supported +--! for TYPE' so unsupported domain operators surface a clear +--! error rather than fall through to native jsonb behaviour. +--! @param type_name Domain type name (eql_v2_int4*) +--! @param operator_name Operator symbol (=, <, @>, ->, etc.) +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.encrypted_domain_unsupported_bool(type_name text, operator_name text) +RETURNS boolean +IMMUTABLE PARALLEL SAFE +SET search_path = pg_catalog, extensions, public +AS $$ +BEGIN + RAISE EXCEPTION 'operator % is not supported for %', operator_name, type_name; +END; +$$ LANGUAGE plpgsql; diff --git a/src/ore_block_u64_8_256/operators.sql b/src/ore_block_u64_8_256/operators.sql index e9e34561..06a4fa65 100644 --- a/src/ore_block_u64_8_256/operators.sql +++ b/src/ore_block_u64_8_256/operators.sql @@ -123,10 +123,17 @@ $$; --! @brief = operator for ORE block types +--! +--! COMMUTATOR is the operator itself: equality is symmetric. The clause +--! is required for a MERGES (mergejoinable) operator — without it the +--! planner raises "could not find commutator" the first time an +--! ore_block equality is used as a join qual (e.g. via the inlined +--! eql_v2_int4_ord_ore equality wrappers). CREATE OPERATOR = ( FUNCTION=eql_v2.ore_block_u64_8_256_eq, LEFTARG=eql_v2.ore_block_u64_8_256, RIGHTARG=eql_v2.ore_block_u64_8_256, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel, @@ -137,10 +144,14 @@ CREATE OPERATOR = ( --! @brief <> operator for ORE block types +--! +--! COMMUTATOR is the operator itself: inequality is symmetric. Required +--! alongside the MERGES flag — see the = operator above. CREATE OPERATOR <> ( FUNCTION=eql_v2.ore_block_u64_8_256_neq, LEFTARG=eql_v2.ore_block_u64_8_256, RIGHTARG=eql_v2.ore_block_u64_8_256, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = eqsel, JOIN = eqjoinsel, diff --git a/tests/codegen/reference/README.md b/tests/codegen/reference/README.md new file mode 100644 index 00000000..c1fa5118 --- /dev/null +++ b/tests/codegen/reference/README.md @@ -0,0 +1,5 @@ +# Codegen reference + +The SQL files under `/` are the original, hand-written reference implementation for each encrypted-domain scalar type. + +They are the parity baseline for the generator in `tasks/codegen/`. `tasks/codegen/test_against_reference.py` renders the generator's output and asserts it matches these files byte-for-byte. If the generator diverges, either it regressed (fix `tasks/codegen/`) or the reference is being updated deliberately (commit the new reference in the same PR). diff --git a/tests/codegen/reference/int4/int4_eq_functions.sql b/tests/codegen/reference/int4/int4_eq_functions.sql new file mode 100644 index 00000000..f1fe0d70 --- /dev/null +++ b/tests/codegen/reference/int4/int4_eq_functions.sql @@ -0,0 +1,406 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/functions.sql +-- REQUIRE: src/hmac_256/functions.sql + +--! @file encrypted_domain/int4/int4_eq_functions.sql +--! @brief Equality-only domain of the int4 encrypted-domain family — comparison/path functions. + +--! @brief Index extractor for the eql_v2_int4_eq variant. +--! @param a eql_v2_int4_eq +--! @return eql_v2.hmac_256 +CREATE FUNCTION eql_v2.eq_term(a eql_v2_int4_eq) +RETURNS eql_v2.hmac_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.hmac_256(a::jsonb) $$; + +--! @brief Equality wrapper for eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean +CREATE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.eq_term(a) = eql_v2.eq_term(b) $$; + +--! @brief Equality wrapper for eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.eq_term(a) = eql_v2.eq_term(b::eql_v2_int4_eq) $$; + +--! @brief Equality wrapper for eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean +CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.eq_term(a::eql_v2_int4_eq) = eql_v2.eq_term(b) $$; + +--! @brief Inequality wrapper for eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean +CREATE FUNCTION eql_v2.neq(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.eq_term(a) <> eql_v2.eq_term(b) $$; + +--! @brief Inequality wrapper for eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.neq(a eql_v2_int4_eq, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.eq_term(a) <> eql_v2.eq_term(b::eql_v2_int4_eq) $$; + +--! @brief Inequality wrapper for eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean +CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.eq_term(a::eql_v2_int4_eq) <> eql_v2.eq_term(b) $$; + +--! @brief Blocker for < on eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lt(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for < on eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lt(a eql_v2_int4_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for < on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <= on eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lte(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <= on eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lte(a eql_v2_int4_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <= on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for > on eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gt(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for > on eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gt(a eql_v2_int4_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for > on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for >= on eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gte(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for >= on eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gte(a eql_v2_int4_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for >= on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_eq (domain, text). +--! @param a eql_v2_int4_eq +--! @param selector text +--! @return eql_v2_int4_eq (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4_eq, selector text) +RETURNS eql_v2_int4_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_eq (domain, integer). +--! @param a eql_v2_int4_eq +--! @param selector integer +--! @return eql_v2_int4_eq (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4_eq, selector integer) +RETURNS eql_v2_int4_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4_eq +--! @return eql_v2_int4_eq (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4_eq) +RETURNS eql_v2_int4_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_eq (domain, text). +--! @param a eql_v2_int4_eq +--! @param selector text +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_eq, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_eq (domain, integer). +--! @param a eql_v2_int4_eq +--! @param selector integer +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_eq, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4_eq +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4_eq) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ? on eql_v2_int4_eq (domain, text). +--! @param a eql_v2_int4_eq +--! @param b text +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?"(a eql_v2_int4_eq, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?| on eql_v2_int4_eq (domain, text[]). +--! @param a eql_v2_int4_eq +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?|"(a eql_v2_int4_eq, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '?|'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?& on eql_v2_int4_eq (domain, text[]). +--! @param a eql_v2_int4_eq +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?&"(a eql_v2_int4_eq, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '?&'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @? on eql_v2_int4_eq (domain, jsonpath). +--! @param a eql_v2_int4_eq +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@?"(a eql_v2_int4_eq, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @@ on eql_v2_int4_eq (domain, jsonpath). +--! @param a eql_v2_int4_eq +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@@"(a eql_v2_int4_eq, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #> on eql_v2_int4_eq (domain, text[]). +--! @param a eql_v2_int4_eq +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#>"(a eql_v2_int4_eq, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #>> on eql_v2_int4_eq (domain, text[]). +--! @param a eql_v2_int4_eq +--! @param b text[] +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4_eq, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_eq (domain, text). +--! @param a eql_v2_int4_eq +--! @param b text +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_eq, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_eq (domain, integer). +--! @param a eql_v2_int4_eq +--! @param b integer +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_eq, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_eq (domain, text[]). +--! @param a eql_v2_int4_eq +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_eq, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #- on eql_v2_int4_eq (domain, text[]). +--! @param a eql_v2_int4_eq +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#-"(a eql_v2_int4_eq, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_eq. +--! @param a eql_v2_int4_eq +--! @param b eql_v2_int4_eq +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4_eq, b eql_v2_int4_eq) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_eq (domain, jsonb). +--! @param a eql_v2_int4_eq +--! @param b jsonb +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4_eq, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_eq (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_eq +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4_eq) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_eq'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_eq_operators.sql b/tests/codegen/reference/int4/int4_eq_operators.sql new file mode 100644 index 00000000..85d8353c --- /dev/null +++ b/tests/codegen/reference/int4/int4_eq_operators.sql @@ -0,0 +1,271 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/int4/int4_eq_functions.sql + +--! @file encrypted_domain/int4/int4_eq_operators.sql +--! @brief Equality-only domain of the int4 encrypted-domain family — operator declarations. + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +-- Placeholder: this domain's term set does not support <; the backing function always raises. +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support <; the backing function always raises. +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <; the backing function always raises. +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support <=; the backing function always raises. +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support <=; the backing function always raises. +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <=; the backing function always raises. +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support >; the backing function always raises. +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support >; the backing function always raises. +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support >; the backing function always raises. +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support >=; the backing function always raises. +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support >=; the backing function always raises. +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support >=; the backing function always raises. +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4_eq, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4_eq, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support ?; the backing function always raises. +CREATE OPERATOR ? ( + FUNCTION = eql_v2."?", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ?|; the backing function always raises. +CREATE OPERATOR ?| ( + FUNCTION = eql_v2."?|", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ?&; the backing function always raises. +CREATE OPERATOR ?& ( + FUNCTION = eql_v2."?&", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support @?; the backing function always raises. +CREATE OPERATOR @? ( + FUNCTION = eql_v2."@?", + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support @@; the backing function always raises. +CREATE OPERATOR @@ ( + FUNCTION = eql_v2."@@", + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support #>; the backing function always raises. +CREATE OPERATOR #> ( + FUNCTION = eql_v2."#>", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #>>; the backing function always raises. +CREATE OPERATOR #>> ( + FUNCTION = eql_v2."#>>", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_eq, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #-; the backing function always raises. +CREATE OPERATOR #- ( + FUNCTION = eql_v2."#-", + LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq +); diff --git a/tests/codegen/reference/int4/int4_functions.sql b/tests/codegen/reference/int4/int4_functions.sql new file mode 100644 index 00000000..27936e1d --- /dev/null +++ b/tests/codegen/reference/int4/int4_functions.sql @@ -0,0 +1,403 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/functions.sql + +--! @file encrypted_domain/int4/int4_functions.sql +--! @brief Storage-only domain of the int4 encrypted-domain family — comparison/path functions. + +--! @brief Blocker for = on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.eq(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for = on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.eq(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for = on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <> on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.neq(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <> on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.neq(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <> on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for < on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lt(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for < on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lt(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for < on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <= on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lte(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <= on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lte(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <= on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for > on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gt(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for > on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gt(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for > on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for >= on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gte(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for >= on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gte(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for >= on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>='); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4 (domain, text). +--! @param a eql_v2_int4 +--! @param selector text +--! @return eql_v2_int4 (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4, selector text) +RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4 (domain, integer). +--! @param a eql_v2_int4 +--! @param selector integer +--! @return eql_v2_int4 (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4, selector integer) +RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4 +--! @return eql_v2_int4 (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4) +RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4 (domain, text). +--! @param a eql_v2_int4 +--! @param selector text +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4 (domain, integer). +--! @param a eql_v2_int4 +--! @param selector integer +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4 +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ? on eql_v2_int4 (domain, text). +--! @param a eql_v2_int4 +--! @param b text +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?"(a eql_v2_int4, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?| on eql_v2_int4 (domain, text[]). +--! @param a eql_v2_int4 +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?|"(a eql_v2_int4, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '?|'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?& on eql_v2_int4 (domain, text[]). +--! @param a eql_v2_int4 +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?&"(a eql_v2_int4, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '?&'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @? on eql_v2_int4 (domain, jsonpath). +--! @param a eql_v2_int4 +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@?"(a eql_v2_int4, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @@ on eql_v2_int4 (domain, jsonpath). +--! @param a eql_v2_int4 +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@@"(a eql_v2_int4, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #> on eql_v2_int4 (domain, text[]). +--! @param a eql_v2_int4 +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#>"(a eql_v2_int4, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #>> on eql_v2_int4 (domain, text[]). +--! @param a eql_v2_int4 +--! @param b text[] +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4 (domain, text). +--! @param a eql_v2_int4 +--! @param b text +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4 (domain, integer). +--! @param a eql_v2_int4 +--! @param b integer +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4 (domain, text[]). +--! @param a eql_v2_int4 +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #- on eql_v2_int4 (domain, text[]). +--! @param a eql_v2_int4 +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#-"(a eql_v2_int4, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4. +--! @param a eql_v2_int4 +--! @param b eql_v2_int4 +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4, b eql_v2_int4) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4 (domain, jsonb). +--! @param a eql_v2_int4 +--! @param b jsonb +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4 (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4 +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_operators.sql b/tests/codegen/reference/int4/int4_operators.sql new file mode 100644 index 00000000..fc3dd7cf --- /dev/null +++ b/tests/codegen/reference/int4/int4_operators.sql @@ -0,0 +1,271 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/int4/int4_functions.sql + +--! @file encrypted_domain/int4/int4_operators.sql +--! @brief Storage-only domain of the int4 encrypted-domain family — operator declarations. + +-- Placeholder: this domain's term set does not support =; the backing function always raises. +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support =; the backing function always raises. +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support =; the backing function always raises. +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <>; the backing function always raises. +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <>; the backing function always raises. +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <>; the backing function always raises. +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <; the backing function always raises. +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <; the backing function always raises. +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <; the backing function always raises. +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <=; the backing function always raises. +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <=; the backing function always raises. +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <=; the backing function always raises. +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support >; the backing function always raises. +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support >; the backing function always raises. +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support >; the backing function always raises. +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support >=; the backing function always raises. +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support >=; the backing function always raises. +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support >=; the backing function always raises. +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support ?; the backing function always raises. +CREATE OPERATOR ? ( + FUNCTION = eql_v2."?", + LEFTARG = eql_v2_int4, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ?|; the backing function always raises. +CREATE OPERATOR ?| ( + FUNCTION = eql_v2."?|", + LEFTARG = eql_v2_int4, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ?&; the backing function always raises. +CREATE OPERATOR ?& ( + FUNCTION = eql_v2."?&", + LEFTARG = eql_v2_int4, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support @?; the backing function always raises. +CREATE OPERATOR @? ( + FUNCTION = eql_v2."@?", + LEFTARG = eql_v2_int4, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support @@; the backing function always raises. +CREATE OPERATOR @@ ( + FUNCTION = eql_v2."@@", + LEFTARG = eql_v2_int4, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support #>; the backing function always raises. +CREATE OPERATOR #> ( + FUNCTION = eql_v2."#>", + LEFTARG = eql_v2_int4, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #>>; the backing function always raises. +CREATE OPERATOR #>> ( + FUNCTION = eql_v2."#>>", + LEFTARG = eql_v2_int4, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #-; the backing function always raises. +CREATE OPERATOR #- ( + FUNCTION = eql_v2."#-", + LEFTARG = eql_v2_int4, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4 +); diff --git a/tests/codegen/reference/int4/int4_ord_functions.sql b/tests/codegen/reference/int4/int4_ord_functions.sql new file mode 100644 index 00000000..9d3ba2a2 --- /dev/null +++ b/tests/codegen/reference/int4/int4_ord_functions.sql @@ -0,0 +1,395 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/functions.sql +-- REQUIRE: src/ore_block_u64_8_256/functions.sql +-- REQUIRE: src/ore_block_u64_8_256/operators.sql + +--! @file encrypted_domain/int4/int4_ord_functions.sql +--! @brief Ordered domain of the int4 encrypted-domain family — comparison/path functions. + +--! @brief Index extractor for the eql_v2_int4_ord variant. +--! @param a eql_v2_int4_ord +--! @return eql_v2.ore_block_u64_8_256 +CREATE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord) +RETURNS eql_v2.ore_block_u64_8_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; + +--! @brief Equality wrapper for eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b) $$; + +--! @brief Equality wrapper for eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b::eql_v2_int4_ord) $$; + +--! @brief Equality wrapper for eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) = eql_v2.ord_term(b) $$; + +--! @brief Inequality wrapper for eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b) $$; + +--! @brief Inequality wrapper for eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b::eql_v2_int4_ord) $$; + +--! @brief Inequality wrapper for eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) <> eql_v2.ord_term(b) $$; + +--! @brief Less-than wrapper for eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b) $$; + +--! @brief Less-than wrapper for eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b::eql_v2_int4_ord) $$; + +--! @brief Less-than wrapper for eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) < eql_v2.ord_term(b) $$; + +--! @brief Less-than-or-equal wrapper for eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b) $$; + +--! @brief Less-than-or-equal wrapper for eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b::eql_v2_int4_ord) $$; + +--! @brief Less-than-or-equal wrapper for eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) <= eql_v2.ord_term(b) $$; + +--! @brief Greater-than wrapper for eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b) $$; + +--! @brief Greater-than wrapper for eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b::eql_v2_int4_ord) $$; + +--! @brief Greater-than wrapper for eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) > eql_v2.ord_term(b) $$; + +--! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b) $$; + +--! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b::eql_v2_int4_ord) $$; + +--! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean +CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) >= eql_v2.ord_term(b) $$; + +--! @brief Blocker for @> on eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_ord (domain, text). +--! @param a eql_v2_int4_ord +--! @param selector text +--! @return eql_v2_int4_ord (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord, selector text) +RETURNS eql_v2_int4_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_ord (domain, integer). +--! @param a eql_v2_int4_ord +--! @param selector integer +--! @return eql_v2_int4_ord (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord, selector integer) +RETURNS eql_v2_int4_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4_ord +--! @return eql_v2_int4_ord (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4_ord) +RETURNS eql_v2_int4_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_ord (domain, text). +--! @param a eql_v2_int4_ord +--! @param selector text +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_ord (domain, integer). +--! @param a eql_v2_int4_ord +--! @param selector integer +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4_ord +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4_ord) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ? on eql_v2_int4_ord (domain, text). +--! @param a eql_v2_int4_ord +--! @param b text +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?"(a eql_v2_int4_ord, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?| on eql_v2_int4_ord (domain, text[]). +--! @param a eql_v2_int4_ord +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?|"(a eql_v2_int4_ord, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '?|'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?& on eql_v2_int4_ord (domain, text[]). +--! @param a eql_v2_int4_ord +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?&"(a eql_v2_int4_ord, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '?&'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @? on eql_v2_int4_ord (domain, jsonpath). +--! @param a eql_v2_int4_ord +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@?"(a eql_v2_int4_ord, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @@ on eql_v2_int4_ord (domain, jsonpath). +--! @param a eql_v2_int4_ord +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@@"(a eql_v2_int4_ord, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #> on eql_v2_int4_ord (domain, text[]). +--! @param a eql_v2_int4_ord +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#>"(a eql_v2_int4_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #>> on eql_v2_int4_ord (domain, text[]). +--! @param a eql_v2_int4_ord +--! @param b text[] +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4_ord, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_ord (domain, text). +--! @param a eql_v2_int4_ord +--! @param b text +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_ord (domain, integer). +--! @param a eql_v2_int4_ord +--! @param b integer +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_ord (domain, text[]). +--! @param a eql_v2_int4_ord +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #- on eql_v2_int4_ord (domain, text[]). +--! @param a eql_v2_int4_ord +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#-"(a eql_v2_int4_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_ord. +--! @param a eql_v2_int4_ord +--! @param b eql_v2_int4_ord +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord, b eql_v2_int4_ord) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_ord (domain, jsonb). +--! @param a eql_v2_int4_ord +--! @param b jsonb +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_ord (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4_ord) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_ord_operators.sql b/tests/codegen/reference/int4/int4_ord_operators.sql new file mode 100644 index 00000000..3e3657f9 --- /dev/null +++ b/tests/codegen/reference/int4/int4_ord_operators.sql @@ -0,0 +1,271 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql + +--! @file encrypted_domain/int4/int4_ord_operators.sql +--! @brief Ordered domain of the int4 encrypted-domain family — operator declarations. + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4_ord, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4_ord, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord +); + +-- Placeholder: this domain's term set does not support ?; the backing function always raises. +CREATE OPERATOR ? ( + FUNCTION = eql_v2."?", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ?|; the backing function always raises. +CREATE OPERATOR ?| ( + FUNCTION = eql_v2."?|", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ?&; the backing function always raises. +CREATE OPERATOR ?& ( + FUNCTION = eql_v2."?&", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support @?; the backing function always raises. +CREATE OPERATOR @? ( + FUNCTION = eql_v2."@?", + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support @@; the backing function always raises. +CREATE OPERATOR @@ ( + FUNCTION = eql_v2."@@", + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support #>; the backing function always raises. +CREATE OPERATOR #> ( + FUNCTION = eql_v2."#>", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #>>; the backing function always raises. +CREATE OPERATOR #>> ( + FUNCTION = eql_v2."#>>", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_ord, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #-; the backing function always raises. +CREATE OPERATOR #- ( + FUNCTION = eql_v2."#-", + LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord +); diff --git a/tests/codegen/reference/int4/int4_ord_ore_functions.sql b/tests/codegen/reference/int4/int4_ord_ore_functions.sql new file mode 100644 index 00000000..bd6fe8b4 --- /dev/null +++ b/tests/codegen/reference/int4/int4_ord_ore_functions.sql @@ -0,0 +1,395 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/functions.sql +-- REQUIRE: src/ore_block_u64_8_256/functions.sql +-- REQUIRE: src/ore_block_u64_8_256/operators.sql + +--! @file encrypted_domain/int4/int4_ord_ore_functions.sql +--! @brief Ordered domain of the int4 encrypted-domain family — comparison/path functions. + +--! @brief Index extractor for the eql_v2_int4_ord_ore variant. +--! @param a eql_v2_int4_ord_ore +--! @return eql_v2.ore_block_u64_8_256 +CREATE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord_ore) +RETURNS eql_v2.ore_block_u64_8_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; + +--! @brief Equality wrapper for eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b) $$; + +--! @brief Equality wrapper for eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; + +--! @brief Equality wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) = eql_v2.ord_term(b) $$; + +--! @brief Inequality wrapper for eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b) $$; + +--! @brief Inequality wrapper for eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; + +--! @brief Inequality wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) <> eql_v2.ord_term(b) $$; + +--! @brief Less-than wrapper for eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b) $$; + +--! @brief Less-than wrapper for eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; + +--! @brief Less-than wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) < eql_v2.ord_term(b) $$; + +--! @brief Less-than-or-equal wrapper for eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b) $$; + +--! @brief Less-than-or-equal wrapper for eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; + +--! @brief Less-than-or-equal wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) <= eql_v2.ord_term(b) $$; + +--! @brief Greater-than wrapper for eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b) $$; + +--! @brief Greater-than wrapper for eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; + +--! @brief Greater-than wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) > eql_v2.ord_term(b) $$; + +--! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b) $$; + +--! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; + +--! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean +CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) >= eql_v2.ord_term(b) $$; + +--! @brief Blocker for @> on eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @> on eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@>'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord_ore, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for <@ on eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '<@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_ord_ore (domain, text). +--! @param a eql_v2_int4_ord_ore +--! @param selector text +--! @return eql_v2_int4_ord_ore (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord_ore, selector text) +RETURNS eql_v2_int4_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_ord_ore (domain, integer). +--! @param a eql_v2_int4_ord_ore +--! @param selector integer +--! @return eql_v2_int4_ord_ore (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord_ore, selector integer) +RETURNS eql_v2_int4_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for -> on eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4_ord_ore +--! @return eql_v2_int4_ord_ore (never returns; always raises) +CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4_ord_ore) +RETURNS eql_v2_int4_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_ord_ore (domain, text). +--! @param a eql_v2_int4_ord_ore +--! @param selector text +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord_ore, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_ord_ore (domain, integer). +--! @param a eql_v2_int4_ord_ore +--! @param selector integer +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord_ore, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ->> on eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param selector eql_v2_int4_ord_ore +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4_ord_ore) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ? on eql_v2_int4_ord_ore (domain, text). +--! @param a eql_v2_int4_ord_ore +--! @param b text +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?"(a eql_v2_int4_ord_ore, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?| on eql_v2_int4_ord_ore (domain, text[]). +--! @param a eql_v2_int4_ord_ore +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?|"(a eql_v2_int4_ord_ore, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '?|'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for ?& on eql_v2_int4_ord_ore (domain, text[]). +--! @param a eql_v2_int4_ord_ore +--! @param b text[] +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."?&"(a eql_v2_int4_ord_ore, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '?&'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @? on eql_v2_int4_ord_ore (domain, jsonpath). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@?"(a eql_v2_int4_ord_ore, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@?'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for @@ on eql_v2_int4_ord_ore (domain, jsonpath). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonpath +--! @return boolean (never returns; always raises) +CREATE FUNCTION eql_v2."@@"(a eql_v2_int4_ord_ore, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@@'); END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #> on eql_v2_int4_ord_ore (domain, text[]). +--! @param a eql_v2_int4_ord_ore +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#>"(a eql_v2_int4_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #>> on eql_v2_int4_ord_ore (domain, text[]). +--! @param a eql_v2_int4_ord_ore +--! @param b text[] +--! @return text (never returns; always raises) +CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4_ord_ore, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_ord_ore (domain, text). +--! @param a eql_v2_int4_ord_ore +--! @param b text +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord_ore, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_ord_ore (domain, integer). +--! @param a eql_v2_int4_ord_ore +--! @param b integer +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord_ore, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for - on eql_v2_int4_ord_ore (domain, text[]). +--! @param a eql_v2_int4_ord_ore +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for #- on eql_v2_int4_ord_ore (domain, text[]). +--! @param a eql_v2_int4_ord_ore +--! @param b text[] +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."#-"(a eql_v2_int4_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_ord_ore. +--! @param a eql_v2_int4_ord_ore +--! @param b eql_v2_int4_ord_ore +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_ord_ore (domain, jsonb). +--! @param a eql_v2_int4_ord_ore +--! @param b jsonb +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord_ore, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Blocker for || on eql_v2_int4_ord_ore (jsonb, domain). +--! @param a jsonb +--! @param b eql_v2_int4_ord_ore +--! @return jsonb (never returns; always raises) +CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4_ord_ore) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord_ore'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_ord_ore_operators.sql b/tests/codegen/reference/int4/int4_ord_ore_operators.sql new file mode 100644 index 00000000..ee1f84cf --- /dev/null +++ b/tests/codegen/reference/int4/int4_ord_ore_operators.sql @@ -0,0 +1,271 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql + +--! @file encrypted_domain/int4/int4_ord_ore_operators.sql +--! @brief Ordered domain of the int4 encrypted-domain family — operator declarations. + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v2.eq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v2.neq, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v2.lt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v2.lte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v2.gt, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v2.gte, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support @>; the backing function always raises. +CREATE OPERATOR @> ( + FUNCTION = eql_v2.contains, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support <@; the backing function always raises. +CREATE OPERATOR <@ ( + FUNCTION = eql_v2.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->; the backing function always raises. +CREATE OPERATOR -> ( + FUNCTION = eql_v2."->", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support ->>; the backing function always raises. +CREATE OPERATOR ->> ( + FUNCTION = eql_v2."->>", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore +); + +-- Placeholder: this domain's term set does not support ?; the backing function always raises. +CREATE OPERATOR ? ( + FUNCTION = eql_v2."?", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support ?|; the backing function always raises. +CREATE OPERATOR ?| ( + FUNCTION = eql_v2."?|", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ?&; the backing function always raises. +CREATE OPERATOR ?& ( + FUNCTION = eql_v2."?&", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support @?; the backing function always raises. +CREATE OPERATOR @? ( + FUNCTION = eql_v2."@?", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support @@; the backing function always raises. +CREATE OPERATOR @@ ( + FUNCTION = eql_v2."@@", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonpath +); + +-- Placeholder: this domain's term set does not support #>; the backing function always raises. +CREATE OPERATOR #> ( + FUNCTION = eql_v2."#>", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #>>; the backing function always raises. +CREATE OPERATOR #>> ( + FUNCTION = eql_v2."#>>", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = integer +); + +-- Placeholder: this domain's term set does not support -; the backing function always raises. +CREATE OPERATOR - ( + FUNCTION = eql_v2."-", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support #-; the backing function always raises. +CREATE OPERATOR #- ( + FUNCTION = eql_v2."#-", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb +); + +-- Placeholder: this domain's term set does not support ||; the backing function always raises. +CREATE OPERATOR || ( + FUNCTION = eql_v2."||", + LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore +); diff --git a/tests/codegen/reference/int4/int4_types.sql b/tests/codegen/reference/int4/int4_types.sql new file mode 100644 index 00000000..f7616539 --- /dev/null +++ b/tests/codegen/reference/int4/int4_types.sql @@ -0,0 +1,72 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql + +--! @file encrypted_domain/int4/int4_types.sql +--! @brief Encrypted-domain type family for int4. + +DO $$ +BEGIN + --! @brief Storage-only encrypted int4 domain. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'eql_v2_int4' AND typnamespace = 'public'::regnamespace + ) THEN + CREATE DOMAIN public.eql_v2_int4 AS jsonb + CHECK ( + jsonb_typeof(VALUE) = 'object' + AND VALUE ? 'v' + AND VALUE ? 'i' + AND VALUE ? 'c' + AND VALUE->>'v' = '2' + ); + END IF; + + --! @brief Equality-only encrypted int4 domain. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'eql_v2_int4_eq' AND typnamespace = 'public'::regnamespace + ) THEN + CREATE DOMAIN public.eql_v2_int4_eq AS jsonb + CHECK ( + jsonb_typeof(VALUE) = 'object' + AND VALUE ? 'v' + AND VALUE ? 'i' + AND VALUE ? 'c' + AND VALUE ? 'hm' + AND VALUE->>'v' = '2' + ); + END IF; + + --! @brief Ordered encrypted int4 domain. Scheme-explicit twin pinning the ore scheme; prefer the converged int4_ord name. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'eql_v2_int4_ord_ore' AND typnamespace = 'public'::regnamespace + ) THEN + CREATE DOMAIN public.eql_v2_int4_ord_ore AS jsonb + CHECK ( + jsonb_typeof(VALUE) = 'object' + AND VALUE ? 'v' + AND VALUE ? 'i' + AND VALUE ? 'c' + AND VALUE ? 'ob' + AND VALUE->>'v' = '2' + ); + END IF; + + --! @brief Ordered encrypted int4 domain. Recommended converged name for this role. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'eql_v2_int4_ord' AND typnamespace = 'public'::regnamespace + ) THEN + CREATE DOMAIN public.eql_v2_int4_ord AS jsonb + CHECK ( + jsonb_typeof(VALUE) = 'object' + AND VALUE ? 'v' + AND VALUE ? 'i' + AND VALUE ? 'c' + AND VALUE ? 'ob' + AND VALUE->>'v' = '2' + ); + END IF; +END +$$; From fa528a40392ba5df625484f633dec5ec2f041e3a Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 12:32:37 +1000 Subject: [PATCH 11/93] feat(aggregates): per-domain MIN/MAX with parallel aggregation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MIN/MAX aggregates on ord-capable int4 domains route comparison through the domain's ORE operators — no decryption. min/max are associative, so the state function doubles as combinefunc; with PARALLEL SAFE sfuncs and parallel = safe, PostgreSQL can use partial/parallel aggregation on the large GROUP BY workloads these aggregates exist to serve. Part of PR #239. --- .../reference/int4/int4_ord_aggregates.sql | 86 +++++++++++++++++++ .../int4/int4_ord_ore_aggregates.sql | 86 +++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 tests/codegen/reference/int4/int4_ord_aggregates.sql create mode 100644 tests/codegen/reference/int4/int4_ord_ore_aggregates.sql diff --git a/tests/codegen/reference/int4/int4_ord_aggregates.sql b/tests/codegen/reference/int4/int4_ord_aggregates.sql new file mode 100644 index 00000000..52a64ec1 --- /dev/null +++ b/tests/codegen/reference/int4/int4_ord_aggregates.sql @@ -0,0 +1,86 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql +-- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql + +--! @file encrypted_domain/int4/int4_ord_aggregates.sql +--! @brief Ordered domain of the int4 encrypted-domain family — MIN/MAX aggregates. + +--! @brief State function for min aggregate on eql_v2_int4_ord. +--! @internal +--! +--! @param state eql_v2_int4_ord running extremum +--! @param value eql_v2_int4_ord next non-NULL value +--! @return eql_v2_int4_ord the minimum of state and value +-- LANGUAGE plpgsql, not sql: aggregate state functions are not index +-- expressions, so opacity to the planner is fine, and a multi-statement +-- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would +-- also work, but the procedural form mirrors the blocker convention.) +CREATE FUNCTION eql_v2.min_sfunc(state eql_v2_int4_ord, value eql_v2_int4_ord) +RETURNS eql_v2_int4_ord +LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE +SET search_path = pg_catalog, extensions, public +AS $$ +BEGIN + IF value < state THEN + RETURN value; + END IF; + RETURN state; +END; +$$; + +--! @brief Find the minimum encrypted value in a group of eql_v2_int4_ord values. +--! +--! Comparison routes through the domain's `<` operator, which uses the ORE block term — no decryption. +--! +--! @param input eql_v2_int4_ord encrypted values to aggregate +--! @return eql_v2_int4_ord minimum of the group, or NULL if all inputs are NULL +-- combinefunc = sfunc: min/max are associative, so merging two partial +-- extrema is the same comparison. PARALLEL SAFE enables partial and +-- parallel aggregation on large GROUP BY workloads, with no decryption. +CREATE AGGREGATE eql_v2.min(eql_v2_int4_ord) ( + sfunc = eql_v2.min_sfunc, + stype = eql_v2_int4_ord, + combinefunc = eql_v2.min_sfunc, + parallel = safe +); + +--! @brief State function for max aggregate on eql_v2_int4_ord. +--! @internal +--! +--! @param state eql_v2_int4_ord running extremum +--! @param value eql_v2_int4_ord next non-NULL value +--! @return eql_v2_int4_ord the maximum of state and value +-- LANGUAGE plpgsql, not sql: aggregate state functions are not index +-- expressions, so opacity to the planner is fine, and a multi-statement +-- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would +-- also work, but the procedural form mirrors the blocker convention.) +CREATE FUNCTION eql_v2.max_sfunc(state eql_v2_int4_ord, value eql_v2_int4_ord) +RETURNS eql_v2_int4_ord +LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE +SET search_path = pg_catalog, extensions, public +AS $$ +BEGIN + IF value > state THEN + RETURN value; + END IF; + RETURN state; +END; +$$; + +--! @brief Find the maximum encrypted value in a group of eql_v2_int4_ord values. +--! +--! Comparison routes through the domain's `>` operator, which uses the ORE block term — no decryption. +--! +--! @param input eql_v2_int4_ord encrypted values to aggregate +--! @return eql_v2_int4_ord maximum of the group, or NULL if all inputs are NULL +-- combinefunc = sfunc: min/max are associative, so merging two partial +-- extrema is the same comparison. PARALLEL SAFE enables partial and +-- parallel aggregation on large GROUP BY workloads, with no decryption. +CREATE AGGREGATE eql_v2.max(eql_v2_int4_ord) ( + sfunc = eql_v2.max_sfunc, + stype = eql_v2_int4_ord, + combinefunc = eql_v2.max_sfunc, + parallel = safe +); diff --git a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql new file mode 100644 index 00000000..f2f1e81e --- /dev/null +++ b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql @@ -0,0 +1,86 @@ +-- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted_domain/int4/int4_types.sql +-- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql +-- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_operators.sql + +--! @file encrypted_domain/int4/int4_ord_ore_aggregates.sql +--! @brief Ordered domain of the int4 encrypted-domain family — MIN/MAX aggregates. + +--! @brief State function for min aggregate on eql_v2_int4_ord_ore. +--! @internal +--! +--! @param state eql_v2_int4_ord_ore running extremum +--! @param value eql_v2_int4_ord_ore next non-NULL value +--! @return eql_v2_int4_ord_ore the minimum of state and value +-- LANGUAGE plpgsql, not sql: aggregate state functions are not index +-- expressions, so opacity to the planner is fine, and a multi-statement +-- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would +-- also work, but the procedural form mirrors the blocker convention.) +CREATE FUNCTION eql_v2.min_sfunc(state eql_v2_int4_ord_ore, value eql_v2_int4_ord_ore) +RETURNS eql_v2_int4_ord_ore +LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE +SET search_path = pg_catalog, extensions, public +AS $$ +BEGIN + IF value < state THEN + RETURN value; + END IF; + RETURN state; +END; +$$; + +--! @brief Find the minimum encrypted value in a group of eql_v2_int4_ord_ore values. +--! +--! Comparison routes through the domain's `<` operator, which uses the ORE block term — no decryption. +--! +--! @param input eql_v2_int4_ord_ore encrypted values to aggregate +--! @return eql_v2_int4_ord_ore minimum of the group, or NULL if all inputs are NULL +-- combinefunc = sfunc: min/max are associative, so merging two partial +-- extrema is the same comparison. PARALLEL SAFE enables partial and +-- parallel aggregation on large GROUP BY workloads, with no decryption. +CREATE AGGREGATE eql_v2.min(eql_v2_int4_ord_ore) ( + sfunc = eql_v2.min_sfunc, + stype = eql_v2_int4_ord_ore, + combinefunc = eql_v2.min_sfunc, + parallel = safe +); + +--! @brief State function for max aggregate on eql_v2_int4_ord_ore. +--! @internal +--! +--! @param state eql_v2_int4_ord_ore running extremum +--! @param value eql_v2_int4_ord_ore next non-NULL value +--! @return eql_v2_int4_ord_ore the maximum of state and value +-- LANGUAGE plpgsql, not sql: aggregate state functions are not index +-- expressions, so opacity to the planner is fine, and a multi-statement +-- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would +-- also work, but the procedural form mirrors the blocker convention.) +CREATE FUNCTION eql_v2.max_sfunc(state eql_v2_int4_ord_ore, value eql_v2_int4_ord_ore) +RETURNS eql_v2_int4_ord_ore +LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE +SET search_path = pg_catalog, extensions, public +AS $$ +BEGIN + IF value > state THEN + RETURN value; + END IF; + RETURN state; +END; +$$; + +--! @brief Find the maximum encrypted value in a group of eql_v2_int4_ord_ore values. +--! +--! Comparison routes through the domain's `>` operator, which uses the ORE block term — no decryption. +--! +--! @param input eql_v2_int4_ord_ore encrypted values to aggregate +--! @return eql_v2_int4_ord_ore maximum of the group, or NULL if all inputs are NULL +-- combinefunc = sfunc: min/max are associative, so merging two partial +-- extrema is the same comparison. PARALLEL SAFE enables partial and +-- parallel aggregation on large GROUP BY workloads, with no decryption. +CREATE AGGREGATE eql_v2.max(eql_v2_int4_ord_ore) ( + sfunc = eql_v2.max_sfunc, + stype = eql_v2_int4_ord_ore, + combinefunc = eql_v2.max_sfunc, + parallel = safe +); From 4f143f58160a27790d553d7220b09b0a464a6971 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 12:32:37 +1000 Subject: [PATCH 12/93] feat(lint): encrypted-domain lint rules Add blocker_language, blocker_strict, domain_over_domain, and domain_opclass structural lints enforcing the encrypted-domain footguns (blockers must be plpgsql and non-STRICT; no domain-over-domain; no opclass on a domain). pin_search_path recognises the converged extractor/wrapper names intrinsically. Part of PR #239. --- src/lint/lints.sql | 147 ++++++++++++++++++++++++++++++++++++++ tasks/pin_search_path.sql | 55 ++++++++++++-- 2 files changed, 195 insertions(+), 7 deletions(-) diff --git a/src/lint/lints.sql b/src/lint/lints.sql index 12ffea00..b378f1bb 100644 --- a/src/lint/lints.sql +++ b/src/lint/lints.sql @@ -38,6 +38,24 @@ --! but its body invokes a non-inlinable function --! (depth 1; the planner can't peek through --! that boundary). +--! `blocker_language` — encrypted-domain blocker is not LANGUAGE +--! plpgsql. The planner can inline / elide a +--! LANGUAGE sql body when the result is +--! provably unused, silently bypassing the +--! RAISE that the blocker exists to perform. +--! `blocker_strict` — encrypted-domain blocker is STRICT. +--! PostgreSQL skips the body and returns NULL +--! on NULL arguments, silently bypassing the +--! RAISE. +--! `domain_over_domain` — an `eql_v2_*` domain is derived from another +--! `eql_v2_*` domain rather than jsonb. +--! Operators resolve against the ultimate base +--! type, so the derived domain does not +--! inherit the base domain's blocker surface. +--! `domain_opclass` — an operator class is declared FOR TYPE on an +--! `eql_v2_*` domain. Opclasses on domains +--! bypass operator resolution; use a +--! functional index on the extractor instead. --! --! @example --! ``` @@ -85,6 +103,7 @@ AS $$ eo.opname, eo.lhs, eo.rhs, + eo.implfunc AS impl_oid, eo.impl_signature::text AS impl_signature, lang_l.lanname AS lang, p.provolatile AS volatility, @@ -94,6 +113,39 @@ AS $$ FROM eql_operators eo JOIN pg_proc p ON p.oid = eo.implfunc JOIN pg_language lang_l ON lang_l.oid = p.prolang + ), + + -- Encrypted-domain blockers: functions in `eql_v2` whose body contains + -- one of the two blocker markers emitted by the codegen + -- (`encrypted_domain_unsupported_bool` for boolean blockers; the literal + -- `is not supported for` for path-operator blockers) AND that take at + -- least one `public.eql_v2_*` domain over jsonb argument. The argument + -- filter excludes the shared `encrypted_domain_unsupported_bool(text, + -- text)` helper itself, which contains the marker in its body but is + -- not a blocker. + encrypted_domain_blockers AS ( + SELECT + p.oid AS oid, + p.oid::regprocedure::text AS signature, + lang_l.lanname AS lang, + p.proisstrict AS isstrict + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + JOIN pg_catalog.pg_language lang_l ON lang_l.oid = p.prolang + WHERE n.nspname = 'eql_v2' + AND (p.prosrc LIKE '%encrypted_domain_unsupported_bool%' + OR p.prosrc LIKE '%is not supported for%') + AND EXISTS ( + SELECT 1 + FROM pg_catalog.unnest(p.proargtypes::oid[]) AS arg(typ) + JOIN pg_catalog.pg_type dt ON dt.oid = arg.typ + JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace + JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype + WHERE dt.typtype = 'd' + AND dn.nspname = 'public' + AND dt.typname LIKE 'eql_v2\_%' + AND bt.typname = 'jsonb' + ) ) -- ┌─────────────────────────────────────────────────────────────────┐ @@ -113,6 +165,10 @@ AS $$ lang, opname) AS message FROM op_impl WHERE lang <> 'sql' + AND NOT EXISTS ( + SELECT 1 FROM encrypted_domain_blockers b + WHERE b.oid = op_impl.impl_oid + ) UNION ALL @@ -125,6 +181,10 @@ AS $$ opname) FROM op_impl WHERE volatility = 'v' + AND NOT EXISTS ( + SELECT 1 FROM encrypted_domain_blockers b + WHERE b.oid = op_impl.impl_oid + ) UNION ALL @@ -136,6 +196,10 @@ AS $$ 'Operator implementation function has a `SET` clause (e.g. `SET search_path = ...`). Per Postgres function-inlining rules, any `SET` clause blocks inlining. Use schema-qualified identifiers in the body and remove the `SET` clause to allow the planner to inline.') FROM op_impl WHERE config IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM encrypted_domain_blockers b + WHERE b.oid = op_impl.impl_oid + ) UNION ALL @@ -146,6 +210,10 @@ AS $$ 'Operator implementation function is `SECURITY DEFINER`. Such functions cannot be inlined; remove `SECURITY DEFINER` or use a non-inlinable wrapper layer.' FROM op_impl WHERE secdef + AND NOT EXISTS ( + SELECT 1 FROM encrypted_domain_blockers b + WHERE b.oid = op_impl.impl_oid + ) -- ┌─────────────────────────────────────────────────────────────────┐ -- │ Transitive inlinability: an operator implementation function │ @@ -201,6 +269,85 @@ AS $$ OR called.prosecdef ) + -- ┌─────────────────────────────────────────────────────────────────┐ + -- │ Encrypted-domain footguns: blockers exist to RAISE, so they │ + -- │ have inverted inlinability requirements vs operator impls. │ + -- │ A LANGUAGE sql blocker can be elided by the planner; a STRICT │ + -- │ blocker returns NULL on NULL args. Both silently re-enable │ + -- │ operators the storage variant is supposed to block. │ + -- └─────────────────────────────────────────────────────────────────┘ + + UNION ALL + + SELECT + 'error', + 'blocker_language', + format('function %s', signature), + format( + 'Encrypted-domain blocker is `LANGUAGE %s`; must be `LANGUAGE plpgsql` so the RAISE is opaque to the planner. A `LANGUAGE sql` body is inlinable and may be elided when the result is provably unused, silently re-enabling the operator.', + lang) + FROM encrypted_domain_blockers + WHERE lang <> 'plpgsql' + + UNION ALL + + SELECT + 'error', + 'blocker_strict', + format('function %s', signature), + 'Encrypted-domain blocker is `STRICT`. PostgreSQL skips the body and returns NULL on a NULL argument, silently bypassing the RAISE. Remove `STRICT`.' + FROM encrypted_domain_blockers + WHERE isstrict + + -- ┌─────────────────────────────────────────────────────────────────┐ + -- │ Domain identity: an eql_v2_* domain must be defined directly │ + -- │ over jsonb. Operators resolve against the ultimate base type, │ + -- │ so domain-over-domain inherits jsonb's operator surface and not │ + -- │ the base domain's blockers. │ + -- └─────────────────────────────────────────────────────────────────┘ + + UNION ALL + + SELECT + 'error', + 'domain_over_domain', + format('domain %I.%I', dn.nspname, dt.typname), + format( + 'Domain `%s.%s` is derived from another eql_v2_* domain `%s.%s` rather than jsonb. Operators resolve against the ultimate base type, so the derived domain does not inherit the base domain''s operator surface and storage blockers do not engage. Define this domain directly over jsonb.', + dn.nspname, dt.typname, bn.nspname, bt.typname) + FROM pg_catalog.pg_type dt + JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace + JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype + JOIN pg_catalog.pg_namespace bn ON bn.oid = bt.typnamespace + WHERE dt.typtype = 'd' + AND dn.nspname = 'public' + AND dt.typname LIKE 'eql_v2\_%' + AND bt.typtype = 'd' + AND bt.typname LIKE 'eql_v2\_%' + + -- ┌─────────────────────────────────────────────────────────────────┐ + -- │ Domain opclass: an operator class declared FOR TYPE on an │ + -- │ eql_v2_* domain bypasses operator resolution at index time. │ + -- │ Use a functional index on the extractor instead. │ + -- └─────────────────────────────────────────────────────────────────┘ + + UNION ALL + + SELECT + 'error', + 'domain_opclass', + format('opclass %I.%I FOR TYPE %s.%s', cn.nspname, oc.opcname, tn.nspname, t.typname), + format( + 'Operator class `%s.%s` is declared FOR TYPE `%s.%s`, which is an eql_v2_* domain. Opclasses on domains bypass operator resolution. Use a functional index on the extractor (e.g. `eql_v2.eq_term(col)`, `eql_v2.ord_term(col)`) instead.', + cn.nspname, oc.opcname, tn.nspname, t.typname) + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_type t ON t.oid = oc.opcintype + JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace + JOIN pg_catalog.pg_namespace cn ON cn.oid = oc.opcnamespace + WHERE t.typtype = 'd' + AND tn.nspname = 'public' + AND t.typname LIKE 'eql_v2\_%' + ORDER BY 1, 2, 3; $$; diff --git a/tasks/pin_search_path.sql b/tasks/pin_search_path.sql index 8369589e..168a9478 100644 --- a/tasks/pin_search_path.sql +++ b/tasks/pin_search_path.sql @@ -215,13 +215,16 @@ BEGIN OR p.proargtypes[1] = (SELECT t.oid FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = 'pg_catalog' AND t.typname = 'int4'))) - -- XOR-aware equality term extractor on a ste_vec entry. Must - -- inline so `eql_v2.eq_term(col -> 'sel')` folds into the - -- calling query and matches a functional hash index built on - -- the same expression. - OR (p.pronargs = 1 - AND p.proname = 'eq_term' - AND p.proargtypes[0] = entry_oid) + -- Equality-term and order-term extractors — `eq_term` / `ord_term` + -- on a ste_vec entry and on the encrypted-domain family. Must + -- inline so `eql_v2.eq_term(col)` / `eql_v2.ord_term(col)` fold + -- into the calling query and match a functional index built on the + -- same expression. Name-only match (any arity-1 overload). The + -- encrypted-domain overloads are also covered by the identity + -- predicate's structural skip in the pin loop; these name-only + -- clauses are kept as belt-and-suspenders. + OR (p.pronargs = 1 AND p.proname = 'eq_term') + OR (p.pronargs = 1 AND p.proname = 'ord_term') -- Type-safe `@>` / `<@` overloads with typed needles -- (`stevec_query`, `ste_vec_entry`). Inline to the existing -- `ste_vec_contains` machinery — must stay unpinned to engage @@ -259,6 +262,44 @@ BEGIN WHERE c LIKE 'search_path=%' ) AND NOT (p.oid = ANY (coalesce(inline_critical_oids, '{}'::oid[]))) + -- Encrypted-domain family — structural skip (hybrid primary mechanism). + -- A new encrypted-domain type needs NO edit here: its inline-critical + -- extractors and comparison wrappers are recognised by the identity + -- predicate — LANGUAGE sql, IMMUTABLE, and taking at least one argument + -- typed as a jsonb-backed DOMAIN in `public` named `eql_v2_*`. The + -- predicate is proconfig-independent: the outer loop has already + -- excluded any function with a pinned `search_path`, so the only + -- functions reaching here are unpinned. This catches no core function: + -- `eql_v2_encrypted` is a composite type (not a domain), `ste_vec_entry` + -- is a domain in `eql_v2` (not `public`), and `hmac_256` is a domain + -- over `text` (not `jsonb`). + AND NOT ( + p.prolang = (SELECT l.oid FROM pg_catalog.pg_language l + WHERE l.lanname = 'sql') + AND p.provolatile = 'i' + AND EXISTS ( + SELECT 1 + FROM pg_catalog.unnest(p.proargtypes::oid[]) AS arg(typ) + JOIN pg_catalog.pg_type dt ON dt.oid = arg.typ + JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace + WHERE dt.typtype = 'd' + AND dn.nspname = 'public' + AND dt.typname LIKE 'eql_v2\_%' + AND dt.typbasetype = jsonb_oid + ) + ) + -- Encrypted-domain family — comment-marker fallback. Covers a + -- hand-written extension function that is inline-critical but takes no + -- domain argument (invisible to the identity predicate). The generator + -- does NOT emit this marker — every function it produces takes a domain + -- argument and is covered by the structural skip above. The marker is a + -- manual opt-in for hand-written extension functions only. + AND NOT EXISTS ( + SELECT 1 FROM pg_catalog.pg_description d + WHERE d.objoid = p.oid + AND d.classoid = 'pg_catalog.pg_proc'::regclass + AND d.description LIKE 'eql-inline-critical%' + ) LOOP -- oid::regprocedure renders as `schema.name(argtype, argtype)` and is a -- valid target for ALTER FUNCTION regardless of caller search_path. From 0715c80863e2c8d3f2706f48a8ae48aa1f44cf13 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 12:32:52 +1000 Subject: [PATCH 13/93] test(encrypted-domain): SQLx scalar matrix, fixtures & jsonb-surface guard One ScalarType impl plus an ordered_numeric_matrix! invocation generates the full SQLx suite for a scalar. Fixture values are single-sourced from the manifest. Coverage includes: - always-on cost-preference proof (~5000 rows, enable_seqscan ON) that asserts on the EXPLAIN node type (Index/Index Only/Bitmap Index Scan), not an index-name substring; eq_count pinned to == 1 so the derived <> count is load-bearing - a live-DB structural guard querying pg_operator that fails if any native jsonb operator is absent from the generator's blocked surface - ORDER BY NULLS FIRST/LAST coverage for ordered domains Part of PR #239. --- tasks/fixtures.toml | 6 +- tasks/test.sh | 12 +- tasks/test/splinter.sh | 32 +- tests/codegen/reference/int4/int4_values.rs | 28 + tests/sqlx/Cargo.lock | 7 + tests/sqlx/Cargo.toml | 8 + tests/sqlx/fixtures/FIXTURE_SCHEMA.md | 3 +- tests/sqlx/snapshots/int4_matrix_tests.txt | 211 ++ tests/sqlx/src/assertions.rs | 48 + tests/sqlx/src/fixtures/cipherstash.rs | 214 +- tests/sqlx/src/fixtures/driver.rs | 99 +- tests/sqlx/src/fixtures/eql_plaintext.rs | 11 +- tests/sqlx/src/fixtures/eql_v2_int4.rs | 35 +- tests/sqlx/src/fixtures/index_kind.rs | 59 + tests/sqlx/src/fixtures/int4_values.rs | 31 + tests/sqlx/src/fixtures/mod.rs | 13 +- tests/sqlx/src/fixtures/spec.rs | 43 +- tests/sqlx/src/helpers.rs | 13 + tests/sqlx/src/lib.rs | 15 +- tests/sqlx/src/matrix.rs | 2745 +++++++++++++++++ tests/sqlx/src/scalar_domains.rs | 308 ++ tests/sqlx/tests/aggregate_tests.rs | 18 +- tests/sqlx/tests/constraint_tests.rs | 127 +- tests/sqlx/tests/encrypted_domain.rs | 12 + .../encrypted_domain/family/inlinability.rs | 252 ++ .../family/jsonb_operator_surface.rs | 75 + .../sqlx/tests/encrypted_domain/family/mod.rs | 7 + .../encrypted_domain/family/mutations.rs | 428 +++ .../tests/encrypted_domain/family/support.rs | 329 ++ .../tests/encrypted_domain/scalars/int4.rs | 14 + .../tests/encrypted_domain/scalars/mod.rs | 4 + tests/sqlx/tests/eql_v2_int4_fixture_tests.rs | 40 +- tests/sqlx/tests/lint_tests.rs | 262 +- 33 files changed, 5191 insertions(+), 318 deletions(-) create mode 100644 tests/codegen/reference/int4/int4_values.rs create mode 100644 tests/sqlx/snapshots/int4_matrix_tests.txt create mode 100644 tests/sqlx/src/fixtures/index_kind.rs create mode 100644 tests/sqlx/src/fixtures/int4_values.rs create mode 100644 tests/sqlx/src/matrix.rs create mode 100644 tests/sqlx/src/scalar_domains.rs create mode 100644 tests/sqlx/tests/encrypted_domain.rs create mode 100644 tests/sqlx/tests/encrypted_domain/family/inlinability.rs create mode 100644 tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs create mode 100644 tests/sqlx/tests/encrypted_domain/family/mod.rs create mode 100644 tests/sqlx/tests/encrypted_domain/family/mutations.rs create mode 100644 tests/sqlx/tests/encrypted_domain/family/support.rs create mode 100644 tests/sqlx/tests/encrypted_domain/scalars/int4.rs create mode 100644 tests/sqlx/tests/encrypted_domain/scalars/mod.rs diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml index 808200cd..a9804f8b 100644 --- a/tasks/fixtures.toml +++ b/tasks/fixtures.toml @@ -15,8 +15,12 @@ description = "Generate a SQLx fixture script via cipherstash-client" dir = "{{config_root}}/tests/sqlx" run = """ fixture="{{arg(name="fixture")}}" +# Match the Rust `FixtureIdentifier` rule: `^[a-z][a-z0-9_]*$`. Reject +# empty, leading-digit, and any non-lowercase-alphanumeric-underscore +# input here so the failure mode is a clear shell error rather than a +# Rust panic during the cargo test invocation. case "$fixture" in - (*[!a-z0-9_]*|'') echo "Invalid fixture name: $fixture (expected [a-z0-9_]+)" >&2; exit 1 ;; + (''|[0-9]*|*[!a-z0-9_]*) echo "Invalid fixture name: $fixture (expected ^[a-z][a-z0-9_]*$)" >&2; exit 1 ;; esac cargo test --features fixture-gen --lib \ diff --git a/tasks/test.sh b/tasks/test.sh index 2e7988e9..806d6e99 100755 --- a/tasks/test.sh +++ b/tasks/test.sh @@ -22,17 +22,24 @@ echo "" echo "Building EQL..." mise run --output prefix --force build +# Run encrypted-domain codegen generator tests +echo "" +echo "==============================================" +echo "1/3: Running encrypted-domain codegen tests" +echo "==============================================" +mise run --output prefix test:codegen + # Run lints on sqlx tests echo "" echo "==============================================" -echo "1/2: Running linting checks on SQLx Rust tests" +echo "2/3: Running linting checks on SQLx Rust tests" echo "==============================================" mise run --output prefix test:lint # Run SQLx Rust tests echo "" echo "==============================================" -echo "2/2: Running SQLx Rust Tests" +echo "3/3: Running SQLx Rust Tests" echo "==============================================" mise run --output prefix test:sqlx @@ -42,6 +49,7 @@ echo "✅ ALL TESTS PASSED" echo "==============================================" echo "" echo "Summary:" +echo " ✓ Encrypted-domain codegen tests" echo " ✓ SQLx Rust lint checks" echo " ✓ SQLx Rust tests" echo "" diff --git a/tasks/test/splinter.sh b/tasks/test/splinter.sh index dae147d6..6c203233 100755 --- a/tasks/test/splinter.sh +++ b/tasks/test/splinter.sh @@ -9,6 +9,9 @@ set -euo pipefail +# Scope: only findings in EQL-owned schemas are gated. +EQL_OWNED_SCHEMAS="('eql_v2')" + # Pinned to splinter main as of 2026-04-27. Bump intentionally. SPLINTER_SHA="55db5b1f28e58d816f7d9136eed87eabcd95868d" SPLINTER_URL="https://raw.githubusercontent.com/supabase/splinter/${SPLINTER_SHA}/splinter.sql" @@ -81,12 +84,12 @@ function_search_path_mutable eql_v2 jsonb_contained_by function GIN-inlining: sa function_search_path_mutable eql_v2 ore_cllw function Consolidated ORE-CLLW extractor (U-006): inlinable SQL so the planner can fold `eql_v2.ore_cllw(col -> 'sel')` calls into the calling query. SET search_path would silently undo the inlining and prevent functional-index match through the extractor form. Two overloads: (jsonb), (eql_v2.ste_vec_entry). function_search_path_mutable eql_v2 has_ore_cllw function Consolidated ORE-CLLW presence check (U-006): inlinable SQL counterpart to `eql_v2.ore_cllw`. Same rationale as `ore_cllw` — must stay unpinned to inline into the calling query. Two overloads: (jsonb), (eql_v2.ste_vec_entry). function_search_path_mutable eql_v2 selector function STE-vec entry selector extractor (#219): typed (eql_v2.ste_vec_entry) overload, inlinable so the planner can fold `eql_v2.selector(col -> 'sel')` into the calling query. -function_search_path_mutable eql_v2 eq function Equality backing function for `eql_v2.ste_vec_entry × eql_v2.ste_vec_entry` (#219). Inlines to `hmac_256(a) = hmac_256(b)`; the `=` operator must reach the functional hash index on `eql_v2.hmac_256(col -> 'sel')` for bare-form field equality to engage Index Scan. -function_search_path_mutable eql_v2 neq function Inequality backing function for `eql_v2.ste_vec_entry`. Same rationale as `eq`. -function_search_path_mutable eql_v2 lt function Less-than backing function for `eql_v2.ste_vec_entry`. Inlines to `ore_cllw(a) < ore_cllw(b)`; must reach the functional btree opclass on `eql_v2.ore_cllw` for ordered field queries to engage Index Scan. -function_search_path_mutable eql_v2 lte function Less-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. -function_search_path_mutable eql_v2 gt function Greater-than backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. -function_search_path_mutable eql_v2 gte function Greater-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. +function_search_path_mutable eql_v2 eq function Equality backing function for `eql_v2.ste_vec_entry × eql_v2.ste_vec_entry` (#219). Inlines to `hmac_256(a) = hmac_256(b)`; the `=` operator must reach the functional hash index on `eql_v2.hmac_256(col -> 'sel')` for bare-form field equality to engage Index Scan. Splinter matches by name only, so this row also covers the converged eql_v2.eq wrappers on eql_v2_int4_eq / _ord / _ord_ore (PR #225). +function_search_path_mutable eql_v2 neq function Inequality backing function for `eql_v2.ste_vec_entry`. Same rationale as `eq`. Also covers the converged eql_v2.neq wrappers on eql_v2_int4_eq / _ord / _ord_ore (PR #225). +function_search_path_mutable eql_v2 lt function Less-than backing function for `eql_v2.ste_vec_entry`. Inlines to `ore_cllw(a) < ore_cllw(b)`; must reach the functional btree opclass on `eql_v2.ore_cllw` for ordered field queries to engage Index Scan. Splinter matches by name only, so this row also covers the converged eql_v2.lt wrappers on eql_v2_int4_ord / _ord_ore (PR #225). +function_search_path_mutable eql_v2 lte function Less-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. Also covers the converged eql_v2.lte wrappers on eql_v2_int4_ord / _ord_ore (PR #225). +function_search_path_mutable eql_v2 gt function Greater-than backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. Also covers the converged eql_v2.gt wrappers on eql_v2_int4_ord / _ord_ore (PR #225). +function_search_path_mutable eql_v2 gte function Greater-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. Also covers the converged eql_v2.gte wrappers on eql_v2_int4_ord / _ord_ore (PR #225). function_search_path_mutable eql_v2 ore_cllw_eq function Inner comparator for the `eql_v2.ore_cllw` type's `=` operator (#221). The outer same-type operators back the btree opclass on `eql_v2.ore_cllw`; the planner only carries the inlined form through to functional-index match if this inner function is also inlinable (no SET, IMMUTABLE). Mirrors ore_block_u64_8_256_eq. function_search_path_mutable eql_v2 ore_cllw_neq function Inner comparator for the `eql_v2.ore_cllw` type's `<>` operator (#221). Same rationale as `ore_cllw_eq`. function_search_path_mutable eql_v2 ore_cllw_lt function Inner comparator for the `eql_v2.ore_cllw` type's `<` operator (#221). Same rationale as `ore_cllw_eq`. @@ -94,10 +97,11 @@ function_search_path_mutable eql_v2 ore_cllw_lte function Inner comparator for t function_search_path_mutable eql_v2 ore_cllw_gt function Inner comparator for the `eql_v2.ore_cllw` type's `>` operator (#221). Same rationale as `ore_cllw_eq`. function_search_path_mutable eql_v2 ore_cllw_gte function Inner comparator for the `eql_v2.ore_cllw` type's `>=` operator (#221). Same rationale as `ore_cllw_eq`. function_search_path_mutable eql_v2 -> function Typed sv-element selector lookup (U-007): inlinable SQL so the planner can fold `col -> ''` into the calling query, preserving functional-index match for the chained recipes `WHERE col -> 'sel' = $1::ste_vec_entry` (via eq_term) and `ORDER BY eql_v2.ore_cllw(col -> 'sel')`. Three overloads: (enc, text), (enc, enc), (enc, int). -function_search_path_mutable eql_v2 eq_term function XOR-aware equality term extractor on a ste_vec entry (U-007): coalesces hm and oc as bytea. Must inline so `eql_v2.eq_term(col -> 'sel')` folds into the calling query and matches a functional hash index built on the same expression — same precedent as ore_cllw / hmac_256 extractors on ste_vec_entry. +function_search_path_mutable eql_v2 eq_term function XOR-aware equality term extractor on a ste_vec entry (U-007): coalesces hm and oc as bytea. Must inline so `eql_v2.eq_term(col -> 'sel')` folds into the calling query and matches a functional hash index built on the same expression — same precedent as ore_cllw / hmac_256 extractors on ste_vec_entry. Also covers the eql_v2_int4_eq eq_term overload (PR #225). function_search_path_mutable eql_v2 min function Aggregate (splinter labels these type=function): ALTER AGGREGATE has no SET configuration_parameter syntax, and ALTER ROUTINE/FUNCTION reject aggregates. The aggregate's SFUNC has a pinned search_path. function_search_path_mutable eql_v2 max function Aggregate: same as min. function_search_path_mutable eql_v2 grouped_value function Aggregate: same as min. +function_search_path_mutable eql_v2 ord_term function eql_v2_int4 ordered-variant index extractor: returns eql_v2.ore_block_u64_8_256 (carrying main DEFAULT btree opclass). Used inside the inlinable comparison wrappers and as the functional-index expression USING btree (eql_v2.ord_term(col)); must inline. SET search_path would disable SQL function inlining (see PostgreSQL inline_function). Covers both ord_term overloads (eql_v2_int4_ord_ore, eql_v2_int4_ord). ALLOW # Wrap splinter (a single bare SELECT expression) into a subquery we can @@ -106,6 +110,7 @@ ALLOW splinter_body="$(tail -n +2 "$splinter_sql" | sed 's/;[[:space:]]*$//')" # Pull all findings with their metadata, then split into allowlisted vs not. +# Scoped to EQL-owned schemas — see EQL_OWNED_SCHEMAS at the top of this file. "${PSQL[@]}" -At -F $'\t' --quiet < "$all_findings_tsv" BEGIN; SET LOCAL search_path = ''; @@ -117,6 +122,7 @@ SELECT coalesce(metadata->>'name', ''), coalesce(metadata->>'type', '') FROM (${splinter_body}) splinter +WHERE coalesce(metadata->>'schema', '') IN ${EQL_OWNED_SCHEMAS} ORDER BY level, name, detail; COMMIT; SQL @@ -155,11 +161,14 @@ awk -F'\t' \ # Touch in case awk didn't write either file (no findings at all). touch "$findings_tsv" "$allowlisted_tsv" +# Summary scoped to the same schemas the gate considers, so the count line +# matches what was actually checked. "${PSQL[@]}" -At -F $'\t' --quiet < "$summary_by_rule" BEGIN; SET LOCAL search_path = ''; SELECT level, name, count(*) FROM (${splinter_body}) splinter +WHERE coalesce(metadata->>'schema', '') IN ${EQL_OWNED_SCHEMAS} GROUP BY level, name ORDER BY CASE level WHEN 'ERROR' THEN 0 WHEN 'WARN' THEN 1 WHEN 'INFO' THEN 2 ELSE 3 END, @@ -175,7 +184,7 @@ warns="$(awk -F'\t' '$2 == "WARN"' "$findings_tsv" | wc -l | tr -d ' ')" infos="$(awk -F'\t' '$2 == "INFO"' "$findings_tsv" | wc -l | tr -d ' ')" echo -echo "Splinter findings: raw=${raw_total} (allowlisted=${allowlisted_total}, unallowlisted=${total} — ERROR=${errors} WARN=${warns} INFO=${infos})" +echo "Splinter findings: raw=${raw_total} (allowlisted=${allowlisted_total}, unmatched=${total} — ERROR=${errors} WARN=${warns} INFO=${infos})" echo printf 'LEVEL\tRULE\tCOUNT (raw)\n' cat "$summary_by_rule" @@ -188,7 +197,7 @@ fi if [[ "$total" -gt 0 ]]; then echo - echo "Unallowlisted findings:" + echo "Findings not covered by the allowlist:" awk -F'\t' '{ printf " - [%s] %s — %s\n", $2, $1, $3 }' "$findings_tsv" fi @@ -198,11 +207,12 @@ if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then echo "## Supabase splinter (database linter)" echo echo "Pinned to [\`splinter@${SPLINTER_SHA:0:12}\`](https://github.com/supabase/splinter/tree/${SPLINTER_SHA})." + echo "Scope: schemas owned by EQL (${EQL_OWNED_SCHEMAS//[\'()]/}). Findings outside these schemas are not reported." echo - echo "**${raw_total} raw findings** (allowlisted: ${allowlisted_total}, unallowlisted: ${total} — ERROR: ${errors}, WARN: ${warns}, INFO: ${infos})" + echo "**${raw_total} raw findings** (allowlisted: ${allowlisted_total}, unmatched: ${total} — ERROR: ${errors}, WARN: ${warns}, INFO: ${infos})" echo if [[ "$total" -gt 0 ]]; then - echo "### Unallowlisted findings (action required)" + echo "### Unmatched findings (action required)" echo echo "| Level | Rule | Detail |" echo "| --- | --- | --- |" diff --git a/tests/codegen/reference/int4/int4_values.rs b/tests/codegen/reference/int4/int4_values.rs new file mode 100644 index 00000000..3e6b1ec6 --- /dev/null +++ b/tests/codegen/reference/int4/int4_values.rs @@ -0,0 +1,28 @@ +// REFERENCE: hand-reviewed parity baseline for tasks/codegen/ — see ../README.md +//! Fixture plaintext values for the int4 encrypted-domain family. +//! +//! Generated from tasks/codegen/types/int4.toml `[fixture] values` — +//! the single source of truth shared by the fixture generator +//! (`fixtures::eql_v2_int4`) and the matrix oracle +//! (`ScalarType::FIXTURE_VALUES`). + +/// Distinct plaintext values present in the `eql_v2_int4` fixture. +pub const VALUES: &[i32] = &[ + i32::MIN, + -100, + -1, + 0, + 1, + 2, + 5, + 10, + 17, + 25, + 42, + 50, + 100, + 250, + 1000, + 9999, + i32::MAX, +]; diff --git a/tests/sqlx/Cargo.lock b/tests/sqlx/Cargo.lock index e39e030b..18dd84e0 100644 --- a/tests/sqlx/Cargo.lock +++ b/tests/sqlx/Cargo.lock @@ -1163,6 +1163,7 @@ dependencies = [ "cipherstash-client", "hex", "jsonschema", + "paste", "serde", "serde_json", "sqlx", @@ -2525,6 +2526,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index 153aebf3..a1851398 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -12,6 +12,7 @@ anyhow = "1" hex = "0.4" jsonschema = { version = "0.46.4", default-features = false } cipherstash-client = { version = "0.35", features = ["tokio"] } +paste = "1" [dev-dependencies] # None needed - tests live in this crate @@ -23,6 +24,13 @@ default = [] # it on push to main and on a nightly schedule. Run locally with: # mise run test:bench bench = [] +# Opt-in to the matrix's per-(variant, index) scale tests. Each builds +# ~5000 rows of filler plus a single selective pivot and asserts the +# planner *prefers* the functional index with `enable_seqscan` left on. +# The default index tests force seqscan off and only prove the index is +# *usable*. Off by default to keep `mise run test` fast; CI runs with +# `--features scale`. +scale = [] # Opt-in to compiling the fixture generators. Without this feature the # `#[cfg(feature = "fixture-gen")]` generator tests do not exist, so # `cargo test` and CI never see them. Generators need a live Postgres and diff --git a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md index 9ac804b7..8eab33ee 100644 --- a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md +++ b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md @@ -9,7 +9,6 @@ EQL Extension (via migrations) ├── encrypted_json.sql │ └── array_data.sql (extends `encrypted` table from encrypted_json) ├── match_data.sql - ├── aggregate_minmax_data.sql ├── config_tables.sql ├── constraint_tables.sql ├── encryptindex_tables.sql @@ -231,7 +230,7 @@ CREATE TABLE fixtures.eql_v2_int4 ( (`k = "ct"`, `v = 2`). **Used By:** -- eql_v2_int4_fixture_tests.rs (structural verification) +- `__scalar_matrix_fixture_shape!` arm in `tests/sqlx/src/matrix.rs` (structural verification, generated per type) - (#225) the `eql_v2_int4` domain operator tests, via per-query `payload` casts **Opt-in:** Not a migration — a SQLx fixture script. Each consuming test opts diff --git a/tests/sqlx/snapshots/int4_matrix_tests.txt b/tests/sqlx/snapshots/int4_matrix_tests.txt new file mode 100644 index 00000000..1fab59bd --- /dev/null +++ b/tests/sqlx/snapshots/int4_matrix_tests.txt @@ -0,0 +1,211 @@ +scalars::int4::matrix_int4_eq_aggregate_typecheck_max +scalars::int4::matrix_int4_eq_aggregate_typecheck_min +scalars::int4::matrix_int4_eq_contained_by_blocker +scalars::int4::matrix_int4_eq_contains_blocker +scalars::int4::matrix_int4_eq_count_distinct_extractor +scalars::int4::matrix_int4_eq_count_path_cast +scalars::int4::matrix_int4_eq_count_typed_column +scalars::int4::matrix_int4_eq_eq_pivot_max_correctness +scalars::int4::matrix_int4_eq_eq_pivot_max_cross_shape +scalars::int4::matrix_int4_eq_eq_pivot_min_correctness +scalars::int4::matrix_int4_eq_eq_pivot_min_cross_shape +scalars::int4::matrix_int4_eq_eq_pivot_zero_correctness +scalars::int4::matrix_int4_eq_eq_pivot_zero_cross_shape +scalars::int4::matrix_int4_eq_eq_supported_null +scalars::int4::matrix_int4_eq_gt_blocker +scalars::int4::matrix_int4_eq_gte_blocker +scalars::int4::matrix_int4_eq_index_engages_btree +scalars::int4::matrix_int4_eq_index_engages_hash +scalars::int4::matrix_int4_eq_lt_blocker +scalars::int4::matrix_int4_eq_lte_blocker +scalars::int4::matrix_int4_eq_native_absent_ops +scalars::int4::matrix_int4_eq_neq_pivot_max_correctness +scalars::int4::matrix_int4_eq_neq_pivot_max_cross_shape +scalars::int4::matrix_int4_eq_neq_pivot_min_correctness +scalars::int4::matrix_int4_eq_neq_pivot_min_cross_shape +scalars::int4::matrix_int4_eq_neq_pivot_zero_correctness +scalars::int4::matrix_int4_eq_neq_pivot_zero_cross_shape +scalars::int4::matrix_int4_eq_neq_supported_null +scalars::int4::matrix_int4_eq_path_op_blockers +scalars::int4::matrix_int4_eq_payload_check +scalars::int4::matrix_int4_eq_planner_metadata_eq +scalars::int4::matrix_int4_eq_sanity +scalars::int4::matrix_int4_eq_typed_column_blocker +scalars::int4::matrix_int4_fixture_shape +scalars::int4::matrix_int4_ord_aggregate_group_by_max +scalars::int4::matrix_int4_ord_aggregate_group_by_min +scalars::int4::matrix_int4_ord_aggregate_max +scalars::int4::matrix_int4_ord_aggregate_max_all_null +scalars::int4::matrix_int4_ord_aggregate_max_empty +scalars::int4::matrix_int4_ord_aggregate_max_mixed_null +scalars::int4::matrix_int4_ord_aggregate_min +scalars::int4::matrix_int4_ord_aggregate_min_all_null +scalars::int4::matrix_int4_ord_aggregate_min_empty +scalars::int4::matrix_int4_ord_aggregate_min_mixed_null +scalars::int4::matrix_int4_ord_aggregate_parallel_safe +scalars::int4::matrix_int4_ord_contained_by_blocker +scalars::int4::matrix_int4_ord_contains_blocker +scalars::int4::matrix_int4_ord_count_distinct_extractor +scalars::int4::matrix_int4_ord_count_path_cast +scalars::int4::matrix_int4_ord_count_typed_column +scalars::int4::matrix_int4_ord_eq_pivot_max_correctness +scalars::int4::matrix_int4_ord_eq_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_eq_pivot_min_correctness +scalars::int4::matrix_int4_ord_eq_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_eq_pivot_zero_correctness +scalars::int4::matrix_int4_ord_eq_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_eq_supported_null +scalars::int4::matrix_int4_ord_gt_pivot_max_correctness +scalars::int4::matrix_int4_ord_gt_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_gt_pivot_min_correctness +scalars::int4::matrix_int4_ord_gt_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_gt_pivot_zero_correctness +scalars::int4::matrix_int4_ord_gt_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_gt_supported_null +scalars::int4::matrix_int4_ord_gte_pivot_max_correctness +scalars::int4::matrix_int4_ord_gte_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_gte_pivot_min_correctness +scalars::int4::matrix_int4_ord_gte_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_gte_pivot_zero_correctness +scalars::int4::matrix_int4_ord_gte_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_gte_supported_null +scalars::int4::matrix_int4_ord_index_engages_btree +scalars::int4::matrix_int4_ord_lt_pivot_max_correctness +scalars::int4::matrix_int4_ord_lt_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_lt_pivot_min_correctness +scalars::int4::matrix_int4_ord_lt_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_lt_pivot_zero_correctness +scalars::int4::matrix_int4_ord_lt_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_lt_supported_null +scalars::int4::matrix_int4_ord_lte_pivot_max_correctness +scalars::int4::matrix_int4_ord_lte_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_lte_pivot_min_correctness +scalars::int4::matrix_int4_ord_lte_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_lte_pivot_zero_correctness +scalars::int4::matrix_int4_ord_lte_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_lte_supported_null +scalars::int4::matrix_int4_ord_native_absent_ops +scalars::int4::matrix_int4_ord_neq_pivot_max_correctness +scalars::int4::matrix_int4_ord_neq_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_neq_pivot_min_correctness +scalars::int4::matrix_int4_ord_neq_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_neq_pivot_zero_correctness +scalars::int4::matrix_int4_ord_neq_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_neq_supported_null +scalars::int4::matrix_int4_ord_ord_routes_through_ob +scalars::int4::matrix_int4_ord_order_by_asc_no_where +scalars::int4::matrix_int4_ord_order_by_asc_nulls_first +scalars::int4::matrix_int4_ord_order_by_asc_nulls_last +scalars::int4::matrix_int4_ord_order_by_asc_with_where +scalars::int4::matrix_int4_ord_order_by_desc_no_where +scalars::int4::matrix_int4_ord_order_by_desc_nulls_first +scalars::int4::matrix_int4_ord_order_by_desc_nulls_last +scalars::int4::matrix_int4_ord_order_by_desc_with_where +scalars::int4::matrix_int4_ord_order_by_using_gt_rejects +scalars::int4::matrix_int4_ord_order_by_using_gte_rejects +scalars::int4::matrix_int4_ord_order_by_using_lt_rejects +scalars::int4::matrix_int4_ord_order_by_using_lte_rejects +scalars::int4::matrix_int4_ord_ore_aggregate_group_by_max +scalars::int4::matrix_int4_ord_ore_aggregate_group_by_min +scalars::int4::matrix_int4_ord_ore_aggregate_max +scalars::int4::matrix_int4_ord_ore_aggregate_max_all_null +scalars::int4::matrix_int4_ord_ore_aggregate_max_empty +scalars::int4::matrix_int4_ord_ore_aggregate_max_mixed_null +scalars::int4::matrix_int4_ord_ore_aggregate_min +scalars::int4::matrix_int4_ord_ore_aggregate_min_all_null +scalars::int4::matrix_int4_ord_ore_aggregate_min_empty +scalars::int4::matrix_int4_ord_ore_aggregate_min_mixed_null +scalars::int4::matrix_int4_ord_ore_aggregate_parallel_safe +scalars::int4::matrix_int4_ord_ore_contained_by_blocker +scalars::int4::matrix_int4_ord_ore_contains_blocker +scalars::int4::matrix_int4_ord_ore_count_distinct_extractor +scalars::int4::matrix_int4_ord_ore_count_path_cast +scalars::int4::matrix_int4_ord_ore_count_typed_column +scalars::int4::matrix_int4_ord_ore_eq_pivot_max_correctness +scalars::int4::matrix_int4_ord_ore_eq_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_ore_eq_pivot_min_correctness +scalars::int4::matrix_int4_ord_ore_eq_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_correctness +scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_ore_eq_supported_null +scalars::int4::matrix_int4_ord_ore_gt_pivot_max_correctness +scalars::int4::matrix_int4_ord_ore_gt_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_ore_gt_pivot_min_correctness +scalars::int4::matrix_int4_ord_ore_gt_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_correctness +scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_ore_gt_supported_null +scalars::int4::matrix_int4_ord_ore_gte_pivot_max_correctness +scalars::int4::matrix_int4_ord_ore_gte_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_ore_gte_pivot_min_correctness +scalars::int4::matrix_int4_ord_ore_gte_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_correctness +scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_ore_gte_supported_null +scalars::int4::matrix_int4_ord_ore_index_engages_btree +scalars::int4::matrix_int4_ord_ore_lt_pivot_max_correctness +scalars::int4::matrix_int4_ord_ore_lt_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_ore_lt_pivot_min_correctness +scalars::int4::matrix_int4_ord_ore_lt_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_correctness +scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_ore_lt_supported_null +scalars::int4::matrix_int4_ord_ore_lte_pivot_max_correctness +scalars::int4::matrix_int4_ord_ore_lte_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_ore_lte_pivot_min_correctness +scalars::int4::matrix_int4_ord_ore_lte_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_correctness +scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_ore_lte_supported_null +scalars::int4::matrix_int4_ord_ore_native_absent_ops +scalars::int4::matrix_int4_ord_ore_neq_pivot_max_correctness +scalars::int4::matrix_int4_ord_ore_neq_pivot_max_cross_shape +scalars::int4::matrix_int4_ord_ore_neq_pivot_min_correctness +scalars::int4::matrix_int4_ord_ore_neq_pivot_min_cross_shape +scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_correctness +scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_cross_shape +scalars::int4::matrix_int4_ord_ore_neq_supported_null +scalars::int4::matrix_int4_ord_ore_ord_routes_through_ob +scalars::int4::matrix_int4_ord_ore_order_by_asc_no_where +scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_first +scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_last +scalars::int4::matrix_int4_ord_ore_order_by_asc_with_where +scalars::int4::matrix_int4_ord_ore_order_by_desc_no_where +scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_first +scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_last +scalars::int4::matrix_int4_ord_ore_order_by_desc_with_where +scalars::int4::matrix_int4_ord_ore_order_by_using_gt_rejects +scalars::int4::matrix_int4_ord_ore_order_by_using_gte_rejects +scalars::int4::matrix_int4_ord_ore_order_by_using_lt_rejects +scalars::int4::matrix_int4_ord_ore_order_by_using_lte_rejects +scalars::int4::matrix_int4_ord_ore_ore_injectivity +scalars::int4::matrix_int4_ord_ore_path_op_blockers +scalars::int4::matrix_int4_ord_ore_payload_check +scalars::int4::matrix_int4_ord_ore_planner_metadata_eq +scalars::int4::matrix_int4_ord_ore_planner_metadata_ord +scalars::int4::matrix_int4_ord_ore_sanity +scalars::int4::matrix_int4_ord_ore_typed_column_blocker +scalars::int4::matrix_int4_ord_path_op_blockers +scalars::int4::matrix_int4_ord_payload_check +scalars::int4::matrix_int4_ord_planner_metadata_eq +scalars::int4::matrix_int4_ord_planner_metadata_ord +scalars::int4::matrix_int4_ord_sanity +scalars::int4::matrix_int4_ord_scale_preference_default_btree +scalars::int4::matrix_int4_ord_typed_column_blocker +scalars::int4::matrix_int4_storage_aggregate_typecheck_max +scalars::int4::matrix_int4_storage_aggregate_typecheck_min +scalars::int4::matrix_int4_storage_contained_by_blocker +scalars::int4::matrix_int4_storage_contains_blocker +scalars::int4::matrix_int4_storage_count_path_cast +scalars::int4::matrix_int4_storage_count_typed_column +scalars::int4::matrix_int4_storage_eq_blocker +scalars::int4::matrix_int4_storage_gt_blocker +scalars::int4::matrix_int4_storage_gte_blocker +scalars::int4::matrix_int4_storage_lt_blocker +scalars::int4::matrix_int4_storage_lte_blocker +scalars::int4::matrix_int4_storage_native_absent_ops +scalars::int4::matrix_int4_storage_neq_blocker +scalars::int4::matrix_int4_storage_path_op_blockers +scalars::int4::matrix_int4_storage_payload_check +scalars::int4::matrix_int4_storage_sanity +scalars::int4::matrix_int4_storage_typed_column_blocker diff --git a/tests/sqlx/src/assertions.rs b/tests/sqlx/src/assertions.rs index 2fa7d4b6..538db6a1 100644 --- a/tests/sqlx/src/assertions.rs +++ b/tests/sqlx/src/assertions.rs @@ -148,3 +148,51 @@ impl<'a> QueryAssertion<'a> { ); } } + +/// Assert a `sqlx::Error` is a database error with the given SQLSTATE, +/// optionally with the given constraint name. Includes the actual error +/// in the panic message so a failing test prints *why* it failed, not +/// just *that* it failed — `assert!(result.is_err(), "…")` swallows the +/// underlying error so a constraint engagement against the wrong +/// constraint or SQLSTATE passes silently. +/// +/// # SQLSTATEs commonly seen on encrypted columns +/// - `23505` — unique_violation +/// - `23502` — not_null_violation +/// - `23514` — check_violation +/// - `23503` — foreign_key_violation +/// - `P0001` — raise_exception (PL/pgSQL `RAISE EXCEPTION`) +/// - `42704` — undefined_object (no operator class found, etc.) +/// +/// # Example +/// ```ignore +/// let result = sqlx::query(...).execute(&pool).await.unwrap_err(); +/// assert_db_error(&result, "23514", Some("encrypted_check_c_constrained")); +/// ``` +pub fn assert_db_error( + err: &sqlx::Error, + expected_sqlstate: &str, + expected_constraint: Option<&str>, +) { + let db_err = err + .as_database_error() + .unwrap_or_else(|| panic!("expected database error, got: {err:?}")); + + let code = db_err.code(); + assert_eq!( + code.as_deref(), + Some(expected_sqlstate), + "expected SQLSTATE {expected_sqlstate}, got {code:?} (message: {})", + db_err.message(), + ); + + if let Some(expected) = expected_constraint { + let constraint = db_err.constraint(); + assert_eq!( + constraint, + Some(expected), + "expected constraint name {expected:?}, got {constraint:?} (message: {})", + db_err.message(), + ); + } +} diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs index bb3a69bc..d7a8e8ca 100644 --- a/tests/sqlx/src/fixtures/cipherstash.rs +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -8,11 +8,12 @@ //! existed only because the Proxy was the encryption oracle. //! //! `cipherstash-client` 0.35 exposes the same surface natively. This module -//! owns the bootstrap — `cipher()` lazily builds a process-wide -//! `ScopedCipher` — and the per-value helper -//! `encrypt_store()` that wraps `eql::encrypt_eql` and returns the resulting -//! EQL ciphertext as a `serde_json::Value` ready to bind into a `jsonb` -//! column. +//! owns the bootstrap — `build_cipher()` builds a `ScopedCipher` — +//! and the batched helper `encrypt_store()` that wraps `eql::encrypt_eql` and +//! returns the resulting EQL ciphertexts as `serde_json::Value`s ready to bind +//! into a `jsonb` column. A fixture-generator process makes exactly one +//! `encrypt_store` call, so the cipher is built once per process by +//! construction — no static cache, no cross-runtime hazard. //! //! `column_config_for` is the bridge between the fixture spec's string-typed //! index names (`"unique"`, `"ore"`, …) and the typed `IndexType` enum @@ -32,70 +33,38 @@ use cipherstash_client::schema::column::{Index, IndexType}; use cipherstash_client::schema::{ColumnConfig, ColumnType}; use cipherstash_client::zerokms::{EnvKeyProvider, ZeroKMSBuilder}; use cipherstash_client::AutoStrategy; -use tokio::sync::OnceCell; use super::eql_plaintext::{Cast, EqlPlaintext}; -use super::validation::FixtureIdentifier; - -/// Process-wide `ScopedCipher`. Built on first use and held for the lifetime -/// of the test binary — `ScopedCipher` is documented as -/// "initialise once per process, hold an `Arc` for the process lifetime" -/// (see the upstream doc comment in `scoped_cipher.rs`). Re-initialising it -/// per call discards the warm reqwest pool and the cached auth token, and -/// makes the generator slower for no benefit. -static CIPHER: OnceCell>> = OnceCell::const_new(); - -/// Lazily initialise the process-wide cipher. On the first call this performs -/// the AutoStrategy detection, the ZeroKMS handshake, and the keyset load — -/// each subsequent call is an `Arc` clone. -/// -/// Errors surface as `anyhow::Error` with `.context(...)` naming the step -/// that failed (credential detection vs ZeroKMS connect vs keyset load). -pub async fn cipher() -> Result>> { - CIPHER - .get_or_try_init(|| async { - let zerokms = ZeroKMSBuilder::auto() - .context( - "building ZeroKMSBuilder via AutoStrategy::detect() — check \ - CS_CLIENT_ACCESS_KEY or CS_WORKSPACE_CRN env vars", - )? - .with_key_provider(EnvKeyProvider) - .build() - .await - .context( - "building ZeroKMS client — check CS_CLIENT_ID + CS_CLIENT_KEY \ - env vars (loaded by EnvKeyProvider)", - )?; - - let cipher = ScopedCipher::init_default(Arc::new(zerokms)) - .await - .context("initialising ScopedCipher for the default keyset")?; - - Ok::<_, anyhow::Error>(Arc::new(cipher)) - }) - .await - .cloned() +use super::index_kind::IndexKind; + +/// Build a fresh `ScopedCipher`. Performs `AutoStrategy::detect()`, the +/// ZeroKMS handshake, and the keyset load on every call — fine because +/// every fixture-generator process calls this exactly once via the +/// single batched `encrypt_store`. +async fn build_cipher() -> Result>> { + let zerokms = ZeroKMSBuilder::auto()? + .with_key_provider(EnvKeyProvider) + .build() + .await?; + + let cipher = ScopedCipher::init_default(Arc::new(zerokms)).await?; + + Ok(Arc::new(cipher)) } /// Build a `ColumnConfig` from the fixture spec's index list + cast. /// -/// The fixture spec uses EQL's string-typed index identifiers (`"unique"`, -/// `"ore"`, `"match"`, `"ste_vec"`); cipherstash-config uses the typed -/// `IndexType` enum. The mapping here is the single point of contact -/// between the two — extending fixture coverage to a new index means one -/// new arm here plus the corresponding `EqlPlaintext::CAST` constant. -/// -/// Unknown identifiers raise immediately with the offending name in the -/// error so a typo at spec-construction surfaces at run time (the -/// `FixtureIdentifier` newtype only proves the string is a valid SQL -/// identifier, not that it names a real index type). -pub fn column_config_for(spec_indexes: &[FixtureIdentifier], cast: Cast) -> Result { +/// `IndexKind` is a typed enum — every value is a real EQL index by +/// construction, so the mapping is total and `column_config_for` cannot +/// fail on an unknown index name. Extending fixture coverage to a new +/// index is one variant on `IndexKind` plus one arm here, both compile- +/// time checked. +pub fn column_config_for(spec_indexes: &[IndexKind], cast: Cast) -> Result { let column_type = cast_to_column_type(cast)?; let mut config = ColumnConfig::build("payload").casts_as(column_type); for ix in spec_indexes { - let index_type = index_type_for(ix.as_str())?; - config = config.add_index(Index::new(index_type)); + config = config.add_index(Index::new(index_type_for(*ix))); } Ok(config) @@ -126,19 +95,17 @@ fn cast_to_column_type(cast: Cast) -> Result { } } -/// Map the fixture spec's string-typed index identifier onto a typed -/// `IndexType`. Reuses the canonical constructors on `Index` -/// (`Index::new_unique`, etc.) so the defaults stay in sync with whatever -/// cipherstash-config considers the canonical shape for each index. -fn index_type_for(name: &str) -> Result { - match name { - "unique" => Ok(Index::new_unique().index_type), - "ore" => Ok(IndexType::Ore), - "match" => Ok(Index::new_match().index_type), - other => Err(anyhow!( - "unknown EQL index identifier {other:?} — supported: \ - unique, ore, match" - )), +/// Map an `IndexKind` variant onto cipherstash-config's `IndexType`. +/// Reuses the canonical constructors on `Index` (`Index::new_unique`, +/// etc.) so the defaults stay in sync with whatever cipherstash-config +/// considers the canonical shape for each index. Total — every variant +/// has an arm; adding a new variant is a compile error here, which is +/// the point. +fn index_type_for(kind: IndexKind) -> IndexType { + match kind { + IndexKind::Unique => Index::new_unique().index_type, + IndexKind::Ore => IndexType::Ore, + IndexKind::Match => Index::new_match().index_type, } } @@ -158,9 +125,10 @@ fn index_type_for(name: &str) -> Result { /// index filter — the same defaults Proxy uses for column-config-driven /// inserts. /// -/// An empty `values` slice short-circuits before `cipher()` so a caller -/// with nothing to encrypt does not pay the ZeroKMS bootstrap cost. -pub async fn encrypt_store( +/// An empty `values` slice short-circuits before `build_cipher()` so a +/// caller with nothing to encrypt does not pay the ZeroKMS bootstrap +/// cost. +pub async fn encrypt_store( table: &str, column: &str, values: &[T], @@ -170,7 +138,7 @@ pub async fn encrypt_store( return Ok(Vec::new()); } - let cipher = cipher().await?; + let cipher = build_cipher().await?; // `Identifier::new` does two `String` allocations per call — cheap // enough that constructing per-iteration is preferred over assuming @@ -228,13 +196,9 @@ pub async fn encrypt_store( mod tests { use super::*; - fn ident(s: &str) -> FixtureIdentifier { - FixtureIdentifier::try_from(s).unwrap() - } - #[test] fn column_config_for_int_with_unique_and_ore_builds_a_two_index_config() { - let indexes = [ident("unique"), ident("ore")]; + let indexes = [IndexKind::Unique, IndexKind::Ore]; let config = column_config_for(&indexes, Cast::INT).unwrap(); assert_eq!(config.name, "payload"); @@ -244,51 +208,34 @@ mod tests { assert!(config.indexes.iter().any(|i| i.is_ore())); } - #[test] - fn column_config_for_rejects_an_unknown_index_name() { - let indexes = [ident("bogus")]; - let err = column_config_for(&indexes, Cast::INT).unwrap_err(); - assert!( - format!("{err:#}").contains("unknown EQL index identifier"), - "error should name the unknown identifier: {err:#}" - ); - } - - #[test] - fn index_type_for_maps_known_names_to_their_canonical_index_type() { - // The named EQL index identifiers each round-trip into the - // `IndexType` cipherstash-config considers canonical for that - // name. Compared via the public `Index` surface (`is_unique`, - // `is_ore`, `is_match`) so the assertion does not depend on the - // shape of the non-exhaustive `IndexType` enum. - let unique = Index::new(index_type_for("unique").unwrap()); - assert!(unique.is_unique(), "'unique' must map to the unique index"); - - let ore = Index::new(index_type_for("ore").unwrap()); - assert!(ore.is_ore(), "'ore' must map to the ORE index"); - - let m = Index::new(index_type_for("match").unwrap()); - assert!(m.is_match(), "'match' must map to the match (bloom) index"); - } + // Note: the "unknown index name rejected at runtime" test is gone — + // `IndexKind` is a closed enum, so a typo is a compile error. #[test] - fn index_type_for_rejects_an_unknown_index_name() { - let err = index_type_for("bogus").unwrap_err(); - let msg = format!("{err:#}"); - assert!( - msg.contains("unknown EQL index identifier") && msg.contains("bogus"), - "error should name the offending identifier: {msg}" - ); + fn index_type_for_maps_every_variant_to_its_canonical_index_type() { + // Each `IndexKind` variant round-trips into the `IndexType` + // cipherstash-config considers canonical for that name. Compared + // via the public `Index` surface (`is_unique`, `is_ore`, + // `is_match`) so the assertion does not depend on the shape of + // the non-exhaustive `IndexType` enum. + let unique = Index::new(index_type_for(IndexKind::Unique)); + assert!(unique.is_unique(), "Unique must map to the unique index"); + + let ore = Index::new(index_type_for(IndexKind::Ore)); + assert!(ore.is_ore(), "Ore must map to the ORE index"); + + let m = Index::new(index_type_for(IndexKind::Match)); + assert!(m.is_match(), "Match must map to the match (bloom) index"); } #[tokio::test] - async fn encrypt_store_with_empty_values_returns_an_empty_vec_without_calling_cipher() { - // Empty input short-circuits before `cipher()` so a caller with - // nothing to encrypt does not pay the ZeroKMS bootstrap cost. + async fn encrypt_store_with_empty_values_returns_an_empty_vec_without_building_cipher() { + // Empty input short-circuits before `build_cipher()` so a caller + // with nothing to encrypt does not pay the ZeroKMS bootstrap cost. // Running this test under `cargo test` (no `fixture-gen` feature, - // no CS_* env vars) proves the short-circuit: if `cipher()` were - // reached, the missing credentials would surface as an error. - let config = column_config_for(&[ident("unique")], Cast::INT).unwrap(); + // no CS_* env vars) proves the short-circuit: if `build_cipher()` + // were reached, the missing credentials would surface as an error. + let config = column_config_for(&[IndexKind::Unique], Cast::INT).unwrap(); let out = encrypt_store::("t", "c", &[], &config).await.unwrap(); assert!(out.is_empty(), "empty input must yield empty output"); } @@ -326,20 +273,11 @@ mod tests { /// by `fixture-gen` so default `cargo test` runs do not require /// `CS_CLIENT_ACCESS_KEY` / `CS_WORKSPACE_CRN`. Each test is /// `#[ignore]` so it only runs under -/// `cargo test --features fixture-gen -- --ignored --test-threads=1`, -/// mirroring the `generate` test in `eql_v2_int4.rs`. -/// -/// **Must run serially (`--test-threads=1`).** The process-wide -/// `CIPHER` `OnceCell` caches a `ScopedCipher` whose reqwest connection -/// pool is bound to the tokio runtime that initialised it. Each -/// `#[tokio::test]` builds its own runtime, so under parallel -/// execution the second test's calls go through a pool whose -/// dispatcher has been dropped — failing with -/// "SendRequest: dispatch task is gone". Production fixture runs (one -/// `#[tokio::main]` runtime) are unaffected. +/// `cargo test --features fixture-gen -- --ignored`, mirroring the +/// `generate` test in `eql_v2_int4.rs`. /// /// These complement the structural fixture-tests in -/// `tests/sqlx/tests/eql_v2_int4_fixture_tests.rs`: those assert over the +/// the `__scalar_matrix_fixture_shape!` arm in `tests/sqlx/src/matrix.rs`: those assert over the /// regenerated SQL file end-to-end; these isolate the /// `encrypt_store` call so an SDK API drift surfaces here before the /// whole fixture pipeline fails. @@ -348,19 +286,15 @@ mod live_tests { use super::*; use serde_json::Value; - fn ident(s: &str) -> FixtureIdentifier { - FixtureIdentifier::try_from(s).unwrap() - } - - /// Config used by every live test — `unique` drives the `hm` term, - /// `ore` drives the `ob` term, so the returned payloads carry both. + /// Config used by every live test — `Unique` drives the `hm` term, + /// `Ore` drives the `ob` term, so the returned payloads carry both. fn int_config_with_hm_and_ob() -> ColumnConfig { - column_config_for(&[ident("unique"), ident("ore")], Cast::INT).unwrap() + column_config_for(&[IndexKind::Unique, IndexKind::Ore], Cast::INT).unwrap() } /// Assert the well-formed Store shape: the payload is a JSON object /// with non-null `v`, `c`, `hm`, `ob`, and `i` fields. Mirrors the - /// per-key assertions in `eql_v2_int4_fixture_tests.rs`. + /// per-key assertions in `tests/encrypted_domain/scalars/int4/fixture.rs`. fn assert_store_shape(payload: &Value) { let obj = payload.as_object().expect("payload must be a JSON object"); for key in ["v", "c", "hm", "ob", "i"] { diff --git a/tests/sqlx/src/fixtures/driver.rs b/tests/sqlx/src/fixtures/driver.rs index 90060a5f..1d6c3f45 100644 --- a/tests/sqlx/src/fixtures/driver.rs +++ b/tests/sqlx/src/fixtures/driver.rs @@ -110,11 +110,15 @@ where /// Generate and write `tests/sqlx/fixtures/.sql`. /// /// The production entry point. Parses the env-driven `DriverConfig` - /// once, opens a direct Postgres connection, then delegates the - /// schema + teardown orchestration to `run_with`, supplying - /// `insert_direct` as the closure. After `run_with` returns the - /// rendered INSERT lines, this method composes them with + /// once, opens a single direct Postgres connection, runs the + /// schema/insert/render/drop pipeline inline against that connection + /// (no second connection needed — encryption happens in Rust via + /// cipherstash-client), then composes the rendered INSERT lines with /// `fixture_script_preamble` and writes the committed script to disk. + /// + /// The pipeline mirrors the teardown contract in `run_with`: drop the + /// working table unconditionally once it has been created, and + /// propagate failures in causal order (insert error first). pub async fn run(&self) -> Result<()> { let config = DriverConfig::from_env()?; @@ -125,21 +129,42 @@ where .await .context("connecting to Postgres (direct)")?; - // Second direct connection for the inserter closure. `run_with` - // borrows the first connection mutably for the duration of the - // pipeline, so the inserter must hold its own. - let mut inserter_conn = config - .direct - .clone() - .connect() + self.check_complete().context("invalid FixtureSpec")?; + + sqlx::raw_sql(&self.working_schema_sql()) + .execute(&mut direct) .await - .context("connecting to Postgres (direct inserter)")?; + .context("applying working-table schema")?; - let lines = self - .run_with(&mut direct, || self.insert_direct(&mut inserter_conn)) - .await?; + // Insert directly on the same connection used for schema/render/drop. + // The earlier two-connection design existed because `run_with` borrows + // `direct` mutably across the closure call; production has no such + // need — `insert_direct` is the only caller of cipherstash-client and + // can hold the same `&mut direct` for its duration. + let insert_result = self.insert_direct(&mut direct).await; + let render_result = if insert_result.is_ok() { + sqlx::query(&self.render_rows_sql()) + .fetch_all(&mut direct) + .await + .context("rendering fixture rows") + } else { + Ok(Vec::new()) + }; + + let working = self.working_table(); + let drop_result = sqlx::raw_sql(&format!("DROP TABLE IF EXISTS public.{working};")) + .execute(&mut direct) + .await; + + insert_result?; + let rows = render_result?; + drop_result.context("dropping the working table")?; + + let lines: Vec = rows + .iter() + .map(|r| r.try_get::(0).context("reading rendered INSERT")) + .collect::>()?; - let _ = inserter_conn.close().await; let _ = direct.close().await; let mut script = self.fixture_script_preamble(); @@ -190,29 +215,34 @@ where Ok(()) } - /// Orchestrates the schema-apply / insert / render / teardown pipeline - /// against a caller-supplied `direct` connection, with the insert step - /// pluggable via `insert_rows`. The pipeline is: + /// **Test seam** for the schema-apply / insert / render / teardown + /// pipeline. Production code uses `run()`, which inlines the same + /// pipeline on a single connection. This entry point exists so tests + /// can plug in arbitrary insert behavior (hand-crafted JSONB, + /// deliberate failures) without going through cipherstash-client. + /// Gated behind `#[cfg(test)]` so it is never linked into a + /// production build. /// + /// Pipeline: /// 1. Check the spec is complete. /// 2. Apply `working_schema_sql` on `direct`. After this succeeds the /// `public._fixture_` table exists and MUST be dropped before /// return, whatever happens next. - /// 3. Run `insert_rows()`. Its result is captured (not `?`-propagated) - /// so the drop in step 5 always runs. + /// 3. Run `insert_rows()`. Its result is captured (not + /// `?`-propagated) so the drop in step 5 always runs. /// 4. If the inserter succeeded, render the committed rows via /// `render_rows_sql` on `direct`. Skipped on inserter error. /// 5. Drop the working table on `direct` unconditionally. /// 6. Propagate failures in causal order: inserter error first /// (root cause), then render, then drop. /// - /// `run()` calls this with `insert_direct`. Tests call it with - /// closures that insert hand-crafted JSONB payloads directly (no - /// cipherstash-client required), or with closures that return `Err` - /// to exercise the teardown contract. + /// The closure has no `&mut PgConnection` parameter because the + /// caller (a test) closes over its own pool / connection — the + /// production path's single-connection invariant is enforced inside + /// `run`, not here. /// - /// Private by design: this is a test seam, not a public API. Other - /// fixtures must go through `run`. + /// Private by design: this is a test seam, not a public API. + #[cfg(test)] async fn run_with( &self, direct: &mut PgConnection, @@ -263,10 +293,11 @@ mod tests { /// A small int4 spec for driver tests. Three values keeps the test fast; /// the driver's orchestration is independent of value count. fn small_spec(name: &'static str) -> FixtureSpec<'static, i32> { + use super::super::index_kind::IndexKind; const VALUES: &[i32] = &[-1, 1, 42]; FixtureSpec::new(name) - .with_index("unique") - .with_index("ore") + .with_index(IndexKind::Unique) + .with_index(IndexKind::Ore) .with_column_type("jsonb") .with_values(VALUES) } @@ -280,9 +311,13 @@ mod tests { let mut conn = pool.acquire().await?; + // `run_with` is the test seam; it borrows `&mut conn` for the + // schema/render/drop steps, so a test that wants to insert via + // sqlx must close over its own connection — exactly the + // two-connection shape production (`run`) was rewritten to + // avoid. Tests pay this cost so production doesn't have to. let lines = spec - .run_with(&mut *conn, move || async move { - // Working table should exist while the closure runs. + .run_with(&mut conn, move || async move { let mut c = pool_for_closure.acquire().await?; let exists: Option = sqlx::query_scalar(&format!( "SELECT to_regclass('public.{working_for_closure}')::text" @@ -344,7 +379,7 @@ mod tests { let mut conn = pool.acquire().await?; let result = spec - .run_with(&mut *conn, || async { + .run_with(&mut conn, || async { anyhow::bail!("forced failure for test") }) .await; diff --git a/tests/sqlx/src/fixtures/eql_plaintext.rs b/tests/sqlx/src/fixtures/eql_plaintext.rs index 4cdc807d..0db9482a 100644 --- a/tests/sqlx/src/fixtures/eql_plaintext.rs +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -81,15 +81,18 @@ pub trait EqlPlaintext: sealed::Sealed { /// EQL encryption pipeline consumes. The mapping is total — every /// `EqlPlaintext` impl maps cleanly onto a `Plaintext::*(Some(_))` /// variant. - fn to_plaintext(self) -> Plaintext; + /// + /// Takes `&self` so future non-`Copy` plaintexts (`String`, + /// `BigDecimal`, `Vec`) implement without unnecessary clones. + fn to_plaintext(&self) -> Plaintext; } impl EqlPlaintext for i32 { const CAST: Cast = Cast::INT; const PLAINTEXT_SQL_TYPE: PlaintextSqlType = PlaintextSqlType::INTEGER; - fn to_plaintext(self) -> Plaintext { - Plaintext::Int(Some(self)) + fn to_plaintext(&self) -> Plaintext { + Plaintext::Int(Some(*self)) } } @@ -114,7 +117,7 @@ mod tests { fn i32_to_plaintext_wraps_in_int_variant() { // The trait must lift the raw i32 into the EQL pipeline's Plaintext // enum so the fixture driver can hand it to `eql::encrypt_eql`. - match (42_i32).to_plaintext() { + match 42_i32.to_plaintext() { Plaintext::Int(Some(value)) => assert_eq!(value, 42), other => panic!("expected Plaintext::Int(Some(42)), got {other:?}"), } diff --git a/tests/sqlx/src/fixtures/eql_v2_int4.rs b/tests/sqlx/src/fixtures/eql_v2_int4.rs index f32e93a5..316eac48 100644 --- a/tests/sqlx/src/fixtures/eql_v2_int4.rs +++ b/tests/sqlx/src/fixtures/eql_v2_int4.rs @@ -1,22 +1,21 @@ //! The `eql_v2_int4` fixture — the framework's reference example and proof. //! -//! 14 integers spanning a negative boundary and small/medium/large/extreme -//! magnitudes. The generated `tests/sqlx/fixtures/eql_v2_int4.sql` is a plain -//! `jsonb`-payload table with no EQL dependency; #225 layers the `eql_v2_int4` -//! domain on top by casting `payload` per query. - +//! 17 integers spanning a negative boundary, the i32 signed extremes +//! (`MIN`/`MAX`), zero, and small/medium/large magnitudes. The generated +//! `tests/sqlx/fixtures/eql_v2_int4.sql` is a plain `jsonb`-payload table with +//! no EQL dependency; #225 layers the `eql_v2_int4` domain on top by casting +//! `payload` per query. + +use super::index_kind::IndexKind; +use super::int4_values::VALUES; use super::spec::FixtureSpec; -/// 14 values: a negative boundary plus small/medium/large/extreme magnitudes, -/// chosen so range pivots produce distinct cardinalities. -const VALUES: &[i32] = &[-100, -1, 1, 2, 5, 10, 17, 25, 42, 50, 100, 250, 1000, 9999]; - -/// The complete fixture definition. `.with_index("unique")` drives `=` / `<>` -/// (HMAC); `.with_index("ore")` drives `<` `<=` `>` `>=` (ORE block terms). +/// The complete fixture definition. `IndexKind::Unique` drives `=` / `<>` +/// (HMAC); `IndexKind::Ore` drives `<` `<=` `>` `>=` (ORE block terms). pub fn spec() -> FixtureSpec<'static, i32> { FixtureSpec::new("eql_v2_int4") - .with_index("unique") - .with_index("ore") + .with_index(IndexKind::Unique) + .with_index(IndexKind::Ore) .with_column_type("jsonb") .with_values(VALUES) } @@ -40,8 +39,14 @@ mod tests { } #[test] - fn spec_has_14_values() { - assert_eq!(spec().values().len(), 14); + fn spec_includes_signed_extremes() { + // i32::MIN / MAX exercise ORE block-encoding sign-bit edges + // that the smaller earlier list did not cover. + let spec = spec(); + let values = spec.values(); + assert!(values.contains(&i32::MIN), "spec must include i32::MIN"); + assert!(values.contains(&i32::MAX), "spec must include i32::MAX"); + assert!(values.contains(&0), "spec must include 0"); } #[test] diff --git a/tests/sqlx/src/fixtures/index_kind.rs b/tests/sqlx/src/fixtures/index_kind.rs new file mode 100644 index 00000000..f1633a03 --- /dev/null +++ b/tests/sqlx/src/fixtures/index_kind.rs @@ -0,0 +1,59 @@ +//! `IndexKind` — the typed EQL search-index identifier. +//! +//! Replaces the `&str` / `FixtureIdentifier`-validated string at the +//! spec/driver boundary. `FixtureIdentifier` proves the value matches +//! `^[a-z][a-z0-9_]*$`; it does NOT prove the name is a real index type. +//! `IndexKind` proves both, at compile time. A typo at spec construction +//! (`.with_index(IndexKind::Uniqu)`) is a compile error rather than a +//! runtime "unknown EQL index identifier" panic deep in the driver. + +use std::fmt; + +/// One of the EQL search-index identifiers cipherstash-config recognises. +/// Construction is through the variants — by construction every value is +/// in the allowlist. The wire-form `&str` (used in cipherstash-config and +/// the SQL renderers) is available via `as_str` / `Display`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum IndexKind { + /// `unique` — drives `=` / `<>` via HMAC. + Unique, + /// `ore` — drives `<` / `<=` / `>` / `>=` via ORE block terms. + Ore, + /// `match` — drives `LIKE` / `ILIKE` via the bloom filter. + Match, +} + +impl IndexKind { + pub fn as_str(self) -> &'static str { + match self { + IndexKind::Unique => "unique", + IndexKind::Ore => "ore", + IndexKind::Match => "match", + } + } +} + +impl fmt::Display for IndexKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn renders_as_the_eql_wire_form_string() { + assert_eq!(IndexKind::Unique.as_str(), "unique"); + assert_eq!(IndexKind::Ore.as_str(), "ore"); + assert_eq!(IndexKind::Match.as_str(), "match"); + } + + #[test] + fn display_matches_as_str() { + assert_eq!(format!("{}", IndexKind::Unique), "unique"); + assert_eq!(format!("{}", IndexKind::Ore), "ore"); + assert_eq!(format!("{}", IndexKind::Match), "match"); + } +} diff --git a/tests/sqlx/src/fixtures/int4_values.rs b/tests/sqlx/src/fixtures/int4_values.rs new file mode 100644 index 00000000..92f6491d --- /dev/null +++ b/tests/sqlx/src/fixtures/int4_values.rs @@ -0,0 +1,31 @@ +// AUTO-GENERATED — DO NOT EDIT. +// Regenerated by `mise run build` (or `mise run codegen:domain `). +// Source of truth: tasks/codegen/types/.toml `[fixture] values`. +// This file IS committed and verified in CI (git diff --exit-code). +//! Fixture plaintext values for the int4 encrypted-domain family. +//! +//! Generated from tasks/codegen/types/int4.toml `[fixture] values` — +//! the single source of truth shared by the fixture generator +//! (`fixtures::eql_v2_int4`) and the matrix oracle +//! (`ScalarType::FIXTURE_VALUES`). + +/// Distinct plaintext values present in the `eql_v2_int4` fixture. +pub const VALUES: &[i32] = &[ + i32::MIN, + -100, + -1, + 0, + 1, + 2, + 5, + 10, + 17, + 25, + 42, + 50, + 100, + 250, + 1000, + 9999, + i32::MAX, +]; diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index d2c90c3f..416a3b02 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -1,8 +1,9 @@ //! Type-checked fixture generation framework. //! //! A fixture is one Rust file under `src/fixtures/` declaring a `FixtureSpec`. -//! `FixtureSpec::run()` generates the committed SQLx fixture script -//! `tests/sqlx/fixtures/.sql`. +//! `FixtureSpec::run()` generates the SQLx fixture script +//! `tests/sqlx/fixtures/.sql` (gitignored — regenerated on every +//! `mise run test:sqlx`). pub mod validation; @@ -10,6 +11,10 @@ pub mod eql_plaintext; pub use eql_plaintext::EqlPlaintext; +pub mod index_kind; + +pub use index_kind::IndexKind; + pub mod spec; pub use spec::FixtureSpec; @@ -18,4 +23,8 @@ pub mod cipherstash; pub mod driver; +/// Generated from tasks/codegen/types/int4.toml `[fixture] values`. +/// Committed and verified by CI; never hand-edit (`mise run codegen:domain int4`). +pub mod int4_values; + pub mod eql_v2_int4; diff --git a/tests/sqlx/src/fixtures/spec.rs b/tests/sqlx/src/fixtures/spec.rs index 35e82615..5f9b952d 100644 --- a/tests/sqlx/src/fixtures/spec.rs +++ b/tests/sqlx/src/fixtures/spec.rs @@ -21,12 +21,13 @@ //! is finished. use super::eql_plaintext::EqlPlaintext; +use super::index_kind::IndexKind; use super::validation::{ColumnType, FixtureIdentifier}; /// A fully specified fixture, ready to `.run()`. pub struct FixtureSpec<'a, T> { name: FixtureIdentifier, - indexes: Vec, + indexes: Vec, column_type: ColumnType, values: &'a [T], } @@ -51,14 +52,10 @@ impl<'a, T> FixtureSpec<'a, T> { } } - /// Add a search index (`"unique"`, `"ore"`, ...). Chainable. - /// - /// # Panics - /// Panics if `index_name` is not a valid identifier. - pub fn with_index(mut self, index_name: &str) -> Self { - let id = - FixtureIdentifier::try_from(index_name).unwrap_or_else(|e| panic!("index name: {e}")); - self.indexes.push(id); + /// Add a search index. `IndexKind` is a closed enum — a typo at the + /// call site is a compile error rather than a runtime panic. + pub fn with_index(mut self, kind: IndexKind) -> Self { + self.indexes.push(kind); self } @@ -90,7 +87,7 @@ impl<'a, T> FixtureSpec<'a, T> { self.name.as_str() } - pub fn indexes(&self) -> &[FixtureIdentifier] { + pub fn indexes(&self) -> &[IndexKind] { &self.indexes } @@ -140,7 +137,7 @@ impl<'a, T> FixtureSpec<'a, T> { CREATE TABLE public.{working} (\n \ id BIGINT PRIMARY KEY,\n \ plaintext {plaintext_type} NOT NULL,\n \ - payload jsonb\n);\n", + payload jsonb NOT NULL\n);\n", plaintext_type = T::PLAINTEXT_SQL_TYPE, ) } @@ -216,8 +213,8 @@ mod tests { fn int4_spec() -> FixtureSpec<'static, i32> { const VALUES: &[i32] = &[-1, 1, 42]; FixtureSpec::new("eql_v2_int4") - .with_index("unique") - .with_index("ore") + .with_index(IndexKind::Unique) + .with_index(IndexKind::Ore) .with_column_type("jsonb") .with_values(VALUES) } @@ -233,14 +230,15 @@ mod tests { #[test] fn records_indexes_in_order() { let s = int4_spec(); - let names: Vec<&str> = s.indexes().iter().map(FixtureIdentifier::as_str).collect(); - assert_eq!(names, vec!["unique", "ore"]); + assert_eq!(s.indexes(), &[IndexKind::Unique, IndexKind::Ore]); } #[test] fn column_type_defaults_to_jsonb() { const V: &[i32] = &[1]; - let s = FixtureSpec::new("x").with_index("unique").with_values(V); + let s = FixtureSpec::new("x") + .with_index(IndexKind::Unique) + .with_values(V); assert_eq!(s.column_type().as_str(), "jsonb"); } @@ -263,12 +261,9 @@ mod tests { let _ = FixtureSpec::<'static, i32>::new("x").with_column_type("text"); } - #[test] - #[should_panic(expected = "is not a valid identifier")] - fn validation_rejects_a_bad_index_name() { - // A bad index name panics in `.with_index()`. - let _ = FixtureSpec::<'static, i32>::new("x").with_index("BAD IX"); - } + // Note: `with_index` formerly panicked on a malformed identifier (a + // `FixtureIdentifier::try_from` failure). The typed `IndexKind` enum + // makes that case unrepresentable — a typo is now a compile error. #[test] fn completeness_rejects_a_spec_with_no_indexes() { @@ -280,7 +275,9 @@ mod tests { #[test] fn completeness_rejects_a_spec_with_no_values() { const V: &[i32] = &[]; - let s = FixtureSpec::new("x").with_index("unique").with_values(V); + let s = FixtureSpec::new("x") + .with_index(IndexKind::Unique) + .with_values(V); assert!(s.check_complete().is_err()); } diff --git a/tests/sqlx/src/helpers.rs b/tests/sqlx/src/helpers.rs index 6bf5fc4c..2e111e55 100644 --- a/tests/sqlx/src/helpers.rs +++ b/tests/sqlx/src/helpers.rs @@ -6,6 +6,19 @@ use anyhow::{Context, Result}; use serde_json; use sqlx::{PgPool, Row}; +/// Sentinel payload that satisfies every encrypted-domain CHECK in the +/// `eql_v2_{,_eq,_ord,_ord_ore}` family. Carries the EQL envelope +/// (`v`, `i`, `c`) plus *both* term keys (`hm`, `ob`) so one bind value +/// works for any variant's cast. +/// +/// Used by blocker / null-result tests where the payload is bound but +/// never decrypted — the blocker raises (or the STRICT wrapper +/// short-circuits) before the term values matter. **Not a representative +/// payload.** Real encrypted payloads come from the fixture +/// (Proxy-encrypted). +pub const PLACEHOLDER_PAYLOAD: &str = + r#"{"v":2,"i":{"t":"t","c":"c"},"c":"sample","hm":"sample","ob":["00"]}"#; + /// Fetch ORE encrypted value from pre-seeded ore table /// /// The ore table is created by migration `002_install_ore_data.sql` diff --git a/tests/sqlx/src/lib.rs b/tests/sqlx/src/lib.rs index 2f915e37..c37c176e 100644 --- a/tests/sqlx/src/lib.rs +++ b/tests/sqlx/src/lib.rs @@ -8,9 +8,17 @@ pub mod assertions; pub mod fixtures; pub mod helpers; pub mod index_types; +pub mod matrix; +pub mod scalar_domains; pub mod selectors; -pub use assertions::QueryAssertion; +// Re-export `paste` under a stable path so the `scalar_domain_matrix!` macro +// can refer to `$crate::paste::paste!` without requiring callers to depend on +// the `paste` crate directly. +#[doc(hidden)] +pub use paste; + +pub use assertions::{assert_db_error, QueryAssertion}; pub use helpers::{ analyze_table, assert_no_seq_scan, assert_sequential_ids, assert_uses_index, assert_uses_seq_scan, create_jsonb_gin_index, ensure_pg_stat_statements, explain_analyze_avg, @@ -19,8 +27,13 @@ pub use helpers::{ get_ore_text_encrypted_as_jsonb, get_ste_vec_encrypted, get_ste_vec_encrypted_pair, get_ste_vec_selector_term, get_ste_vec_sv_element, get_ste_vec_term_by_id, read_pg_stat_statements, reset_pg_stat_statements, ExplainStats, PgStatEntry, + PLACEHOLDER_PAYLOAD, }; pub use index_types as IndexTypes; +pub use scalar_domains::{ + assert_null, assert_raises, assert_scalar_plaintexts, blocker_msg, commute_op, + fetch_fixture_payload, sql_string_literal, ScalarDomainSpec, ScalarType, Variant, +}; pub use selectors::Selectors; /// Reset pg_stat_user_functions tracking before tests diff --git a/tests/sqlx/src/matrix.rs b/tests/sqlx/src/matrix.rs new file mode 100644 index 00000000..0d277af9 --- /dev/null +++ b/tests/sqlx/src/matrix.rs @@ -0,0 +1,2745 @@ +//! Type-generic test matrix for encrypted scalar domains. +//! +//! Two entry points: +//! +//! - **`ordered_numeric_matrix!`** — the recommended wrapper. For an +//! ordered numeric scalar (i32, i64, f64, date, numeric, timestamp, +//! ...) all four variants are present, the operator surface is +//! identical, and the only inputs that change per type are the scalar +//! itself, the suite token (used to derive domain + test names), the +//! EQL type name (the fixture `scripts(...)` ref), and the pivot +//! values. Invocation is ~5 lines. +//! +//! - **`scalar_domain_matrix!`** — the lower-level macro the wrapper +//! expands to. Use directly only for types with a non-standard surface +//! (e.g. equality-only scalars like bool). +//! +//! Each invocation emits one `#[sqlx::test]` per (category, domain, +//! operator, pivot) tuple. Categories: sanity, correctness, cross-shape, +//! supported-NULL, blocker raises, index engagement, ORDER BY, ORDER BY +//! USING. +//! +//! Per-domain capability and payload metadata live in `Variant` (see +//! `scalar_domains.rs`); the macro derives the runtime `ScalarDomainSpec` +//! from `<$scalar as ScalarType>::PG_TYPE` + `Variant::` so no +//! per-type constants are needed. + +// ============================================================================ +// EXPLAIN plan inspection — node-type-aware index-engagement assertion. +// +// The index-engagement arms (`*_index_engages_*`, `*_ord_routes_through_ob`) +// previously asserted `plan_text.contains(index_name)` on a *text* EXPLAIN. +// That substring match is too weak in two independent ways: +// +// 1. It cannot distinguish an actual index-scan node from an incidental +// textual mention of the index name (e.g. inside an `Index Cond`, a +// filter expression, or a "Recheck Cond" line) — any line carrying the +// string passes, even if the relation is still read in full. +// 2. It says nothing about *which kind* of node read the relation. A +// Bitmap-recheck that still touches every heap row, or a node that +// merely references the index, looks identical to a clean Index Scan. +// +// `assert_index_scan_uses` parses `EXPLAIN (FORMAT JSON)` and requires a +// genuine index-scan node (`Index Scan` / `Index Only Scan` / +// `Bitmap Index Scan`) whose `Index Name` is the expected index. This is a +// structurally meaningful assertion even with `enable_seqscan = off`. +// +// LOUD CAVEAT — VALIDITY, NOT PREFERENCE. Even after this upgrade, the +// index-engagement arms run against a ~17-row fixture with +// `SET LOCAL enable_seqscan = off`. With the only cheaper alternative +// (seqscan) forcibly disabled, the planner will pick essentially any usable +// index. So these arms prove the index is USABLE / VALID (the operator +// resolves through the functional index and produces a real index-scan node) +// — they do NOT prove the planner would PREFER the index under realistic +// costs. Cost-preference is proven exclusively by `__scalar_matrix_scale_case` +// (the `*_scale_preference_*` tests), which build ~5000 rows and leave +// `enable_seqscan` ON. Those are `#[cfg(feature = "scale")]` and are OFF in +// default PR CI. Do not read a green index-engagement arm as "the planner +// chooses this index" — it only means "the planner *can* use this index". +// ============================================================================ + +/// Assert that a JSON EXPLAIN plan contains a real index-scan node whose +/// `Index Name` matches `index_name`. +/// +/// Recursively walks the plan tree. A node qualifies only if its `Node Type` +/// is one of `Index Scan`, `Index Only Scan`, or `Bitmap Index Scan` AND its +/// `Index Name` equals `index_name`. This is strictly stronger than a +/// substring match on the text plan, which would also accept an index name +/// appearing in an `Index Cond` / `Recheck Cond` / filter expression without +/// any index-scan node actually reading the relation. +/// +/// `query` is the bare SQL (no `EXPLAIN` prefix); it is interpolated directly, +/// so it must be a trusted/hardcoded string. `tx` is any sqlx executor. +/// +/// Returns `Err` (with the full pretty-printed plan) if no qualifying node is +/// found, so it composes with the `?` operator inside the generated arms. +pub async fn assert_index_scan_uses<'e, E>( + executor: E, + query: &str, + index_name: &str, + context: &str, +) -> anyhow::Result<()> +where + E: sqlx::Executor<'e, Database = sqlx::Postgres>, +{ + let sql = format!("EXPLAIN (FORMAT JSON) {query}"); + let plan: serde_json::Value = sqlx::query_scalar(&sql) + .fetch_one(executor) + .await + .map_err(|e| anyhow::anyhow!("running `{sql}`: {e}"))?; + + let mut index_scan_nodes: Vec<(String, String)> = Vec::new(); + collect_index_scan_nodes(&plan, &mut index_scan_nodes); + + let matched = index_scan_nodes + .iter() + .any(|(_node_type, name)| name == index_name); + + anyhow::ensure!( + matched, + "{context}: expected an index-scan node (Index Scan / Index Only Scan / \ + Bitmap Index Scan) referencing index `{index_name}`, but found none. \ + Index-scan nodes present: {index_scan_nodes:?}. Full plan:\n{}", + serde_json::to_string_pretty(&plan).unwrap_or_else(|_| plan.to_string()), + ); + Ok(()) +} + +/// Recursively collect `(Node Type, Index Name)` pairs for every index-scan +/// node in a JSON EXPLAIN plan tree. Only the three index-scan node types are +/// collected; other nodes (Seq Scan, Aggregate, Sort, ...) are skipped but +/// their children are still walked. +fn collect_index_scan_nodes(value: &serde_json::Value, found: &mut Vec<(String, String)>) { + match value { + serde_json::Value::Object(map) => { + if let Some(node_type) = map.get("Node Type").and_then(|v| v.as_str()) { + if matches!( + node_type, + "Index Scan" | "Index Only Scan" | "Bitmap Index Scan" + ) { + let index_name = map + .get("Index Name") + .and_then(|v| v.as_str()) + .unwrap_or(""); + found.push((node_type.to_string(), index_name.to_string())); + } + } + for v in map.values() { + collect_index_scan_nodes(v, found); + } + } + serde_json::Value::Array(arr) => { + for item in arr { + collect_index_scan_nodes(item, found); + } + } + _ => {} + } +} + +/// Convention wrapper for ordered numeric scalars. Expands to a +/// `scalar_domain_matrix!` invocation with the standard 4 variants, 6 +/// supported comparison operators, 2 path operators, and the standard +/// blocker / index partitions. +/// +/// `eql_type` is the EQL domain type name (e.g. `"eql_v2_int4"`). It is +/// used as the SQLx fixture `scripts(...)` ref, which sqlx parses as a +/// token-level string literal — so it must be a literal, not derived. +/// +/// Pivots — the comparison anchors swept by the correctness / cross-shape +/// arms — are derived from the scalar type: `MIN`, `MAX`, and zero +/// (`Default::default()`). The fixture must contain those three plaintext +/// rows, since each pivot's ciphertext is fetched at test time via +/// `fetch_fixture_payload`. +#[macro_export] +macro_rules! ordered_numeric_matrix { + ( + suite = $suite:ident, + scalar = $scalar:ty, + eql_type = $eql_type:literal $(,)? + ) => { + $crate::scalar_domain_matrix! { + suite = $suite, + scalar = $scalar, + eql_type = $eql_type, + // Relative to the suite source file at + // tests/sqlx/tests/encrypted_domain/scalars/.rs; sqlx's + // include_str! resolves it against that file. Every scalar + // suite lives at this depth, so the path is fixed here rather + // than repeated per invocation. + fixture_path = "../../../fixtures", + all_domains = [(storage, Storage), (eq, Eq), (ord, Ord), (ord_ore, OrdOre)], + eq_domains = [(eq, Eq), (ord, Ord), (ord_ore, OrdOre)], + ord_domains = [(ord, Ord), (ord_ore, OrdOre)], + ord_ore_domains = [(ord_ore, OrdOre)], + pivots = [ + (min, <$scalar>::MIN), + (max, <$scalar>::MAX), + (zero, <$scalar as ::core::default::Default>::default()), + ], + eq_ops = [(eq, "="), (neq, "<>")], + ord_ops = [(lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")], + index_combos = [ + (eq, Eq, "eql_v2.eq_term", "btree", [(eq, "=")]), + (eq, Eq, "eql_v2.eq_term", "hash", [(eq, "=")]), + (ord, Ord, "eql_v2.ord_term", "btree", + [(eq, "="), (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")]), + (ord_ore, OrdOre, "eql_v2.ord_term", "btree", + [(eq, "="), (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")]), + ], + blocker_combos = [ + (storage, Storage, [ + (eq, "="), (neq, "<>"), + (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">="), + (contains, "@>"), (contained_by, "<@"), + ]), + (eq, Eq, [ + (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">="), + (contains, "@>"), (contained_by, "<@"), + ]), + (ord, Ord, [(contains, "@>"), (contained_by, "<@")]), + (ord_ore, OrdOre, [(contains, "@>"), (contained_by, "<@")]), + ], + // Always-on cost-preference proof (#239 thread 17): the recommended + // converged ordered domain, ord_term btree. One curated combo keeps + // PR CI cost bounded. + scale_default_combos = [ + (ord, Ord, "eql_v2.ord_term", "btree"), + ], + } + }; +} + +/// Convention wrapper for equality-only scalars (no ord variants). Bool +/// is the canonical consumer: `=` / `<>` are meaningful; the four ord +/// operators are deliberate blockers. +/// +/// Expands to `scalar_domain_matrix!` with `ord_domains = []`, +/// `ord_ore_domains = []`, no btree-ord index combo, and blocker_combos +/// covering the ord operators on every materialised variant. Order-by / +/// order-by-using arms emit zero tests because they iterate empty +/// ord_domains. +/// +/// **Status:** this umbrella has no in-tree consumer yet. It exists so +/// that adding `bool` (or any other equality-only scalar) is one +/// `impl ScalarType` + fixture + one-line macro invocation, with no +/// macro authoring required. Runtime validation lands with bool. +#[macro_export] +macro_rules! eq_only_scalar_matrix { + ( + suite = $suite:ident, + scalar = $scalar:ty, + eql_type = $eql_type:literal, + pivots = [$($pivot:tt),+ $(,)?] $(,)? + ) => { + $crate::scalar_domain_matrix! { + suite = $suite, + scalar = $scalar, + eql_type = $eql_type, + // Fixed path; see `ordered_numeric_matrix!` for the rationale. + fixture_path = "../../../fixtures", + all_domains = [(storage, Storage), (eq, Eq)], + eq_domains = [(eq, Eq)], + ord_domains = [], + ord_ore_domains = [], + pivots = [$($pivot),+], + eq_ops = [(eq, "="), (neq, "<>")], + ord_ops = [(lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")], + index_combos = [ + (eq, Eq, "eql_v2.eq_term", "btree", [(eq, "=")]), + (eq, Eq, "eql_v2.eq_term", "hash", [(eq, "=")]), + ], + blocker_combos = [ + (storage, Storage, [ + (eq, "="), (neq, "<>"), + (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">="), + (contains, "@>"), (contained_by, "<@"), + ]), + (eq, Eq, [ + (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">="), + (contains, "@>"), (contained_by, "<@"), + ]), + ], + // Equality-only scalars have no ordered functional index to prefer. + scale_default_combos = [], + } + }; +} + +/// Low-level entry point. Use `ordered_numeric_matrix!` instead unless +/// your type's surface deviates from the standard ordered-numeric shape. +#[macro_export] +macro_rules! scalar_domain_matrix { + ( + suite = $suite:ident, + scalar = $scalar:ty, + eql_type = $eql_type:literal, + fixture_path = $fixture_path:literal, + all_domains = [$(($all_name:ident, $all_variant:ident)),+ $(,)?], + eq_domains = [$($eq_dom:tt),+ $(,)?], + ord_domains = [$($ord_dom:tt),* $(,)?], + ord_ore_domains = [$($ord_ore_dom:tt),* $(,)?], + pivots = [$($pivot:tt),+ $(,)?], + eq_ops = [$($eq_op:tt),+ $(,)?], + ord_ops = [$($ord_op:tt),+ $(,)?], + index_combos = [$($index_combo:tt),+ $(,)?], + blocker_combos = [$($blocker_combo:tt),+ $(,)?], + // Curated combo(s) that get an ALWAYS-ON cost-preference test (#239 + // thread 17). May be empty (e.g. equality-only scalars have no ordered + // index to prefer). + scale_default_combos = [$($scale_default_combo:tt),* $(,)?] $(,)? + ) => { + $crate::__scalar_matrix_sanity! { + suite = $suite, scalar = $scalar, + domains = [$(($all_name, $all_variant)),+], + } + $crate::__scalar_matrix_dxop_outer! { + case = __scalar_matrix_correctness_case, + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($eq_dom),+], ops_list = [$($eq_op),+], + pivots_list = [$($pivot),+], + } + $crate::__scalar_matrix_dxop_outer! { + case = __scalar_matrix_correctness_case, + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], ops_list = [$($ord_op),+], + pivots_list = [$($pivot),+], + } + $crate::__scalar_matrix_dxop_outer! { + case = __scalar_matrix_cross_shape_case, + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($eq_dom),+], ops_list = [$($eq_op),+], + pivots_list = [$($pivot),+], + } + $crate::__scalar_matrix_dxop_outer! { + case = __scalar_matrix_cross_shape_case, + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], ops_list = [$($ord_op),+], + pivots_list = [$($pivot),+], + } + $crate::__scalar_matrix_dxo_outer! { + case = __scalar_matrix_supported_null_case, + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($eq_dom),+], ops_list = [$($eq_op),+], + } + $crate::__scalar_matrix_dxo_outer! { + case = __scalar_matrix_supported_null_case, + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], ops_list = [$($ord_op),+], + } + $crate::__scalar_matrix_blocker_outer! { + suite = $suite, scalar = $scalar, + combos = [$($blocker_combo),+], + } + $crate::__scalar_matrix_payload_check_outer! { + suite = $suite, scalar = $scalar, + domains = [$(($all_name, $all_variant)),+], + } + $crate::__scalar_matrix_path_op_outer! { + suite = $suite, scalar = $scalar, + domains = [$(($all_name, $all_variant)),+], + } + $crate::__scalar_matrix_native_absent_outer! { + suite = $suite, scalar = $scalar, + domains = [$(($all_name, $all_variant)),+], + } + $crate::__scalar_matrix_typed_column_outer! { + suite = $suite, scalar = $scalar, + combos = [$($blocker_combo),+], + } + $crate::__scalar_matrix_planner_metadata_outer! { + suite = $suite, scalar = $scalar, group = eq, + domains = [$($eq_dom),+], + ops_list = [$($eq_op),+], + } + $crate::__scalar_matrix_planner_metadata_outer! { + suite = $suite, scalar = $scalar, group = ord, + domains = [$($ord_dom),*], + ops_list = [$($ord_op),+], + } + $crate::__scalar_matrix_index_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + combos = [$($index_combo),+], + } + $crate::__scalar_matrix_scale_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + combos = [$($index_combo),+], + } + $crate::__scalar_matrix_scale_default_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + combos = [$($scale_default_combo),*], + } + $crate::__scalar_matrix_fixture_shape! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + } + $crate::__scalar_matrix_ord_routes_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], + } + $crate::__scalar_matrix_ore_injectivity_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_ore_dom),*], + } + $crate::__scalar_matrix_aggregate_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], + } + $crate::__scalar_matrix_aggregate_group_by_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], + } + $crate::__scalar_matrix_aggregate_parallel_outer! { + suite = $suite, scalar = $scalar, + domains = [$($ord_dom),*], + } + $crate::__scalar_matrix_aggregate_typecheck_outer! { + suite = $suite, scalar = $scalar, + domains = [$(($all_name, $all_variant)),+], + } + $crate::__scalar_matrix_count_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$(($all_name, $all_variant)),+], + } + $crate::__scalar_matrix_order_by_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], + } + $crate::__scalar_matrix_order_by_nulls_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], + } + $crate::__scalar_matrix_order_by_using_outer! { + suite = $suite, scalar = $scalar, script = $eql_type, script_path = $fixture_path, + domains = [$($ord_dom),*], ops_list = [$($ord_op),+], + } + }; +} + +// ============================================================================ +// Helpers: spec construction inside generated test bodies. +// ============================================================================ + +/// Inside a generated test body, build the runtime `ScalarDomainSpec` +/// from `<$scalar>::PG_TYPE` + `Variant::$variant`. All categories use +/// this — keeps the per-case body short. +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_spec { + ($scalar:ty, $variant:ident) => { + $crate::scalar_domains::ScalarDomainSpec::new::<$scalar>( + $crate::scalar_domains::Variant::$variant, + ) + }; +} + +// ============================================================================ +// Sanity category — one test per domain. Cheap thread-through check that +// the macro expanded and the trait wires up. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_sanity { + ( + suite = $suite:ident, + scalar = $scalar:ty, + domains = [$(($name:ident, $variant:ident)),+ $(,)?] $(,)? + ) => { + $( + $crate::paste::paste! { + #[sqlx::test] + async fn [](_pool: sqlx::PgPool) + -> anyhow::Result<()> + { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + assert!(!spec.sql_domain.is_empty()); + assert!(<$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name() + .starts_with("fixtures.")); + Ok(()) + } + } + )+ + }; +} + +// ============================================================================ +// Shared cartesian-product drivers. `macro_rules!` cannot cross-product +// independent lists in one repetition (`$($($(…)*)*)*` over flat depth-1 +// lists does not compile — every metavariable is bound at depth 1), so one +// recursion level fixes one dimension. These generic drivers do that fan-out +// once and dispatch to a per-category leaf macro named by `case`. The +// dimension lists are independent: this is a product, not a zip. +// ============================================================================ + +// domain × op × pivot. +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_dxop_outer { + ( + case = $case:ident, + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$($domain:tt),* $(,)?], + ops_list = $ops_list:tt, pivots_list = $pivots_list:tt $(,)? + ) => { + $( + $crate::__scalar_matrix_dxop_mid! { + case = $case, + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + domain = $domain, ops_list = $ops_list, pivots_list = $pivots_list, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_dxop_mid { + ( + case = $case:ident, + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domain = ($dom_name:ident, $variant:ident), + ops_list = [$($op:tt),+ $(,)?], pivots_list = $pivots_list:tt $(,)? + ) => { + $( + $crate::__scalar_matrix_dxop_inner! { + case = $case, + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + op = $op, pivots_list = $pivots_list, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_dxop_inner { + ( + case = $case:ident, + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + op = ($op_name:ident, $op:literal), + pivots_list = [$($pivot:tt),+ $(,)?] $(,)? + ) => { + $( + $crate::$case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + op_name = $op_name, op = $op, pivot = $pivot, + } + )+ + }; +} + +// domain × op. +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_dxo_outer { + ( + case = $case:ident, + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$($domain:tt),* $(,)?], ops_list = $ops_list:tt $(,)? + ) => { + $( + $crate::__scalar_matrix_dxo_inner! { + case = $case, + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + domain = $domain, ops_list = $ops_list, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_dxo_inner { + ( + case = $case:ident, + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domain = ($dom_name:ident, $variant:ident), + ops_list = [$($op:tt),+ $(,)?] $(,)? + ) => { + $( + $crate::$case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, op = $op, + } + )+ + }; +} + +// ============================================================================ +// Correctness category — leaf for the domain × op × pivot driver: assert the +// row set from `WHERE col op pivot` matches `T::expected_forward(op, pivot)`. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_correctness_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + op_name = $op_name:ident, op = $op:literal, + pivot = ($pivot_name:ident, $pivot_val:expr) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let pivot: $scalar = $pivot_val; + let payload = + $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, pivot).await?; + let lit = $crate::scalar_domains::sql_string_literal(&payload); + let predicate = format!( + "payload::{d} {op} {lit}::jsonb::{d}", + d = &spec.sql_domain, op = $op, + ); + let expected = + <$scalar as $crate::scalar_domains::ScalarType>::expected_forward($op, pivot); + $crate::scalar_domains::assert_scalar_plaintexts::<$scalar>( + &pool, &spec.sql_domain, $op, &predicate, &expected, + ) + .await + } + } + }; +} + +// ============================================================================ +// Cross-shape category — leaf for the domain × op × pivot driver: per +// (domain, op, pivot) sweep the three operator argument shapes (d,d), (d,j), +// (j,d) and assert each returns the right row count. The `j_d` shape uses the +// commuted operator's expected set. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_cross_shape_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + op_name = $op_name:ident, op = $op:literal, + pivot = ($pivot_name:ident, $pivot_val:expr) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let pivot: $scalar = $pivot_val; + let payload = + $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, pivot).await?; + let lit = $crate::scalar_domains::sql_string_literal(&payload); + let forward_count = + <$scalar as $crate::scalar_domains::ScalarType>::expected_forward($op, pivot) + .len() as i64; + let commuted_count = <$scalar as $crate::scalar_domains::ScalarType>::expected_forward( + $crate::scalar_domains::commute_op($op), pivot, + ).len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ("d_d", format!("payload::{d} {op} {lit}::jsonb::{d}", op = $op), forward_count), + ("d_j", format!("payload::{d} {op} {lit}::jsonb", op = $op), forward_count), + ("j_d", format!("{lit}::jsonb {op} payload::{d}", op = $op), commuted_count), + ]; + let table = <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = format!("SELECT count(*) FROM {table} WHERE {predicate}"); + let count: i64 = sqlx::query_scalar(&count_sql).fetch_one(&pool).await?; + assert_eq!( + count, expected_count, + "domain={} op={} pivot={:?} shape={shape_label} SQL={count_sql} \ + expected {expected_count} rows, got {count}", + d, $op, pivot + ); + } + Ok(()) + } + } + }; +} + +// ============================================================================ +// Supported-NULL category — leaf for the domain × op driver: STRICT wrappers +// must propagate NULL on all three NULL positions (left, right, both). +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_supported_null_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + op = ($op_name:ident, $op:literal) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let payload = $crate::helpers::PLACEHOLDER_PAYLOAD; + let sql = format!( + "SELECT $1::jsonb::{d} {op} $2::jsonb::{d}", + d = &spec.sql_domain, op = $op, + ); + $crate::scalar_domains::assert_null(&pool, &sql, &[Some(payload), None]).await?; + $crate::scalar_domains::assert_null(&pool, &sql, &[None, Some(payload)]).await?; + $crate::scalar_domains::assert_null(&pool, &sql, &[None, None]).await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// Blocker category — per blocked (domain, op), sweep 3 arg shapes (all +// must raise) and 3 NULL positions on the (d, d) shape (non-STRICT proof). +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_blocker_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, + combos = [$($combo:tt),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_blocker_combo! { + suite = $suite, scalar = $scalar, combo = $combo, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_blocker_combo { + ( + suite = $suite:ident, scalar = $scalar:ty, + combo = ($dom_name:ident, $variant:ident, [$($op:tt),+ $(,)?]) $(,)? + ) => { + $( + $crate::__scalar_matrix_blocker_case! { + suite = $suite, scalar = $scalar, + dom_name = $dom_name, variant = $variant, op = $op, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_blocker_case { + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = $variant:ident, + op = ($op_name:ident, $op:literal) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let payload = $crate::helpers::PLACEHOLDER_PAYLOAD; + let msg = $crate::scalar_domains::blocker_msg(&spec.sql_domain, $op); + let d = &spec.sql_domain; + + // Sweep 3 arg shapes — every overload must engage. + let shapes: [(String, String); 3] = [ + (format!("$1::jsonb::{d}"), format!("$2::jsonb::{d}")), + (format!("$1::jsonb::{d}"), "$2::jsonb".into()), + ("$1::jsonb".into(), format!("$2::jsonb::{d}")), + ]; + for (lhs, rhs) in shapes { + let sql = format!("SELECT {lhs} {op} {rhs}", op = $op); + $crate::scalar_domains::assert_raises( + &pool, &sql, &[Some(payload), Some(payload)], &msg, + ).await?; + } + + // Sweep 3 NULL positions on the (d, d) shape — blockers + // are non-STRICT so they must engage on every NULL config. + let null_sql = format!( + "SELECT $1::jsonb::{d} {op} $2::jsonb::{d}", op = $op, + ); + $crate::scalar_domains::assert_raises(&pool, &null_sql, &[None, Some(payload)], &msg).await?; + $crate::scalar_domains::assert_raises(&pool, &null_sql, &[Some(payload), None], &msg).await?; + $crate::scalar_domains::assert_raises(&pool, &null_sql, &[None, None], &msg).await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// Payload-check category — per variant, the domain CHECK rejects payloads +// missing required keys (envelope `v`/`i`/`c` plus `Variant::required_term()`) +// and rejects non-object payloads. Required keys are derived from +// `Variant::payload_required_keys()` so future variants pick up coverage. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_payload_check_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, + domains = [$(($dom_name:ident, $variant:ident)),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_payload_check_case! { + suite = $suite, scalar = $scalar, + dom_name = $dom_name, variant = $variant, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_payload_check_case { + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let baseline = $crate::helpers::PLACEHOLDER_PAYLOAD; + + // Each required key must trigger CHECK rejection when stripped. + for key in spec.variant.payload_required_keys() { + let sql = format!( + "SELECT ('{baseline}'::jsonb - '{key}')::{d}", + ); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err(&format!( + "{d} must reject payload missing `{key}`: {sql}" + )) + .to_string(); + anyhow::ensure!( + err.contains("violates check constraint"), + "expected check-constraint violation for missing `{key}` on {d}, got: {err}", + ); + } + + // Non-object payloads are rejected for every variant. + let sql = format!(r#"SELECT '["v","i","c"]'::jsonb::{d}"#); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err(&format!("{d} must reject non-object payload")) + .to_string(); + anyhow::ensure!( + err.contains("violates check constraint"), + "expected check-constraint violation for non-object on {d}, got: {err}", + ); + Ok(()) + } + } + }; +} + +// ============================================================================ +// Path-operator category — `->` and `->>` must raise the blocker on every +// variant (encrypted domains don't expose JSON path access). Three arg +// shapes per op, matching the parameter blocker arm's coverage. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_path_op_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, + domains = [$(($dom_name:ident, $variant:ident)),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_path_op_case! { + suite = $suite, scalar = $scalar, + dom_name = $dom_name, variant = $variant, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_path_op_case { + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let payload = $crate::helpers::PLACEHOLDER_PAYLOAD; + + for op in ["->", "->>"] { + let msg = $crate::scalar_domains::blocker_msg(d, op); + for sql in [ + format!("SELECT $1::jsonb::{d} {op} 'field'::text"), + format!("SELECT $1::jsonb::{d} {op} 0::integer"), + format!("SELECT $1::jsonb {op} $1::jsonb::{d}"), + ] { + $crate::scalar_domains::assert_raises( + &pool, &sql, &[Some(payload)], &msg, + ).await?; + } + } + Ok(()) + } + } + }; +} + +// ============================================================================ +// Native-absent category — `~~` / `~~*` (LIKE / ILIKE) are deliberately +// not declared on encrypted-domain types (no pattern-match capability), +// so resolution falls back to PostgreSQL's "operator does not exist" +// rather than an EQL blocker. Pin that they stay absent on every variant. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_native_absent_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, + domains = [$(($dom_name:ident, $variant:ident)),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_native_absent_case! { + suite = $suite, scalar = $scalar, + dom_name = $dom_name, variant = $variant, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_native_absent_case { + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let payload = $crate::helpers::PLACEHOLDER_PAYLOAD; + + for op in ["~~", "~~*"] { + let sql = format!("SELECT $1::jsonb::{d} {op} $2::jsonb::{d}"); + $crate::scalar_domains::assert_raises( + &pool, &sql, + &[Some(payload), Some(payload)], + "operator does not exist", + ).await?; + } + Ok(()) + } + } + }; +} + +// ============================================================================ +// Typed-column blocker category — pins the bare `WHERE col op col` form a +// real caller writes. The parameter blocker arm uses $1/$2 binds; this +// form resolves the same overloads through a different planner path +// (column-typed operand vs. cast-expression operand). One test per +// (variant, blocker-ops list), savepoint-isolated to avoid abort. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_typed_column_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, + combos = [$($combo:tt),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_typed_column_case! { + suite = $suite, scalar = $scalar, combo = $combo, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_typed_column_case { + ( + suite = $suite:ident, scalar = $scalar:ty, + combo = ($dom_name:ident, $variant:ident, [$(($op_name:ident, $op:literal)),+ $(,)?]) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let payload = $crate::helpers::PLACEHOLDER_PAYLOAD; + + let mut tx = pool.begin().await?; + let create_sql = format!( + "CREATE TEMP TABLE typed_col (\ + id integer GENERATED ALWAYS AS IDENTITY,\ + value {d}\ + ) ON COMMIT DROP" + ); + sqlx::query(&create_sql).execute(&mut *tx).await?; + let insert_sql = format!( + "INSERT INTO typed_col(value) VALUES ($1::jsonb::{d})" + ); + sqlx::query(&insert_sql).bind(payload).execute(&mut *tx).await?; + + $( + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = format!("SELECT * FROM typed_col WHERE value {op} value", op = $op); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err(&format!("{d} column {op} must raise", op = $op)) + .to_string(); + let expected = $crate::scalar_domains::blocker_msg(d, $op); + anyhow::ensure!( + err.contains(&expected), + "unexpected error for {sql}: got {err}, want {expected}", + ); + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + )+ + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// Planner-metadata category — for every (variant, supported-op) the +// declared operator must carry COMMUTATOR, NEGATOR, and the RESTRICT / +// JOIN selectivity estimators on all 3 arg-shapes. Without these the +// planner cannot normalise commuted/negated predicates or cost them. +// Called twice from `scalar_domain_matrix!`: once for (eq_domains, +// eq_ops), once for (ord_domains, ord_ops). Storage variants have no +// supported ops and so don't emit a test. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_planner_metadata_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, group = $group:ident, + domains = [$(($dom_name:ident, $variant:ident)),* $(,)?], + ops_list = $ops_list:tt $(,)? + ) => { + $( + $crate::__scalar_matrix_planner_metadata_case! { + suite = $suite, scalar = $scalar, group = $group, + dom_name = $dom_name, variant = $variant, + ops_list = $ops_list, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_planner_metadata_case { + ( + suite = $suite:ident, scalar = $scalar:ty, group = $group:ident, + dom_name = $dom_name:ident, variant = $variant:ident, + ops_list = [$(($op_name:ident, $op:literal)),+ $(,)?] $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let ops: &[&str] = &[$($op),+]; + let op_list = ops.iter() + .map(|o| format!("'{o}'")) + .collect::>() + .join(", "); + let sql = format!( + r#" + SELECT o.oprname, + lt.typname AS lhs, + rt.typname AS rhs, + o.oprcom <> 0 AS has_commutator, + o.oprnegate <> 0 AS has_negator, + o.oprrest::oid <> 0 AS has_restrict, + o.oprjoin::oid <> 0 AS has_join + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft + JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright + WHERE o.oprname IN ({op_list}) + AND (lt.typname = '{d}' OR rt.typname = '{d}') + "# + ); + let rows: Vec<(String, String, String, bool, bool, bool, bool)> = + sqlx::query_as(&sql).fetch_all(&pool).await?; + + let expected = ops.len() * 3; + anyhow::ensure!( + rows.len() == expected, + "expected {expected} rows ({n_ops} ops x 3 arg shapes) on {d}, got {got}", + n_ops = ops.len(), + got = rows.len(), + ); + for (op, lhs, rhs, has_com, has_neg, has_rest, has_join) in &rows { + anyhow::ensure!(*has_com, + "operator {op}({lhs},{rhs}) must declare COMMUTATOR"); + anyhow::ensure!(*has_neg, + "operator {op}({lhs},{rhs}) must declare NEGATOR"); + anyhow::ensure!(*has_rest, + "operator {op}({lhs},{rhs}) must declare RESTRICT"); + anyhow::ensure!(*has_join, + "operator {op}({lhs},{rhs}) must declare JOIN"); + } + Ok(()) + } + } + }; +} + +// ============================================================================ +// Scale-preference category — feature-gated. Builds a temp table with +// ~5000 filler rows plus one selective pivot, creates the functional +// index, and asserts the planner *prefers* the index with +// `enable_seqscan` left on. The index_engages arm forces seqscan off and +// only proves the index is *usable*; this proves the planner picks it. +// Off by default (`#[cfg(feature = "scale")]`) so PR CI stays fast. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_scale_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + combos = [$($combo:tt),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_scale_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, combo = $combo, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_scale_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + combo = ( + $dom_name:ident, $variant:ident, + $extractor:literal, $using:literal, + [$(($op_name:ident, $op:literal)),+ $(,)?] $(,)? + ) $(,)? + ) => { + $crate::paste::paste! { + #[cfg(feature = "scale")] + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let table = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), + "_scale_", $using, + ); + let index = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), + "_scale_", $using, "_idx", + ); + + let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + anyhow::ensure!(values.len() >= 2, + "scale test requires >= 2 fixture rows for distinct filler/pivot"); + let filler = values[0]; + let pivot = values[values.len() / 2]; + let filler_payload = + $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, filler).await?; + let pivot_payload = + $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, pivot).await?; + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE {table} (value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO {table}(value) \ +SELECT $1::jsonb::{d} FROM generate_series(1, 5000)", + )).bind(&filler_payload).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO {table}(value) VALUES ($1::jsonb::{d})", + )).bind(&pivot_payload).execute(&mut *tx).await?; + sqlx::query(&format!( + "CREATE INDEX {index} ON {table} USING {using} ({extractor}(value))", using = $using, extractor = $extractor, + )).execute(&mut *tx).await?; + sqlx::query(&format!("ANALYZE {table}")) + .execute(&mut *tx).await?; + + let lit = pivot_payload.replace('\'', "''"); + let plan: Vec = sqlx::query_scalar(&format!( + "EXPLAIN SELECT * FROM {table} WHERE value = '{lit}'::jsonb::{d}", + )).fetch_all(&mut *tx).await?; + let plan_text = plan.join("\n"); + anyhow::ensure!(plan_text.contains(index), + "with seqscan enabled the planner must prefer the {extractor} \ +{using} index for a selective = ; plan:\n{plan_text}", + extractor = $extractor, using = $using, + ); + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// Scale-preference DEFAULT category — the always-on counterpart of the +// feature-gated scale sweep above (#239 thread 17). For one curated combo +// (the recommended ordered domain, ord_term btree) it builds ~5000 filler +// rows + one selective pivot, ANALYZEs, and — leaving `enable_seqscan` ON — +// asserts the planner PREFERS the functional index under realistic costs. +// Unlike the index-engagement arms (validity only, seqscan forced off), this +// proves cost-preference; unlike the `*_scale_preference_*` sweep it runs in +// default PR CI. The assertion is node-type-aware via `assert_index_scan_uses` +// (a genuine Index/Index-Only/Bitmap-Index-Scan node referencing the index), +// so it cannot be satisfied by an incidental textual mention of the index. +// Curated to a single combo so PR CI cost stays bounded. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_scale_default_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + combos = [$($combo:tt),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_scale_default_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, combo = $combo, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_scale_default_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + combo = ($dom_name:ident, $variant:ident, $extractor:literal, $using:literal) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let table = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), + "_scaledef_", $using, + ); + let index = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), + "_scaledef_", $using, "_idx", + ); + + let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + anyhow::ensure!(values.len() >= 2, + "scale test requires >= 2 fixture rows for distinct filler/pivot"); + let filler = values[0]; + let pivot = values[values.len() / 2]; + let filler_payload = + $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, filler).await?; + let pivot_payload = + $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, pivot).await?; + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE {table} (value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO {table}(value) \ +SELECT $1::jsonb::{d} FROM generate_series(1, 5000)", + )).bind(&filler_payload).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO {table}(value) VALUES ($1::jsonb::{d})", + )).bind(&pivot_payload).execute(&mut *tx).await?; + sqlx::query(&format!( + "CREATE INDEX {index} ON {table} USING {using} ({extractor}(value))", using = $using, extractor = $extractor, + )).execute(&mut *tx).await?; + sqlx::query(&format!("ANALYZE {table}")) + .execute(&mut *tx).await?; + // enable_seqscan left ON: this is a cost-preference proof, not a + // validity check. With ~5000 filler rows and a single selective + // pivot, a correctly-costed plan must choose the functional index. + + let lit = pivot_payload.replace('\'', "''"); + $crate::matrix::assert_index_scan_uses( + &mut *tx, + &format!("SELECT * FROM {table} WHERE value = '{lit}'::jsonb::{d}"), + index, + "with seqscan ON the planner must PREFER the ord_term functional index for a selective =", + ).await?; + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// Fixture-shape category — one test per type that pins the fixture's +// structural invariants: row count matches `T::FIXTURE_VALUES.len()`, +// ids are sequential from 1, plaintext column matches FIXTURE_VALUES in +// order, every payload carries the variant terms (`hm`, `ob`, `c`), +// distinct plaintexts produce distinct hm terms, every payload declares +// `v=2`. A single test runs all assertions to keep pool-setup cost +// bounded. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_fixture_shape { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let table = <$scalar as ScalarType>::fixture_table_name(); + let expected: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + let n = expected.len() as i64; + + let count: i64 = sqlx::query_scalar(&format!( + "SELECT COUNT(*) FROM {table}", + )).fetch_one(&pool).await?; + anyhow::ensure!(count == n, + "row count must match FIXTURE_VALUES.len(): want {n}, got {count}"); + + let ids: Vec = sqlx::query_scalar(&format!( + "SELECT id FROM {table} ORDER BY id", + )).fetch_all(&pool).await?; + anyhow::ensure!(ids == (1..=n).collect::>(), + "ids must be sequential from 1: got {ids:?}"); + + let plaintexts: Vec<$scalar> = sqlx::query_scalar(&format!( + "SELECT plaintext FROM {table} ORDER BY id", + )).fetch_all(&pool).await?; + anyhow::ensure!(plaintexts == expected, + "plaintext column must match FIXTURE_VALUES in order"); + + for (label, predicate) in [ + ("hm string", "payload->'hm' IS NULL OR jsonb_typeof(payload->'hm') <> 'string'"), + ("ob array", "payload->'ob' IS NULL OR jsonb_typeof(payload->'ob') <> 'array'"), + ("c string", "payload->'c' IS NULL OR jsonb_typeof(payload->'c') <> 'string'"), + ] { + let missing: i64 = sqlx::query_scalar(&format!( + "SELECT COUNT(*) FROM {table} WHERE {predicate}", + )).fetch_one(&pool).await?; + anyhow::ensure!(missing == 0, + "every payload must carry a `{label}` term; missing = {missing}"); + } + + let distinct_hm: i64 = sqlx::query_scalar(&format!( + "SELECT COUNT(DISTINCT payload->>'hm') FROM {table}", + )).fetch_one(&pool).await?; + anyhow::ensure!(distinct_hm == n, + "{n} distinct values -> {n} distinct hm terms; got {distinct_hm}"); + + let mismatched_version: i64 = sqlx::query_scalar(&format!( + "SELECT COUNT(*) FROM {table} \ + WHERE payload->'v' IS NULL OR payload->>'v' <> '2'", + )).fetch_one(&pool).await?; + anyhow::ensure!(mismatched_version == 0, + "every payload must declare v = '2'"); + + // Value-filtering oracle: take the midpoint of FIXTURE_VALUES, + // derive its expected id from position, assert exactly one row. + if !expected.is_empty() { + let probe = expected[expected.len() / 2]; + let probe_lit = <$scalar as ScalarType>::to_sql_literal(probe); + let expected_id = (expected.len() / 2 + 1) as i64; + let ids: Vec = sqlx::query_scalar(&format!( + "SELECT id FROM {table} WHERE plaintext = {lit} ORDER BY id", lit = probe_lit, + )).fetch_all(&pool).await?; + anyhow::ensure!(ids == vec![expected_id], + "expected exactly one row with plaintext = {probe:?} at id {expected_id}, got {ids:?}"); + } + + Ok(()) + } + } + }; +} + +// ============================================================================ +// Ord-routes-through-ob category — ordered variants carry `c + ob` and +// drop `hm`. Equality on an ord variant must therefore route through +// `eql_v2.ord_term` (the `ob` term), never HMAC. Strip `hm` from every +// fixture payload so an accidental regression to HMAC equality fails +// rather than passing on the hm-carrying fixture. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_ord_routes_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$(($dom_name:ident, $variant:ident)),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_ord_routes_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_ord_routes_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let table = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), "_no_hm", + ); + let index = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), "_no_hm_idx", + ); + let fixture_table = + <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + let pivot: $scalar = + <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES[0]; + let pivot_lit = + <$scalar as $crate::scalar_domains::ScalarType>::to_sql_literal(pivot); + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE {table} (plaintext {pg}, value {d}) ON COMMIT DROP", + pg = <$scalar as $crate::scalar_domains::ScalarType>::PG_TYPE, + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO {table}(plaintext, value) \ + SELECT plaintext, (payload - 'hm')::{d} FROM {fixture}", fixture = fixture_table, + )).execute(&mut *tx).await?; + let with_hm: i64 = sqlx::query_scalar(&format!( + "SELECT count(*) FROM {table} WHERE jsonb_exists(value::jsonb, 'hm')", + )).fetch_one(&mut *tx).await?; + anyhow::ensure!(with_hm == 0, "test rows must not carry hm"); + + sqlx::query(&format!( + "CREATE INDEX {index} ON {table} USING btree (eql_v2.ord_term(value))", + )).execute(&mut *tx).await?; + sqlx::query(&format!("ANALYZE {table}")) + .execute(&mut *tx).await?; + sqlx::query("SET LOCAL enable_seqscan = off") + .execute(&mut *tx).await?; + + let pivot_payload: String = sqlx::query_scalar(&format!( + "SELECT (payload - 'hm')::text FROM {fixture} WHERE plaintext = {lit}", + fixture = fixture_table, lit = pivot_lit, + )).fetch_one(&mut *tx).await?; + + // The fixture plaintexts are distinct, so the pivot row is + // unique: `=` via ob must match EXACTLY one row, not "at + // least one". A weaker `>= 1` here is not independent of the + // `<>` check below — `expected_neq` is `len - eq_count`, so an + // `=` that over-matches inflates `eq_count` and deflates + // `expected_neq` in lockstep and both assertions still pass. + // Pinning `== 1` makes both this and the derived `<>` count + // load-bearing. + let eq_count: i64 = sqlx::query_scalar(&format!( + "SELECT count(*) FROM {table} WHERE value = $1::jsonb::{d}", + )).bind(&pivot_payload).fetch_one(&mut *tx).await?; + anyhow::ensure!(eq_count == 1, + "= must match exactly the pivot row via ob with no hm present (want 1, got {eq_count})"); + + // Derive from the pinned `eq_count == 1`: every other fixture + // row must be `<>`. Kept as `len - eq_count` (not a bare + // `len - 1`) so that if the `== 1` invariant above is ever + // relaxed the two assertions cannot silently compensate for + // each other — the derivation stays honest regardless. + let expected_neq = + <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES.len() as i64 + - eq_count; + let neq_count: i64 = sqlx::query_scalar(&format!( + "SELECT count(*) FROM {table} WHERE value <> $1::jsonb::{d}", + )).bind(&pivot_payload).fetch_one(&mut *tx).await?; + anyhow::ensure!(neq_count == expected_neq, + "<> must match every non-pivot fixture row (want {expected_neq}, got {neq_count})", + ); + + // VALIDITY, NOT PREFERENCE: this runs with + // `enable_seqscan = off` (set above) on the ~17-row fixture, + // so the planner picks the only usable alternative. A green + // assertion proves the `eql_v2.ord_term` functional btree is + // *usable* for `=` with no hm present, NOT that the planner + // would *prefer* it at realistic scale. Cost-preference lives + // in the `*_scale_preference_*` tests + // (`#[cfg(feature = "scale")]`, OFF in PR CI). See the module + // header on `assert_index_scan_uses` for the full caveat. + // + // Node-type-aware (not a name substring): we require a genuine + // Index/Index-Only/Bitmap-Index-Scan node referencing `index`, + // so an incidental textual mention of the index name in an + // Index Cond / filter can no longer satisfy the assertion. + let lit = pivot_payload.replace('\'', "''"); + $crate::matrix::assert_index_scan_uses( + &mut *tx, + &format!("SELECT * FROM {table} WHERE value = '{lit}'::jsonb::{d}"), + index, + "= must engage the eql_v2.ord_term functional btree with no hm", + ).await?; + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// ORE-injectivity category — for OrdOre variants, distinct plaintexts in +// the fixture must produce distinct ORE blocks. Pairwise self-join over +// the fixture: zero collisions. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_ore_injectivity_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$(($dom_name:ident, $variant:ident)),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_ore_injectivity_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_ore_injectivity_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let fixture_table = + <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + let collisions: i64 = sqlx::query_scalar(&format!( + "SELECT count(*) \ +FROM {fixture} a \ +JOIN {fixture} b ON a.id < b.id \ +WHERE a.payload::{d} = b.payload::{d}", + fixture = fixture_table, + )).fetch_one(&pool).await?; + anyhow::ensure!(collisions == 0, + "no two distinct plaintexts may share an ORE term on {d}"); + Ok(()) + } + } + }; +} + +// ============================================================================ +// Index-engagement category — per (domain, extractor, using, ops) build a +// typed temp table from the fixture, create the functional index, sweep +// ops × rhs-casts asserting EXPLAIN contains a genuine index-scan node +// referencing the index (via `assert_index_scan_uses`, not a name substring). +// +// VALIDITY ONLY: forces `enable_seqscan = off` on the ~17-row fixture, so a +// green arm proves the index is *usable*, NOT that the planner would *prefer* +// it. Cost-preference is the `*_scale_preference_*` tests +// (`#[cfg(feature = "scale")]`, OFF in PR CI). See the module-level comment on +// `assert_index_scan_uses` for the full caveat. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_index_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + combos = [$($combo:tt),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_index_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, combo = $combo, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_index_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + combo = ( + $dom_name:ident, $variant:ident, + $extractor:literal, $using:literal, + [$(($op_name:ident, $op:literal)),+ $(,)?] $(,)? + ) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let table = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), + "_idx_", $using, + ); + let index = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), + "_idx_", $using, "_idx", + ); + let fixture_table = + <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + let mut tx = pool.begin().await?; + + sqlx::query(&format!( + "CREATE TEMP TABLE {table} (plaintext {pg}, value {d}) ON COMMIT DROP", + pg = <$scalar as $crate::scalar_domains::ScalarType>::PG_TYPE, + d = &spec.sql_domain, + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO {table}(plaintext, value) \ + SELECT plaintext, payload::{d} FROM {fixture}", d = &spec.sql_domain, fixture = fixture_table, + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "CREATE INDEX {index} ON {table} USING {using} ({extractor}(value))", using = $using, extractor = $extractor, + )).execute(&mut *tx).await?; + sqlx::query(&format!("ANALYZE {table}")) + .execute(&mut *tx).await?; + sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; + + let pivot: $scalar = <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES[0]; + let payload = + $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, pivot).await?; + let lit = $crate::scalar_domains::sql_string_literal(&payload); + + // VALIDITY, NOT PREFERENCE: `enable_seqscan = off` is set + // above and the table holds only the ~17 fixture rows, so the + // planner has no cheaper option than the functional index. + // These arms therefore prove the index is *usable* for each + // (op, rhs-cast) shape — that the operator resolves through + // `{extractor}` and produces a real index-scan node — NOT that + // the planner would *prefer* the index under realistic costs. + // Cost-preference is proven ONLY by the `*_scale_preference_*` + // tests (`#[cfg(feature = "scale")]`), which are OFF in default + // PR CI. See the module header on `assert_index_scan_uses`. + // + // The assertion is node-type-aware (Index / Index Only / + // Bitmap Index Scan referencing `index`), not a bare substring + // match on the text plan, so an index name that merely appears + // in an Index Cond / Recheck Cond / filter cannot pass it. + let rhs_casts = [format!("::{d}", d = &spec.sql_domain), String::new()]; + $( + for rhs_cast in &rhs_casts { + let query = format!( + "SELECT * FROM {table} WHERE value {op} {lit}::jsonb{cast}", op = $op, cast = rhs_cast, + ); + $crate::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &format!( + "domain={} op={} rhs_cast={:?} must use index={}", + &spec.sql_domain, $op, rhs_cast, index, + ), + ).await?; + } + )+ + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// ORDER BY category — per ord domain × {ASC,DESC} × {no-WHERE, WHERE>0}. +// Fixture has no NULL plaintexts so NULLS FIRST/LAST is moot. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$($domain:tt),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_order_by_domain! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, domain = $domain, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_domain { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domain = ($dom_name:ident, $variant:ident) $(,)? + ) => { + $crate::__scalar_matrix_order_by_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = asc_no_where, direction = "ASC", where_clause = "", + } + $crate::__scalar_matrix_order_by_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = desc_no_where, direction = "DESC", where_clause = "", + } + $crate::__scalar_matrix_order_by_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = asc_with_where, direction = "ASC", + where_clause = " WHERE plaintext > 0", + } + $crate::__scalar_matrix_order_by_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = desc_with_where, direction = "DESC", + where_clause = " WHERE plaintext > 0", + } + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + mode_name = $mode_name:ident, direction = $direction:literal, + where_clause = $where_clause:literal $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let fixture_table = + <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + let sql = format!( + "SELECT plaintext FROM {fixture}{where_clause} \ +ORDER BY eql_v2.ord_term(payload::{d}) {dir}", + fixture = fixture_table, where_clause = $where_clause, + d = &spec.sql_domain, dir = $direction, + ); + let actual: Vec<$scalar> = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + + let zero: $scalar = Default::default(); + let mut expected: Vec<$scalar> = + <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES.to_vec(); + expected.sort(); + if $where_clause.contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if $direction == "DESC" { expected.reverse(); } + + assert_eq!(actual, expected, + "domain={} mode={} SQL={} expected {:?}, got {:?}", + &spec.sql_domain, stringify!($mode_name), sql, expected, actual); + Ok(()) + } + } + }; +} + +// ============================================================================ +// ORDER BY NULLS FIRST/LAST category — per ord domain × {ASC,DESC} × +// {NULLS FIRST, NULLS LAST}. The plain ORDER BY arm above sorts the fixture, +// which has no NULL rows, so NULLS placement goes untested there. This arm +// builds an isolated temp table mixing NULL-valued rows with the fixture rows +// and pins that the NULL sort keys land at the requested end while the +// non-NULL rows stay in plaintext order. `eql_v2.ord_term` is STRICT, so a +// NULL domain value yields a NULL sort key; a regression making it non-STRICT +// would let NULL rows interleave — see the `family::mutations` negative +// control for that dimension. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_nulls_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$($domain:tt),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_order_by_nulls_domain! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, domain = $domain, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_nulls_domain { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domain = ($dom_name:ident, $variant:ident) $(,)? + ) => { + $crate::__scalar_matrix_order_by_nulls_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = asc_nulls_first, direction = "ASC", nulls = "FIRST", + } + $crate::__scalar_matrix_order_by_nulls_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = asc_nulls_last, direction = "ASC", nulls = "LAST", + } + $crate::__scalar_matrix_order_by_nulls_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = desc_nulls_first, direction = "DESC", nulls = "FIRST", + } + $crate::__scalar_matrix_order_by_nulls_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + mode_name = desc_nulls_last, direction = "DESC", nulls = "LAST", + } + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_nulls_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + mode_name = $mode_name:ident, direction = $direction:literal, nulls = $nulls:literal $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + // Number of NULL-valued rows mixed in; >1 proves they cluster. + const NULL_ROWS: usize = 3; + + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let table = concat!( + "matrix_", stringify!($suite), "_", stringify!($dom_name), + "_order_by_", stringify!($mode_name), + ); + let fixture_table = + <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + let pg = <$scalar as $crate::scalar_domains::ScalarType>::PG_TYPE; + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE {table} (plaintext {pg}, value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + // Non-NULL rows: every fixture row, carrying its plaintext. + sqlx::query(&format!( + "INSERT INTO {table}(plaintext, value) \ +SELECT plaintext, payload::{d} FROM {fixture}", fixture = fixture_table, + )).execute(&mut *tx).await?; + // NULL-valued rows: NULL plaintext too, so they surface as None + // and their position is what the assertion pins. + sqlx::query(&format!( + "INSERT INTO {table}(plaintext, value) \ +SELECT NULL::{pg}, NULL::{d} FROM generate_series(1, {n})", n = NULL_ROWS, + )).execute(&mut *tx).await?; + + let sql = format!( + "SELECT plaintext FROM {table} \ +ORDER BY eql_v2.ord_term(value) {dir} NULLS {nulls}", + dir = $direction, nulls = $nulls, + ); + let actual: Vec> = + sqlx::query_scalar(&sql).fetch_all(&mut *tx).await?; + + // Ground truth: non-NULL plaintexts sorted (reversed for DESC), + // with NULL_ROWS Nones at the requested end. + let mut non_null: Vec<$scalar> = + <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES.to_vec(); + non_null.sort(); + if $direction == "DESC" { non_null.reverse(); } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if $nulls == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + + assert_eq!(actual, expected, + "domain={} mode={} SQL={} expected {:?}, got {:?}", + d, stringify!($mode_name), sql, expected, actual); + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// ORDER BY USING category — every op × ord domain must reject +// `ORDER BY col USING ` because the design forbids opclasses on +// these domains. If a refactor accidentally adds one, this fails. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_using_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$($domain:tt),* $(,)?], ops_list = $ops_list:tt $(,)? + ) => { + $( + $crate::__scalar_matrix_order_by_using_inner! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + domain = $domain, ops_list = $ops_list, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_using_inner { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domain = ($dom_name:ident, $variant:ident), + ops_list = [$(($op_name:ident, $op:literal)),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_order_by_using_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + op_name = $op_name, op = $op, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_order_by_using_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + op_name = $op_name:ident, op = $op:literal $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let fixture_table = + <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + let sql = format!( + "SELECT plaintext FROM {fixture} ORDER BY payload::{d} USING {op}", + fixture = fixture_table, d = &spec.sql_domain, op = $op, + ); + let err = sqlx::query_scalar::<_, $scalar>(&sql) + .fetch_all(&pool) + .await + .expect_err(&format!( + "domain={} op={} SQL={} must reject ORDER BY USING (no opclass on \ +domain by design) but succeeded", + &spec.sql_domain, $op, sql, + )); + // SQLSTATE 42809 (wrong_object_type) — "operator X is not a + // valid ordering operator". The boolean operator exists on the + // domain but lacks a btree opclass entry, so ORDER BY USING + // refuses to use it. Pinning this catches the regression where + // a stray opclass would make ORDER BY USING start succeeding + // for the wrong reason — `is_err()` alone could not. + $crate::assert_db_error(&err, "42809", None); + Ok(()) + } + } + }; +} + +// ============================================================================ +// Aggregate category — per (ord domain, op ∈ {min, max}), three tests: +// extremum identity (payload of the min/max FIXTURE_VALUES row), all-NULL +// returns NULL, and mixed NULL/non-NULL returns the correct extremum from +// the non-NULL subset. Pins that `eql_v2.min` / `eql_v2.max` aggregates +// route through the domain's `<` / `>` and that the STRICT state function +// correctly seeds + skips NULLs. Emits zero tests when ord_domains is +// empty — eq-only umbrellas pick that up naturally. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$($domain:tt),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_aggregate_mid! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + domain = $domain, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_mid { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domain = ($dom_name:ident, $variant:ident) $(,)? + ) => { + $crate::__scalar_matrix_aggregate_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + op_name = min, agg_fn = "min", picker = min, + } + $crate::__scalar_matrix_aggregate_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + op_name = max, agg_fn = "max", picker = max, + } + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + op_name = $op_name:ident, agg_fn = $agg_fn:literal, picker = $picker:ident $(,)? + ) => { + $crate::paste::paste! { + // Extremum identity: aggregate returns the exact payload of the + // smallest (or largest) fixture row. Domain-cast on both sides + // so the comparator routes through the variant's `<` / `>`. + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let fixture = <$scalar as ScalarType>::fixture_table_name(); + let extremum: $scalar = <$scalar as ScalarType>::FIXTURE_VALUES + .iter() + .copied() + .$picker() + .expect("FIXTURE_VALUES must be non-empty"); + let extremum_lit = <$scalar as ScalarType>::to_sql_literal(extremum); + + let expected: String = sqlx::query_scalar(&format!( + "SELECT payload::text FROM {fixture} WHERE plaintext = {lit}", lit = extremum_lit, + )).fetch_one(&pool).await?; + + let actual: String = sqlx::query_scalar(&format!( + "SELECT eql_v2.{agg}(payload::{d})::text FROM {fixture}", + agg = $agg_fn, + )).fetch_one(&pool).await?; + + assert_eq!( + actual, expected, + "eql_v2.{}({}) must return the payload of plaintext={:?} (the fixture {})", + $agg_fn, d, extremum, $agg_fn, + ); + + // Secondary diagnostic: when the primary identity holds, + // the ORE comparator must agree. The check is reached only + // on success of `assert_eq!`, so it's a self-consistency + // assertion on the comparator — catches the regression + // where payload text matches but `ord_term` resolves to a + // different value (e.g. due to payload-key reordering). + let ord_terms_match: bool = sqlx::query_scalar(&format!( + "SELECT eql_v2.ord_term(eql_v2.{agg}(payload::{d})) \ + = eql_v2.ord_term($1::jsonb::{d}) \ + FROM {fixture}", + agg = $agg_fn, + )) + .bind(&expected) + .fetch_one(&pool) + .await?; + anyhow::ensure!( + ord_terms_match, + "eql_v2.ord_term(eql_v2.{}({})) must equal eql_v2.ord_term() \ + for plaintext={:?}", + $agg_fn, d, extremum, + ); + Ok(()) + } + + // Empty rowset: aggregate over zero rows returns NULL, + // structurally distinct from the all-NULL case (no rows fed + // at all vs. rows fed but every value NULL). Both must + // return NULL but they exercise different sfunc paths. + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE empty_agg (value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + let result: Option = sqlx::query_scalar(&format!( + "SELECT eql_v2.{agg}(value)::text FROM empty_agg", + agg = $agg_fn, + )).fetch_one(&mut *tx).await?; + anyhow::ensure!( + result.is_none(), + "empty rowset to eql_v2.{} on {} must return NULL, got {:?}", + $agg_fn, d, result, + ); + tx.commit().await?; + Ok(()) + } + + // All-NULL input: STRICT sfunc never seeds the state, final + // result is NULL. No fixture needed. + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let sql = format!( + "SELECT eql_v2.{agg}(NULL::{d})::text FROM generate_series(1, 3)", + agg = $agg_fn, + ); + let result: Option = sqlx::query_scalar(&sql) + .fetch_one(&pool) + .await?; + anyhow::ensure!( + result.is_none(), + "all-NULL input to eql_v2.{} on {} must return NULL, got {:?}; SQL={}", + $agg_fn, d, result, sql, + ); + Ok(()) + } + + // Mixed NULL / non-NULL: feeds [NULL, mid, NULL, high, NULL] and + // asserts the aggregate returns the correct extremum of {mid, + // high}. A non-STRICT sfunc would crash on (state=NULL, value=mid) + // because `value < state` would be NULL; the STRICT contract + // skips NULL inputs and seeds with the first non-NULL value. + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let fixture = <$scalar as ScalarType>::fixture_table_name(); + let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + anyhow::ensure!( + values.len() >= 2, + "mixed-NULL test needs >= 2 fixture values; got {}", + values.len(), + ); + let mut sorted: Vec<$scalar> = values.to_vec(); + sorted.sort(); + // Span the fixture's extremes — for signed numeric scalars this + // exercises the ORE sign-bit edges in addition to pinning STRICT + // sfunc behaviour. + let low: $scalar = *sorted.first().expect("non-empty after len check"); + let high: $scalar = *sorted.last().expect("non-empty after len check"); + // .min() / .max() on two values resolves to the correct picker. + let expected_plaintext: $scalar = low.$picker(high); + let low_lit = <$scalar as ScalarType>::to_sql_literal(low); + let high_lit = <$scalar as ScalarType>::to_sql_literal(high); + let expected_lit = <$scalar as ScalarType>::to_sql_literal(expected_plaintext); + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE mixed_null (value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO mixed_null(value) \ + SELECT NULL::{d} \ + UNION ALL SELECT payload::{d} FROM {fixture} WHERE plaintext = {low} \ + UNION ALL SELECT NULL::{d} \ + UNION ALL SELECT payload::{d} FROM {fixture} WHERE plaintext = {high} \ + UNION ALL SELECT NULL::{d}", low = low_lit, high = high_lit, + )).execute(&mut *tx).await?; + + let expected: String = sqlx::query_scalar(&format!( + "SELECT payload::text FROM {fixture} WHERE plaintext = {lit}", lit = expected_lit, + )).fetch_one(&mut *tx).await?; + + let actual: Option = sqlx::query_scalar(&format!( + "SELECT eql_v2.{agg}(value)::text FROM mixed_null", + agg = $agg_fn, + )).fetch_one(&mut *tx).await?; + + anyhow::ensure!( + actual.as_deref() == Some(expected.as_str()), + "eql_v2.{} on mixed NULL/non-NULL must return the {} non-NULL value (plaintext={:?}); want {expected:?}, got {actual:?}", + $agg_fn, $agg_fn, expected_plaintext, + ); + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// Aggregate parallelism category — per ord domain, assert that the catalog +// declares MIN/MAX as PARALLEL SAFE with a combine function. Without those, +// PostgreSQL silently forecloses partial/parallel aggregation on exactly the +// large GROUP BY workloads these ORE aggregates exist to serve (#239 thread +// 22). A catalog-level structural guard (cheap, deterministic, no plan +// dependence) rather than a flaky "force a parallel plan" behavioural test. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_parallel_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, + domains = [$($domain:tt),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_aggregate_parallel_case! { + suite = $suite, scalar = $scalar, domain = $domain, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_parallel_case { + ( + suite = $suite:ident, scalar = $scalar:ty, + domain = ($dom_name:ident, $variant:ident) $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + for agg in ["min", "max"] { + let (proparallel, has_combine): (String, bool) = sqlx::query_as( + "SELECT p.proparallel::text, a.aggcombinefn <> 0 \ + FROM pg_proc p \ + JOIN pg_aggregate a ON a.aggfnoid = p.oid \ + WHERE p.proname = $1 \ + AND p.pronamespace = 'eql_v2'::regnamespace \ + AND p.proargtypes[0]::regtype = $2::regtype", + ) + .bind(agg) + .bind(d) + .fetch_one(&pool) + .await?; + anyhow::ensure!(proparallel == "s", + "eql_v2.{agg}({d}) must be PARALLEL SAFE (proparallel='s'), got {proparallel:?}"); + anyhow::ensure!(has_combine, + "eql_v2.{agg}({d}) must declare a combinefunc for partial aggregation"); + } + Ok(()) + } + } + }; +} + +// ============================================================================ +// Aggregate GROUP BY category — per (ord domain, op ∈ {min, max}), build a +// temp table partitioned into two groups, populate each with a known +// subset of fixture rows, GROUP BY the group key, and assert that +// `eql_v2.(value)` returns the correct extremum payload per group. +// Pins that the aggregate composes correctly under GROUP BY (state is +// reset between groups, the sfunc routes through the variant's +// comparator inside each partition). +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_group_by_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$($domain:tt),* $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_aggregate_group_by_mid! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + domain = $domain, + } + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_group_by_mid { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domain = ($dom_name:ident, $variant:ident) $(,)? + ) => { + $crate::__scalar_matrix_aggregate_group_by_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + op_name = min, agg_fn = "min", picker = min, + } + $crate::__scalar_matrix_aggregate_group_by_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + op_name = max, agg_fn = "max", picker = max, + } + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_group_by_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident, + op_name = $op_name:ident, agg_fn = $agg_fn:literal, picker = $picker:ident $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let fixture = <$scalar as ScalarType>::fixture_table_name(); + let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + anyhow::ensure!( + values.len() >= 5, + "GROUP BY test needs >= 5 fixture values; got {}", + values.len(), + ); + + // Partition FIXTURE_VALUES[..3] into group 1 and [3..5] + // into group 2. Per-group extremum is computed in Rust as + // the ground truth. + let group1: &[$scalar] = &values[..3]; + let group2: &[$scalar] = &values[3..5]; + let group1_extremum: $scalar = group1.iter().copied().$picker() + .expect("group 1 is non-empty"); + let group2_extremum: $scalar = group2.iter().copied().$picker() + .expect("group 2 is non-empty"); + let g1_lit = <$scalar as ScalarType>::to_sql_literal(group1_extremum); + let g2_lit = <$scalar as ScalarType>::to_sql_literal(group2_extremum); + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE group_test (group_key int, value {d}) \ +ON COMMIT DROP", + )).execute(&mut *tx).await?; + + // Insert group 1 rows. + for v in group1 { + let lit = <$scalar as ScalarType>::to_sql_literal(*v); + sqlx::query(&format!( + "INSERT INTO group_test(group_key, value) \ +SELECT 1, payload::{d} FROM {fixture} WHERE plaintext = {lit}", + )).execute(&mut *tx).await?; + } + // Insert group 2 rows. + for v in group2 { + let lit = <$scalar as ScalarType>::to_sql_literal(*v); + sqlx::query(&format!( + "INSERT INTO group_test(group_key, value) \ +SELECT 2, payload::{d} FROM {fixture} WHERE plaintext = {lit}", + )).execute(&mut *tx).await?; + } + + // Lookup the expected payload texts for each group's extremum. + let g1_expected: String = sqlx::query_scalar(&format!( + "SELECT payload::text FROM {fixture} WHERE plaintext = {lit}", lit = g1_lit, + )).fetch_one(&mut *tx).await?; + let g2_expected: String = sqlx::query_scalar(&format!( + "SELECT payload::text FROM {fixture} WHERE plaintext = {lit}", lit = g2_lit, + )).fetch_one(&mut *tx).await?; + + let rows: Vec<(i32, String)> = sqlx::query_as(&format!( + "SELECT group_key, eql_v2.{agg}(value)::text \ +FROM group_test GROUP BY group_key ORDER BY group_key", + agg = $agg_fn, + )).fetch_all(&mut *tx).await?; + + anyhow::ensure!( + rows.len() == 2, + "GROUP BY must return 2 rows, got {}", + rows.len(), + ); + anyhow::ensure!( + rows[0].0 == 1 && rows[0].1 == g1_expected, + "group 1 eql_v2.{}({}) must yield payload for plaintext={:?}; \ +want ({}, {:?}), got {:?}", + $agg_fn, d, group1_extremum, 1, g1_expected, rows[0], + ); + anyhow::ensure!( + rows[1].0 == 2 && rows[1].1 == g2_expected, + "group 2 eql_v2.{}({}) must yield payload for plaintext={:?}; \ +want ({}, {:?}), got {:?}", + $agg_fn, d, group2_extremum, 2, g2_expected, rows[1], + ); + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// Aggregate type-safety category — for variants that do NOT support ord +// (Storage, Eq), `eql_v2.min()` / `eql_v2.max(...)` must +// resolve to "function does not exist" (SQLSTATE 42883). Pins that +// codegen correctly omits MIN/MAX wrappers for these variants — a +// SQL-level regression test complementing the codegen unit test. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_typecheck_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, + domains = [$(($dom_name:ident, $variant:ident)),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_aggregate_typecheck_dispatch! { + suite = $suite, scalar = $scalar, + dom_name = $dom_name, variant = $variant, + } + )+ + }; +} + +// Dispatch on variant ident: ord-capable variants (Ord, OrdOre) emit no +// typecheck test — they DO declare min/max. Non-ord variants (Storage, +// Eq) emit one test per aggregate op asserting the call fails with +// SQLSTATE 42883. +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_typecheck_dispatch { + // Ord, OrdOre: no typecheck test — these variants declare min/max. + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = Ord $(,)? + ) => {}; + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = OrdOre $(,)? + ) => {}; + // Storage, Eq: emit min + max typecheck tests. + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::__scalar_matrix_aggregate_typecheck_case! { + suite = $suite, scalar = $scalar, + dom_name = $dom_name, variant = $variant, + op_name = min, agg_fn = "min", + } + $crate::__scalar_matrix_aggregate_typecheck_case! { + suite = $suite, scalar = $scalar, + dom_name = $dom_name, variant = $variant, + op_name = max, agg_fn = "max", + } + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_aggregate_typecheck_case { + ( + suite = $suite:ident, scalar = $scalar:ty, + dom_name = $dom_name:ident, variant = $variant:ident, + op_name = $op_name:ident, agg_fn = $agg_fn:literal $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let payload = $crate::helpers::PLACEHOLDER_PAYLOAD; + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE typecheck_table (value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO typecheck_table(value) VALUES ($1::jsonb::{d})", + )).bind(payload).execute(&mut *tx).await?; + + // Savepoint-isolate the probe so the failed lookup + // doesn't abort the outer transaction and tx.commit() + // can succeed cleanly. + sqlx::query("SAVEPOINT probe").execute(&mut *tx).await?; + let sql = format!( + "SELECT eql_v2.{agg}(value) FROM typecheck_table", + agg = $agg_fn, + ); + let err = sqlx::query_scalar::<_, String>(&sql) + .fetch_one(&mut *tx) + .await + .expect_err(&format!( + "eql_v2.{} on non-ord variant {} must raise but succeeded", + $agg_fn, d, + )); + // 42883 = undefined_function (no overload defined at all); + // 42725 = ambiguous_function (multiple overloads resolve, + // none specific to this variant). Either confirms the + // variant carries no MIN/MAX of its own — the generic + // eql_v2_encrypted overload is reachable via cast but + // can't be resolved unambiguously from a domain-typed + // column. Both outcomes are acceptable "not supported". + let db_err = err.as_database_error() + .expect("expected database error from typecheck probe"); + let code = db_err.code(); + anyhow::ensure!( + code.as_deref() == Some("42883") || code.as_deref() == Some("42725"), + "expected SQLSTATE 42883 (undefined_function) or 42725 \ +(ambiguous_function) for eql_v2.{}({}), got {:?} (message: {})", + $agg_fn, d, code, db_err.message(), + ); + sqlx::query("ROLLBACK TO SAVEPOINT probe").execute(&mut *tx).await?; + + tx.commit().await?; + Ok(()) + } + } + }; +} + +// ============================================================================ +// COUNT category — pins three forms per variant: plain COUNT(value) on a +// typed column, COUNT(payload::variant) on the fixture, and +// COUNT(DISTINCT extractor(value)) using the variant's own extractor. The +// DISTINCT case dispatches per-variant: Storage has no extractor and so +// emits no DISTINCT test; Eq uses eq_term, Ord/OrdOre use ord_term. +// +// This is net new coverage relative to the legacy aggregate_tests.rs file, +// which only covered plain COUNT and only against the eql_v2_encrypted +// type. Pinning per-variant DISTINCT catches the breakage class where +// picking the wrong extractor would fail at runtime ("function +// eql_v2.eq_term(eql_v2_int4_ord) does not exist") — exactly the kind of +// thing the variant-aware matrix is meant to surface mechanically. +// ============================================================================ + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_count_outer { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + domains = [$(($dom_name:ident, $variant:ident)),+ $(,)?] $(,)? + ) => { + $( + $crate::__scalar_matrix_count_case! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + } + $crate::__scalar_matrix_count_distinct_dispatch! { + suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, + dom_name = $dom_name, variant = $variant, + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_count_case { + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::paste::paste! { + // COUNT(value) on a typed column — pins that PG's native COUNT + // works on a domain-typed column without an aggregate declaration. + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let fixture = <$scalar as ScalarType>::fixture_table_name(); + let expected = <$scalar as ScalarType>::FIXTURE_VALUES.len() as i64; + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE typed_count (value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO typed_count(value) SELECT payload::{d} FROM {fixture}", + )).execute(&mut *tx).await?; + + let actual: i64 = sqlx::query_scalar( + "SELECT COUNT(value) FROM typed_count", + ).fetch_one(&mut *tx).await?; + anyhow::ensure!( + actual == expected, + "COUNT(value) on typed {} column: want {}, got {}", + d, expected, actual, + ); + + tx.commit().await?; + Ok(()) + } + + // COUNT(payload::variant) on the fixture — pins COUNT on a + // path-cast expression. No temp table; the cast happens inline. + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let fixture = <$scalar as ScalarType>::fixture_table_name(); + let expected = <$scalar as ScalarType>::FIXTURE_VALUES.len() as i64; + + let sql = format!( + "SELECT COUNT(payload::{d}) FROM {fixture}", + ); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + anyhow::ensure!( + actual == expected, + "COUNT(payload::{}) on {}: want {}, got {}; SQL={}", + d, fixture, expected, actual, sql, + ); + Ok(()) + } + } + }; +} + +// Dispatch on variant ident: Storage has no discriminating extractor, so +// emits no DISTINCT test. The other three (Eq, Ord, OrdOre) each emit one +// test that reads the extractor function name from the runtime +// `ScalarDomainSpec::extractor_fn()` accessor (Eq -> `eql_v2.eq_term`, +// Ord/OrdOre -> `eql_v2.ord_term`) and appends `(value)` at the call site. +#[macro_export] +#[doc(hidden)] +macro_rules! __scalar_matrix_count_distinct_dispatch { + // Storage: no DISTINCT case — no extractor to deduplicate by. + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = Storage $(,)? + ) => {}; + // Eq, Ord, OrdOre — emit the DISTINCT test. + ( + suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, + dom_name = $dom_name:ident, variant = $variant:ident $(,)? + ) => { + $crate::paste::paste! { + #[sqlx::test(fixtures(path = $script_path, scripts($script)))] + async fn []( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; + let spec = $crate::__scalar_matrix_spec!($scalar, $variant); + let d = &spec.sql_domain; + let extractor_fn = spec.extractor_fn() + .expect("non-Storage variant must expose an extractor"); + let extractor = format!("{extractor_fn}(value)"); + let fixture = <$scalar as ScalarType>::fixture_table_name(); + let expected = <$scalar as ScalarType>::FIXTURE_VALUES.len() as i64; + + let mut tx = pool.begin().await?; + sqlx::query(&format!( + "CREATE TEMP TABLE distinct_count (value {d}) ON COMMIT DROP", + )).execute(&mut *tx).await?; + sqlx::query(&format!( + "INSERT INTO distinct_count(value) SELECT payload::{d} FROM {fixture}", + )).execute(&mut *tx).await?; + + let sql = format!( + "SELECT COUNT(DISTINCT {extr}) FROM distinct_count", + extr = extractor, + ); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&mut *tx).await?; + anyhow::ensure!( + actual == expected, + "COUNT(DISTINCT {}) on {}: want {} (one per FIXTURE_VALUES row), got {}; SQL={}", + extractor, d, expected, actual, sql, + ); + + tx.commit().await?; + Ok(()) + } + } + }; +} diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs new file mode 100644 index 00000000..b31c1a55 --- /dev/null +++ b/tests/sqlx/src/scalar_domains.rs @@ -0,0 +1,308 @@ +//! Type-generic substrate for the encrypted-scalar-domain test matrix. +//! +//! Adding a new encrypted scalar type (e.g. `i64` for int8, `f64` for +//! float8) is a 4-line `impl ScalarType` plus a Proxy-encrypted fixture. +//! Everything else — the four `eql_v2_{,_eq,_ord,_ord_ore}` domains, +//! per-domain payload shapes, supported operators, index extractor +//! expressions, ground-truth result sets — is derived from +//! `T::PG_TYPE`, `T::FIXTURE_VALUES`, and the `Variant` enum. + +use anyhow::{bail, Context, Result}; +use sqlx::PgPool; +use std::fmt::{Debug, Display}; + +/// One impl per scalar type. Two `const`s and the rest defaults. +pub trait ScalarType: + Copy + + Ord + + Default + + Debug + + Display + + Send + + Sync + + Unpin + + 'static + + for<'r> sqlx::Decode<'r, sqlx::Postgres> + + sqlx::Type +{ + /// Postgres native type token — also the suffix in the SQL domain + /// name and the fixture script name. Examples: `"int4"`, `"int8"`. + const PG_TYPE: &'static str; + + /// Distinct plaintext values present in the fixture. Order doesn't + /// matter — `expected_forward` sorts before returning. + /// + /// For types driven by `ordered_numeric_matrix!`, the fixture MUST + /// include `MIN`, `MAX`, and zero (`Default::default()`): the matrix + /// uses those three as comparison pivots and fetches each one's + /// ciphertext via `fetch_fixture_payload`, which fails loudly if the + /// row is absent. + const FIXTURE_VALUES: &'static [Self]; + + /// `fixtures.eql_v2_`. + fn fixture_table_name() -> String { + format!("fixtures.eql_v2_{}", Self::PG_TYPE) + } + + /// SQL-literal rendering via `Display`. Override for types whose + /// `Display` form isn't a valid SQL literal (e.g. strings, dates). + fn to_sql_literal(value: Self) -> String { + value.to_string() + } + + /// Ground-truth result set for `WHERE col op pivot`. Default works + /// for any `Ord` scalar; override only for non-orderable types. + fn expected_forward(op: &str, pivot: Self) -> Vec { + let predicate: fn(Self, Self) -> bool = match op { + "=" => |a, b| a == b, + "<>" => |a, b| a != b, + "<" => |a, b| a < b, + "<=" => |a, b| a <= b, + ">" => |a, b| a > b, + ">=" => |a, b| a >= b, + other => panic!("expected_forward: unsupported operator {other}"), + }; + let mut values: Vec = Self::FIXTURE_VALUES + .iter() + .copied() + .filter(|v| predicate(*v, pivot)) + .collect(); + values.sort(); + values + } +} + +impl ScalarType for i32 { + const PG_TYPE: &'static str = "int4"; + /// Single-sourced from `tasks/codegen/types/int4.toml` `[fixture] values` + /// via the generated `fixtures::int4_values::VALUES` const — the same list + /// the fixture generator encrypts, so the oracle cannot drift from the + /// fixture. Spans the negative boundary, the i32 signed extremes, and zero. + const FIXTURE_VALUES: &'static [i32] = crate::fixtures::int4_values::VALUES; +} + +/// Per-domain capability + payload shape. Storage carries no terms, `Eq` +/// adds `hm`, `Ord`/`OrdOre` add `ob`. `Ord` and `OrdOre` are deliberate +/// twins — same operator surface, different SQL domain names — for the +/// scheme-explicit vs converged-name migration story. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Variant { + Storage, + Eq, + Ord, + OrdOre, +} + +impl Variant { + /// Every variant the family currently materialises, in declaration + /// order. Tests iterate over this rather than hand-listing variants + /// so adding a future variant requires no test edit. + pub const ALL: &'static [Variant] = + &[Variant::Storage, Variant::Eq, Variant::Ord, Variant::OrdOre]; + + pub const fn suffix(self) -> &'static str { + match self { + Variant::Storage => "", + Variant::Eq => "_eq", + Variant::Ord => "_ord", + Variant::OrdOre => "_ord_ore", + } + } + + /// Term key the variant requires on its CHECK constraint. `Storage` + /// requires nothing beyond the envelope; `Eq` requires `hm`; + /// `Ord` / `OrdOre` require `ob`. Read by tests that need to know + /// "what term does this variant carry?" — not by payload builders; + /// see `PLACEHOLDER_PAYLOAD`. + pub const fn required_term(self) -> Option<&'static str> { + match self { + Variant::Storage => None, + Variant::Eq => Some("hm"), + Variant::Ord | Variant::OrdOre => Some("ob"), + } + } + + /// Top-level JSONB keys the variant's domain CHECK requires. + /// Storage requires the EQL envelope (`v`, `i`, `c`); ord-capable + /// variants additionally require their term key (`hm` / `ob`). The + /// matrix `payload_check` arm iterates this to assert each key's + /// absence is rejected at the cast. + pub fn payload_required_keys(self) -> impl Iterator { + ["v", "i", "c"].into_iter().chain(self.required_term()) + } + + pub const fn supports_eq(self) -> bool { + !matches!(self, Variant::Storage) + } + + pub const fn supports_ord(self) -> bool { + matches!(self, Variant::Ord | Variant::OrdOre) + } + + /// Function name of the discriminating extractor for this variant, + /// or `None` if the variant carries no extractor (`Storage`). Returns + /// just the function name — call sites append `(column)` themselves so + /// the accessor is decoupled from any specific column-naming + /// convention. `Eq` resolves to `eql_v2.eq_term`; `Ord` and `OrdOre` + /// both resolve to `eql_v2.ord_term`. + pub const fn extractor_fn(self) -> Option<&'static str> { + match self { + Variant::Storage => None, + Variant::Eq => Some("eql_v2.eq_term"), + Variant::Ord | Variant::OrdOre => Some("eql_v2.ord_term"), + } + } +} + +/// Runtime spec built from `(T, Variant)`. The matrix macro consumes +/// this; nothing here is `const` because `sql_domain` is derived via +/// `format!` from `T::PG_TYPE`. +#[derive(Debug, Clone)] +pub struct ScalarDomainSpec { + pub sql_domain: String, + pub variant: Variant, +} + +impl ScalarDomainSpec { + pub fn new(variant: Variant) -> Self { + Self { + sql_domain: format!("eql_v2_{}{}", T::PG_TYPE, variant.suffix()), + variant, + } + } + + pub fn supports_eq(&self) -> bool { + self.variant.supports_eq() + } + + pub fn supports_ord(&self) -> bool { + self.variant.supports_ord() + } + + pub fn extractor_fn(&self) -> Option<&'static str> { + self.variant.extractor_fn() + } +} + +/// SQL string-literal escaping for direct interpolation. +pub fn sql_string_literal(value: &str) -> String { + format!("'{}'", value.replace('\'', "''")) +} + +/// `a op b` and `b op' a` return the same row set when `op'` is the +/// commutator of `op`. Used by the cross-shape arm when the column moves +/// to the right operand. +pub fn commute_op(op: &str) -> &'static str { + match op { + "=" => "=", + "<>" => "<>", + "<" => ">", + "<=" => ">=", + ">" => "<", + ">=" => "<=", + other => panic!("commute_op: unsupported operator {other}"), + } +} + +/// Fetch the payload row keyed by `plaintext` from `T`'s fixture table. +pub async fn fetch_fixture_payload(pool: &PgPool, plaintext: T) -> Result { + let sql = format!( + "SELECT payload::text FROM {table} WHERE plaintext = {lit}", + table = T::fixture_table_name(), + lit = T::to_sql_literal(plaintext), + ); + sqlx::query_scalar(&sql) + .fetch_one(pool) + .await + .with_context(|| { + format!( + "fetching {} payload for plaintext={:?}", + T::fixture_table_name(), + plaintext + ) + }) +} + +/// Sorted plaintexts matching `predicate` against `T`'s fixture table. +async fn scalar_plaintexts_matching( + pool: &PgPool, + predicate: &str, +) -> Result> { + let sql = format!( + "SELECT plaintext FROM {table} WHERE {predicate} ORDER BY plaintext", + table = T::fixture_table_name(), + ); + let mut rows: Vec = sqlx::query_scalar(&sql) + .fetch_all(pool) + .await + .with_context(|| format!("running scalar plaintext query: {sql}"))?; + rows.sort(); + Ok(rows) +} + +/// Run `predicate` against `T`'s fixture; assert plaintexts equal `expected`. +pub async fn assert_scalar_plaintexts( + pool: &PgPool, + domain: &str, + op: &str, + predicate: &str, + expected: &[T], +) -> Result<()> { + let actual = scalar_plaintexts_matching::(pool, predicate).await?; + let mut want = expected.to_vec(); + want.sort(); + assert_eq!( + actual, want, + "domain={domain} operator={op} predicate={predicate} must match expected plaintexts" + ); + Ok(()) +} + +/// Unified raise-assertion: query must error and the message must contain +/// `expected_msg`. Covers blocker raises (`expected_msg = "operator X is +/// not supported for {domain}"`) and native-operator absence +/// (`"operator does not exist"`). Bind slots are `Option<&str>`: `Some` +/// = bind the payload, `None` = bind NULL. +pub async fn assert_raises( + pool: &PgPool, + sql: &str, + binds: &[Option<&str>], + expected_msg: &str, +) -> Result<()> { + let mut q = sqlx::query(sql); + for b in binds { + q = q.bind(*b); + } + let result = q.fetch_one(pool).await; + let err = match result { + Ok(_) => bail!("SQL must raise: {sql}"), + Err(e) => e.to_string(), + }; + if !err.contains(expected_msg) { + bail!("SQL={sql} expected error containing {expected_msg:?}, got {err}"); + } + Ok(()) +} + +/// Unified NULL-result assertion: the query must succeed and return NULL. +/// Used for supported operators where STRICT semantics propagate NULL. +pub async fn assert_null(pool: &PgPool, sql: &str, binds: &[Option<&str>]) -> Result<()> { + let mut q = sqlx::query_scalar::<_, Option>(sql); + for b in binds { + q = q.bind(*b); + } + let result: Option = q + .fetch_one(pool) + .await + .with_context(|| format!("running null-result assertion: {sql}"))?; + if result.is_some() { + bail!("SQL={sql} with NULL operand must yield NULL, got {result:?}"); + } + Ok(()) +} + +/// Blocker error message — the contract every encrypted-domain blocker +/// must satisfy regardless of arg shape or NULL configuration. +pub fn blocker_msg(domain: &str, op: &str) -> String { + format!("operator {op} is not supported for {domain}") +} diff --git a/tests/sqlx/tests/aggregate_tests.rs b/tests/sqlx/tests/aggregate_tests.rs index 9306df26..f942a51e 100644 --- a/tests/sqlx/tests/aggregate_tests.rs +++ b/tests/sqlx/tests/aggregate_tests.rs @@ -1,14 +1,20 @@ //! Aggregate function tests //! -//! Tests COUNT, MAX, MIN with encrypted data including eql_v2.min() and eql_v2.max() +//! Covers native `COUNT` / `GROUP BY` on `eql_v2_encrypted` and the +//! `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates +//! on the composite type. Per-domain aggregates +//! (`eql_v2.min(eql_v2__ord)` etc.) are additionally covered by the +//! encrypted-domain test matrix (`tests/sqlx/src/matrix.rs`, instantiated per +//! scalar type from `tests/sqlx/tests/encrypted_domain/scalars/.rs`). use anyhow::Result; use sqlx::PgPool; #[sqlx::test] async fn count_aggregate_on_encrypted_column(pool: PgPool) -> Result<()> { - // Test: COUNT works on encrypted columns (counts non-NULL encrypted values) - + // COUNT on an `eql_v2_encrypted` column is PostgreSQL-native — no + // aggregate declaration is required. Pin that it still counts non-NULL + // encrypted rows on the legacy composite type. let count: i64 = sqlx::query_scalar("SELECT COUNT(e) FROM ore") .fetch_one(&pool) .await?; @@ -68,9 +74,9 @@ async fn min_aggregate_on_encrypted_column(pool: PgPool) -> Result<()> { #[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))] async fn group_by_with_encrypted_column(pool: PgPool) -> Result<()> { - // Test: GROUP BY works with encrypted data - // Fixture creates 3 distinct encrypted records, each unique - + // GROUP BY on `eql_v2_encrypted` works natively against the fixture's + // distinct payloads. Pin that grouping by an encrypted column returns + // the expected number of groups. let group_count: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM ( SELECT e, COUNT(*) FROM encrypted GROUP BY e diff --git a/tests/sqlx/tests/constraint_tests.rs b/tests/sqlx/tests/constraint_tests.rs index baf51a13..7e2871c1 100644 --- a/tests/sqlx/tests/constraint_tests.rs +++ b/tests/sqlx/tests/constraint_tests.rs @@ -3,6 +3,7 @@ //! Tests UNIQUE, NOT NULL, CHECK constraints on encrypted columns use anyhow::Result; +use eql_tests::assert_db_error; use sqlx::PgPool; #[sqlx::test(fixtures(path = "../fixtures", scripts("constraint_tables")))] @@ -25,17 +26,15 @@ async fn unique_constraint_on_encrypted_column(pool: PgPool) -> Result<()> { assert_eq!(count, 1, "Should have 1 record after insert"); // Attempt duplicate insert - let result = sqlx::query( + let err = sqlx::query( "INSERT INTO constrained (unique_field, not_null_field, check_field) VALUES (create_encrypted_json(1, 'hm'), create_encrypted_json(2, 'hm'), create_encrypted_json(2, 'hm'))" ) .execute(&pool) - .await; + .await + .expect_err("UNIQUE constraint should prevent duplicate"); - assert!( - result.is_err(), - "UNIQUE constraint should prevent duplicate" - ); + assert_db_error(&err, "23505", Some("constrained_unique_field_key")); // Verify count unchanged after failed insert let count_after: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM constrained") @@ -51,14 +50,17 @@ async fn unique_constraint_on_encrypted_column(pool: PgPool) -> Result<()> { async fn not_null_constraint_on_encrypted_column(pool: PgPool) -> Result<()> { // Test: NOT NULL constraint enforced (2 assertions) - let result = sqlx::query( + let err = sqlx::query( "INSERT INTO constrained (unique_field) VALUES (create_encrypted_json(2, 'hm'))", ) .execute(&pool) - .await; + .await + .expect_err("NOT NULL constraint should prevent NULL"); - assert!(result.is_err(), "NOT NULL constraint should prevent NULL"); + // NOT NULL is a column attribute, not a named constraint — `constraint()` + // returns None, so only pin the SQLSTATE. + assert_db_error(&err, "23502", None); // Verify no records were inserted let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM constrained") @@ -74,7 +76,7 @@ async fn not_null_constraint_on_encrypted_column(pool: PgPool) -> Result<()> { async fn check_constraint_on_encrypted_column(pool: PgPool) -> Result<()> { // Test: CHECK constraint enforced (2 assertions) - let result = sqlx::query( + let err = sqlx::query( "INSERT INTO constrained (unique_field, not_null_field, check_field) VALUES ( create_encrypted_json(3, 'hm'), @@ -83,9 +85,10 @@ async fn check_constraint_on_encrypted_column(pool: PgPool) -> Result<()> { )", ) .execute(&pool) - .await; + .await + .expect_err("CHECK constraint should prevent NULL"); - assert!(result.is_err(), "CHECK constraint should prevent NULL"); + assert_db_error(&err, "23514", Some("constrained_check_field_check")); // Verify no records were inserted let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM constrained") @@ -199,15 +202,13 @@ async fn foreign_key_constraint_with_encrypted(pool: PgPool) -> Result<()> { ); // Attempt to insert child with different encrypted value (should fail FK check) - let different_insert_result = + let err = sqlx::query("INSERT INTO child (id, parent_id) VALUES (2, create_encrypted_json(2, 'hm'))") .execute(&pool) - .await; + .await + .expect_err("FK constraint should reject non-existent parent reference"); - assert!( - different_insert_result.is_err(), - "FK constraint should reject non-existent parent reference" - ); + assert_db_error(&err, "23503", Some("child_parent_id_fkey")); // Verify child count unchanged let final_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM child") @@ -258,14 +259,17 @@ async fn add_encrypted_constraint_prevents_invalid_data(pool: PgPool) -> Result< .await?; // Now attempt to insert invalid data - should fail - let result = sqlx::query("INSERT INTO encrypted (e) VALUES ('{}'::jsonb::eql_v2_encrypted)") + let err = sqlx::query("INSERT INTO encrypted (e) VALUES ('{}'::jsonb::eql_v2_encrypted)") .execute(&pool) - .await; + .await + .expect_err("Constraint should prevent insert of invalid eql_v2_encrypted (empty JSONB)"); - assert!( - result.is_err(), - "Constraint should prevent insert of invalid eql_v2_encrypted (empty JSONB)" - ); + // `check_encrypted` RAISEs on invalid payloads, so the CHECK constraint + // propagates the underlying SQLSTATE (P0001 raise_exception) rather than + // 23514. The raise message identifies which check failed (missing v, + // invalid v, missing root c/sv, etc.) — that's the value over a bare + // `is_err()` check. + assert_db_error(&err, "P0001", None); // Verify count unchanged after failed insert let final_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM encrypted") @@ -290,14 +294,17 @@ async fn remove_encrypted_constraint_allows_invalid_data(pool: PgPool) -> Result .await?; // Verify constraint is working - invalid data should be rejected - let result = sqlx::query("INSERT INTO encrypted (e) VALUES ('{}'::jsonb::eql_v2_encrypted)") + let err = sqlx::query("INSERT INTO encrypted (e) VALUES ('{}'::jsonb::eql_v2_encrypted)") .execute(&pool) - .await; + .await + .expect_err("Constraint should prevent insert of invalid eql_v2_encrypted"); - assert!( - result.is_err(), - "Constraint should prevent insert of invalid eql_v2_encrypted" - ); + // `check_encrypted` RAISEs on invalid payloads, so the CHECK constraint + // propagates the underlying SQLSTATE (P0001 raise_exception) rather than + // 23514. The raise message identifies which check failed (missing v, + // invalid v, missing root c/sv, etc.) — that's the value over a bare + // `is_err()` check. + assert_db_error(&err, "P0001", None); // Remove the constraint sqlx::query("SELECT eql_v2.remove_encrypted_constraint('encrypted', 'e')") @@ -342,18 +349,21 @@ async fn version_metadata_validation_on_insert(pool: PgPool) -> Result<()> { .fetch_one(&pool) .await?; - // Attempt to insert without version field - should fail - let result = sqlx::query(&format!( - "INSERT INTO encrypted (e) VALUES ('{}'::jsonb::eql_v2_encrypted)", - encrypted_without_version - )) - .execute(&pool) - .await; + // Attempt to insert without version field - should fail. Bind the payload + // rather than format!-interpolate it — JSONB strings can carry quotes + // and would otherwise need hand-rolled escaping. + let err = sqlx::query("INSERT INTO encrypted (e) VALUES ($1::jsonb::eql_v2_encrypted)") + .bind(&encrypted_without_version) + .execute(&pool) + .await + .expect_err("Insert should fail when version field is missing"); - assert!( - result.is_err(), - "Insert should fail when version field is missing" - ); + // `check_encrypted` RAISEs on invalid payloads, so the CHECK constraint + // propagates the underlying SQLSTATE (P0001 raise_exception) rather than + // 23514. The raise message identifies which check failed (missing v, + // invalid v, missing root c/sv, etc.) — that's the value over a bare + // `is_err()` check. + assert_db_error(&err, "P0001", None); // Create encrypted value with invalid version (v=1 instead of v=2) let encrypted_invalid_version: String = @@ -362,17 +372,18 @@ async fn version_metadata_validation_on_insert(pool: PgPool) -> Result<()> { .await?; // Attempt to insert with invalid version - should fail - let result = sqlx::query(&format!( - "INSERT INTO encrypted (e) VALUES ('{}'::jsonb::eql_v2_encrypted)", - encrypted_invalid_version - )) - .execute(&pool) - .await; + let err = sqlx::query("INSERT INTO encrypted (e) VALUES ($1::jsonb::eql_v2_encrypted)") + .bind(&encrypted_invalid_version) + .execute(&pool) + .await + .expect_err("Insert should fail when version field is invalid (v=1)"); - assert!( - result.is_err(), - "Insert should fail when version field is invalid (v=1)" - ); + // `check_encrypted` RAISEs on invalid payloads, so the CHECK constraint + // propagates the underlying SQLSTATE (P0001 raise_exception) rather than + // 23514. The raise message identifies which check failed (missing v, + // invalid v, missing root c/sv, etc.) — that's the value over a bare + // `is_err()` check. + assert_db_error(&err, "P0001", None); // Insert with valid version (v=2) should succeed sqlx::query("INSERT INTO encrypted (e) VALUES (create_encrypted_json(1))") @@ -455,17 +466,17 @@ async fn check_encrypted_accepts_stevec_payload(pool: PgPool) -> Result<()> { ); // Sanity-check the negative path: a root that carries neither `c` nor - // `sv` is still rejected with the updated error message. - let neither: Result = sqlx::query_scalar( + // `sv` is still rejected with the updated error message. Calling + // `check_encrypted` directly RAISEs (not a CHECK constraint), so + // SQLSTATE P0001 (raise_exception) rather than 23514. + let err = sqlx::query_scalar::<_, bool>( "SELECT eql_v2.check_encrypted('{\"v\": 2, \"i\": {\"t\": \"users\", \"c\": \"x\"}}'::jsonb)", ) .fetch_one(&pool) - .await; + .await + .expect_err("payload with neither c nor sv at root must be rejected"); - assert!( - neither.is_err(), - "payload with neither c nor sv at root must be rejected" - ); + assert_db_error(&err, "P0001", None); Ok(()) } diff --git a/tests/sqlx/tests/encrypted_domain.rs b/tests/sqlx/tests/encrypted_domain.rs new file mode 100644 index 00000000..0ac40b22 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain.rs @@ -0,0 +1,12 @@ +//! Umbrella integration-test binary for the encrypted-domain type family. +//! +//! Cargo's default discovery picks this file up as a test binary; the +//! module tree under `encrypted_domain/` is pulled in via the `#[path]` +//! attributes below. Legacy tests under `tests/sqlx/tests/*.rs` continue +//! to compile as their own separate binaries. + +#[path = "encrypted_domain/family/mod.rs"] +mod family; + +#[path = "encrypted_domain/scalars/mod.rs"] +mod scalars; diff --git a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs new file mode 100644 index 00000000..37347228 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs @@ -0,0 +1,252 @@ +//! Global guard for the encrypted-domain inline-critical SQL surface. +//! +//! `tasks/pin_search_path.sql` runs after every build and pins a fixed +//! `search_path` on every `eql_v2` function — except the inline-critical +//! ones, which must stay unpinned so the planner can inline them and the +//! documented functional indexes (`eql_v2.eq_term(col)`, +//! `eql_v2.ord_term(col)`, …) engage. +//! +//! The encrypted-domain family is skipped by a structural rule anchored +//! on the *identity predicate*: a `LANGUAGE sql`, `IMMUTABLE` function +//! taking at least one argument typed as a jsonb-backed DOMAIN in +//! `public` named `eql_v2_*`. The identity predicate is +//! proconfig-independent — it describes what a function intrinsically +//! IS, not whether it has been pinned. +//! +//! This test is the global net for that rule. It uses the identity +//! predicate VERBATIM and appends one offender filter: +//! `proconfig IS NOT NULL` — a function matching the family shape that +//! nonetheless carries a pinned `search_path`. It asserts that offender +//! set is empty. Because the test and the pin-loop skip clause share the +//! identity predicate exactly (the guard only adds the offender filter), +//! they cannot drift apart on identity. +//! +//! A non-empty result means `pin_search_path.sql` pinned an +//! inline-critical encrypted-domain function — index engagement is +//! silently broken for that type. This is not int4-specific: a missed +//! skip for ANY encrypted-domain type — present or future — fails here, +//! so a new type's author does not have to remember to add a per-type +//! inlinability assertion. + +use anyhow::Result; +use sqlx::PgPool; + +#[sqlx::test] +async fn no_encrypted_domain_inline_critical_function_is_pinned(pool: PgPool) -> Result<()> { + // The identity predicate is shared verbatim with the structural skip + // clause in tasks/pin_search_path.sql: LANGUAGE sql, IMMUTABLE, and + // taking at least one argument typed as a `public.eql_v2_*` domain + // over jsonb. It is proconfig-independent. The ONLY addition here is + // the offender filter `p.proconfig IS NOT NULL` — a function that + // matches the identity predicate but DID get pinned. That set must be + // empty. + let offenders: Vec<(String, String)> = sqlx::query_as( + r#" + SELECT p.oid::regprocedure::text AS signature, + array_to_string(p.proconfig, ', ') AS proconfig + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + JOIN pg_catalog.pg_language l ON l.oid = p.prolang + WHERE n.nspname = 'eql_v2' + AND l.lanname = 'sql' + AND p.provolatile = 'i' + AND p.proconfig IS NOT NULL + AND EXISTS ( + SELECT 1 + FROM pg_catalog.unnest(p.proargtypes::oid[]) AS arg(typ) + JOIN pg_catalog.pg_type dt ON dt.oid = arg.typ + JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace + JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype + WHERE dt.typtype = 'd' + AND dn.nspname = 'public' + AND dt.typname LIKE 'eql_v2\_%' + AND bt.typname = 'jsonb' + ) + ORDER BY signature + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + offenders.is_empty(), + "pin_search_path.sql pinned {} inline-critical encrypted-domain \ + SQL function(s) — index engagement is silently broken. \ + Offenders (signature → proconfig):\n{}", + offenders.len(), + offenders + .iter() + .map(|(sig, cfg)| format!(" {sig} → {cfg}")) + .collect::>() + .join("\n"), + ); + Ok(()) +} + +#[sqlx::test] +async fn every_inline_critical_eligible_domain_has_inline_critical_functions( + pool: PgPool, +) -> Result<()> { + // Stronger than a bare `count > 0`: if a future change accidentally + // narrows the structural predicate (e.g. hard-codes `eql_v2_int4_%`), + // a `count > 0` assertion would still pass while int8/bool/date + // domains silently lose inline-critical coverage. Instead, assert + // that EVERY inline-critical-eligible domain (any `public.eql_v2_*` + // domain over jsonb that carries a capability suffix — `_eq`, `_ord`, + // `_ord_ore`) appears as an argument type of at least one + // inline-critical function. + // + // Storage-only variants (the bare `eql_v2_` domain, with no + // capability suffix) intentionally have NO inline-critical surface + // and are excluded from the eligibility set. + let unbound: Vec = sqlx::query_scalar( + r#" + SELECT dt.typname + FROM pg_catalog.pg_type dt + JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace + JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype + WHERE dt.typtype = 'd' + AND dn.nspname = 'public' + AND bt.typname = 'jsonb' + AND dt.typname LIKE 'eql_v2\_%' + AND ( + dt.typname LIKE '%\_eq' + OR dt.typname LIKE '%\_ord' + OR dt.typname LIKE '%\_ord\_ore' + ) + AND NOT EXISTS ( + SELECT 1 + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + JOIN pg_catalog.pg_language l ON l.oid = p.prolang + WHERE n.nspname = 'eql_v2' + AND l.lanname = 'sql' + AND p.provolatile = 'i' + AND dt.oid = ANY(p.proargtypes::oid[]) + ) + ORDER BY dt.typname + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + unbound.is_empty(), + "the following inline-critical-eligible domains have NO \ + inline-critical function bound — index engagement is broken \ + for them: {unbound:?}" + ); + Ok(()) +} + +/// Encrypted-domain blockers must be `LANGUAGE plpgsql` and **never** +/// `STRICT`. A LANGUAGE sql blocker is inlinable (the planner can elide +/// it when the result is provably unused); a STRICT blocker returns NULL +/// on a NULL argument, silently bypassing the RAISE. Either footgun +/// re-enables an operator the storage variant exists to block. +/// +/// This is a structural guard that does NOT depend on `eql_v2.lints()` — +/// a regression to the lint catalog itself cannot hide a regression to +/// the blocker surface from this test. +#[sqlx::test] +async fn encrypted_domain_blockers_are_plpgsql_and_non_strict(pool: PgPool) -> Result<()> { + let offenders: Vec<(String, String, bool)> = sqlx::query_as( + r#" + SELECT p.oid::regprocedure::text AS signature, + l.lanname, + p.proisstrict + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + JOIN pg_catalog.pg_language l ON l.oid = p.prolang + WHERE n.nspname = 'eql_v2' + AND (p.prosrc LIKE '%encrypted_domain_unsupported_bool%' + OR p.prosrc LIKE '%is not supported for%') + AND EXISTS ( + SELECT 1 + FROM pg_catalog.unnest(p.proargtypes::oid[]) AS arg(typ) + JOIN pg_catalog.pg_type dt ON dt.oid = arg.typ + JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace + JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype + WHERE dt.typtype = 'd' + AND dn.nspname = 'public' + AND dt.typname LIKE 'eql_v2\_%' + AND bt.typname = 'jsonb' + ) + AND (l.lanname <> 'plpgsql' OR p.proisstrict) + ORDER BY signature + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + offenders.is_empty(), + "encrypted-domain blockers must be LANGUAGE plpgsql and non-STRICT. \ + Offenders (signature, language, isstrict): {offenders:#?}" + ); + Ok(()) +} + +/// No `eql_v2_*` domain may be derived from another `eql_v2_*` domain — +/// operators resolve against the ultimate base type, so a derived domain +/// inherits jsonb's operator surface and not the base domain's blockers. +/// All family domains must be defined directly over jsonb. +#[sqlx::test] +async fn no_eql_v2_domain_is_derived_from_another_eql_v2_domain(pool: PgPool) -> Result<()> { + let offenders: Vec<(String, String)> = sqlx::query_as( + r#" + SELECT format('%I.%I', dn.nspname, dt.typname) AS derived, + format('%I.%I', bn.nspname, bt.typname) AS base + FROM pg_catalog.pg_type dt + JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace + JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype + JOIN pg_catalog.pg_namespace bn ON bn.oid = bt.typnamespace + WHERE dt.typtype = 'd' + AND dn.nspname = 'public' + AND dt.typname LIKE 'eql_v2\_%' + AND bt.typtype = 'd' + AND bt.typname LIKE 'eql_v2\_%' + ORDER BY derived + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + offenders.is_empty(), + "eql_v2_* domains must be defined directly over jsonb, not derived \ + from another eql_v2_* domain. Offenders (derived, base): {offenders:#?}" + ); + Ok(()) +} + +/// No operator class may be declared `FOR TYPE` on an `eql_v2_*` domain. +/// Opclasses on domains bypass the operator-resolution that storage +/// blockers depend on. The recommended index pattern is a functional +/// index on the extractor (e.g. `eql_v2.eq_term(col)`). +#[sqlx::test] +async fn no_opclass_targets_eql_v2_domain(pool: PgPool) -> Result<()> { + let offenders: Vec<(String, String)> = sqlx::query_as( + r#" + SELECT format('%I.%I', cn.nspname, oc.opcname) AS opclass, + format('%I.%I', tn.nspname, t.typname) AS for_type + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_type t ON t.oid = oc.opcintype + JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace + JOIN pg_catalog.pg_namespace cn ON cn.oid = oc.opcnamespace + WHERE t.typtype = 'd' + AND tn.nspname = 'public' + AND t.typname LIKE 'eql_v2\_%' + ORDER BY opclass + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + offenders.is_empty(), + "no operator class may target an eql_v2_* domain — use a functional \ + index on the extractor instead. Offenders (opclass, for_type): {offenders:#?}" + ); + Ok(()) +} diff --git a/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs b/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs new file mode 100644 index 00000000..8dc2e049 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs @@ -0,0 +1,75 @@ +//! Structural guard for the blocked native-jsonb operator enumeration. +//! +//! The storage-only domains (`eql_v2_int4`, future scalars) promise that +//! *every* native jsonb operator is blocked, so an encrypted column can never +//! fall through to plaintext-jsonb semantics. That promise rests on three +//! hand-maintained lists in `tasks/codegen/operator_surface.py` +//! (`SYMMETRIC_OPERATORS`, `PATH_OPERATORS`, `BLOCKER_ONLY_OPERATORS`), whose +//! union is `KNOWN_JSONB_OPERATORS`. +//! +//! Those lists are an *enumeration*, not a structural guarantee: a future PG +//! version could add a jsonb operator that nobody adds here, and it would +//! silently route to native jsonb behaviour. This test closes that gap by +//! asking the live catalog which operators actually touch `jsonb` and failing +//! if any symbol is absent from the known union. +//! +//! Source of truth: `tasks/codegen/operator_surface.py::KNOWN_JSONB_OPERATORS` +//! (asserted complete by `tasks/codegen/test_operator_surface.py`). The set +//! below is hardcoded — the lowest-friction bridge from a Python constant to a +//! Rust test — and must be kept in sync with that module. If you add an +//! operator there, add it here; the Python test pins the union so the two can +//! only drift in this file. + +use anyhow::Result; +use sqlx::PgPool; + +/// Mirror of `KNOWN_JSONB_OPERATORS` in +/// `tasks/codegen/operator_surface.py`. Keep in sync with that module. +const KNOWN_JSONB_OPERATORS: &[&str] = &[ + // symmetric (supported wrappers) + "=", "<>", "<", "<=", ">", ">=", "@>", "<@", // + // path + "->", "->>", // + // blocker-only native jsonb fallbacks + "?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||", +]; + +#[sqlx::test] +async fn every_native_jsonb_operator_is_known_to_the_generator(pool: PgPool) -> Result<()> { + // Distinct operator symbols whose left OR right argument is `jsonb`. This + // is the full surface a value typed as a jsonb-backed domain can reach via + // operator resolution against the ultimate base type. + let native: Vec = sqlx::query_scalar( + r#" + SELECT DISTINCT o.oprname + FROM pg_catalog.pg_operator o + WHERE o.oprleft = 'jsonb'::regtype + OR o.oprright = 'jsonb'::regtype + ORDER BY 1 + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + !native.is_empty(), + "expected pg_operator to expose jsonb operators; query returned none" + ); + + let missing: Vec<&String> = native + .iter() + .filter(|sym| !KNOWN_JSONB_OPERATORS.contains(&sym.as_str())) + .collect(); + + assert!( + missing.is_empty(), + "PostgreSQL exposes jsonb operator(s) not enumerated in \ + tasks/codegen/operator_surface.py (KNOWN_JSONB_OPERATORS): {missing:#?}. \ + A storage-only encrypted domain would route these to native \ + plaintext-jsonb semantics instead of an EQL blocker. Add each symbol \ + to the appropriate list in operator_surface.py (and to the mirror in \ + this test) and regenerate the SQL surface." + ); + + Ok(()) +} diff --git a/tests/sqlx/tests/encrypted_domain/family/mod.rs b/tests/sqlx/tests/encrypted_domain/family/mod.rs new file mode 100644 index 00000000..6622e0f8 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/family/mod.rs @@ -0,0 +1,7 @@ +//! Family-level tests: invariants that apply across every scalar type in +//! the encrypted-domain family (not int4-specific). + +pub mod inlinability; +pub mod jsonb_operator_surface; +pub mod mutations; +pub mod support; diff --git a/tests/sqlx/tests/encrypted_domain/family/mutations.rs b/tests/sqlx/tests/encrypted_domain/family/mutations.rs new file mode 100644 index 00000000..2745e1ae --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/family/mutations.rs @@ -0,0 +1,428 @@ +//! Negative controls (mutation tests) for the scalar-domain matrix. +//! +//! A green matrix proves the SUT behaves correctly *today*, but it cannot +//! prove the matrix arms would catch a regression — an arm could be +//! vacuous and still pass. Each test here applies one surgical mutation to +//! the installed `eql_v2` schema and asserts that the property a specific +//! matrix arm guards now flips. If a mutation does NOT flip the property, +//! that arm has no teeth. +//! +//! Mechanism: `CREATE OR REPLACE FUNCTION` keeps the function oid, so the +//! operators / aggregates that reference it keep resolving to the (now +//! mutated) body — that's what lets us re-route a comparison or disable a +//! blocker without touching operator definitions. Each `#[sqlx::test]` +//! gets its own fresh database (EQL pre-installed via the auto-applied +//! `migrations/001_install_eql.sql`), so the mutation is discarded when the +//! per-test DB is dropped — no cleanup, no rebuild. +//! +//! Pattern per test: assert the baseline property holds, mutate, assert it +//! now breaks. The baseline assertion is load-bearing — it proves the +//! probe is non-vacuous before the mutation. + +use anyhow::{ensure, Result}; +use eql_tests::{ + assert_null, assert_raises, blocker_msg, fetch_fixture_payload, ScalarType, PLACEHOLDER_PAYLOAD, +}; +use sqlx::PgPool; + +/// Apply one DDL mutation to the installed schema. +async fn mutate(pool: &PgPool, ddl: &str) -> Result<()> { + sqlx::query(ddl).execute(pool).await?; + Ok(()) +} + +// 1. Storage `=` blocker — disabling it lets the storage variant compare +// equal. Proves the `blocker` arm (and `typed_column_blocker`) would +// catch a blocker that silently stopped raising. +#[sqlx::test] +async fn disabling_storage_eq_blocker_flips_blocker_arm(pool: PgPool) -> Result<()> { + let sql = "SELECT $1::jsonb::eql_v2_int4 = $2::jsonb::eql_v2_int4"; + + // Baseline: the storage `=` blocker raises. + assert_raises( + &pool, + sql, + &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], + &blocker_msg("eql_v2_int4", "="), + ) + .await?; + + // Mutation: replace the plpgsql blocker with an inlinable SQL body that + // returns true. CREATE OR REPLACE keeps the oid, so the `=` operator on + // (eql_v2_int4, eql_v2_int4) now resolves to this no-raise body. + mutate( + &pool, + "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4, b eql_v2_int4) \ + RETURNS boolean LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$ SELECT true $$", + ) + .await?; + + // Post: the operator returns true instead of raising — arm has teeth. + let result: Option = sqlx::query_scalar(sql) + .bind(PLACEHOLDER_PAYLOAD) + .bind(PLACEHOLDER_PAYLOAD) + .fetch_one(&pool) + .await?; + ensure!( + result == Some(true), + "after disabling the storage `=` blocker, `=` must return true (got {result:?})" + ); + Ok(()) +} + +// 2. Planner-metadata RESTRICT selectivity — unsetting it makes the +// `planner_metadata` arm's `oprrest <> 0` check report false. (COMMUTATOR +// cannot be unset via ALTER, so RESTRICT is the pragmatic teeth probe for +// this arm.) +#[sqlx::test] +async fn unsetting_restrict_flips_planner_metadata_arm(pool: PgPool) -> Result<()> { + async fn restrict_present(pool: &PgPool) -> Result { + let present: bool = sqlx::query_scalar( + r#" + SELECT o.oprrest::oid <> 0 + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft + JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright + WHERE o.oprname = '=' + AND lt.typname = 'eql_v2_int4_ord' + AND rt.typname = 'eql_v2_int4_ord' + "#, + ) + .fetch_one(pool) + .await?; + Ok(present) + } + + // Baseline: `=` on (ord, ord) declares a RESTRICT estimator. + ensure!( + restrict_present(&pool).await?, + "baseline: `=` on eql_v2_int4_ord must declare a RESTRICT estimator" + ); + + // Mutation: unset RESTRICT. DROP OPERATOR would hit COMMUTATOR/NEGATOR + // dependency links; ALTER ... SET (RESTRICT = NONE) avoids that. + mutate( + &pool, + "ALTER OPERATOR = (eql_v2_int4_ord, eql_v2_int4_ord) SET (RESTRICT = NONE)", + ) + .await?; + + // Post: the planner-metadata check now reports false — arm has teeth. + ensure!( + !restrict_present(&pool).await?, + "after SET (RESTRICT = NONE), the planner-metadata check must report false" + ); + Ok(()) +} + +// 3. `_ord` equality must route through `ord_term` (`ob`), never HMAC. +// Rerouting it through `hmac_256` (`hm`) over hm-stripped rows makes `=` +// stop matching. Proves the `ord_routes_through_ob` arm has teeth. +#[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] +async fn rerouting_ord_eq_through_hm_flips_ord_routes_arm(pool: PgPool) -> Result<()> { + // Strip `hm` per-row inline; the `_ord` CHECK only requires `ob`, so the + // cast still succeeds. The pivot is likewise hm-stripped. + let pivot: i32 = 42; + let pivot_payload: String = sqlx::query_scalar(&format!( + "SELECT (payload - 'hm')::text FROM fixtures.eql_v2_int4 WHERE plaintext = {pivot}", + )) + .fetch_one(&pool) + .await?; + + let count_sql = "SELECT count(*) FROM fixtures.eql_v2_int4 \ + WHERE (payload - 'hm')::eql_v2_int4_ord = $1::jsonb::eql_v2_int4_ord"; + + // Baseline: with `hm` stripped, `=` still matches the pivot via `ord_term` + // (the `ob` term survives) — exactly one row. + let baseline: i64 = sqlx::query_scalar(count_sql) + .bind(&pivot_payload) + .fetch_one(&pool) + .await?; + ensure!( + baseline == 1, + "baseline: `_ord` `=` must match exactly the pivot via ob with hm stripped (got {baseline})" + ); + + // Mutation: reroute `_ord` `=` through HMAC. `eql_v2.hmac_256(jsonb)` is + // STRICT and the `hm` key is absent, so it yields NULL and `=` matches + // nothing. + mutate( + &pool, + "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4_ord, b eql_v2_int4_ord) \ + RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ + AS $$ SELECT eql_v2.hmac_256(a::jsonb) = eql_v2.hmac_256(b::jsonb) $$", + ) + .await?; + + // Post: routing through the absent `hm` matches zero rows — arm has teeth. + let mutated: i64 = sqlx::query_scalar(count_sql) + .bind(&pivot_payload) + .fetch_one(&pool) + .await?; + ensure!( + mutated == 0, + "after rerouting `_ord` `=` through hm, it must match zero hm-stripped rows (got {mutated})" + ); + Ok(()) +} + +// 4. Supported `=` on `_eq` is STRICT — it must propagate NULL. Dropping +// STRICT (and returning non-NULL) makes `x = NULL` return a value. Proves +// the `supported_null` arm has teeth. +#[sqlx::test] +async fn dropping_strict_on_eq_flips_supported_null_arm(pool: PgPool) -> Result<()> { + let sql = "SELECT $1::jsonb::eql_v2_int4_eq = $2::jsonb::eql_v2_int4_eq"; + + // Baseline: STRICT `=` propagates NULL when one side is NULL. + assert_null(&pool, sql, &[Some(PLACEHOLDER_PAYLOAD), None]).await?; + + // Mutation: drop STRICT and return a constant non-NULL. CREATE OR REPLACE + // keeps the oid; the operator now ignores NULL semantics. + mutate( + &pool, + "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b eql_v2_int4_eq) \ + RETURNS boolean LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$ SELECT true $$", + ) + .await?; + + // Post: `x = NULL` returns true instead of NULL — arm has teeth. + let result: Option = sqlx::query_scalar(sql) + .bind(PLACEHOLDER_PAYLOAD) + .bind(Option::<&str>::None) + .fetch_one(&pool) + .await?; + ensure!( + result == Some(true), + "after dropping STRICT on `_eq` `=`, `x = NULL` must return true, not NULL (got {result:?})" + ); + Ok(()) +} + +// 5. Ord `<` correctness routes through `eql_v2.lt`. Turning `lt` into a +// blocker makes `<` raise — proving the ord `<` correctness arm has teeth. +// Crucially, ORDER BY routes through `ord_term`, NOT `<`, so it must stay +// green here. This is the #5-vs-#7 split: #5 attacks `<`, #7 attacks the +// sort key. Blocking `<` alone must not disturb ORDER BY. +#[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] +async fn blocking_lt_flips_lt_arm_but_not_order_by(pool: PgPool) -> Result<()> { + let lt_sql = "SELECT $1::jsonb::eql_v2_int4_ord < $2::jsonb::eql_v2_int4_ord"; + let order_by_sql = "SELECT plaintext FROM fixtures.eql_v2_int4 \ + ORDER BY eql_v2.ord_term(payload::eql_v2_int4_ord) ASC"; + + let mut ascending: Vec = ::FIXTURE_VALUES.to_vec(); + ascending.sort(); + + // Baseline: `<` works (no raise) and ORDER BY is plaintext-sorted. + let lt_baseline: Option = sqlx::query_scalar(lt_sql) + .bind(PLACEHOLDER_PAYLOAD) + .bind(PLACEHOLDER_PAYLOAD) + .fetch_one(&pool) + .await?; + ensure!( + lt_baseline.is_some(), + "baseline: `_ord` `<` must return a boolean (got {lt_baseline:?})" + ); + let order_baseline: Vec = sqlx::query_scalar(order_by_sql).fetch_all(&pool).await?; + ensure!( + order_baseline == ascending, + "baseline: ORDER BY ord_term ASC must be plaintext-sorted" + ); + + // Mutation: turn `eql_v2.lt(_ord, _ord)` into a blocker. Must be + // LANGUAGE plpgsql and non-STRICT so the RAISE always fires. + mutate( + &pool, + "CREATE OR REPLACE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b eql_v2_int4_ord) \ + RETURNS boolean LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE \ + AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<'); END; $$", + ) + .await?; + + // Post: `<` now raises — the ord `<` arm has teeth. + assert_raises( + &pool, + lt_sql, + &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], + &blocker_msg("eql_v2_int4_ord", "<"), + ) + .await?; + + // Post: ORDER BY is UNCHANGED — it routes through ord_term, not `<`. + // This is the whole point of separating #5 from #7. + let order_after: Vec = sqlx::query_scalar(order_by_sql).fetch_all(&pool).await?; + ensure!( + order_after == ascending, + "blocking `<` must NOT disturb ORDER BY (it routes through ord_term); got {order_after:?}" + ); + Ok(()) +} + +// 6. `_eq` equality must route through `eq_term` (`hm`), never ORE — the +// mirror of #3 for the eq path. Rerouting it through +// `ore_block_u64_8_256` (`ob`) over ob-stripped rows breaks equality. +// +// Two notes on why this is shaped differently from the plan's literal +// "returns 0 where forward expects 1": +// - The fixture payloads carry BOTH `hm` and `ob`, so rerouting `_eq` +// `=` through ORE on the RAW fixture would still match (both terms are +// injective per plaintext) — vacuous. Stripping `ob` forces the +// rerouted operator onto an absent term, exactly as #3 strips `hm`. +// - `ore_block_u64_8_256(jsonb)` RAISES on an absent `ob` ("Expected an +// ore index (ob)"), whereas `hmac_256(jsonb)` returns NULL on an absent +// `hm`. So the eq path breaks via a raise, not a 0-count. Either way the +// correct hm-routed equality matches and the rerouted one does not. +#[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] +async fn rerouting_eq_eq_through_ob_flips_eq_arm(pool: PgPool) -> Result<()> { + // Strip `ob` per-row inline; the `_eq` CHECK only requires `hm`, so the + // cast still succeeds. The pivot is likewise ob-stripped. + let pivot: i32 = 42; + let pivot_payload: String = sqlx::query_scalar(&format!( + "SELECT (payload - 'ob')::text FROM fixtures.eql_v2_int4 WHERE plaintext = {pivot}", + )) + .fetch_one(&pool) + .await?; + + let count_sql = "SELECT count(*) FROM fixtures.eql_v2_int4 \ + WHERE (payload - 'ob')::eql_v2_int4_eq = $1::jsonb::eql_v2_int4_eq"; + + // Baseline: with `ob` stripped, `=` still matches the pivot via `eq_term` + // (the `hm` term survives) — exactly one row. + let baseline: i64 = sqlx::query_scalar(count_sql) + .bind(&pivot_payload) + .fetch_one(&pool) + .await?; + ensure!( + baseline == 1, + "baseline: `_eq` `=` must match exactly the pivot via hm with ob stripped (got {baseline})" + ); + + // Mutation: reroute `_eq` `=` through ORE. The `ob` key is absent, so + // `eql_v2.ore_block_u64_8_256(jsonb)` raises rather than matching. + mutate( + &pool, + "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b eql_v2_int4_eq) \ + RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ + AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) = eql_v2.ore_block_u64_8_256(b::jsonb) $$", + ) + .await?; + + // Post: routing through the absent `ob` raises ("Expected an ore index") + // instead of matching the pivot — equality is broken, arm has teeth. + let err = sqlx::query_scalar::<_, i64>(count_sql) + .bind(&pivot_payload) + .fetch_one(&pool) + .await + .expect_err("rerouting `_eq` `=` through the absent ob term must fail") + .to_string(); + ensure!( + err.contains("Expected an ore index"), + "rerouted `_eq` `=` must fail on the absent ob term; got: {err}" + ); + Ok(()) +} + +// 7. ORDER BY routes through `ord_term` — the sort key, NOT `<` (see #5). +// Collapsing `ord_term` to a constant makes ORDER BY DESC no longer +// plaintext-sorted. Proves the ORDER BY arm has teeth independently of the +// `<` arm. +// +// A constant key collapses ASC and DESC to the same heap order. The +// fixture inserts rows in ascending plaintext (id 1..n), so a seq scan +// returns ascending order — which can never equal the descending +// expectation. Asserting against DESC therefore detects the collapse +// regardless of heap order (the ascending-fixture caveat from the plan). +#[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] +async fn collapsing_ord_term_flips_order_by_arm(pool: PgPool) -> Result<()> { + let order_by_desc = "SELECT plaintext FROM fixtures.eql_v2_int4 \ + ORDER BY eql_v2.ord_term(payload::eql_v2_int4_ord) DESC"; + + let mut descending: Vec = ::FIXTURE_VALUES.to_vec(); + descending.sort(); + descending.reverse(); + + // Baseline: ORDER BY ord_term DESC is plaintext-descending. + let baseline: Vec = sqlx::query_scalar(order_by_desc).fetch_all(&pool).await?; + ensure!( + baseline == descending, + "baseline: ORDER BY ord_term DESC must be plaintext-descending" + ); + + // Mutation: collapse ord_term to a constant ORE block. Use a REAL fixture + // payload as the source (guaranteed to construct a valid ore_block) and a + // unique dollar-quote tag so the embedded jsonb literal can't break the + // function body. + let const_payload = fetch_fixture_payload::(&pool, 0).await?; + let ddl = format!( + "CREATE OR REPLACE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord) \ + RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ + AS $mutbody$ SELECT eql_v2.ore_block_u64_8_256('{esc}'::jsonb) $mutbody$", + esc = const_payload.replace('\'', "''"), + ); + mutate(&pool, &ddl).await?; + + // Post: every row now sorts equal, so DESC collapses to heap (ascending) + // order and can no longer equal the descending expectation — arm has teeth. + let mutated: Vec = sqlx::query_scalar(order_by_desc).fetch_all(&pool).await?; + ensure!( + mutated != descending, + "after collapsing ord_term to a constant, ORDER BY DESC must no longer be \ + plaintext-descending (got {mutated:?})" + ); + Ok(()) +} + +// 8. ORDER BY NULLS placement depends on `ord_term` being STRICT: a NULL domain +// value yields a NULL sort key, so `NULLS LAST` parks those rows at the tail. +// Dropping STRICT (coalescing a NULL input to a real payload) gives NULL-valued +// rows a concrete sort key, so they stop clustering at the end. Proves the +// ORDER BY NULLS arm has teeth on the NULL-placement dimension — one #5 (block +// `lt`) and #7 (collapse `ord_term`) do not exercise, since both run on the +// NULL-free fixture. A UNION ALL subquery supplies the NULL rows inline, so no +// session-local temp table is needed and the global `mutate()` stays valid. +#[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] +async fn making_ord_term_non_strict_flips_order_by_nulls_arm(pool: PgPool) -> Result<()> { + const NULL_ROWS: usize = 3; + let order_by = format!( + "SELECT plaintext FROM ( \ + SELECT plaintext, payload::eql_v2_int4_ord AS value FROM fixtures.eql_v2_int4 \ + UNION ALL \ + SELECT NULL::int4, NULL::eql_v2_int4_ord FROM generate_series(1, {NULL_ROWS}) \ + ) s \ + ORDER BY eql_v2.ord_term(value) ASC NULLS LAST" + ); + + let tail_all_none = + |rows: &[Option]| rows.iter().rev().take(NULL_ROWS).all(|x| x.is_none()); + + // Baseline: STRICT ord_term -> NULL value -> NULL sort key -> NULLS LAST + // parks the NULL-valued rows at the tail. + let baseline: Vec> = sqlx::query_scalar(&order_by).fetch_all(&pool).await?; + ensure!( + tail_all_none(&baseline), + "baseline: the {NULL_ROWS} NULL-valued rows must cluster at the tail under \ + NULLS LAST (got {baseline:?})" + ); + + // Mutation: drop STRICT and coalesce a NULL input to a REAL fixture payload, + // so NULL-valued rows gain a concrete (non-NULL) sort key; non-NULL rows are + // unchanged. Unique dollar-quote tag guards the embedded jsonb literal. + let const_payload = fetch_fixture_payload::(&pool, 0).await?; + let ddl = format!( + "CREATE OR REPLACE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord) \ + RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE PARALLEL SAFE \ + AS $mutbody$ SELECT eql_v2.ore_block_u64_8_256(\ + coalesce(a, '{esc}'::jsonb::eql_v2_int4_ord)::jsonb) $mutbody$", + esc = const_payload.replace('\'', "''"), + ); + mutate(&pool, &ddl).await?; + + // Post: NULL-valued rows now carry a concrete key, so they no longer park at + // the tail — the NULLS arm catches the lost STRICT contract. + let mutated: Vec> = sqlx::query_scalar(&order_by).fetch_all(&pool).await?; + ensure!( + !tail_all_none(&mutated), + "after dropping STRICT on ord_term, the NULL-valued rows must no longer \ + cluster at the tail (got {mutated:?})" + ); + Ok(()) +} diff --git a/tests/sqlx/tests/encrypted_domain/family/support.rs b/tests/sqlx/tests/encrypted_domain/family/support.rs new file mode 100644 index 00000000..b062b9ce --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/family/support.rs @@ -0,0 +1,329 @@ +//! Self-checks for the type-generic matrix substrate +//! (`tests/sqlx/src/scalar_domains.rs`). Each test pins one piece of the +//! `ScalarType` / `Variant` / assertion-helper API that the matrix +//! depends on. + +use anyhow::Result; +use eql_tests::{ + assert_null, assert_raises, assert_scalar_plaintexts, blocker_msg, fetch_fixture_payload, + sql_string_literal, ScalarDomainSpec, ScalarType, Variant, PLACEHOLDER_PAYLOAD, +}; +use sqlx::PgPool; + +#[test] +fn variant_derives_consistent_sql_domain_and_capabilities() { + let storage = ScalarDomainSpec::new::(Variant::Storage); + assert_eq!(storage.sql_domain, "eql_v2_int4"); + assert!(!storage.supports_eq()); + assert!(!storage.supports_ord()); + assert_eq!(storage.extractor_fn(), None); + assert_eq!(Variant::Storage.required_term(), None); + + let eq = ScalarDomainSpec::new::(Variant::Eq); + assert_eq!(eq.sql_domain, "eql_v2_int4_eq"); + assert!(eq.supports_eq()); + assert!(!eq.supports_ord()); + assert_eq!(eq.extractor_fn(), Some("eql_v2.eq_term")); + assert_eq!(Variant::Eq.required_term(), Some("hm")); + + let ord = ScalarDomainSpec::new::(Variant::Ord); + assert_eq!(ord.sql_domain, "eql_v2_int4_ord"); + assert!(ord.supports_ord()); + assert_eq!(ord.extractor_fn(), Some("eql_v2.ord_term")); + assert_eq!(Variant::Ord.required_term(), Some("ob")); + + let ord_ore = ScalarDomainSpec::new::(Variant::OrdOre); + assert_eq!(ord_ore.sql_domain, "eql_v2_int4_ord_ore"); + assert!(ord_ore.supports_ord()); + assert_eq!(ord_ore.extractor_fn(), Some("eql_v2.ord_term")); +} + +#[test] +fn expected_forward_default_is_numeric_ground_truth() { + // Pinned against the full 17-row fixture (extremes + zero + the + // original 14). The output is sorted-ascending by `expected_forward`, + // so a regression in the default impl's filter or sort shows up + // here. + assert_eq!(::expected_forward("=", 10), vec![10]); + assert_eq!( + ::expected_forward("<", 10), + vec![i32::MIN, -100, -1, 0, 1, 2, 5] + ); + assert_eq!( + ::expected_forward("<=", 10), + vec![i32::MIN, -100, -1, 0, 1, 2, 5, 10] + ); + assert_eq!( + ::expected_forward(">", 10), + vec![17, 25, 42, 50, 100, 250, 1000, 9999, i32::MAX] + ); + assert_eq!( + ::expected_forward(">=", 10), + vec![10, 17, 25, 42, 50, 100, 250, 1000, 9999, i32::MAX] + ); + assert_eq!( + ::expected_forward("<>", 42), + vec![ + i32::MIN, + -100, + -1, + 0, + 1, + 2, + 5, + 10, + 17, + 25, + 50, + 100, + 250, + 1000, + 9999, + i32::MAX + ] + ); +} + +#[test] +fn sql_string_literal_escapes_single_quotes() { + assert_eq!(sql_string_literal("abc'def"), "'abc''def'"); +} + +#[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] +async fn fetch_fixture_payload_returns_keyed_row(pool: PgPool) -> Result<()> { + // Parse the payload as JSON rather than substring-matching — whitespace + // and key ordering in the serialised form are not contract. + let payload = fetch_fixture_payload::(&pool, 42).await?; + let value: serde_json::Value = serde_json::from_str(&payload)?; + assert_eq!(value["v"], serde_json::json!(2), "payload must carry v=2"); + assert!(value.get("c").is_some(), "payload must carry a c field"); + Ok(()) +} + +#[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] +async fn assert_scalar_plaintexts_reports_sql_context(pool: PgPool) -> Result<()> { + let lit = sql_string_literal(&fetch_fixture_payload::(&pool, 42).await?); + let predicate = format!("payload::eql_v2_int4_ord_ore = {lit}::jsonb::eql_v2_int4_ord_ore"); + assert_scalar_plaintexts::(&pool, "eql_v2_int4_ord_ore", "=", &predicate, &[42]).await?; + Ok(()) +} + +#[sqlx::test] +async fn placeholder_payload_satisfies_every_variant_check(pool: PgPool) -> Result<()> { + // The whole point of PLACEHOLDER_PAYLOAD: one sentinel that casts + // successfully to every domain in the family. If a variant CHECK + // tightens, this test fails and PLACEHOLDER_PAYLOAD needs updating. + // + // Iterates `Variant::ALL` against `::PG_TYPE` + // rather than hardcoding domain names — when `int8` (or any future + // scalar) lands, this test picks it up automatically by extending + // the type list below. + for variant in Variant::ALL { + let spec = ScalarDomainSpec::new::(*variant); + let sql = format!("SELECT $1::jsonb::{}", spec.sql_domain); + sqlx::query(&sql) + .bind(PLACEHOLDER_PAYLOAD) + .fetch_one(&pool) + .await + .map_err(|e| { + anyhow::anyhow!("PLACEHOLDER_PAYLOAD must cast to {}: {e}", spec.sql_domain) + })?; + } + Ok(()) +} + +#[sqlx::test] +async fn assert_raises_two_bind_blocker(pool: PgPool) -> Result<()> { + let msg = blocker_msg("eql_v2_int4", "="); + assert_raises( + &pool, + "SELECT $1::jsonb::eql_v2_int4 = $2::jsonb::eql_v2_int4", + &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], + &msg, + ) + .await +} + +#[sqlx::test] +async fn assert_raises_one_bind_path_blocker(pool: PgPool) -> Result<()> { + let msg = blocker_msg("eql_v2_int4", "->"); + assert_raises( + &pool, + "SELECT $1::jsonb::eql_v2_int4 -> 'field'::text", + &[Some(PLACEHOLDER_PAYLOAD)], + &msg, + ) + .await +} + +#[sqlx::test] +async fn assert_raises_native_operator_absent(pool: PgPool) -> Result<()> { + // ~~ (LIKE) isn't declared on int4 — error message is PG's native + // "operator does not exist", not an EQL blocker message. + assert_raises( + &pool, + "SELECT $1::jsonb::eql_v2_int4 ~~ $2::jsonb::eql_v2_int4", + &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], + "operator does not exist", + ) + .await +} + +#[sqlx::test] +async fn omitted_native_jsonb_operators_raise_eql_blockers(pool: PgPool) -> Result<()> { + let cases: &[(&str, &[Option<&str>], &str)] = &[ + ( + "SELECT $1::jsonb::eql_v2_int4 ? 'c'::text", + &[Some(PLACEHOLDER_PAYLOAD)], + "?", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 ?| ARRAY['c']", + &[Some(PLACEHOLDER_PAYLOAD)], + "?|", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 ?& ARRAY['c']", + &[Some(PLACEHOLDER_PAYLOAD)], + "?&", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 #> ARRAY['i']", + &[Some(PLACEHOLDER_PAYLOAD)], + "#>", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 #>> ARRAY['i', 'c']", + &[Some(PLACEHOLDER_PAYLOAD)], + "#>>", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 @? '$.c'::jsonpath", + &[Some(PLACEHOLDER_PAYLOAD)], + "@?", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 @@ '$.c == \"placeholder\"'::jsonpath", + &[Some(PLACEHOLDER_PAYLOAD)], + "@@", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 - 'c'::text", + &[Some(PLACEHOLDER_PAYLOAD)], + "-", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 - 0", + &[Some(PLACEHOLDER_PAYLOAD)], + "-", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 - ARRAY['c']", + &[Some(PLACEHOLDER_PAYLOAD)], + "-", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 #- ARRAY['i']", + &[Some(PLACEHOLDER_PAYLOAD)], + "#-", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 || $2::jsonb", + &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], + "||", + ), + ( + "SELECT $1::jsonb || $2::jsonb::eql_v2_int4", + &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], + "||", + ), + ( + "SELECT $1::jsonb::eql_v2_int4 || $2::jsonb::eql_v2_int4", + &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], + "||", + ), + ]; + + for (sql, binds, op) in cases { + assert_raises(&pool, sql, binds, &blocker_msg("eql_v2_int4", op)).await?; + } + Ok(()) +} + +#[sqlx::test] +async fn assert_raises_engages_on_all_null(pool: PgPool) -> Result<()> { + // Non-STRICT blocker proof — must raise even with NULL on both sides. + let msg = blocker_msg("eql_v2_int4", "="); + assert_raises( + &pool, + "SELECT $1::jsonb::eql_v2_int4 = $2::jsonb::eql_v2_int4", + &[None, None], + &msg, + ) + .await +} + +#[sqlx::test] +async fn assert_null_propagates_through_supported_op(pool: PgPool) -> Result<()> { + // STRICT supported op with one NULL operand yields NULL. + assert_null( + &pool, + "SELECT $1::jsonb::eql_v2_int4_eq = $2::jsonb::eql_v2_int4_eq", + &[Some(PLACEHOLDER_PAYLOAD), None], + ) + .await +} + +#[sqlx::test] +async fn neq_propagates_null_under_three_valued_logic(pool: PgPool) -> Result<()> { + // `<>` with a NULL operand must yield NULL (not true, not false). + // Three-valued logic is easy to get wrong in domain wrappers; a + // STRICT supported `<>` returns NULL on either NULL side. + for binds in [ + &[Some(PLACEHOLDER_PAYLOAD), None][..], + &[None, Some(PLACEHOLDER_PAYLOAD)][..], + &[None, None][..], + ] { + assert_null( + &pool, + "SELECT $1::jsonb::eql_v2_int4_eq <> $2::jsonb::eql_v2_int4_eq", + binds, + ) + .await?; + } + Ok(()) +} + +#[sqlx::test] +async fn no_cross_variant_equality_operator_is_declared(pool: PgPool) -> Result<()> { + // The family deliberately does NOT define operators that mix two + // different capability variants — `eql_v2_int4_eq = eql_v2_int4_ord` + // would resolve against jsonb (the ultimate base type) and silently + // bypass the per-variant blockers. If someone accidentally adds such + // an operator, this test fails. + // + // The check is structural (`pg_operator`) rather than dynamic + // ("invoke and see it raise") so a future PG version with stricter + // operator resolution doesn't mask the regression. + let cross_variant: Vec = sqlx::query_scalar( + r#" + SELECT format('%s(%s, %s)', + o.oprname, lt.typname, rt.typname) + FROM pg_catalog.pg_operator o + JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft + JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright + WHERE lt.typname LIKE 'eql_v2\_%' + AND rt.typname LIKE 'eql_v2\_%' + AND lt.typname <> rt.typname + ORDER BY 1 + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + cross_variant.is_empty(), + "no operator should mix two different eql_v2_* domain types, but found: {cross_variant:#?}" + ); + Ok(()) +} diff --git a/tests/sqlx/tests/encrypted_domain/scalars/int4.rs b/tests/sqlx/tests/encrypted_domain/scalars/int4.rs new file mode 100644 index 00000000..6ec665d3 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/scalars/int4.rs @@ -0,0 +1,14 @@ +//! `eql_v2_int4` — the reference scalar implementation. +//! +//! Adding a new ordered numeric scalar (i64, f64, date, ...) is one +//! `impl ScalarType` in `tests/sqlx/src/scalar_domains.rs` plus an +//! `ordered_numeric_matrix!` invocation like this one. The matrix covers +//! everything generic over `T: ScalarType`. + +use eql_tests::ordered_numeric_matrix; + +ordered_numeric_matrix! { + suite = int4, + scalar = i32, + eql_type = "eql_v2_int4", +} diff --git a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs new file mode 100644 index 00000000..8abc1857 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs @@ -0,0 +1,4 @@ +//! Per-scalar tests. Each subdirectory targets one scalar type; future +//! additions (`int8`, `bool`, `date`, …) become sibling modules here. + +pub mod int4; diff --git a/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs b/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs index 3cf73225..04233f15 100644 --- a/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs +++ b/tests/sqlx/tests/eql_v2_int4_fixture_tests.rs @@ -8,28 +8,46 @@ use anyhow::Result; use sqlx::PgPool; -/// The 14 values from `src/fixtures/eql_v2_int4.rs`, in id order. Kept here +/// The 17 values from `src/fixtures/eql_v2_int4.rs`, in id order. Kept here /// only to assert the in-table `plaintext` oracle matches what was generated. /// If `plaintext_column_matches_the_generated_values` fails, the generator's /// `VALUES` and this constant have drifted — re-run /// `mise run fixture:generate eql_v2_int4` and update this list to match. -const EXPECTED_PLAINTEXTS: &[i32] = &[-100, -1, 1, 2, 5, 10, 17, 25, 42, 50, 100, 250, 1000, 9999]; +const EXPECTED_PLAINTEXTS: &[i32] = &[ + i32::MIN, + -100, + -1, + 0, + 1, + 2, + 5, + 10, + 17, + 25, + 42, + 50, + 100, + 250, + 1000, + 9999, + i32::MAX, +]; #[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] -async fn fixture_has_fourteen_rows(pool: PgPool) -> Result<()> { +async fn fixture_has_seventeen_rows(pool: PgPool) -> Result<()> { let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM fixtures.eql_v2_int4") .fetch_one(&pool) .await?; - assert_eq!(count, 14, "eql_v2_int4 fixture should have 14 rows"); + assert_eq!(count, 17, "eql_v2_int4 fixture should have 17 rows"); Ok(()) } #[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] -async fn ids_are_sequential_one_to_fourteen(pool: PgPool) -> Result<()> { +async fn ids_are_sequential_one_to_seventeen(pool: PgPool) -> Result<()> { let ids: Vec = sqlx::query_scalar("SELECT id FROM fixtures.eql_v2_int4 ORDER BY id") .fetch_all(&pool) .await?; - assert_eq!(ids, (1..=14).collect::>()); + assert_eq!(ids, (1..=17).collect::>()); Ok(()) } @@ -95,22 +113,22 @@ async fn plaintext_oracle_supports_value_filtering(pool: PgPool) -> Result<()> { .await?; assert_eq!( ids, - vec![9], - "expected exactly one row with plaintext = 42 at id 9" + vec![11], + "expected exactly one row with plaintext = 42 at id 11" ); Ok(()) } #[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_int4")))] async fn hmac_equality_terms_are_distinct_for_distinct_values(pool: PgPool) -> Result<()> { - // All 14 plaintext values are distinct, so all 14 `hm` terms must be too. + // All 17 plaintext values are distinct, so all 17 `hm` terms must be too. let distinct_hm: i64 = sqlx::query_scalar("SELECT COUNT(DISTINCT payload->>'hm') FROM fixtures.eql_v2_int4") .fetch_one(&pool) .await?; assert_eq!( - distinct_hm, 14, - "14 distinct values -> 14 distinct hm terms" + distinct_hm, 17, + "17 distinct values -> 17 distinct hm terms" ); Ok(()) } diff --git a/tests/sqlx/tests/lint_tests.rs b/tests/sqlx/tests/lint_tests.rs index 1bd080c9..f9194b82 100644 --- a/tests/sqlx/tests/lint_tests.rs +++ b/tests/sqlx/tests/lint_tests.rs @@ -12,8 +12,15 @@ //! appropriate. use anyhow::Result; +use eql_tests::Variant; use sqlx::PgPool; +/// Pg-type tokens for the encrypted-scalar-domain families currently +/// materialised. Extending the family (e.g. when `int8`/`bool`/`date` +/// land) is a one-line array extension here — every downstream +/// parameterised test picks it up automatically. +const SCALAR_PG_TYPES: &[&str] = &["int4"]; + #[derive(Debug, sqlx::FromRow)] struct LintRow { severity: String, @@ -33,16 +40,13 @@ async fn fetch_lints(pool: &PgPool) -> Result> { } #[sqlx::test] -async fn lint_function_exists_and_returns_rows(pool: PgPool) -> Result<()> { - let rows = fetch_lints(&pool).await?; - // The current state of EQL has a non-trivial number of inlinability - // violations on the operator surface. Confirm the lint produces output - // and the columns parse correctly. - assert!( - !rows.is_empty(), - "Expected lint to surface at least one inlinability violation \ - against the current EQL surface; got 0 rows" - ); +async fn lint_function_exists_and_row_schema_parses(pool: PgPool) -> Result<()> { + // Schema-only check: `eql_v2.lints()` exists and its rows decode into + // `LintRow`. Previous incarnation asserted `!rows.is_empty()` and so + // would fail on a *cleaner* build (e.g. when Phase 1+ removes the + // current noisy violations), reading like a regression for a good + // reason. The rule-specific tests below pin actual behaviour. + let _rows = fetch_lints(&pool).await?; Ok(()) } @@ -70,6 +74,10 @@ async fn lint_categories_are_well_known(pool: PgPool) -> Result<()> { "inlinability_set_clause", "inlinability_secdef", "inlinability_transitive", + "blocker_language", + "blocker_strict", + "domain_over_domain", + "domain_opclass", ]; for row in rows { assert!( @@ -82,6 +90,175 @@ async fn lint_categories_are_well_known(pool: PgPool) -> Result<()> { Ok(()) } +/// A blocker rendered in `LANGUAGE sql` instead of `plpgsql` is the +/// inverse of the extractor/wrapper inlinability rule: a blocker's job is +/// to RAISE, and `LANGUAGE sql` bodies are inlinable — which means the +/// planner can fold or elide the call when the result is provably unused +/// (a dead CASE branch, a folded predicate), silently bypassing the RAISE +/// and re-enabling the operator. See CLAUDE.md footguns. This test plants +/// a fake LANGUAGE sql blocker on `eql_v2_int4` and asserts the lint +/// surfaces it under category `blocker_language`. +#[sqlx::test] +async fn lint_flags_blocker_in_language_sql(pool: PgPool) -> Result<()> { + sqlx::query( + r#" + CREATE FUNCTION eql_v2.test_bad_blocker_sql(a eql_v2_int4, b eql_v2_int4) + RETURNS boolean LANGUAGE sql IMMUTABLE + AS $$ SELECT eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '=') $$; + "#, + ) + .execute(&pool) + .await?; + + let rows = fetch_lints(&pool).await?; + let violations: Vec<&LintRow> = rows + .iter() + .filter(|r| { + r.category == "blocker_language" && r.object_name.contains("test_bad_blocker_sql") + }) + .collect(); + + assert!( + !violations.is_empty(), + "Expected `blocker_language` to flag the LANGUAGE sql fake blocker, \ + but got no matching row. All lint rows:\n{:#?}", + rows + ); + assert_eq!( + violations[0].severity, "error", + "blocker_language must be severity=error" + ); + Ok(()) +} + +/// A blocker marked `STRICT` lets PostgreSQL skip the body and return NULL +/// on a NULL argument — silently bypassing the "operator not supported" +/// RAISE. See CLAUDE.md footguns. This test plants a fake STRICT plpgsql +/// blocker on `eql_v2_int4` and asserts the lint surfaces it under +/// `blocker_strict`. +#[sqlx::test] +async fn lint_flags_strict_blocker(pool: PgPool) -> Result<()> { + sqlx::query( + r#" + CREATE FUNCTION eql_v2.test_bad_blocker_strict(a eql_v2_int4, b eql_v2_int4) + RETURNS boolean LANGUAGE plpgsql IMMUTABLE STRICT + AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$; + "#, + ) + .execute(&pool) + .await?; + + let rows = fetch_lints(&pool).await?; + let violations: Vec<&LintRow> = rows + .iter() + .filter(|r| { + r.category == "blocker_strict" && r.object_name.contains("test_bad_blocker_strict") + }) + .collect(); + + assert!( + !violations.is_empty(), + "Expected `blocker_strict` to flag the STRICT fake blocker, \ + but got no matching row. All lint rows:\n{:#?}", + rows + ); + assert_eq!( + violations[0].severity, "error", + "blocker_strict must be severity=error" + ); + Ok(()) +} + +/// Generated encrypted-domain blockers intentionally use non-inlinable +/// plpgsql functions. They should be checked by the blocker-specific lint +/// rules, not reported as normal operator inlinability failures. +#[sqlx::test] +async fn lint_does_not_report_generated_blockers_as_inlinability_errors( + pool: PgPool, +) -> Result<()> { + let rows = fetch_lints(&pool).await?; + let violations: Vec<&LintRow> = rows + .iter() + .filter(|r| { + matches!( + r.category.as_str(), + "inlinability_language" + | "inlinability_volatility" + | "inlinability_set_clause" + | "inlinability_secdef" + ) && r.object_name.contains("eql_v2_int4") + && (r.object_name.contains("operator =(") + || r.object_name.contains("operator ->(") + || r.object_name.contains("operator ?(")) + }) + .collect(); + + assert!( + violations.is_empty(), + "generated encrypted-domain blockers must not be reported by direct \ + inlinability rules; got: {violations:#?}" + ); + Ok(()) +} + +/// An `eql_v2_*` domain whose base type is another `eql_v2_*` domain (not +/// jsonb) silently bypasses the storage variant's blockers: operators +/// resolve against the ultimate base type, so a derived domain does not +/// inherit the base domain's operator surface. See CLAUDE.md footguns. +/// This test plants a domain-over-domain offender and asserts the lint +/// surfaces it under `domain_over_domain`. +#[sqlx::test] +async fn lint_flags_domain_over_domain(pool: PgPool) -> Result<()> { + sqlx::query(r#"CREATE DOMAIN public.eql_v2_test_baddom AS public.eql_v2_int4;"#) + .execute(&pool) + .await?; + + let rows = fetch_lints(&pool).await?; + let violations: Vec<&LintRow> = rows + .iter() + .filter(|r| { + r.category == "domain_over_domain" && r.object_name.contains("eql_v2_test_baddom") + }) + .collect(); + + assert!( + !violations.is_empty(), + "Expected `domain_over_domain` to flag the derived domain, \ + but got no matching row. All lint rows:\n{:#?}", + rows + ); + assert_eq!( + violations[0].severity, "error", + "domain_over_domain must be severity=error" + ); + Ok(()) +} + +/// An operator class declared `FOR TYPE` on an `eql_v2_*` domain bypasses +/// the operator-resolution that the storage blockers depend on. The +/// recommended pattern is a functional index on the extractor; opclasses +/// on domains must never appear. See CLAUDE.md footguns. The current +/// build emits zero opclasses on `eql_v2_*` domains, so this test is +/// negative: it asserts the rule category is well-known and surfaces no +/// rows. A positive test would require constructing a valid opclass on a +/// domain, which is non-trivial scaffolding — the `domain_opclass` +/// structural guard in `tests/encrypted_domain/family/inlinability.rs` is the +/// independent net for regressions. +#[sqlx::test] +async fn lint_domain_opclass_surface_is_clean(pool: PgPool) -> Result<()> { + let rows = fetch_lints(&pool).await?; + let violations: Vec<&LintRow> = rows + .iter() + .filter(|r| r.category == "domain_opclass") + .collect(); + assert!( + violations.is_empty(), + "domain_opclass surface should be empty in a clean build, got: {:#?}", + violations + ); + Ok(()) +} + /// Phase 1 regression: the operators rewritten in #193 (=, <>, ~~, ~~*, /// @>, <@ on eql_v2_encrypted) must report zero lint violations. If this /// test fails, an inlinability regression has been introduced into one @@ -119,3 +296,68 @@ async fn lint_phase_1_operators_are_clean(pool: PgPool) -> Result<()> { ); Ok(()) } + +/// Every encrypted-scalar-domain family's inlinable operator surface +/// must report zero lint violations. The supported operators on the +/// `_eq`, `_ord`, and `_ord_ore` variants are codegen-emitted SQL +/// wrappers (LANGUAGE sql, IMMUTABLE, no pinned `search_path`); the +/// planner can fold them into the documented functional indexes. A +/// regression to plpgsql or a pinned `search_path` breaks index +/// engagement. +/// +/// Storage-only variants (the bare `eql_v2_` domain with no +/// capability suffix) are intentionally excluded — every operator on +/// them is a non-STRICT plpgsql blocker, which doesn't need to be +/// inlinable. +/// +/// Discovers the eligible operator set from `pg_operator` rather than +/// hardcoding the int4 inventory — when `int8` (or `bool`, `date`, ...) +/// lands, this test picks it up automatically with no edit. The earlier +/// hardcoded list was a copy-paste hazard. +#[sqlx::test] +async fn scalar_family_inlinable_operators_are_clean(pool: PgPool) -> Result<()> { + // Build the inline-critical signature set Rust-side from + // `SCALAR_PG_TYPES × Variant::ALL × supported-operators`. Eq-only + // variants declare `<`/`<=`/`>`/`>=` as blockers (intentionally + // non-inlinable), so they must NOT be expected to be clean here — + // only the ops the variant actually supports as wrappers count. + // + // Storage variants contribute no inline-critical surface; their + // entire operator set is blockers by design. + let mut prefixes: Vec = Vec::new(); + for pg_type in SCALAR_PG_TYPES { + for variant in Variant::ALL { + if matches!(variant, Variant::Storage) { + continue; + } + let domain = format!("eql_v2_{pg_type}{}", variant.suffix()); + let supported_ops: &[&str] = if variant.supports_ord() { + &["=", "<>", "<", "<=", ">", ">="] + } else { + // Eq variants support equality only; ordering ops on `_eq` + // are blockers. + &["=", "<>"] + }; + for op in supported_ops { + // Domain-on-left and jsonb-on-left arg shapes both + // need to be inlinable; the domain-on-right shape is + // the `(jsonb, domain)` operator. + prefixes.push(format!("operator {op}({domain},")); + prefixes.push(format!("operator {op}(jsonb, {domain})")); + } + } + } + + let rows = fetch_lints(&pool).await?; + let violations: Vec<&LintRow> = rows + .iter() + .filter(|row| prefixes.iter().any(|p| row.object_name.starts_with(p))) + .collect(); + + assert!( + violations.is_empty(), + "scalar-family inline-critical operators should report zero \ + lint violations, but got: {violations:#?}" + ); + Ok(()) +} From 46fc825bb56891ce2731c8702323c8bb8ea96451 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 12:32:52 +1000 Subject: [PATCH 14/93] docs(encrypted-domain): implementation spec, generator reference & changelog Add encrypted-domain implementation spec and generator reference; document both aggregate paths in eql-functions / sql-support; record the int4 family under CHANGELOG Added. CLAUDE.md gains the encrypted-domain materializer guidance and footgun list. Part of PR #239. --- CHANGELOG.md | 5 + CLAUDE.md | 23 +- docs/development/documentation-inventory.md | 7 - docs/reference/encrypted-domain-generator.md | 398 ++++++++++++++++++ .../encrypted-domain-implementation-spec.md | 339 +++++++++++++++ docs/reference/eql-functions.md | 65 ++- docs/reference/sql-support.md | 24 +- tasks/docs/generate.sh | 2 + tasks/docs/validate.sh | 2 + tasks/docs/validate/coverage.sh | 2 + tasks/docs/validate/documented-sql.sh | 2 + tasks/docs/validate/required-tags.sh | 2 + 12 files changed, 851 insertions(+), 20 deletions(-) create mode 100644 docs/reference/encrypted-domain-generator.md create mode 100644 docs/reference/encrypted-domain-implementation-spec.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e8c9568..e3d45c0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ Each entry that ships in a published release links to the PR that introduced it. ## [Unreleased] +### Added + +- **`eql_v2_int4` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int4` columns: `eql_v2_int4` (storage-only), `eql_v2_int4_eq` (`=` / `<>` via HMAC), and `eql_v2_int4_ord` / `eql_v2_int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v2.eq_term` / `eql_v2.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) +- **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v2.min(eql_v2__ord)` / `eql_v2.max(eql_v2__ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) + ## [2.3.1] — 2026-05-21 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 65ed6d58..361fd31b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,6 +61,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search - `src/operators/` - SQL operators for encrypted data comparisons - `src/config/` - Configuration management functions - `src/blake3/`, `src/hmac_256/`, `src/bloom_filter/`, `src/ore_*` - Index implementations +- `src/encrypted_domain/` - Encrypted-domain type families (jsonb-backed PostgreSQL domains, one per operator/index capability) - `tasks/` - mise task scripts - `tests/sqlx/` - Rust/SQLx test framework (PostgreSQL 14-17 support) - `release/` - Generated SQL installation files @@ -72,6 +73,25 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search - **Operators**: Support comparisons between encrypted and plain JSONB data - **CipherStash Proxy**: Required for encryption/decryption operations +### Encrypted-Domain Types + +`src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains, one domain per operator/index capability (`eql_v2_` storage-only, `eql_v2__eq`, `eql_v2__ord`). `eql_v2_int4` (PR #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, and `timestamp` follow this materializer pattern. `jsonb` needs a separate design and is out of scope for the scalar materializer. + +Adding a scalar encrypted-domain type is generated from a minimal manifest at `tasks/codegen/types/.toml`: the filename supplies ``, and the `[domain]` table maps each generated domain name to the fixed index terms it carries. Example: `int4_eq = ["hm"]`, `int4_ord = ["ore"]`. Term capabilities are fixed in `tasks/codegen/terms.py`: `hm` provides equality, and `ore` provides equality plus ordering. `mise run build` regenerates the scalar SQL surface into `src/encrypted_domain//` from every manifest at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. Use `mise run codegen:domain ` to refresh a single type manually while iterating on its manifest, or `mise run codegen:domain:all` to regenerate every type at once (the same enumeration `mise run build` uses). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` files are gitignored and never committed — the TOML manifest plus `tasks/codegen/terms.py` are the source of truth. Generated files carry an `AUTO-GENERATED — DO NOT EDIT` header; change the manifest or term catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. + +**Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the manifest only declares domain names and terms. New term behavior belongs in `tasks/codegen/terms.py` with tests, not in free-form TOML fields. + +Regeneration is deterministic: identical manifest + term catalog produce byte-identical SQL. If `mise run build` produces unexpected output, the change is in the manifest, `tasks/codegen/terms.py`, or `tasks/codegen/templates.py` — not in random run-to-run variation. + +Footguns the spec exists to prevent: + +- **Blockers must never be `STRICT`.** A `STRICT` blocker lets PostgreSQL skip the body and return `NULL` on a `NULL` argument, silently bypassing the "operator not supported" exception. +- **No domain-over-domain** (`CREATE DOMAIN a AS b`). Operators resolve against the ultimate base type (`jsonb`), so a derived domain does not inherit the base domain's operator surface — blockers stop engaging. +- **No operator class on a domain.** Index through a functional index on the extractor (`eq_term` / `ord_term`), whose return type already carries a default opclass. +- **Inlinable functions** (extractors, comparison wrappers) need `LANGUAGE sql`, a single-statement `SELECT`, `IMMUTABLE`, and **no `SET` clause** — a pinned `search_path` disables inlining. No per-type allowlist edit: the `pin_search_path.sql` structural rule recognises encrypted-domain functions intrinsically and `tasks/test/splinter.sh` covers the converged extractor/wrapper names. +- **Blockers must be `LANGUAGE plpgsql`, not `LANGUAGE sql`.** The inverse of the rule above. A blocker exists to always raise, but a `LANGUAGE sql` body is inlinable and the planner can elide the call when the result is provably unused (dead `CASE` branch, folded predicate). `LANGUAGE plpgsql` is opaque to the planner, so the call — and its `RAISE` — survives. The generator in `tasks/codegen/templates.py` enforces this; don't "simplify" the rendered blockers to `LANGUAGE sql` even though the body is a single expression. +- **Build with `mise run clean && mise run build`** — a bare build can leave stale `release/*.sql`. + ### Testing Infrastructure - Tests are written in Rust using SQLx, located in `tests/sqlx/` - Tests run against PostgreSQL 14, 15, 16, 17 using Docker containers @@ -199,6 +219,7 @@ Prefer `LANGUAGE SQL` over `LANGUAGE plpgsql` unless you need procedural feature - Exception handling (`BEGIN...EXCEPTION...END`) - Complex control flow (loops, early returns) - Dynamic SQL (`EXECUTE`) +- Functions that must remain opaque to the planner — typically blockers whose only job is to `RAISE`. `LANGUAGE sql` would be inlined and may be elided when the result is provably unused; `LANGUAGE plpgsql` is never inlined, so the body always runs. See the encrypted-domain footgun list above and the blocker renderers in `tasks/codegen/templates.py`. ## Release & changelog discipline @@ -222,7 +243,7 @@ What does *not* need an entry: Pick the right section (`Added` / `Changed` / `Deprecated` / `Removed` / `Fixed` / `Security`). Lead with the user-visible fact, then a short "Why." explanation, then a PR link in parentheses. Match the tone and density of existing entries — a single dense paragraph per entry, not a bullet list. -Example shape (real entry from `2.3.0`): +Example entry (real entry from `2.3.0`): > **`=`, `<>`, `~~` (`LIKE`), `~~*` (`ILIKE`) on `eql_v2_encrypted` are now inlinable SQL functions.** The planner can structurally match these operators against the documented functional indexes (`eql_v2.hmac_256(col)` for equality, `eql_v2.bloom_filter(col)` for `LIKE`/`ILIKE`), so bare-form queries (`WHERE col = $1`) engage the index without per-query rewriting. Previously these operators wrapped multi-branch PL/pgSQL bodies that the planner could not inline, forcing seq scans on Supabase / managed Postgres installations that lack operator-class indexes. ([#193](...), [#196](...)) diff --git a/docs/development/documentation-inventory.md b/docs/development/documentation-inventory.md index bdbe8fd8..e9e89eed 100644 --- a/docs/development/documentation-inventory.md +++ b/docs/development/documentation-inventory.md @@ -77,13 +77,6 @@ Generated: Mon 27 Oct 2025 11:39:50 AEDT ## src/crypto.sql -## src/encrypted/aggregates.sql - -- CREATE FUNCTION eql_v2.min(a eql_v2_encrypted, b eql_v2_encrypted) -- CREATE AGGREGATE eql_v2.min(eql_v2_encrypted) -- CREATE FUNCTION eql_v2.max(a eql_v2_encrypted, b eql_v2_encrypted) -- CREATE AGGREGATE eql_v2.max(eql_v2_encrypted) - ## src/encrypted/casts.sql - CREATE FUNCTION eql_v2.to_encrypted(data jsonb) diff --git a/docs/reference/encrypted-domain-generator.md b/docs/reference/encrypted-domain-generator.md new file mode 100644 index 00000000..9b502690 --- /dev/null +++ b/docs/reference/encrypted-domain-generator.md @@ -0,0 +1,398 @@ +# Encrypted-Domain Code Generator + +How `tasks/codegen/` turns a TOML manifest into the SQL surface for a +scalar encrypted-domain type. This document describes the generator +itself — its inputs, stages, outputs, and the invariants it enforces. +The contract those outputs must satisfy is in +[`encrypted-domain-implementation-spec.md`](./encrypted-domain-implementation-spec.md); +this file describes the machine that produces them. + +The reference type is `eql_v2_int4` (PR #239). `text` and `jsonb` are +outside scope. + +## 1. Why a generator + +A single scalar encrypted-domain type emits several hundred SQL +declarations across eleven files: four domains, three extractors, dozens +of comparison wrappers and blockers, 176 `CREATE OPERATOR` statements (44 +per domain), and MIN/MAX aggregates for every ordered domain. The shape +is mechanical and +the invariants are unforgiving — a `STRICT` blocker silently bypasses +its exception, a pinned `search_path` disables inlining and reverts +queries to seq scans. The generator exists so each new scalar type adds +one TOML file rather than ninety hand-written declarations that must +agree with each other and with `pin_search_path.sql`, +`tasks/test/splinter.sh`, and `src/encrypted_domain/functions.sql`. + +## 2. Pipeline + +`tasks/codegen/` is a small Python package. Entry point: +`python -m tasks.codegen.generate `, wrapped by +`mise run codegen:domain ` (`tasks/codegen/domain.sh:10`). +`tasks/build.sh` invokes the same entry point for every manifest at +the start of every `mise run build`, so the generated SQL is never +checked in — the TOML manifest is the source of truth. + +Stages, in order: + +1. **Load manifest** — `spec.load_spec(toml_path)` reads + `tasks/codegen/types/.toml`, validates the `[domain]` table, + validates the token and every domain name as SQL identifiers + (`_SQL_IDENTIFIER`, `spec.py:12`), checks each domain name starts with the + filename token, resolves every listed term against `terms.TERM_CATALOG`, + and parses the optional `[fixture]` table (`_load_fixture_values`, + `spec.py:36`). Returns a `TypeSpec` (`tasks/codegen/spec.py:98`). +2. **Resolve terms** — for each `DomainSpec`, `terms.require_terms` + maps catalog names (`hm`, `ore`) to `Term` records carrying the + extractor name, return type, JSON envelope key, supported + operators, and the SQL `-- REQUIRE:` edges those terms imply + (`tasks/codegen/terms.py:57-88`). +3. **Render** — `generate.render_types_file`, + `generate.render_functions_file`, `generate.render_operators_file`, + and `generate.render_aggregates_file` (the last only for ordered + domains) build SQL strings via the per-construct functions in + `templates.py`; when the manifest declares a `[fixture]` table, + `templates.render_fixture_values_rs` also renders the committed Rust + value const. No template engine — plain f-strings, with the structural + shape of each declaration encoded in code (`tasks/codegen/generate.py`). +4. **Write** — `writer.write_generated_file` prefixes every SQL output with + the `AUTO-GENERATED — DO NOT EDIT` header (`templates.py:13-17`) and + refuses to overwrite any pre-existing file that lacks that marker + (`tasks/codegen/writer.py:67`). The committed Rust value const is written + by `writer.write_generated_rs` (`writer.py:78`) with its own Rust + `AUTO-GENERATED` header. `generate_type` cleans stale generated files in + the target directory before rewriting so an abandoned domain disappears on + the next regeneration (`generate.py:221`). + +There is no caching layer, no incremental mode, and no rewriting of +hand-written files. Each invocation regenerates every output for one +type from a single manifest. + +## 3. Manifest format + +```toml +[domain] +int4 = [] +int4_eq = ["hm"] +int4_ord_ore = ["ore"] +int4_ord = ["ore"] +``` + +Rules enforced by `spec.load_spec`: + +- The filename stem is the **type token** (`int4` here). It must match + the CLI argument and prefix every domain name. +- The TOML must have a non-empty `[domain]` table at the top level. The + only other recognised top-level key is the optional `[fixture]` table + (see §3a). +- The filename token and every domain key must be valid lowercase SQL + identifiers (`^[a-z][a-z0-9_]*$`); anything else raises `SpecError`. +- Each domain key must equal the token or start with `_`. +- Each value must be a list of strings, and each string must be a key + in `terms.TERM_CATALOG`. Unknown terms raise `SpecError`. + +The `[domain]` table declares nothing else — no extractor names, no +operator lists, no REQUIRE edges. Every behavioural fact comes from the +term catalog. + +Domains may be **twinned** (`int4_ord` and `int4_ord_ore` both carry +`["ore"]`). The generator emits them as independent domains with +byte-identical SQL modulo type name. Twins exist so callers can choose +a name that documents intent ("ordered, regardless of mechanism" vs +"ordered via ORE block") without committing to one term family in a +future migration. + +Manifest order is significant. The generator iterates domains in their +declared TOML order (`generate.py:48`), and that order shows up in the +generated `_types.sql` `DO` block. + +### 3a. Optional `[fixture]` table + +```toml +[fixture] +values = ["MIN", "-1", "ZERO", "1", "MAX"] +``` + +A type may declare an ordered `[fixture] values` list — the single source +of truth for the committed Rust const +`tests/sqlx/src/fixtures/_values.rs`, consumed by the SQLx fixture +generator and the matrix oracle. `_load_fixture_values` (`spec.py:36`) +requires a non-empty list of string tokens; each resolves through the +scalar-kind catalog (`scalars.py`) — the sentinels `MIN` / `MAX` / `ZERO` +plus any numeric literal in the type's representable range. Validation +enforces a **distinct-plaintext contract**: duplicates are rejected against +the *resolved numeric* value, so both copy-paste token dups (`"1", "1"`) and +sentinel/literal aliases (`"MIN"` alongside the same number) raise +`SpecError` — and the set **must include MIN, MAX, and zero** (the matrix +comparison pivots). Unlike the gitignored SQL surface, `_values.rs` +**is committed** (its rendering is deterministic), and CI regenerates it and +runs `git diff --exit-code` to catch an un-regenerated manifest edit. See +implementation spec §9 for the authoring guidance. + +## 4. Term catalog + +`tasks/codegen/terms.py:25-49` defines every term the materializer +recognises. A term is a frozen dataclass: + +```python +Term( + name="hm", # manifest key + json_key="hm", # envelope payload key + extractor="eq_term", # SQL extractor function name + returns="eql_v2.hmac_256", # extractor return type + ctor="hmac_256", # eql_v2 constructor in jsonb + role="eq", # file-header phrasing + operators=("=", "<>"), # operators this term enables + requires=("src/hmac_256/functions.sql",) # SQL REQUIRE edges +) +``` + +Current catalog: + +| Term | JSON key | Extractor | Returns | Operators | +| ----- | -------- | ----------- | -------------------------------- | ---------------------------------- | +| `hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` `<>` | +| `ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` `<>` `<` `<=` `>` `>=` | + +Adding a term is a code change to `terms.py` with matching tests in +`test_terms.py` — never a free-form manifest field. The catalog is the +only source of operator support, extractor identity, and REQUIRE edges; +the manifest is a thin selector over it. + +## 5. The operator surface + +`tasks/codegen/operator_surface.py` enumerates the surface every generated +domain declares: + +- **Supported-capable comparisons**: `=` `<>` `<` `<=` `>` `>=` `@>` `<@` +- **Path blockers**: `->` `->>` +- **Native `jsonb` fallback blockers**: `?` `?|` `?&` `@?` `@@` `#>` `#>>` `-` `#-` `||` + +Comparison and path operators keep the historical three-argument shapes: + +- Symmetric: `(domain, domain)`, `(domain, jsonb)`, `(jsonb, domain)` +- Path: `(domain, text)`, `(domain, integer)`, `(jsonb, domain)` + +Native `jsonb` fallback blockers use only the shapes PostgreSQL exposes +for `jsonb` itself, for a total of **44 `CREATE OPERATOR` statements per +domain**. Supported operators are emitted with full planner metadata +(`COMMUTATOR`, `NEGATOR`, `RESTRICT`, `JOIN` selectivity estimators) and +back onto inlinable wrappers; unsupported operators carry minimal metadata +and back onto blockers. + +Path operators always back onto blockers — neither current term +enables them. The additional native `jsonb` operators are blocker-only. +Untyped string literals are a PostgreSQL resolver edge: `? 'c'` can still +select the built-in `jsonb` operator, while `? 'c'::text` and bound text +parameters select the generated blocker. + +The union of these three lists is `KNOWN_JSONB_OPERATORS`. A live-DB +structural guard +(`tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs`) +queries `pg_operator` for every operator with a `jsonb` argument and asserts +the set is a subset of this union, so a future PostgreSQL version that adds a +`jsonb` operator nobody enumerated here fails the test rather than silently +routing an encrypted column to native plaintext-`jsonb` semantics. +`test_operator_surface.py` pins the Python union; the Rust test mirrors it. + +## 6. Generated outputs + +For a manifest with `D` domains of which `A` are ordered (ord-capable), +the generator writes `1 + 2D + A` SQL files into +`src/encrypted_domain//`, plus — when the manifest carries a +`[fixture]` table — one committed Rust const at +`tests/sqlx/src/fixtures/_values.rs`. For `int4` (`D = 4`, `A = 2`): +eleven SQL files and one Rust file. The SQL outputs are gitignored — `tasks/build.sh` regenerates them at the +start of every build from each `tasks/codegen/types/.toml`, +`mise run codegen:domain ` refreshes a single type manually, and +`mise run codegen:domain:all` regenerates every type in one invocation (the +same `generate.py --all` enumeration the build uses). The manifest plus +`tasks/codegen/terms.py` are the source of truth. + +| File | Content | +| --------------------------------- | ---------------------------------------------------------------------------------------- | +| `_types.sql` | Single idempotent `DO` block creating every domain; each domain `CHECK` pins the payload version (`VALUE->>'v' = '2'`) and required envelope/ciphertext/term keys; one `--! @brief` per domain | +| `_functions.sql` | One extractor per unique term, then 44 wrappers-or-blockers covering the surface | +| `_operators.sql` | 44 `CREATE OPERATOR` statements with planner metadata on supported ops | +| `_aggregates.sql` | MIN/MAX state functions + `CREATE AGGREGATE`; emitted only for ordered (ord-capable) domains | + +Every file: + +- Opens with the `AUTO-GENERATED — DO NOT EDIT` header + (`templates.py:13-17`). +- Declares its `-- REQUIRE:` edges in dependency order — types files + require `src/schema.sql`; function files require schema, types, and + `src/encrypted_domain/functions.sql` plus each term's `requires` set; + operator files require schema, types, and their domain's function + file; aggregate files require schema, types, and their domain's + function and operator files. +- Carries Doxygen `--! @file` / `--! @brief` headers describing its + role. + +### Function-count totals per domain + +| Domain terms | Extractors | Wrappers | Blockers | Functions | Operators | +| ------------ | ---------: | -------: | -------: | --------: | --------: | +| none | 0 | 0 | 44 | 44 | 44 | +| `["hm"]` | 1 | 6 | 38 | 45 | 44 | +| `["ore"]` | 1 | 18 | 26 | 45 | 44 | + +Six wrappers for `hm` = `=` and `<>` × three shapes. Eighteen for `ore` += six operators × three shapes. The 44-operator total never moves; the +wrapper/blocker split is what shifts, and native `jsonb` fallback +operators are always blockers. + +The table above covers `_functions.sql` only. Ordered domains +additionally emit `_aggregates.sql` — two state functions +(`min_sfunc`, `max_sfunc`) and two `CREATE AGGREGATE` declarations +(`eql_v2.min`, `eql_v2.max`). Each aggregate declares +`combinefunc = ` and `parallel = safe`: min/max are associative, so +the state function doubles as the combine function, enabling partial and +parallel aggregation on large `GROUP BY` ORE workloads with no decryption. + +## 7. Invariants the generator enforces + +The generator's job is partly to write SQL and partly to make +incorrect SQL unreachable. Invariants encoded in code: + +- **Blockers are never `STRICT`.** `render_blocker_bool`, + `render_blocker_path`, and `render_blocker_native` emit + `IMMUTABLE PARALLEL SAFE` without the + `STRICT` qualifier (`templates.py:263-345`), so a `NULL` + argument still reaches the `RAISE` and the unsupported-operator + exception fires. There is no code path that produces a strict + blocker. +- **Wrappers are inlinable SQL.** `render_wrapper` and + `render_extractor` emit `LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE` + with a single-statement `SELECT` and no `SET search_path` + (`templates.py:218-260`). `pin_search_path.sql:265-290` + catches them structurally and leaves them unpinned. +- **Aggregate state functions are the deliberate exception.** + `render_aggregate` emits `min_sfunc` / `max_sfunc` as + `LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE` *with* a pinned + `SET search_path` (`templates.py:379-452`). They are aggregate transition + functions, not index expressions, so pinning is correct; the generated + `min` / `max` aggregates are allowlisted by name in `splinter.sh`. The + aggregates are `parallel = safe` with the sfunc reused as `combinefunc`. +- **SQL-literal injection is structurally prevented.** Every string + interpolated into a single-quoted SQL literal — payload keys, operator + symbols, domain names in `RAISE` messages — passes through `_sql_str` + (`templates.py:46`), which doubles embedded single quotes. Today's catalog + strings are all quote-free so it is a no-op, but it guarantees a future + quote-bearing catalog string cannot break out of its literal. +- **No domain-over-domain.** Every domain is `CREATE DOMAIN ... AS + jsonb`, never `AS ` (`templates.py:72`). PostgreSQL + resolves operators against the underlying base type; a derived domain + would silently bypass the fixed operator surface. +- **No operator class on a domain.** The generator emits operators, + not operator classes. Callers index through the extractor function + (e.g. `USING btree (eql_v2.ord_term(col))`), whose return type + already carries a default opclass. +- **Ownership boundary.** `writer.is_generated` recognises owned files + by their header line and refuses to overwrite anything else + (`writer.py:20-26`, `44-53`). A hand-written file at a generated + path is a hard error, not a silent clobber. Stale generated files + for removed domains are cleaned before the new files land + (`writer.py:29-41`). + +## 8. Extension files + +`_extensions.sql` is the hand-written sibling. The generator +never creates, lists, or cleans it; it has no auto-generated header +and must declare its own `-- REQUIRE:` edges. Use it for behaviour +that's specific to the type and not part of the fixed surface — e.g. +cross-domain casts, helper functions, type-specific constraints. + +`pin_search_path.sql:291-302` describes the fallback marker for +inline-critical extension functions that take no domain argument and +so escape the structural skip: + +```sql +COMMENT ON FUNCTION eql_v2.my_helper(...) IS 'eql-inline-critical: ...'; +``` + +The generator does **not** emit this marker; every function it +produces takes a domain argument and is covered by the structural skip +intrinsically. + +## 9. Lint and test integration + +The generator depends on two pieces of build tooling recognising its +output without per-type edits: + +- **`tasks/pin_search_path.sql:265-290`** — structural skip identifies + encrypted-domain functions by language (`sql`), volatility + (`IMMUTABLE`), and the presence of at least one argument typed as a + jsonb-backed `DOMAIN` in `public` named `eql_v2_*`. New scalar types + need no edit here. +- **`tasks/test/splinter.sh`** — name-based allowlist. The converged + wrapper names (`eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `eq_term`, + `ord_term`) are already covered by entries originally added for + `ste_vec_entry` and friends (`splinter.sh:87-104`). Splinter matches + by name only, so a new scalar type that uses the catalog extractors + inherits coverage. Adding a new term whose extractor has a new name + requires a splinter entry. + +## 10. Tests + +`mise run test:codegen` runs the generator test suite — `pytest +tasks/codegen` — with no database required: + +- `test_spec.py`, `test_terms.py`, `test_scalars.py`, + `test_operator_surface.py`, `test_templates.py`, `test_writer.py` — unit + tests per module. +- `test_generate.py` — end-to-end rendering tests asserting file + counts and structural shape. +- `test_against_reference.py` — byte-for-byte match of in-memory + `render_*_file` output against a hand-reviewed (header-stripped) + reference under `tests/codegen/reference/int4/`. Runs anywhere + without depending on materialised `src/encrypted_domain//`. The + reference fixture is the human-readable contract that survives + generator refactors. + +The codegen suite is a prerequisite of the PostgreSQL test matrix +(`tasks/test.sh`), so generated-SQL drift fails CI before any database +test runs. + +## 11. Adding a new scalar type + +The end-to-end shape from a generator perspective: + +1. **Author** `tasks/codegen/types/.toml`. Domain names must + start with the token; term names must already exist in + `terms.TERM_CATALOG`. If `` is a new scalar kind, first register + a `ScalarKind` in `scalars.py` — `load_spec` resolves the scalar before + anything else, so an unregistered token raises + `ScalarError: unknown scalar token ''`. +2. **Regenerate**. Either run `mise run codegen:domain ` while + iterating, or just `mise run build` — the build regenerates every + manifest first. The generator cleans stale generated files, writes + new ones, and refuses any hand-written file at a generated path. + Generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` are + gitignored and never committed. +3. **Hand-write** `_extensions.sql` if the type needs SQL + beyond the fixed surface. Add `eql-inline-critical` markers only on + inline-critical helpers that take no domain argument. This file IS + committed. +4. **Build picks it up automatically** — `tasks/build.sh` regenerates + before computing the `tsort` graph, so the new files appear in the + dependency walk via the `-- REQUIRE:` edges the generator emits. +5. **Baseline & test.** Create a hand-reviewed byte-parity baseline under + `tests/codegen/reference//` (each file marked `-- REFERENCE:` / + `// REFERENCE:`) so `test_against_reference.py` guards the new type — it + only covers types that have a baseline directory. Then run + `mise run test:codegen`, the relevant SQLx suites, and the PostgreSQL + matrix. + +Adding a new **term** is a bigger move — edit `terms.py`, add tests, +audit `splinter.sh` for a name collision, and update the reference +fixture under `tests/codegen/reference/`. + +## 12. Out of scope + +`text` and `jsonb` are not materialised through this generator. There +is no guard preventing a `text.toml` from being authored; the catalog +simply lacks the term shape those types would need. Text and JSONB +encrypted behaviour lives on the composite `eql_v2_encrypted` type and +its hand-written operator surface in `src/encrypted/` and +`src/operators/`, not the scalar materializer. diff --git a/docs/reference/encrypted-domain-implementation-spec.md b/docs/reference/encrypted-domain-implementation-spec.md new file mode 100644 index 00000000..4499c20d --- /dev/null +++ b/docs/reference/encrypted-domain-implementation-spec.md @@ -0,0 +1,339 @@ +# Encrypted Domain Type Implementation Spec + +This is the scalar encrypted-domain generator contract used by `int4`. +It applies to scalar domains whose searchable payloads are represented by +the fixed term catalog in `tasks/codegen/terms.py`. + +`text` and `jsonb` are outside this scalar materializer. + +## 1. Model + +Each generated public domain is a concrete `jsonb` domain named +`public.eql_v2_`. The manifest is intentionally small: + +```toml +[domain] +int4 = [] +int4_eq = ["hm"] +int4_ord_ore = ["ore"] +int4_ord = ["ore"] +``` + +The TOML filename supplies the type token. The `[domain]` table maps each +generated domain name to the fixed terms it carries. The generator +emits files in the manifest's declared order, so order keys in the TOML +in the order you want them to appear in generated output. Term capabilities +come only from `tasks/codegen/terms.py`: + +| Term | JSON key | Extractor | Return type | Supported operators | +|---|---|---|---|---| +| `hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` / `<>` | +| `ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` / `<>` / `<` / `<=` / `>` / `>=` | + +For current `int4`, domains carrying `ore` use JSON key `ob`, extractor +`ord_term`, and the ORE block supports equality plus ordering. A type +that needs a non-ORE equality term on an ordered domain needs a new +catalog term design, not a manifest flag. + +The manifest above declares two ordered domains, `int4_ord` and +`int4_ord_ore`, carrying the same term. They are intentional twins: the +generator emits byte-identical SQL (modulo type name) so callers can pick +a name that documents intent without committing to a term family in a +future migration. + +## 2. Checklist + +- [ ] Author `tasks/codegen/types/.toml`. The filename supplies ``. + The `[domain]` table maps generated domain names to fixed terms: + + ```toml + [domain] + int4 = [] + int4_eq = ["hm"] + int4_ord_ore = ["ore"] + int4_ord = ["ore"] + ``` + + Terms determine operator support: `hm` provides `=` / `<>`; `ore` + provides `=` / `<>` / `<` / `<=` / `>` / `>=`. +- [ ] Add or update catalog terms in `tasks/codegen/terms.py` with tests. +- [ ] **If `` is a new scalar kind, register a `ScalarKind` in + `tasks/codegen/scalars.py`** (use the `int4` entry as the template): its + `token`, `rust_type`, the `MIN` / `MAX` / `ZERO` Rust symbols, and the + numeric `min_value` / `max_value` bounds. This is a code change with + tests, exactly like a new catalog term in `terms.py` — not a manifest + field. `load_spec` resolves the scalar before it validates anything, so + without this entry `mise run codegen:domain ` raises + `ScalarError: unknown scalar token ''` and emits nothing. Then search + the codegen tests for any fixture using `` as a negative "unknown + scalar" example (e.g. `test_spec.py`) and update it — registering the + kind makes that token valid. +- [ ] Declare the fixture plaintext list once in the manifest's `[fixture]` + table (see §9). The list MUST include `MIN`, `MAX`, and zero. +- [ ] Run `mise run codegen:domain ` to materialise generated SQL and the + committed `tests/sqlx/src/fixtures/_values.rs` while iterating, or + just `mise run build` — every build regenerates from the manifest first. + Commit the regenerated `_values.rs` (CI diffs it). +- [ ] Generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / + `*_aggregates.sql` are gitignored and never committed. The TOML + manifest plus `tasks/codegen/terms.py` are the source of truth. + Change the manifest or catalog and rebuild; do not hand-edit + generated SQL. +- [ ] Put optional hand-written SQL in + `src/encrypted_domain//_extensions.sql` with explicit + `-- REQUIRE:` edges. This file IS committed. +- [ ] Create a hand-reviewed byte-parity baseline under + `tests/codegen/reference//` — one file per generated SQL output plus + `_values.rs`, each headed with the `-- REFERENCE:` / `// REFERENCE:` + marker. `tasks/codegen/test_against_reference.py` only guards types that + have a baseline directory, so without it the new type gets no + drift protection. The committed-fixture parity assertion is currently + `int4`-only; extend it to cover ``. +- [ ] Run `mise run test:codegen`, the relevant SQLx suites, and the + PostgreSQL matrix before merging. + +## 3. Domain Generation + +The generator emits `src/encrypted_domain//_types.sql` (gitignored; +materialised on every `mise run build` and on `mise run codegen:domain +`) with one idempotent `DO $$ ... $$` block. Domain `CHECK` +constraints always require: + +- fixed envelope keys `v` and `i`; +- ciphertext key `c`; +- catalog JSON keys for the listed terms; +- the envelope version value: `VALUE->>'v' = '2'`, matching the repo-wide + `eql_v2._encrypted_check_v` rule (`src/encrypted/constraints.sql`). + +For example, a domain with `["ore"]` requires `v`, `i`, `c`, and `ob` present, +with `v` pinned to `2`. Beyond key presence and the version value, a malformed +term can still fail later inside its extractor unless a future catalog design +adds stronger validation. + +Every generated domain is a concrete domain over `jsonb`. Do not define +one generated domain over another generated domain; PostgreSQL resolves +operators against the underlying base type in ways that bypass the fixed +operator surface. + +## 4. Extractors And Wrappers + +Extractor names and return types come from `tasks/codegen/terms.py`, not +from TOML. Generated extractors and supported comparison wrappers are +inline-friendly SQL functions: + +```sql +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT ... $$; +``` + +Extractors and comparison wrappers must not carry a pinned `search_path` +— a `SET` clause disables inlining and reverts index-backed queries to +seq scans. The build tooling recognises these generated functions +structurally, so the generator does not emit `eql-inline-critical` +markers. Aggregate state functions are the one deliberate exception — see +§5 — because they are never index expressions. + +Unsupported operators route to blockers. Blockers are `plpgsql`, +`IMMUTABLE`, `PARALLEL SAFE`, and intentionally not `STRICT`. Both +choices are deliberate: + +- **`plpgsql`, not `sql`.** A `LANGUAGE sql` body would be inlinable, and + the planner could elide the call when the result is provably unused + (dead `CASE` branch, folded predicate), letting a blocked operator + appear to succeed. `plpgsql` is opaque to the planner, so the call — + and its `RAISE` — always survives. +- **Not `STRICT`.** A `STRICT` blocker lets PostgreSQL skip the body and + return `NULL` on a `NULL` argument, silently bypassing the + unsupported-operator exception. + +## 5. Operators + +Every generated domain declares supported scalar comparison operators plus +blockers for the native `jsonb` operator surface that PostgreSQL could +otherwise reach through domain-to-base-type fallback. Each domain emits +44 `CREATE OPERATOR` statements. Supported operators route to wrappers; +everything else routes to blockers. + +| Operators | Forms | +|---|---| +| `=` `<>` `<` `<=` `>` `>=` `@>` `<@` | `(domain, domain)` · `(domain, jsonb)` · `(jsonb, domain)` | +| `->` `->>` | `(domain, text)` · `(domain, integer)` · `(jsonb, domain)` | +| `?` | `(domain, text)` | +| `?\|` `?&` | `(domain, text[])` | +| `@?` `@@` | `(domain, jsonpath)` | +| `#>` `#>>` `#-` | `(domain, text[])` | +| `-` | `(domain, text)` · `(domain, integer)` · `(domain, text[])` | +| `\|\|` | `(domain, domain)` · `(domain, jsonb)` · `(jsonb, domain)` | + +Function counts: + +| Domain terms | Extractors | Wrappers | Blockers | Functions | Operators | +|---|---:|---:|---:|---:|---:| +| none | 0 | 0 | 44 | 44 | 44 | +| `hm` | 1 (`eq_term`) | 6 | 38 | 45 | 44 | +| `ore` | 1 (`ord_term`) | 18 | 26 | 45 | 44 | + +Supported comparison operators carry planner metadata such as +`COMMUTATOR`, `NEGATOR`, `RESTRICT`, and `JOIN`. Blocker operators keep +minimal metadata because they should never be planner-visible supported +paths. + +PostgreSQL's operator resolver still prefers the built-in `jsonb` operator +for untyped string literals in forms such as `payload::eql_v2_int4 ? 'c'`. +Use typed parameters or explicit casts (`'c'::text`) to route those forms +to the generated blocker. The generated surface blocks the typed native +operator shapes exposed by the catalog. + +### Aggregates + +Each ordered (ord-capable) domain additionally gets a generated +`_aggregates.sql` file declaring `MIN` / `MAX`: + +- two state functions, `eql_v2.min_sfunc` and `eql_v2.max_sfunc`, and +- two aggregates, `eql_v2.min()` and `eql_v2.max()`. + +Comparison routes through the domain's `<` / `>` operator (the ORE block +term — no decryption). The state functions are `LANGUAGE plpgsql +IMMUTABLE STRICT PARALLEL SAFE` **with** a pinned `SET search_path`. This is +the one place the "no pinned `search_path`" rule of §4 does not apply: +aggregate transition functions are never index expressions, so pinning is +correct. `STRICT` makes PostgreSQL seed the running state with the first +non-NULL value and skip NULLs, so an all-NULL group returns NULL. + +Each `CREATE AGGREGATE` declares `combinefunc = ` and +`parallel = safe`: min/max are associative, so the state function doubles as +the combine function, and with a `PARALLEL SAFE` sfunc/combinefunc +PostgreSQL can use partial and parallel aggregation on the large `GROUP BY` +ORE workloads these aggregates exist to serve — still with no decryption. +Storage-only and equality-only domains have no comparator and emit no +aggregate file. + +## 6. Extension Files + +Optional hand-written SQL beyond the fixed scalar surface belongs in: + +```text +src/encrypted_domain//_extensions.sql +``` + +The generator must not create this file, list it in TOML, add an +auto-generated header, or clean it during regeneration. The file must +declare its own `-- REQUIRE:` edges, usually to `_types.sql` and +whichever generated function or operator file it extends. Unlike the +generated siblings, `_extensions.sql` IS committed. + +## 7. Indexing + +Do not create operator classes on generated public domains. Index through +the extractor: + +```sql +CREATE INDEX ... ON table_name USING btree (eql_v2.ord_term(col)); +CREATE INDEX ... ON table_name USING hash (eql_v2.eq_term(col)); +``` + +The extractor return type must already have the needed PostgreSQL access +method support. `ore` depends on +`src/ore_block_u64_8_256/functions.sql` and +`src/ore_block_u64_8_256/operators.sql`; `hm` depends on +`src/hmac_256/functions.sql`. + +## 8. Tests + +Cover each generated domain with SQLx tests appropriate to its terms: + +- supported operators return correct rows for all argument forms; +- unsupported operators raise the expected error for all forms; +- blockers raise on `NULL` input; +- supported wrappers return `NULL` for `NULL` operands; +- functional indexes engage and return correct rows; +- constant-on-left comparisons engage the index where applicable; +- domain `CHECK` rejects non-object and under-populated payloads; +- real typed columns are tested, not only cast literals; +- generated ordered-domain twins remain byte-identical modulo type name + (verified by `tasks/codegen/test_against_reference.py` against the + hand-reviewed baseline in `tests/codegen/reference//`). + +For ordered numeric scalars this coverage is generated by the +`ordered_numeric_matrix!` convention wrapper in `tests/sqlx/src/matrix.rs`: +one `impl ScalarType` (`tests/sqlx/src/scalar_domains.rs`) plus a single +invocation taking `suite`, `scalar`, and `eql_type`. The matrix derives +its comparison pivots — the scalar's `MIN`, `MAX`, and zero +(`Default::default()`) — from the type rather than a hand-written list, so +the invocation carries no pivot argument. Equality-only scalars use the +sibling `eq_only_scalar_matrix!`. The `matrix.rs` module header is the +canonical, current list of the test categories the matrix emits (sanity, +correctness, cross-shape, supported-NULL, blocker raises, index engagement, +ORDER BY, ORDER BY USING) — read it rather than maintaining a duplicate +count here. + +For ordered `int4`, keep the assertion that distinct plaintext values +produce distinct ORE blocks. Do not add assertions for term behavior that +the catalog does not promise. + +## 9. Fixtures + +Fixture generation should use real encrypted payloads produced through +CipherStash Proxy. A single payload table may carry every term needed by +the generated domains for that type. For `int4`, the payloads carry `c`, +`hm`, and `ob`; the equality domain reads `hm`, and ordered domains read +`ob`. + +Choose values so range operators produce distinguishable result counts, +include useful boundaries, and cover omitted-term negative cases. For a +scalar driven by `ordered_numeric_matrix!`, the fixture **must** include +the type's `MIN`, `MAX`, and zero (`Default::default()`): the matrix uses +those three as comparison pivots and fetches each one's ciphertext from the +fixture via `fetch_fixture_payload`, which fails loudly if the row is +absent. + +### Single-sourcing the value list + +The plaintext value list is declared **once**, in the manifest's optional +`[fixture]` table, and generated into Rust — never hand-maintained in two +places: + +```toml +[fixture] +values = [ + "MIN", "-100", "-1", "ZERO", "1", "2", "5", "10", "17", "25", + "42", "50", "100", "250", "1000", "9999", "MAX", +] +``` + +Values are strings so the convention is type-agnostic. The sentinels `MIN`, +`MAX`, and `ZERO` map to the scalar's Rust named consts (for `int4`: +`i32::MIN`, `i32::MAX`, `0`); every other token is a numeric literal +validated against the type's representable range. The per-type rendering +rules live in `tasks/codegen/scalars.py` (mirroring `terms.py`), not in +free-form TOML fields. `load_spec` enforces the matrix invariant: the set +**must** include `MIN`, `MAX`, and zero, or the build fails. + +The generator emits `tests/sqlx/src/fixtures/_values.rs` exposing one +`pub const VALUES: &[]`. Both consumers reference that single +symbol — the fixture generator (`fixtures::eql_v2_::spec`) and the matrix +oracle (`impl ScalarType for { const FIXTURE_VALUES }`) — so the +oracle cannot drift from the values the generator encrypts. + +Unlike the gitignored `*_*.sql` surface and the gitignored encrypted +`tests/sqlx/fixtures/eql_v2_.sql` (whose ciphertext is non-deterministic +per-encrypt), `_values.rs` **is committed**: its rendering is +deterministic, so the CI `codegen` job regenerates it and runs +`git diff --exit-code` to catch a manifest edit that wasn't regenerated. +Regenerate with `mise run codegen:domain ` and commit the result; never +hand-edit it. + +## 10. Build And Verification + +- `mise run codegen:domain ` (optional; refreshes one type while + iterating on its manifest before a full build) +- `mise run test:codegen` +- `mise run clean && mise run build` (regenerates every type's SQL + from its manifest first, then builds the release artefacts) +- relevant SQLx suites +- `mise run test` across supported PostgreSQL versions +- `mise run --output prefix test:splinter --postgres 17` after a + PostgreSQL 17 install has built EQL + +The CI codegen job should remain a prerequisite of the PostgreSQL test +matrix so generated SQL drift is caught before database tests run. diff --git a/docs/reference/eql-functions.md b/docs/reference/eql-functions.md index 940cb1ae..5ee40c77 100644 --- a/docs/reference/eql-functions.md +++ b/docs/reference/eql-functions.md @@ -422,6 +422,33 @@ eql_v2.ste_vec(val eql_v2_encrypted) RETURNS eql_v2_encrypted[] eql_v2.ste_vec(val jsonb) RETURNS eql_v2_encrypted[] ``` +### `eql_v2.eq_term()` / `eql_v2.ord_term()` (encrypted-domain) + +Extract the equality (`hm`) or ordering (`ob`) index term from a scalar +encrypted-domain value. Generated per eq/ord-capable variant of every +scalar type — see [Encrypted-Domain Code Generator](./encrypted-domain-generator.md). +The argument type selects the overload, and both are inlinable so a +functional index built on the extractor engages. + +```sql +-- int4 — generated for every scalar type's eq / ord variants. +eql_v2.eq_term(a eql_v2_int4_eq) RETURNS eql_v2.hmac_256 +eql_v2.ord_term(a eql_v2_int4_ord) RETURNS eql_v2.ore_block_u64_8_256 +eql_v2.ord_term(a eql_v2_int4_ord_ore) RETURNS eql_v2.ore_block_u64_8_256 +``` + +**Example:** +```sql +-- Functional indexes on the extracted terms (see Database Indexes) +CREATE INDEX ON users USING hash (eql_v2.eq_term(salary_encrypted)); +CREATE INDEX ON users USING btree (eql_v2.ord_term(salary_encrypted)); +``` + +> The full per-domain operator/wrapper/blocker surface (and the +> `eql_v2_` / `_eq` / `_ord` / `_ord_ore` domain types themselves) is +> documented in [SQL support](./sql-support.md#encrypted-domain-scalar-types-eql_v2_t) +> and the [generator reference](./encrypted-domain-generator.md). + --- ## JSONB Path Functions @@ -540,10 +567,11 @@ eql_v2.meta_data(val jsonb) RETURNS jsonb ### `eql_v2.selector()` -Extract selector hash from encrypted value. +Extract selector hash from an encrypted payload (`jsonb`) or a ste_vec entry. ```sql -eql_v2.selector(val eql_v2_encrypted) RETURNS text +eql_v2.selector(val jsonb) RETURNS text +eql_v2.selector(entry eql_v2.ste_vec_entry) RETURNS text ``` ### `eql_v2.is_ste_vec_array()` @@ -624,34 +652,51 @@ FROM products GROUP BY eql_v2.jsonb_path_query_first(encrypted_json, 'color_selector'); ``` -### `eql_v2.min()` +### `eql_v2.min()` / `eql_v2.max()` (composite type) -Returns the minimum encrypted value in a set (requires `ore` index for ordering). +Returns the minimum or maximum encrypted value in a set on an `eql_v2_encrypted` column (requires `ore` index terms for ordering). ```sql eql_v2.min(eql_v2_encrypted) RETURNS eql_v2_encrypted +eql_v2.max(eql_v2_encrypted) RETURNS eql_v2_encrypted ``` +Comparison routes through the `<` / `>` operator on `eql_v2_encrypted`, which uses the ORE block term — no decryption. + **Example:** ```sql SELECT eql_v2.min(encrypted_date) FROM events; -SELECT eql_v2.min(encrypted_price) FROM products WHERE category = 'electronics'; +SELECT eql_v2.max(encrypted_price) FROM products WHERE category = 'electronics'; ``` -### `eql_v2.max()` +### `eql_v2.min()` / `eql_v2.max()` (per-domain) -Returns the maximum encrypted value in a set (requires `ore` index for ordering). +Returns the minimum or maximum encrypted value in a set on an ordered encrypted-domain column. Defined per ord-capable variant of every scalar type (`eql_v2__ord`, `eql_v2__ord_ore`); the input type selects the aggregate via PostgreSQL's overload resolution. These are type-safe alternatives to the composite-type aggregates above and coexist with them. ```sql -eql_v2.max(eql_v2_encrypted) RETURNS eql_v2_encrypted +-- int4 — generated for every ordered variant of every scalar type. +eql_v2.min(eql_v2_int4_ord) RETURNS eql_v2_int4_ord +eql_v2.max(eql_v2_int4_ord) RETURNS eql_v2_int4_ord +eql_v2.min(eql_v2_int4_ord_ore) RETURNS eql_v2_int4_ord_ore +eql_v2.max(eql_v2_int4_ord_ore) RETURNS eql_v2_int4_ord_ore ``` +Comparison routes through the variant's `<` / `>` operator, which uses the ORE block term — no decryption. The state function is `STRICT`, so `NULL` inputs are skipped and an all-`NULL` input set returns `NULL`. + **Example:** ```sql -SELECT eql_v2.max(encrypted_date) FROM events; -SELECT eql_v2.max(encrypted_price) FROM products WHERE category = 'electronics'; +-- ord-capable column (e.g. price_encrypted typed as eql_v2_int4_ord) +SELECT eql_v2.min(price_encrypted) FROM products; +SELECT eql_v2.max(price_encrypted) FROM products WHERE category = 'electronics'; + +-- Equivalent on a generic jsonb column (cast to the right domain) +SELECT eql_v2.min(price_jsonb::eql_v2_int4_ord) FROM products; ``` +`SUM` / `AVG` and other numeric aggregates are not supported on encrypted columns — decrypt at the application boundary. `MIN` / `MAX` only require comparator-revealing terms; arithmetic aggregates would require homomorphic encryption. + +**See also:** [`docs/reference/sql-support.md`](./sql-support.md) for the per-variant capability table. + --- ## Utility Functions diff --git a/docs/reference/sql-support.md b/docs/reference/sql-support.md index e1bf18e4..ff2de727 100644 --- a/docs/reference/sql-support.md +++ b/docs/reference/sql-support.md @@ -59,6 +59,25 @@ Use the equivalent [`jsonb_path_query`](#jsonb-functions-and-selectors-enabled-b --- +## Encrypted-domain scalar types (`eql_v2_`) + +Scalar encrypted-domain types (e.g. `eql_v2_int4`; see the [generator reference](./encrypted-domain-generator.md)) are a different access model from the matrix above. Instead of configuring a search index on an `eql_v2_encrypted` column, you type the column as a specific domain *variant* whose operator surface is fixed at generation time. The index terms travel in the payload; there is no `add_search_config` step. + +Each scalar type `` generates one storage-only variant plus eq/ord query variants: + +| Domain variant | Term carried | `=` `<>` | `<` `<=` `>` `>=` | `MIN` / `MAX` | `LIKE`/`ILIKE`, JSONB / ste_vec ops | +| ------------------------------- | ------------------- | :------: | :---------------: | :-----------: | :---------------------------------: | +| `eql_v2_` | none (storage only) | ❌ | ❌ | ❌ | ❌ | +| `eql_v2__eq` | `hm` (hmac_256) | ✅ | ❌ | ❌ | ❌ | +| `eql_v2__ord` / `_ord_ore` | `ob` (ore_block) | ✅ | ✅ | ✅ | ❌ | + +- The bare `eql_v2_` variant carries no index term and **blocks every comparison operator** — it is storage / decryption only. Type the column as `_eq` or `_ord` (or cast at the call site) when you need to query. +- Unsupported operators are not silent no-ops: they route to blocker functions that `RAISE` an "operator not supported" exception (a `NULL` operand still raises — the blockers are deliberately not `STRICT`). +- `LIKE` / `ILIKE` and the native JSONB operators (`@>`, `<@`, `->`, `->>`, `?`, `?|`, `?&`, `@?`, `@@`, `#>`, `#>>`, `-`, `#-`, `||`) are blocked on **every** scalar domain variant — they are meaningless on a scalar payload. +- `MIN` / `MAX` are exposed only on the ordered variants as `eql_v2.min(eql_v2__ord)` / `eql_v2.max(...)` — see [EQL Functions Reference](./eql-functions.md#eql_v2min--eql_v2max-per-domain). + +--- + ## SQL syntax / feature support This matrix covers higher-level SQL constructs rather than individual operators. As above, ✅ requires the listed index to be configured on the column; ❌ means the construct cannot be used against that column (without first decrypting via CipherStash Proxy or Protect.js). @@ -76,7 +95,7 @@ This matrix covers higher-level SQL constructs rather than individual operators. | `GROUP BY col` | requires `unique` on the whole column; `ore` / `ope` not yet supported (see note below). Extracted JSON paths have separate caveats — see [ste_vec section](#index-terms-by-json-node-type). | ✅ | ❌ | ❌ | ❌ | ❌ | | `DISTINCT` / `DISTINCT ON (col)` | `unique`, `ore`, or `ope` | ✅ | ✅ | ✅ | ❌ | ❌ | | `HAVING` | same index requirements as the predicates used in `HAVING` (see operator matrix) | varies | varies | varies | varies | varies | -| `MIN(col)` / `MAX(col)` | | ❌ | ✅ | ✅ | ❌ | ❌ | +| `MIN(col)` / `MAX(col)` | `eql_v2.min(eql_v2_encrypted)` / `max` work on any `eql_v2_encrypted` column with `ore` terms. The encrypted-domain family additionally exposes type-safe `eql_v2.min(eql_v2__ord)` / `max` (and the `_ord_ore` twin); `Storage` and `Eq` variants have no comparator and do not declare these aggregates. | ❌ | ✅ | ✅ | ❌ | ❌ | | `COUNT(col)` / `COUNT(DISTINCT col)` | `ore` / `ope` or `unique` for `DISTINCT`; none for plain `COUNT(col)` | ✅ | ✅ | ✅ | ✅ | ✅ | | `JOIN … ON lhs.col = rhs.col` | same index and keyset on both sides | ✅ | ✅ | ✅ | ❌ | ❌ | | `JOIN … ON lhs.col < rhs.col` etc. | same index and keyset on both sides | ❌ | ✅ | ✅ | ❌ | ❌ | @@ -89,7 +108,8 @@ Notes: - **Cross-column / cross-table comparisons** (joins, `IN (subquery)`, `UNION` dedup, etc.) require both sides to have been encrypted with the *same* keyset and the matching search index. Encrypted values from different `ste_vec` prefixes are deliberately incomparable. - **`GROUP BY`** on encrypted columns relies on an operator class which currently only supports encrypted values with a `unique` index term. This is a surprising limitation because it would be natural to expect `ore` / `ope` index terms to also work. This limitation will be lifted in the future. See [Database Indexes](./database-indexes.md#group-by) for performance considerations. - **`ORDER BY`** without an `ore` or `ope` index will still *run* (the EQL `compare` function has a deterministic literal fallback to avoid btree errors), but the resulting order is not meaningful. Configure `ore` (or `ope`) whenever ordering matters. -- **Aggregates beyond `MIN`/`MAX`** (e.g. `SUM`, `AVG`) are not supported on encrypted values — decrypt and perform those aggregate operations on the client-side instead. +- **`MIN(col)` / `MAX(col)`** is available two ways. The composite-type aggregates `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` work on any `eql_v2_encrypted` column carrying `ore` terms. The encrypted-domain family additionally exposes type-safe per-variant aggregates — see `eql_v2.min(eql_v2__ord)` / `eql_v2.max(eql_v2__ord)` (and the `_ord_ore` twin) in [EQL Functions Reference](./eql-functions.md#eql_v2min--eql_v2max-per-domain). For a domain-typed column, type it as the appropriate `_ord` variant or cast at the call site (`eql_v2.min(col::eql_v2_int4_ord)`). +- **Aggregates beyond `MIN`/`MAX`** (e.g. `SUM`, `AVG`) are not supported on encrypted values — they would require homomorphic encryption. Decrypt at the application boundary and perform those aggregates client-side. - **Parameter binding**: CipherStash Proxy rewrites bound parameters in `WHERE`, `JOIN`, and `RETURNING` clauses with `::JSONB::eql_v2_encrypted` casts so that the encrypted operator and any B-tree / GIN indexes are selected. Writing those casts yourself is only required when bypassing the proxy. --- diff --git a/tasks/docs/generate.sh b/tasks/docs/generate.sh index ea5a5658..033bdc20 100755 --- a/tasks/docs/generate.sh +++ b/tasks/docs/generate.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash #MISE description="Generate API documentation (with Doxygen)" +# Build first so generated encrypted-domain SQL exists under src/. +#MISE depends=["build"] set -e diff --git a/tasks/docs/validate.sh b/tasks/docs/validate.sh index 39275596..14b659af 100755 --- a/tasks/docs/validate.sh +++ b/tasks/docs/validate.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash #MISE description="Validate SQL documentation" +# Build first so generated encrypted-domain SQL exists under src/. +#MISE depends=["build"] set -e diff --git a/tasks/docs/validate/coverage.sh b/tasks/docs/validate/coverage.sh index 623f8f2f..4657ec76 100755 --- a/tasks/docs/validate/coverage.sh +++ b/tasks/docs/validate/coverage.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash #MISE description="Checks documentation coverage for SQL files" +# Build first so generated encrypted-domain SQL exists under src/. +#MISE depends=["build"] set -e diff --git a/tasks/docs/validate/documented-sql.sh b/tasks/docs/validate/documented-sql.sh index b7fd166d..9ce3fb34 100755 --- a/tasks/docs/validate/documented-sql.sh +++ b/tasks/docs/validate/documented-sql.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash #MISE description="Validates SQL syntax for all documented files" +# Build first so generated encrypted-domain SQL exists under src/. +#MISE depends=["build"] set -e diff --git a/tasks/docs/validate/required-tags.sh b/tasks/docs/validate/required-tags.sh index 55e59557..602c37c1 100755 --- a/tasks/docs/validate/required-tags.sh +++ b/tasks/docs/validate/required-tags.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash #MISE description="Validates required Doxygen tags are present" +# Build first so generated encrypted-domain SQL exists under src/. +#MISE depends=["build"] set -e From e26e3a57ffcd62a9342826a7337cc7d3673073b1 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 12:32:52 +1000 Subject: [PATCH 15/93] ci(test-eql): pin third-party actions to SHAs, scope permissions Pin third-party actions to commit SHAs, set permissions: contents: read and persist-credentials: false on checkouts, and add a codegen job gating the PG matrix. Part of PR #239. --- .github/workflows/test-eql.yml | 100 ++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 844764b6..436df886 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -29,21 +29,26 @@ defaults: run: shell: bash -l {0} +permissions: + contents: read + jobs: schema: name: "JSON Schema validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - - uses: jdx/mise-action@v4 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 with: version: 2026.4.0 install: true cache: true - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: tests/sqlx shared-key: sqlx-tests @@ -52,10 +57,76 @@ jobs: run: | mise run test:schema + codegen: + name: "Encrypted-domain codegen" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 + with: + version: 2026.4.0 + install: true + cache: true + + - name: Run codegen generator + drift tests + run: | + mise run test:codegen + + # Regenerate the committed Rust fixture-value consts for EVERY type from + # their manifests and fail if any differ from / are missing in the tree. + # The value lists are rendered deterministically (unlike the encrypted + # .sql fixtures, whose ciphertext is non-deterministic and gitignored), so + # a plain diff is the right guard — it catches a manifest edit that wasn't + # regenerated. `git add -N` registers any brand-new untracked const so a + # forgotten-to-commit file also trips the diff. No Postgres needed: this + # only runs the Python generator. + - name: Regenerate and verify fixture-value consts (all types) + run: | + mise run codegen:domain:all + git add -N tests/sqlx/src/fixtures + git diff --exit-code -- tests/sqlx/src/fixtures \ + || { echo "Fixture value const(s) stale or uncommitted — run 'mise run codegen:domain:all' and commit tests/sqlx/src/fixtures."; exit 1; } + + matrix-coverage: + name: "Matrix coverage inventory" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 + with: + version: 2026.4.0 + install: true + cache: true + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + workspaces: tests/sqlx + shared-key: sqlx-tests + + # Regenerate the matrix test-name inventory with the SAME pinned feature + # set the local task uses (`--no-default-features`, scale excluded), then + # fail if it differs from the committed snapshot. A coverage change shows + # up as added/removed names in the PR diff — e.g. emptying `ord_domains` + # drops ~140 names, impossible to miss in review. No Postgres needed: + # `--list` only enumerates, the suite uses runtime queries. + - name: Regenerate and verify the matrix test-name inventory + run: | + mise run test:matrix:inventory + git diff --exit-code -- tests/sqlx/snapshots/int4_matrix_tests.txt \ + || { echo "Coverage inventory stale — run 'mise run test:matrix:inventory' and commit."; exit 1; } + test: name: "Test & Validate EQL (Postgres ${{ matrix.postgres-version }})" runs-on: ubuntu-latest-m - needs: schema + needs: [schema, codegen] strategy: fail-fast: false @@ -64,19 +135,28 @@ jobs: env: POSTGRES_VERSION: ${{ matrix.postgres-version }} + # CS_* are required for `mise run test:sqlx` to regenerate the + # cipherstash-client-encrypted fixtures before the suite runs. + # This repository does not accept fork PRs, so the secrets-on- + # `pull_request` constraint that breaks the fork CI flow does not + # apply here — leave the env block unconditional. CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }} CS_WORKSPACE_CRN: ${{ secrets.CS_WORKSPACE_CRN }} + CS_CLIENT_ID: ${{ secrets.CS_CLIENT_ID }} + CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - - uses: jdx/mise-action@v4 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 with: version: 2026.4.0 install: true # [default: true] run `mise install` cache: true # [default: true] cache mise using GitHub's cache - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: tests/sqlx shared-key: sqlx-tests @@ -103,9 +183,11 @@ jobs: POSTGRES_VERSION: "17" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - - uses: jdx/mise-action@v4 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 with: version: 2026.4.0 install: true From bf179b0f9d89eed4a56f8fd71f6c1a14ed7d718c Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 14:12:43 +1000 Subject: [PATCH 16/93] test(encrypted-domain): scope jsonb-surface guard to native operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The jsonb_operator_surface guard queried pg_operator for every operator with a jsonb operand and asserted the set is a subset of the 20 known native jsonb operators. It swept in EQL's own cross-type operators on the legacy eql_v2_encrypted composite (`eql_v2_encrypted ~~ jsonb`, `jsonb ~~ eql_v2_encrypted`) — they take a jsonb operand but are not native and are unreachable from a storage scalar domain — failing CI with ["~~", "~~*"]. Exclude operands typed eql_v2_encrypted so the guard tests only the native jsonb surface a domain can fall through to. The deliberate design is unchanged: int4 has no LIKE, operator_surface.py pins exactly 20 operators and excludes ~~/~~*, and the matrix native_absent_ops arm asserts ~~/~~* parse-error on storage domains. Verified: full encrypted_domain suite 239 passed / 0 failed (was 238/1); operator_surface Python tests 11 passed; no codegen change. --- .../family/jsonb_operator_surface.rs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs b/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs index 8dc2e049..1a2fb95e 100644 --- a/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs +++ b/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs @@ -10,8 +10,11 @@ //! Those lists are an *enumeration*, not a structural guarantee: a future PG //! version could add a jsonb operator that nobody adds here, and it would //! silently route to native jsonb behaviour. This test closes that gap by -//! asking the live catalog which operators actually touch `jsonb` and failing -//! if any symbol is absent from the known union. +//! asking the live catalog which *native* operators touch `jsonb` and failing +//! if any symbol is absent from the known union. EQL's own cross-type operators +//! on the legacy `eql_v2_encrypted` composite (which also take a jsonb operand, +//! e.g. `~~` / `~~*`) are excluded — they are not native and are unreachable +//! from a storage scalar domain. //! //! Source of truth: `tasks/codegen/operator_surface.py::KNOWN_JSONB_OPERATORS` //! (asserted complete by `tasks/codegen/test_operator_surface.py`). The set @@ -36,15 +39,25 @@ const KNOWN_JSONB_OPERATORS: &[&str] = &[ #[sqlx::test] async fn every_native_jsonb_operator_is_known_to_the_generator(pool: PgPool) -> Result<()> { - // Distinct operator symbols whose left OR right argument is `jsonb`. This - // is the full surface a value typed as a jsonb-backed domain can reach via + // Distinct operator symbols whose left OR right argument is `jsonb` — the + // native surface a value typed as a jsonb-backed domain can reach via // operator resolution against the ultimate base type. + // + // Exclude EQL's own cross-type operators on the legacy `eql_v2_encrypted` + // composite (e.g. `eql_v2_encrypted ~~ jsonb`, `jsonb ~~ eql_v2_encrypted`). + // They take a jsonb operand but are NOT native plaintext-jsonb operators and + // are unreachable from a storage scalar domain: a `eql_v2_int4` operand + // resolves to the domain / its jsonb base, never to `eql_v2_encrypted`, so + // `col ~~ x` finds no operator (asserted by the matrix `native_absent_ops` + // arm). Matching on `typname` is search_path-independent and a harmless + // no-op when the type is absent (e.g. the Protect build variant). let native: Vec = sqlx::query_scalar( r#" SELECT DISTINCT o.oprname FROM pg_catalog.pg_operator o - WHERE o.oprleft = 'jsonb'::regtype - OR o.oprright = 'jsonb'::regtype + WHERE (o.oprleft = 'jsonb'::regtype OR o.oprright = 'jsonb'::regtype) + AND o.oprleft NOT IN (SELECT oid FROM pg_catalog.pg_type WHERE typname = 'eql_v2_encrypted') + AND o.oprright NOT IN (SELECT oid FROM pg_catalog.pg_type WHERE typname = 'eql_v2_encrypted') ORDER BY 1 "#, ) From 258564b7f6386aa4e9c4f728a9dd66b30928a900 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Fri, 29 May 2026 16:10:31 +1000 Subject: [PATCH 17/93] test(int4): split out cargo-expand macro-expansion snapshot guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the int4 matrix `cargo expand` snapshot and its regeneration tooling into this stacked PR so the main int4 PR (#239) no longer carries the ~37k-line generated snapshot. The snapshot is a body-level fidelity backstop for the `ordered_numeric_matrix!` / `scalar_domain_matrix!` macros — it catches changes *inside* generated test bodies, complementing the name-inventory snapshot (`int4_matrix_tests.txt`, checked in test-eql.yml) that catches add/remove of whole arms. - tests/sqlx/snapshots/int4_expanded.rs — the expanded matrix snapshot - .github/workflows/macro-expand-eql.yml — nightly, non-blocking drift check - mise.toml — the `test:matrix:expand` regeneration task (pinned nightly) --- .github/workflows/macro-expand-eql.yml | 69 + mise.toml | 43 + tests/sqlx/snapshots/int4_expanded.rs | 28811 +++++++++++++++++++++++ 3 files changed, 28923 insertions(+) create mode 100644 .github/workflows/macro-expand-eql.yml create mode 100644 tests/sqlx/snapshots/int4_expanded.rs diff --git a/.github/workflows/macro-expand-eql.yml b/.github/workflows/macro-expand-eql.yml new file mode 100644 index 00000000..5ba71543 --- /dev/null +++ b/.github/workflows/macro-expand-eql.yml @@ -0,0 +1,69 @@ +name: "Macro expand EQL" + +# Regenerates the int4 matrix `cargo expand` snapshot and fails if it has +# drifted from the committed copy. This is a body-level fidelity backstop for +# the `ordered_numeric_matrix!` / `scalar_domain_matrix!` macros — the +# name-inventory snapshot (test-eql.yml `matrix-coverage` job) catches +# add/remove of whole arms; this catches changes *inside* the generated bodies. +# +# Non-blocking by design: it is NOT a required PR check. `cargo expand` needs a +# nightly toolchain, so it is isolated off the PR path. +# - nightly schedule (the backstop that flags a forgotten local regen) +# - manual workflow_dispatch +# +# The toolchain is pinned (nightly-2026-05-01) in lockstep with the +# `test:matrix:expand` mise task so the snapshot only moves when the macro +# moves, not when nightly reformats its expansion. Bump both together. +on: + schedule: + # 03:00 UTC daily + - cron: "0 3 * * *" + + workflow_dispatch: + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + MISE_VERBOSE: "1" + +defaults: + run: + shell: bash -l {0} + +permissions: + contents: read + +jobs: + macro-expand: + name: "Macro expand drift (nightly)" + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: jdx/mise-action@v4 + with: + version: 2026.4.0 + install: true + cache: true + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: tests/sqlx + shared-key: sqlx-tests + + # Pinned nightly — keep the date in lockstep with the `cargo +nightly-...` + # invocation in the `test:matrix:expand` mise task. rustfmt formats the + # expansion deterministically. + - name: Install pinned nightly + cargo-expand + run: | + rustup toolchain install nightly-2026-05-01 --profile minimal --component rustfmt + cargo binstall -y cargo-expand + + - name: Regenerate and verify the matrix expansion snapshot + run: | + mise run test:matrix:expand + git diff --exit-code -- tests/sqlx/snapshots/int4_expanded.rs \ + || { echo "Expansion snapshot stale — run 'mise run test:matrix:expand' (needs the pinned nightly) and commit."; exit 1; } diff --git a/mise.toml b/mise.toml index 33c96519..47e03ea4 100644 --- a/mise.toml +++ b/mise.toml @@ -114,3 +114,46 @@ cargo test --no-default-features --test encrypted_domain -- --list | grep '^scalars::int4' | LC_ALL=C sort > snapshots/int4_matrix_tests.txt """ + +[tasks."test:matrix:expand"] +description = "Regenerate the int4 matrix cargo-expand snapshot (requires the pinned nightly + cargo-expand)" +dir = "{{config_root}}/tests/sqlx" +run = """ +# Body-level fidelity backstop for the macro: the expanded source of the int4 +# matrix arms. NIGHTLY is pinned to a known-good date so the snapshot only moves +# when *the macro* moves, not when nightly reformats — bump the date deliberately +# and in lockstep with .github/workflows/macro-expand-eql.yml. +# +# `#[sqlx::test]` embeds one `sqlx::migrate::Migration` per file in migrations/ +# plus the fixture (via include_str) into EVERY generated test — ~477 MB of +# repeated data dwarfing the macro bodies, and non-deterministic across +# environments (the generated 001_install_eql.sql is absent in a bare checkout). +# Normalise both to fixed empties so the snapshot depends only on matrix.rs + +# the sqlx/test harness: swap migrations/ for a single empty placeholder and +# empty the int4 fixture, expand, then restore (trap fires on any exit). This is +# expand-only surgery on gitignored/generated inputs; nothing here is committed. +# +# Non-blocking lane (no Postgres, never compiled): the `.rs` name lives under +# snapshots/, not tests/, so Cargo never treats it as a test target. +set -euo pipefail +mkdir -p snapshots fixtures +BK=$(mktemp -d) +cp -a migrations "$BK/migrations" +# The int4 fixture is gitignored (regenerated) and absent in a bare checkout — +# back it up only if present, and on restore drop the empty stand-in if so. +HAD_FIXTURE=0 +if [ -f fixtures/eql_v2_int4.sql ]; then cp -a fixtures/eql_v2_int4.sql "$BK/eql_v2_int4.sql"; HAD_FIXTURE=1; fi +restore() { + rm -rf migrations && cp -a "$BK/migrations" migrations + if [ "$HAD_FIXTURE" = 1 ]; then cp -af "$BK/eql_v2_int4.sql" fixtures/eql_v2_int4.sql; else rm -f fixtures/eql_v2_int4.sql; fi + rm -rf "$BK" +} +trap restore EXIT +# Wipe + recreate so the expand input is ALWAYS exactly one empty migration + +# one empty fixture, regardless of what the checkout had — this is what makes +# the snapshot deterministic across local and CI. +rm -rf migrations && mkdir migrations +: > migrations/0001_placeholder.sql +: > fixtures/eql_v2_int4.sql +cargo +nightly-2026-05-01 expand --test encrypted_domain scalars::int4 > snapshots/int4_expanded.rs +""" diff --git a/tests/sqlx/snapshots/int4_expanded.rs b/tests/sqlx/snapshots/int4_expanded.rs new file mode 100644 index 00000000..3441b01b --- /dev/null +++ b/tests/sqlx/snapshots/int4_expanded.rs @@ -0,0 +1,28811 @@ +pub mod int4 { + //! `eql_v2_int4` — the reference scalar implementation. + //! + //! Adding a new ordered numeric scalar (i64, f64, date, ...) is one + //! `impl ScalarType` in `tests/sqlx/src/scalar_domains.rs` plus an + //! `ordered_numeric_matrix!` invocation like this one. The matrix covers + //! everything generic over `T: ScalarType`. + use eql_tests::ordered_numeric_matrix; + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_sanity"] + #[doc(hidden)] + pub const matrix_int4_storage_sanity: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_storage_sanity"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 451usize, + start_col: 26usize, + end_line: 451usize, + end_col: 60usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_sanity()), + ), + }; + fn matrix_int4_storage_sanity() -> anyhow::Result<()> { + async fn matrix_int4_storage_sanity(_pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + if !!spec.sql_domain.is_empty() { + ::core::panicking::panic( + "assertion failed: !spec.sql_domain.is_empty()", + ) + } + if !::fixture_table_name() + .starts_with("fixtures.") + { + ::core::panicking::panic( + "assertion failed: ::fixture_table_name().starts_with(\"fixtures.\")", + ) + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_sanity", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_sanity; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_sanity"] + #[doc(hidden)] + pub const matrix_int4_eq_sanity: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_sanity"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 451usize, + start_col: 26usize, + end_line: 451usize, + end_col: 60usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_sanity()), + ), + }; + fn matrix_int4_eq_sanity() -> anyhow::Result<()> { + async fn matrix_int4_eq_sanity(_pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + if !!spec.sql_domain.is_empty() { + ::core::panicking::panic( + "assertion failed: !spec.sql_domain.is_empty()", + ) + } + if !::fixture_table_name() + .starts_with("fixtures.") + { + ::core::panicking::panic( + "assertion failed: ::fixture_table_name().starts_with(\"fixtures.\")", + ) + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_sanity", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_sanity; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_sanity"] + #[doc(hidden)] + pub const matrix_int4_ord_sanity: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_ord_sanity"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 451usize, + start_col: 26usize, + end_line: 451usize, + end_col: 60usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_sanity()), + ), + }; + fn matrix_int4_ord_sanity() -> anyhow::Result<()> { + async fn matrix_int4_ord_sanity(_pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + if !!spec.sql_domain.is_empty() { + ::core::panicking::panic( + "assertion failed: !spec.sql_domain.is_empty()", + ) + } + if !::fixture_table_name() + .starts_with("fixtures.") + { + ::core::panicking::panic( + "assertion failed: ::fixture_table_name().starts_with(\"fixtures.\")", + ) + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_sanity", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_sanity; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_sanity"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_sanity: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_ord_ore_sanity"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 451usize, + start_col: 26usize, + end_line: 451usize, + end_col: 60usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_sanity()), + ), + }; + fn matrix_int4_ord_ore_sanity() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_sanity(_pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + if !!spec.sql_domain.is_empty() { + ::core::panicking::panic( + "assertion failed: !spec.sql_domain.is_empty()", + ) + } + if !::fixture_table_name() + .starts_with("fixtures.") + { + ::core::panicking::panic( + "assertion failed: ::fixture_table_name().starts_with(\"fixtures.\")", + ) + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_sanity", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_sanity; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_eq_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_eq_eq_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_eq_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_eq_pivot_min_correctness()), + ), + }; + fn matrix_int4_eq_eq_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_eq_eq_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_eq_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_eq_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_eq_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_eq_eq_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_eq_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_eq_pivot_max_correctness()), + ), + }; + fn matrix_int4_eq_eq_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_eq_eq_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_eq_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_eq_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_eq_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_eq_eq_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_eq_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_eq_pivot_zero_correctness()), + ), + }; + fn matrix_int4_eq_eq_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_eq_eq_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_eq_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_eq_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_neq_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_eq_neq_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_neq_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_neq_pivot_min_correctness()), + ), + }; + fn matrix_int4_eq_neq_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_eq_neq_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_neq_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_neq_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_neq_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_eq_neq_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_neq_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_neq_pivot_max_correctness()), + ), + }; + fn matrix_int4_eq_neq_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_eq_neq_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_neq_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_neq_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_neq_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_eq_neq_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_neq_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_neq_pivot_zero_correctness()), + ), + }; + fn matrix_int4_eq_neq_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_eq_neq_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_neq_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_neq_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_eq_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_eq_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_eq_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_eq_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_eq_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_eq_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_eq_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_eq_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_eq_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_eq_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_eq_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_eq_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_eq_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_eq_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_eq_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_eq_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_eq_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_eq_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_eq_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_eq_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_eq_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_eq_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_eq_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_eq_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_neq_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_neq_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_neq_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_neq_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_neq_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_neq_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_neq_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_neq_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_neq_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_neq_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_neq_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_neq_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_neq_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_neq_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_neq_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_neq_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_neq_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_neq_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_neq_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_neq_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_neq_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_neq_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_neq_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_neq_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_eq_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_eq_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_eq_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_eq_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_ore_eq_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_eq_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_eq_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_eq_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_eq_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_eq_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_eq_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_eq_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_ore_eq_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_eq_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_eq_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_eq_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_eq_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_eq_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_ore_eq_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_eq_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_eq_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_neq_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_neq_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_neq_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_neq_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_ore_neq_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_neq_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_neq_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_neq_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_neq_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_neq_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_neq_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_neq_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_ore_neq_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_neq_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_neq_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_neq_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_neq_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_neq_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_ore_neq_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_neq_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<>", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<>", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<>", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_neq_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lt_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_lt_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lt_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lt_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_lt_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_lt_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lt_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lt_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lt_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_lt_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lt_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lt_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_lt_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_lt_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lt_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lt_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lt_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_lt_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lt_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lt_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_lt_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_lt_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lt_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lt_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lte_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_lte_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lte_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lte_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_lte_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_lte_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lte_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lte_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lte_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_lte_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lte_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lte_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_lte_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_lte_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lte_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lte_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lte_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_lte_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lte_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lte_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_lte_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_lte_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lte_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lte_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gt_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_gt_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gt_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gt_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_gt_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_gt_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gt_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gt_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gt_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_gt_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gt_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gt_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_gt_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_gt_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gt_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gt_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gt_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_gt_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gt_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gt_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_gt_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_gt_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gt_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gt_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gte_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_gte_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gte_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gte_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_gte_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_gte_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">=", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gte_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gte_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gte_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_gte_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gte_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gte_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_gte_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_gte_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">=", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gte_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gte_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gte_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_gte_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gte_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gte_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_gte_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_gte_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">=", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gte_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gte_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lt_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lt_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lt_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lt_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_ore_lt_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lt_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lt_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lt_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lt_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lt_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lt_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lt_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_ore_lt_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lt_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lt_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lt_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lt_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lt_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_ore_lt_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lt_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lt_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lte_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lte_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lte_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lte_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_ore_lte_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lte_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lte_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lte_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lte_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lte_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lte_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lte_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_ore_lte_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lte_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lte_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lte_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lte_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lte_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_ore_lte_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lte_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + "<=", + lit, + ), + ) + }); + let expected = ::expected_forward( + "<=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, "<=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lte_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gt_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gt_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gt_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gt_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_ore_gt_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gt_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gt_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gt_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gt_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gt_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gt_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gt_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_ore_gt_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gt_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gt_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gt_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gt_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gt_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_ore_gt_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gt_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gt_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gte_pivot_min_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gte_pivot_min_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gte_pivot_min_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gte_pivot_min_correctness()), + ), + }; + fn matrix_int4_ord_ore_gte_pivot_min_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gte_pivot_min_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">=", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gte_pivot_min_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gte_pivot_min_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gte_pivot_max_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gte_pivot_max_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gte_pivot_max_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gte_pivot_max_correctness()), + ), + }; + fn matrix_int4_ord_ore_gte_pivot_max_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gte_pivot_max_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">=", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gte_pivot_max_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gte_pivot_max_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_correctness"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gte_pivot_zero_correctness: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_correctness", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 587usize, + start_col: 22usize, + end_line: 587usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gte_pivot_zero_correctness()), + ), + }; + fn matrix_int4_ord_ore_gte_pivot_zero_correctness() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gte_pivot_zero_correctness( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let predicate = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{0} {1} {2}::jsonb::{0}", + &spec.sql_domain, + ">=", + lit, + ), + ) + }); + let expected = ::expected_forward( + ">=", + pivot, + ); + ::eql_tests::scalar_domains::assert_scalar_plaintexts::< + i32, + >(&pool, &spec.sql_domain, ">=", &predicate, &expected) + .await + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_correctness", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gte_pivot_zero_correctness; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_eq_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_eq_eq_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_eq_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_eq_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_eq_eq_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_eq_eq_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_eq_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_eq_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_eq_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_eq_eq_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_eq_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_eq_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_eq_eq_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_eq_eq_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_eq_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_eq_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_eq_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_eq_eq_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_eq_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_eq_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_eq_eq_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_eq_eq_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_eq_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_eq_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_neq_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_eq_neq_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_neq_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_neq_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_eq_neq_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_eq_neq_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_neq_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_neq_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_neq_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_eq_neq_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_neq_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_neq_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_eq_neq_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_eq_neq_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_neq_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_neq_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_neq_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_eq_neq_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_neq_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_neq_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_eq_neq_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_eq_neq_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_neq_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_neq_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_eq_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_eq_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_eq_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_eq_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_eq_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_eq_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_eq_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_eq_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_eq_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_eq_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_eq_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_eq_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_eq_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_eq_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_eq_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_eq_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_eq_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_eq_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_eq_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_eq_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_eq_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_eq_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_eq_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_eq_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_neq_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_neq_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_neq_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_neq_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_neq_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_neq_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_neq_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_neq_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_neq_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_neq_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_neq_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_neq_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_neq_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_neq_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_neq_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_neq_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_neq_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_neq_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_neq_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_neq_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_neq_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_neq_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_neq_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_neq_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_eq_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_eq_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_eq_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_eq_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_eq_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_eq_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_eq_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_eq_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_eq_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_eq_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_eq_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_eq_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_eq_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_eq_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_eq_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_eq_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_eq_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_eq_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_eq_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_eq_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_eq_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_neq_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_neq_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_neq_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_neq_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_neq_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_neq_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_neq_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_neq_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_neq_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_neq_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_neq_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_neq_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_neq_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_neq_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_neq_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_neq_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_neq_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_neq_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_neq_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_neq_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<>", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<>"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<>", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<>", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<>", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<>", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_neq_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lt_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_lt_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lt_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lt_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_lt_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_lt_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lt_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lt_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lt_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_lt_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lt_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lt_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_lt_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_lt_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lt_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lt_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lt_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_lt_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lt_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lt_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_lt_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_lt_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lt_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lt_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lte_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_lte_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lte_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lte_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_lte_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_lte_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lte_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lte_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lte_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_lte_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lte_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lte_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_lte_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_lte_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lte_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lte_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lte_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_lte_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lte_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lte_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_lte_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_lte_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lte_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_lte_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gt_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_gt_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gt_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gt_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_gt_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_gt_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gt_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gt_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gt_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_gt_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gt_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gt_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_gt_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_gt_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gt_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gt_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gt_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_gt_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gt_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gt_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_gt_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_gt_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gt_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gt_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gte_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_gte_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gte_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gte_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_gte_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_gte_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gte_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gte_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gte_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_gte_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gte_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gte_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_gte_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_gte_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gte_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gte_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gte_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_gte_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gte_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gte_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_gte_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_gte_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gte_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_gte_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lt_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lt_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lt_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lt_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_lt_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lt_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lt_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lt_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lt_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lt_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lt_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lt_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_lt_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lt_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lt_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lt_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lt_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lt_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_lt_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lt_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lt_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lte_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lte_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lte_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lte_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_lte_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lte_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lte_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lte_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lte_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lte_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lte_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lte_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_lte_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lte_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lte_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lte_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lte_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lte_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_lte_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lte_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + "<=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op("<="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + "<=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", "<=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", "<=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + "<=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_lte_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gt_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gt_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gt_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gt_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_gt_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gt_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gt_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gt_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gt_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gt_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gt_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gt_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_gt_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gt_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gt_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gt_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gt_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gt_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_gt_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gt_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">"), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gt_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gte_pivot_min_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gte_pivot_min_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gte_pivot_min_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gte_pivot_min_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_gte_pivot_min_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gte_pivot_min_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MIN; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gte_pivot_min_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gte_pivot_min_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gte_pivot_max_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gte_pivot_max_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gte_pivot_max_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gte_pivot_max_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_gte_pivot_max_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gte_pivot_max_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::MAX; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gte_pivot_max_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gte_pivot_max_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_cross_shape"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gte_pivot_zero_cross_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_cross_shape", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 628usize, + start_col: 22usize, + end_line: 628usize, + end_col: 96usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gte_pivot_zero_cross_shape()), + ), + }; + fn matrix_int4_ord_ore_gte_pivot_zero_cross_shape() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gte_pivot_zero_cross_shape( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let pivot: i32 = ::default(); + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let forward_count = ::expected_forward( + ">=", + pivot, + ) + .len() as i64; + let commuted_count = ::expected_forward( + ::eql_tests::scalar_domains::commute_op(">="), + pivot, + ) + .len() as i64; + let d = &spec.sql_domain; + let shapes = [ + ( + "d_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "payload::{1} {0} {2}::jsonb::{1}", + ">=", + d, + lit, + ), + ) + }), + forward_count, + ), + ( + "d_j", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("payload::{1} {0} {2}::jsonb", ">=", d, lit), + ) + }), + forward_count, + ), + ( + "j_d", + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1}::jsonb {0} payload::{2}", ">=", lit, d), + ) + }), + commuted_count, + ), + ]; + let table = ::fixture_table_name(); + for (shape_label, predicate, expected_count) in shapes { + let count_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }); + let count: i64 = sqlx::query_scalar(&count_sql) + .fetch_one(&pool) + .await?; + match (&count, &expected_count) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} op={1} pivot={2:?} shape={3} SQL={4} expected {5} rows, got {6}", + d, + ">=", + pivot, + shape_label, + count_sql, + expected_count, + count, + ), + ), + ); + } + } + }; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_cross_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_gte_pivot_zero_cross_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_eq_supported_null"] + #[doc(hidden)] + pub const matrix_int4_eq_eq_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_eq_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_eq_supported_null()), + ), + }; + fn matrix_int4_eq_eq_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_eq_eq_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "=", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_eq_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_eq_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_neq_supported_null"] + #[doc(hidden)] + pub const matrix_int4_eq_neq_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_neq_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_neq_supported_null()), + ), + }; + fn matrix_int4_eq_neq_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_eq_neq_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "<>", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_neq_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_neq_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_eq_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_eq_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_eq_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_eq_supported_null()), + ), + }; + fn matrix_int4_ord_eq_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_eq_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "=", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_eq_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_eq_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_neq_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_neq_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_neq_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_neq_supported_null()), + ), + }; + fn matrix_int4_ord_neq_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_neq_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "<>", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_neq_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_neq_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_eq_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_eq_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_eq_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_eq_supported_null()), + ), + }; + fn matrix_int4_ord_ore_eq_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_eq_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "=", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_eq_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_eq_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_neq_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_neq_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_neq_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_neq_supported_null()), + ), + }; + fn matrix_int4_ord_ore_neq_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_neq_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "<>", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_neq_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_neq_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lt_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_lt_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lt_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lt_supported_null()), + ), + }; + fn matrix_int4_ord_lt_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_lt_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "<", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lt_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_lt_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_lte_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_lte_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_lte_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_lte_supported_null()), + ), + }; + fn matrix_int4_ord_lte_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_lte_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "<=", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_lte_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_lte_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gt_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_gt_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gt_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gt_supported_null()), + ), + }; + fn matrix_int4_ord_gt_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_gt_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + ">", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gt_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_gt_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_gte_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_gte_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_gte_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_gte_supported_null()), + ), + }; + fn matrix_int4_ord_gte_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_gte_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + ">=", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_gte_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_gte_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lt_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lt_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lt_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lt_supported_null()), + ), + }; + fn matrix_int4_ord_ore_lt_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lt_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "<", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lt_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_lt_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_lte_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_lte_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_lte_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_lte_supported_null()), + ), + }; + fn matrix_int4_ord_ore_lte_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_lte_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + "<=", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_lte_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_lte_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gt_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gt_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gt_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gt_supported_null()), + ), + }; + fn matrix_int4_ord_ore_gt_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gt_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + ">", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gt_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_gt_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_gte_supported_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_gte_supported_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_gte_supported_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 680usize, + start_col: 22usize, + end_line: 680usize, + end_col: 79usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_gte_supported_null()), + ), + }; + fn matrix_int4_ord_ore_gte_supported_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_gte_supported_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + &spec.sql_domain, + ">=", + ), + ) + }); + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[Some(payload), None], + ) + .await?; + ::eql_tests::scalar_domains::assert_null( + &pool, + &sql, + &[None, Some(payload)], + ) + .await?; + ::eql_tests::scalar_domains::assert_null(&pool, &sql, &[None, None]) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_gte_supported_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_gte_supported_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_eq_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_eq_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_storage_eq_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_eq_blocker()), + ), + }; + fn matrix_int4_storage_eq_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_eq_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "=", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "=", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "=", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_eq_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_eq_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_neq_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_neq_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_storage_neq_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_neq_blocker()), + ), + }; + fn matrix_int4_storage_neq_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_neq_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<>", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<>", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<>", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_neq_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_neq_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_lt_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_lt_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_storage_lt_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_lt_blocker()), + ), + }; + fn matrix_int4_storage_lt_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_lt_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_lt_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_lt_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_lte_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_lte_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_storage_lte_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_lte_blocker()), + ), + }; + fn matrix_int4_storage_lte_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_lte_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<=", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<=", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<=", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_lte_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_lte_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_gt_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_gt_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_storage_gt_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_gt_blocker()), + ), + }; + fn matrix_int4_storage_gt_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_gt_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + ">", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", ">", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", ">", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_gt_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_gt_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_gte_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_gte_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_storage_gte_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_gte_blocker()), + ), + }; + fn matrix_int4_storage_gte_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_gte_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + ">=", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", ">=", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", ">=", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_gte_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_gte_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_contains_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_contains_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_contains_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_contains_blocker()), + ), + }; + fn matrix_int4_storage_contains_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_contains_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "@>", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "@>", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "@>", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_contains_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_contains_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_contained_by_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_contained_by_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_contained_by_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_contained_by_blocker()), + ), + }; + fn matrix_int4_storage_contained_by_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_contained_by_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<@", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<@", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<@", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_contained_by_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_contained_by_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_lt_blocker"] + #[doc(hidden)] + pub const matrix_int4_eq_lt_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_lt_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_lt_blocker()), + ), + }; + fn matrix_int4_eq_lt_blocker() -> anyhow::Result<()> { + async fn matrix_int4_eq_lt_blocker(pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_lt_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_lt_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_lte_blocker"] + #[doc(hidden)] + pub const matrix_int4_eq_lte_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_lte_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_lte_blocker()), + ), + }; + fn matrix_int4_eq_lte_blocker() -> anyhow::Result<()> { + async fn matrix_int4_eq_lte_blocker(pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<=", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<=", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<=", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_lte_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_lte_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_gt_blocker"] + #[doc(hidden)] + pub const matrix_int4_eq_gt_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_gt_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_gt_blocker()), + ), + }; + fn matrix_int4_eq_gt_blocker() -> anyhow::Result<()> { + async fn matrix_int4_eq_gt_blocker(pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + ">", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", ">", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", ">", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_gt_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_gt_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_gte_blocker"] + #[doc(hidden)] + pub const matrix_int4_eq_gte_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_gte_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_gte_blocker()), + ), + }; + fn matrix_int4_eq_gte_blocker() -> anyhow::Result<()> { + async fn matrix_int4_eq_gte_blocker(pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + ">=", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", ">=", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", ">=", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_gte_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_gte_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_contains_blocker"] + #[doc(hidden)] + pub const matrix_int4_eq_contains_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_contains_blocker"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_contains_blocker()), + ), + }; + fn matrix_int4_eq_contains_blocker() -> anyhow::Result<()> { + async fn matrix_int4_eq_contains_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "@>", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "@>", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "@>", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_contains_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_contains_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_contained_by_blocker"] + #[doc(hidden)] + pub const matrix_int4_eq_contained_by_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_contained_by_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_contained_by_blocker()), + ), + }; + fn matrix_int4_eq_contained_by_blocker() -> anyhow::Result<()> { + async fn matrix_int4_eq_contained_by_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<@", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<@", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<@", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_contained_by_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_contained_by_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_contains_blocker"] + #[doc(hidden)] + pub const matrix_int4_ord_contains_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_contains_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_contains_blocker()), + ), + }; + fn matrix_int4_ord_contains_blocker() -> anyhow::Result<()> { + async fn matrix_int4_ord_contains_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "@>", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "@>", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "@>", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_contains_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_contains_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_contained_by_blocker"] + #[doc(hidden)] + pub const matrix_int4_ord_contained_by_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_contained_by_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_contained_by_blocker()), + ), + }; + fn matrix_int4_ord_contained_by_blocker() -> anyhow::Result<()> { + async fn matrix_int4_ord_contained_by_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<@", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<@", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<@", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_contained_by_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_contained_by_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_contains_blocker"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_contains_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_contains_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_contains_blocker()), + ), + }; + fn matrix_int4_ord_ore_contains_blocker() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_contains_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "@>", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "@>", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "@>", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_contains_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_contains_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_contained_by_blocker"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_contained_by_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_contained_by_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 744usize, + start_col: 22usize, + end_line: 744usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_contained_by_blocker()), + ), + }; + fn matrix_int4_ord_ore_contained_by_blocker() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_contained_by_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let msg = ::eql_tests::scalar_domains::blocker_msg( + &spec.sql_domain, + "<@", + ); + let d = &spec.sql_domain; + let shapes: [(String, String); 3] = [ + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$1::jsonb::{0}", d)) + }), + "$2::jsonb".into(), + ), + ( + "$1::jsonb".into(), + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("$2::jsonb::{0}", d)) + }), + ), + ]; + for (lhs, rhs) in shapes { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT {1} {0} {2}", "<@", lhs, rhs), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + &msg, + ) + .await?; + } + let null_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{1} {0} $2::jsonb::{1}", "<@", d), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, Some(payload)], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[Some(payload), None], + &msg, + ) + .await?; + ::eql_tests::scalar_domains::assert_raises( + &pool, + &null_sql, + &[None, None], + &msg, + ) + .await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_contained_by_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_contained_by_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_payload_check"] + #[doc(hidden)] + pub const matrix_int4_storage_payload_check: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_payload_check", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 811usize, + start_col: 22usize, + end_line: 811usize, + end_col: 67usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_payload_check()), + ), + }; + fn matrix_int4_storage_payload_check() -> anyhow::Result<()> { + async fn matrix_int4_storage_payload_check( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let baseline = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for key in spec.variant.payload_required_keys() { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT (\'{0}\'::jsonb - \'{1}\')::{2}", + baseline, + key, + d, + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "{0} must reject payload missing `{1}`: {2}", + d, + key, + sql, + ), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not( + err.contains("violates check constraint"), + ) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for missing `{0}` on {1}, got: {2}", + key, + d, + err, + ), + ); + error + }); + } + } + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT \'[\"v\",\"i\",\"c\"]\'::jsonb::{0}", d), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{0} must reject non-object payload", d), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not(err.contains("violates check constraint")) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for non-object on {0}, got: {1}", + d, + err, + ), + ); + error + }); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_payload_check", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_payload_check; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_payload_check"] + #[doc(hidden)] + pub const matrix_int4_eq_payload_check: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_payload_check"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 811usize, + start_col: 22usize, + end_line: 811usize, + end_col: 67usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_payload_check()), + ), + }; + fn matrix_int4_eq_payload_check() -> anyhow::Result<()> { + async fn matrix_int4_eq_payload_check(pool: sqlx::PgPool) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let baseline = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for key in spec.variant.payload_required_keys() { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT (\'{0}\'::jsonb - \'{1}\')::{2}", + baseline, + key, + d, + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "{0} must reject payload missing `{1}`: {2}", + d, + key, + sql, + ), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not( + err.contains("violates check constraint"), + ) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for missing `{0}` on {1}, got: {2}", + key, + d, + err, + ), + ); + error + }); + } + } + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT \'[\"v\",\"i\",\"c\"]\'::jsonb::{0}", d), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{0} must reject non-object payload", d), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not(err.contains("violates check constraint")) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for non-object on {0}, got: {1}", + d, + err, + ), + ); + error + }); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_payload_check", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_payload_check; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_payload_check"] + #[doc(hidden)] + pub const matrix_int4_ord_payload_check: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_ord_payload_check"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 811usize, + start_col: 22usize, + end_line: 811usize, + end_col: 67usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_payload_check()), + ), + }; + fn matrix_int4_ord_payload_check() -> anyhow::Result<()> { + async fn matrix_int4_ord_payload_check( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let baseline = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for key in spec.variant.payload_required_keys() { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT (\'{0}\'::jsonb - \'{1}\')::{2}", + baseline, + key, + d, + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "{0} must reject payload missing `{1}`: {2}", + d, + key, + sql, + ), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not( + err.contains("violates check constraint"), + ) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for missing `{0}` on {1}, got: {2}", + key, + d, + err, + ), + ); + error + }); + } + } + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT \'[\"v\",\"i\",\"c\"]\'::jsonb::{0}", d), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{0} must reject non-object payload", d), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not(err.contains("violates check constraint")) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for non-object on {0}, got: {1}", + d, + err, + ), + ); + error + }); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_payload_check", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_payload_check; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_payload_check"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_payload_check: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_payload_check", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 811usize, + start_col: 22usize, + end_line: 811usize, + end_col: 67usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_payload_check()), + ), + }; + fn matrix_int4_ord_ore_payload_check() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_payload_check( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let baseline = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for key in spec.variant.payload_required_keys() { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT (\'{0}\'::jsonb - \'{1}\')::{2}", + baseline, + key, + d, + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "{0} must reject payload missing `{1}`: {2}", + d, + key, + sql, + ), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not( + err.contains("violates check constraint"), + ) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for missing `{0}` on {1}, got: {2}", + key, + d, + err, + ), + ); + error + }); + } + } + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT \'[\"v\",\"i\",\"c\"]\'::jsonb::{0}", d), + ) + }); + let err = sqlx::query(&sql) + .fetch_one(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{0} must reject non-object payload", d), + ) + }), + ) + .to_string(); + if ::anyhow::__private::not(err.contains("violates check constraint")) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected check-constraint violation for non-object on {0}, got: {1}", + d, + err, + ), + ); + error + }); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_payload_check", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_payload_check; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_path_op_blockers"] + #[doc(hidden)] + pub const matrix_int4_storage_path_op_blockers: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_path_op_blockers", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 884usize, + start_col: 22usize, + end_line: 884usize, + end_col: 70usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_path_op_blockers()), + ), + }; + fn matrix_int4_storage_path_op_blockers() -> anyhow::Result<()> { + async fn matrix_int4_storage_path_op_blockers( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["->", "->>"] { + let msg = ::eql_tests::scalar_domains::blocker_msg(d, op); + for sql in [ + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} \'field\'::text", + d, + op, + ), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{0} {1} 0::integer", d, op), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb {0} $1::jsonb::{1}", op, d), + ) + }), + ] { + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload)], + &msg, + ) + .await?; + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_path_op_blockers", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_path_op_blockers; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_path_op_blockers"] + #[doc(hidden)] + pub const matrix_int4_eq_path_op_blockers: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_path_op_blockers"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 884usize, + start_col: 22usize, + end_line: 884usize, + end_col: 70usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_path_op_blockers()), + ), + }; + fn matrix_int4_eq_path_op_blockers() -> anyhow::Result<()> { + async fn matrix_int4_eq_path_op_blockers( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["->", "->>"] { + let msg = ::eql_tests::scalar_domains::blocker_msg(d, op); + for sql in [ + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} \'field\'::text", + d, + op, + ), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{0} {1} 0::integer", d, op), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb {0} $1::jsonb::{1}", op, d), + ) + }), + ] { + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload)], + &msg, + ) + .await?; + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_path_op_blockers", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_path_op_blockers; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_path_op_blockers"] + #[doc(hidden)] + pub const matrix_int4_ord_path_op_blockers: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_path_op_blockers", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 884usize, + start_col: 22usize, + end_line: 884usize, + end_col: 70usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_path_op_blockers()), + ), + }; + fn matrix_int4_ord_path_op_blockers() -> anyhow::Result<()> { + async fn matrix_int4_ord_path_op_blockers( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["->", "->>"] { + let msg = ::eql_tests::scalar_domains::blocker_msg(d, op); + for sql in [ + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} \'field\'::text", + d, + op, + ), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{0} {1} 0::integer", d, op), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb {0} $1::jsonb::{1}", op, d), + ) + }), + ] { + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload)], + &msg, + ) + .await?; + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_path_op_blockers", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_path_op_blockers; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_path_op_blockers"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_path_op_blockers: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_path_op_blockers", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 884usize, + start_col: 22usize, + end_line: 884usize, + end_col: 70usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_path_op_blockers()), + ), + }; + fn matrix_int4_ord_ore_path_op_blockers() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_path_op_blockers( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["->", "->>"] { + let msg = ::eql_tests::scalar_domains::blocker_msg(d, op); + for sql in [ + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} \'field\'::text", + d, + op, + ), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb::{0} {1} 0::integer", d, op), + ) + }), + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT $1::jsonb {0} $1::jsonb::{1}", op, d), + ) + }), + ] { + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload)], + &msg, + ) + .await?; + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_path_op_blockers", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_path_op_blockers; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_native_absent_ops"] + #[doc(hidden)] + pub const matrix_int4_storage_native_absent_ops: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_native_absent_ops", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 941usize, + start_col: 22usize, + end_line: 941usize, + end_col: 71usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_native_absent_ops()), + ), + }; + fn matrix_int4_storage_native_absent_ops() -> anyhow::Result<()> { + async fn matrix_int4_storage_native_absent_ops( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["~~", "~~*"] { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + d, + op, + ), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + "operator does not exist", + ) + .await?; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_native_absent_ops", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_native_absent_ops; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_native_absent_ops"] + #[doc(hidden)] + pub const matrix_int4_eq_native_absent_ops: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_native_absent_ops", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 941usize, + start_col: 22usize, + end_line: 941usize, + end_col: 71usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_native_absent_ops()), + ), + }; + fn matrix_int4_eq_native_absent_ops() -> anyhow::Result<()> { + async fn matrix_int4_eq_native_absent_ops( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["~~", "~~*"] { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + d, + op, + ), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + "operator does not exist", + ) + .await?; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_native_absent_ops", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_native_absent_ops; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_native_absent_ops"] + #[doc(hidden)] + pub const matrix_int4_ord_native_absent_ops: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_native_absent_ops", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 941usize, + start_col: 22usize, + end_line: 941usize, + end_col: 71usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_native_absent_ops()), + ), + }; + fn matrix_int4_ord_native_absent_ops() -> anyhow::Result<()> { + async fn matrix_int4_ord_native_absent_ops( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["~~", "~~*"] { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + d, + op, + ), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + "operator does not exist", + ) + .await?; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_native_absent_ops", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_native_absent_ops; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_native_absent_ops"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_native_absent_ops: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_native_absent_ops", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 941usize, + start_col: 22usize, + end_line: 941usize, + end_col: 71usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_native_absent_ops()), + ), + }; + fn matrix_int4_ord_ore_native_absent_ops() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_native_absent_ops( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + for op in ["~~", "~~*"] { + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT $1::jsonb::{0} {1} $2::jsonb::{0}", + d, + op, + ), + ) + }); + ::eql_tests::scalar_domains::assert_raises( + &pool, + &sql, + &[Some(payload), Some(payload)], + "operator does not exist", + ) + .await?; + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_native_absent_ops", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_native_absent_ops; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_typed_column_blocker"] + #[doc(hidden)] + pub const matrix_int4_storage_typed_column_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_typed_column_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 994usize, + start_col: 22usize, + end_line: 994usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_typed_column_blocker()), + ), + }; + fn matrix_int4_storage_typed_column_blocker() -> anyhow::Result<()> { + async fn matrix_int4_storage_typed_column_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + let create_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_col (id integer GENERATED ALWAYS AS IDENTITY,value {0}) ON COMMIT DROP", + d, + ), + ) + }); + sqlx::query(&create_sql).execute(&mut *tx).await?; + let insert_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_col(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }); + sqlx::query(&insert_sql).bind(payload).execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "=", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "=", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "="); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<>", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<>", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<>"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<=", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<=", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<="); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + ">", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", ">", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, ">"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + ">=", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", ">=", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, ">="); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "@>", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "@>", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "@>"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<@", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<@", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<@"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_typed_column_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_typed_column_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_typed_column_blocker"] + #[doc(hidden)] + pub const matrix_int4_eq_typed_column_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_typed_column_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 994usize, + start_col: 22usize, + end_line: 994usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_typed_column_blocker()), + ), + }; + fn matrix_int4_eq_typed_column_blocker() -> anyhow::Result<()> { + async fn matrix_int4_eq_typed_column_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + let create_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_col (id integer GENERATED ALWAYS AS IDENTITY,value {0}) ON COMMIT DROP", + d, + ), + ) + }); + sqlx::query(&create_sql).execute(&mut *tx).await?; + let insert_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_col(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }); + sqlx::query(&insert_sql).bind(payload).execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<=", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<=", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<="); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + ">", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", ">", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, ">"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + ">=", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", ">=", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, ">="); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "@>", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "@>", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "@>"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<@", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<@", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<@"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_typed_column_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_typed_column_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_typed_column_blocker"] + #[doc(hidden)] + pub const matrix_int4_ord_typed_column_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_typed_column_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 994usize, + start_col: 22usize, + end_line: 994usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_typed_column_blocker()), + ), + }; + fn matrix_int4_ord_typed_column_blocker() -> anyhow::Result<()> { + async fn matrix_int4_ord_typed_column_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + let create_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_col (id integer GENERATED ALWAYS AS IDENTITY,value {0}) ON COMMIT DROP", + d, + ), + ) + }); + sqlx::query(&create_sql).execute(&mut *tx).await?; + let insert_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_col(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }); + sqlx::query(&insert_sql).bind(payload).execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "@>", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "@>", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "@>"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<@", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<@", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<@"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_typed_column_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_typed_column_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_typed_column_blocker"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_typed_column_blocker: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_typed_column_blocker", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 994usize, + start_col: 22usize, + end_line: 994usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_typed_column_blocker()), + ), + }; + fn matrix_int4_ord_ore_typed_column_blocker() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_typed_column_blocker( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + let create_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_col (id integer GENERATED ALWAYS AS IDENTITY,value {0}) ON COMMIT DROP", + d, + ), + ) + }); + sqlx::query(&create_sql).execute(&mut *tx).await?; + let insert_sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_col(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }); + sqlx::query(&insert_sql).bind(payload).execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "@>", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "@>", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "@>"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + sqlx::query("SAVEPOINT op_probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM typed_col WHERE value {0} value", + "<@", + ), + ) + }); + let err = sqlx::query(&sql) + .fetch_all(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{1} column {0} must raise", "<@", d), + ) + }), + ) + .to_string(); + let expected = ::eql_tests::scalar_domains::blocker_msg(d, "<@"); + if ::anyhow::__private::not(err.contains(&expected)) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "unexpected error for {0}: got {1}, want {2}", + sql, + err, + expected, + ), + ); + error + }); + } + sqlx::query("ROLLBACK TO SAVEPOINT op_probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_typed_column_blocker", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_typed_column_blocker; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_planner_metadata_eq"] + #[doc(hidden)] + pub const matrix_int4_eq_planner_metadata_eq: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_planner_metadata_eq", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1075usize, + start_col: 22usize, + end_line: 1075usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_planner_metadata_eq()), + ), + }; + fn matrix_int4_eq_planner_metadata_eq() -> anyhow::Result<()> { + async fn matrix_int4_eq_planner_metadata_eq( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let ops: &[&str] = &["=", "<>"]; + let op_list = ops + .iter() + .map(|o| ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("\'{0}\'", o)) + })) + .collect::>() + .join(", "); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + op_list, + d, + ), + ) + }); + let rows: Vec<(String, String, String, bool, bool, bool, bool)> = sqlx::query_as( + &sql, + ) + .fetch_all(&pool) + .await?; + let expected = ops.len() * 3; + if ::anyhow::__private::not(rows.len() == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected {2} rows ({0} ops x 3 arg shapes) on {3}, got {1}", + ops.len(), + rows.len(), + expected, + d, + ), + ) + }), + ), + ); + } + for (op, lhs, rhs, has_com, has_neg, has_rest, has_join) in &rows { + if ::anyhow::__private::not(*has_com) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare COMMUTATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_neg) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare NEGATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_rest) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare RESTRICT", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_join) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare JOIN", + op, + lhs, + rhs, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_planner_metadata_eq", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_planner_metadata_eq; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_planner_metadata_eq"] + #[doc(hidden)] + pub const matrix_int4_ord_planner_metadata_eq: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_planner_metadata_eq", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1075usize, + start_col: 22usize, + end_line: 1075usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_planner_metadata_eq()), + ), + }; + fn matrix_int4_ord_planner_metadata_eq() -> anyhow::Result<()> { + async fn matrix_int4_ord_planner_metadata_eq( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let ops: &[&str] = &["=", "<>"]; + let op_list = ops + .iter() + .map(|o| ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("\'{0}\'", o)) + })) + .collect::>() + .join(", "); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + op_list, + d, + ), + ) + }); + let rows: Vec<(String, String, String, bool, bool, bool, bool)> = sqlx::query_as( + &sql, + ) + .fetch_all(&pool) + .await?; + let expected = ops.len() * 3; + if ::anyhow::__private::not(rows.len() == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected {2} rows ({0} ops x 3 arg shapes) on {3}, got {1}", + ops.len(), + rows.len(), + expected, + d, + ), + ) + }), + ), + ); + } + for (op, lhs, rhs, has_com, has_neg, has_rest, has_join) in &rows { + if ::anyhow::__private::not(*has_com) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare COMMUTATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_neg) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare NEGATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_rest) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare RESTRICT", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_join) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare JOIN", + op, + lhs, + rhs, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_planner_metadata_eq", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_planner_metadata_eq; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_planner_metadata_eq"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_planner_metadata_eq: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_planner_metadata_eq", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1075usize, + start_col: 22usize, + end_line: 1075usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_planner_metadata_eq()), + ), + }; + fn matrix_int4_ord_ore_planner_metadata_eq() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_planner_metadata_eq( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let ops: &[&str] = &["=", "<>"]; + let op_list = ops + .iter() + .map(|o| ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("\'{0}\'", o)) + })) + .collect::>() + .join(", "); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + op_list, + d, + ), + ) + }); + let rows: Vec<(String, String, String, bool, bool, bool, bool)> = sqlx::query_as( + &sql, + ) + .fetch_all(&pool) + .await?; + let expected = ops.len() * 3; + if ::anyhow::__private::not(rows.len() == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected {2} rows ({0} ops x 3 arg shapes) on {3}, got {1}", + ops.len(), + rows.len(), + expected, + d, + ), + ) + }), + ), + ); + } + for (op, lhs, rhs, has_com, has_neg, has_rest, has_join) in &rows { + if ::anyhow::__private::not(*has_com) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare COMMUTATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_neg) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare NEGATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_rest) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare RESTRICT", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_join) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare JOIN", + op, + lhs, + rhs, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_planner_metadata_eq", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_planner_metadata_eq; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_planner_metadata_ord"] + #[doc(hidden)] + pub const matrix_int4_ord_planner_metadata_ord: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_planner_metadata_ord", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1075usize, + start_col: 22usize, + end_line: 1075usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_planner_metadata_ord()), + ), + }; + fn matrix_int4_ord_planner_metadata_ord() -> anyhow::Result<()> { + async fn matrix_int4_ord_planner_metadata_ord( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let ops: &[&str] = &["<", "<=", ">", ">="]; + let op_list = ops + .iter() + .map(|o| ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("\'{0}\'", o)) + })) + .collect::>() + .join(", "); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + op_list, + d, + ), + ) + }); + let rows: Vec<(String, String, String, bool, bool, bool, bool)> = sqlx::query_as( + &sql, + ) + .fetch_all(&pool) + .await?; + let expected = ops.len() * 3; + if ::anyhow::__private::not(rows.len() == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected {2} rows ({0} ops x 3 arg shapes) on {3}, got {1}", + ops.len(), + rows.len(), + expected, + d, + ), + ) + }), + ), + ); + } + for (op, lhs, rhs, has_com, has_neg, has_rest, has_join) in &rows { + if ::anyhow::__private::not(*has_com) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare COMMUTATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_neg) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare NEGATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_rest) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare RESTRICT", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_join) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare JOIN", + op, + lhs, + rhs, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_planner_metadata_ord", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_planner_metadata_ord; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_planner_metadata_ord"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_planner_metadata_ord: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_planner_metadata_ord", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1075usize, + start_col: 22usize, + end_line: 1075usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_planner_metadata_ord()), + ), + }; + fn matrix_int4_ord_ore_planner_metadata_ord() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_planner_metadata_ord( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let ops: &[&str] = &["<", "<=", ">", ">="]; + let op_list = ops + .iter() + .map(|o| ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("\'{0}\'", o)) + })) + .collect::>() + .join(", "); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + op_list, + d, + ), + ) + }); + let rows: Vec<(String, String, String, bool, bool, bool, bool)> = sqlx::query_as( + &sql, + ) + .fetch_all(&pool) + .await?; + let expected = ops.len() * 3; + if ::anyhow::__private::not(rows.len() == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected {2} rows ({0} ops x 3 arg shapes) on {3}, got {1}", + ops.len(), + rows.len(), + expected, + d, + ), + ) + }), + ), + ); + } + for (op, lhs, rhs, has_com, has_neg, has_rest, has_join) in &rows { + if ::anyhow::__private::not(*has_com) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare COMMUTATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_neg) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare NEGATOR", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_rest) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare RESTRICT", + op, + lhs, + rhs, + ), + ); + error + }); + } + if ::anyhow::__private::not(*has_join) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "operator {0}({1},{2}) must declare JOIN", + op, + lhs, + rhs, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_planner_metadata_ord", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_planner_metadata_ord; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_index_engages_btree"] + #[doc(hidden)] + pub const matrix_int4_eq_index_engages_btree: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_index_engages_btree", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1641usize, + start_col: 22usize, + end_line: 1641usize, + end_col: 75usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_index_engages_btree()), + ), + }; + fn matrix_int4_eq_index_engages_btree() -> anyhow::Result<()> { + async fn matrix_int4_eq_index_engages_btree( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let table = "matrix_int4_eq_idx_btree"; + let index = "matrix_int4_eq_idx_btree_idx"; + let fixture_table = ::fixture_table_name(); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {2} (plaintext {0}, value {1}) ON COMMIT DROP", + ::PG_TYPE, + &spec.sql_domain, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {2}(plaintext, value) SELECT plaintext, payload::{0} FROM {1}", + &spec.sql_domain, + fixture_table, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", + "btree", + "eql_v2.eq_term", + index, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("ANALYZE {0}", table)) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; + let pivot: i32 = ::FIXTURE_VALUES[0]; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let rhs_casts = [ + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("::{0}", &spec.sql_domain)) + }), + String::new(), + ]; + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_index_engages_btree", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_index_engages_btree; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_index_engages_hash"] + #[doc(hidden)] + pub const matrix_int4_eq_index_engages_hash: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_index_engages_hash", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1641usize, + start_col: 22usize, + end_line: 1641usize, + end_col: 75usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_index_engages_hash()), + ), + }; + fn matrix_int4_eq_index_engages_hash() -> anyhow::Result<()> { + async fn matrix_int4_eq_index_engages_hash( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let table = "matrix_int4_eq_idx_hash"; + let index = "matrix_int4_eq_idx_hash_idx"; + let fixture_table = ::fixture_table_name(); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {2} (plaintext {0}, value {1}) ON COMMIT DROP", + ::PG_TYPE, + &spec.sql_domain, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {2}(plaintext, value) SELECT plaintext, payload::{0} FROM {1}", + &spec.sql_domain, + fixture_table, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", + "hash", + "eql_v2.eq_term", + index, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("ANALYZE {0}", table)) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; + let pivot: i32 = ::FIXTURE_VALUES[0]; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let rhs_casts = [ + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("::{0}", &spec.sql_domain)) + }), + String::new(), + ]; + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_index_engages_hash", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_index_engages_hash; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_index_engages_btree"] + #[doc(hidden)] + pub const matrix_int4_ord_index_engages_btree: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_index_engages_btree", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1641usize, + start_col: 22usize, + end_line: 1641usize, + end_col: 75usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_index_engages_btree()), + ), + }; + fn matrix_int4_ord_index_engages_btree() -> anyhow::Result<()> { + async fn matrix_int4_ord_index_engages_btree( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let table = "matrix_int4_ord_idx_btree"; + let index = "matrix_int4_ord_idx_btree_idx"; + let fixture_table = ::fixture_table_name(); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {2} (plaintext {0}, value {1}) ON COMMIT DROP", + ::PG_TYPE, + &spec.sql_domain, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {2}(plaintext, value) SELECT plaintext, payload::{0} FROM {1}", + &spec.sql_domain, + fixture_table, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", + "btree", + "eql_v2.ord_term", + index, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("ANALYZE {0}", table)) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; + let pivot: i32 = ::FIXTURE_VALUES[0]; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let rhs_casts = [ + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("::{0}", &spec.sql_domain)) + }), + String::new(), + ]; + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "<", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "<", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "<=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "<=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + ">", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + ">", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + ">=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + ">=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_index_engages_btree", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_index_engages_btree; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_index_engages_btree"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_index_engages_btree: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_index_engages_btree", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1641usize, + start_col: 22usize, + end_line: 1641usize, + end_col: 75usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_index_engages_btree()), + ), + }; + fn matrix_int4_ord_ore_index_engages_btree() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_index_engages_btree( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let table = "matrix_int4_ord_ore_idx_btree"; + let index = "matrix_int4_ord_ore_idx_btree_idx"; + let fixture_table = ::fixture_table_name(); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {2} (plaintext {0}, value {1}) ON COMMIT DROP", + ::PG_TYPE, + &spec.sql_domain, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {2}(plaintext, value) SELECT plaintext, payload::{0} FROM {1}", + &spec.sql_domain, + fixture_table, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", + "btree", + "eql_v2.ord_term", + index, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("ANALYZE {0}", table)) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; + let pivot: i32 = ::FIXTURE_VALUES[0]; + let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let lit = ::eql_tests::scalar_domains::sql_string_literal(&payload); + let rhs_casts = [ + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("::{0}", &spec.sql_domain)) + }), + String::new(), + ]; + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "<", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "<", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + "<=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + "<=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + ">", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + ">", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + for rhs_cast in &rhs_casts { + let query = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {2} WHERE value {0} {3}::jsonb{1}", + ">=", + rhs_cast, + table, + lit, + ), + ) + }); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &query, + index, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} rhs_cast={2:?} must use index={3}", + &spec.sql_domain, + ">=", + rhs_cast, + index, + ), + ) + }), + ) + .await?; + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_index_engages_btree", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_index_engages_btree; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_scale_preference_default_btree"] + #[doc(hidden)] + pub const matrix_int4_ord_scale_preference_default_btree: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_scale_preference_default_btree", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1263usize, + start_col: 22usize, + end_line: 1263usize, + end_col: 86usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_scale_preference_default_btree()), + ), + }; + fn matrix_int4_ord_scale_preference_default_btree() -> anyhow::Result<()> { + async fn matrix_int4_ord_scale_preference_default_btree( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_scaledef_btree"; + let index = "matrix_int4_ord_scaledef_btree_idx"; + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 2) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "scale test requires >= 2 fixture rows for distinct filler/pivot", + ), + ); + error + }); + } + let filler = values[0]; + let pivot = values[values.len() / 2]; + let filler_payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, filler) + .await?; + let pivot_payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< + i32, + >(&pool, pivot) + .await?; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (value {1}) ON COMMIT DROP", + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {0}(value) SELECT $1::jsonb::{1} FROM generate_series(1, 5000)", + table, + d, + ), + ) + }), + ) + .bind(&filler_payload) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {0}(value) VALUES ($1::jsonb::{1})", + table, + d, + ), + ) + }), + ) + .bind(&pivot_payload) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", + "btree", + "eql_v2.ord_term", + index, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("ANALYZE {0}", table)) + }), + ) + .execute(&mut *tx) + .await?; + let lit = pivot_payload.replace('\'', "''"); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {0} WHERE value = \'{1}\'::jsonb::{2}", + table, + lit, + d, + ), + ) + }), + index, + "with seqscan ON the planner must PREFER the ord_term functional index for a selective =", + ) + .await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_scale_preference_default_btree", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_scale_preference_default_btree; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_fixture_shape"] + #[doc(hidden)] + pub const matrix_int4_fixture_shape: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_fixture_shape"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1341usize, + start_col: 22usize, + end_line: 1341usize, + end_col: 55usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_fixture_shape()), + ), + }; + fn matrix_int4_fixture_shape() -> anyhow::Result<()> { + async fn matrix_int4_fixture_shape(pool: sqlx::PgPool) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let table = ::fixture_table_name(); + let expected: &[i32] = ::FIXTURE_VALUES; + let n = expected.len() as i64; + let count: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT COUNT(*) FROM {0}", table), + ) + }), + ) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(count == n) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "row count must match FIXTURE_VALUES.len(): want {0}, got {1}", + n, + count, + ), + ); + error + }); + } + let ids: Vec = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT id FROM {0} ORDER BY id", table), + ) + }), + ) + .fetch_all(&pool) + .await?; + if ::anyhow::__private::not(ids == (1..=n).collect::>()) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!("ids must be sequential from 1: got {0:?}", ids), + ); + error + }); + } + let plaintexts: Vec = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT plaintext FROM {0} ORDER BY id", table), + ) + }), + ) + .fetch_all(&pool) + .await?; + if ::anyhow::__private::not(plaintexts == expected) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "plaintext column must match FIXTURE_VALUES in order", + ), + ); + error + }); + } + for (label, predicate) in [ + ( + "hm string", + "payload->'hm' IS NULL OR jsonb_typeof(payload->'hm') <> 'string'", + ), + ( + "ob array", + "payload->'ob' IS NULL OR jsonb_typeof(payload->'ob') <> 'array'", + ), + ( + "c string", + "payload->'c' IS NULL OR jsonb_typeof(payload->'c') <> 'string'", + ), + ] { + let missing: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT COUNT(*) FROM {0} WHERE {1}", + table, + predicate, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(missing == 0) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "every payload must carry a `{0}` term; missing = {1}", + label, + missing, + ), + ); + error + }); + } + } + let distinct_hm: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT COUNT(DISTINCT payload->>\'hm\') FROM {0}", + table, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(distinct_hm == n) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "{0} distinct values -> {0} distinct hm terms; got {1}", + n, + distinct_hm, + ), + ); + error + }); + } + let mismatched_version: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT COUNT(*) FROM {0} WHERE payload->\'v\' IS NULL OR payload->>\'v\' <> \'2\'", + table, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(mismatched_version == 0) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!("every payload must declare v = \'2\'"), + ); + error + }); + } + if !expected.is_empty() { + let probe = expected[expected.len() / 2]; + let probe_lit = ::to_sql_literal(probe); + let expected_id = (expected.len() / 2 + 1) as i64; + let ids: Vec = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT id FROM {1} WHERE plaintext = {0} ORDER BY id", + probe_lit, + table, + ), + ) + }), + ) + .fetch_all(&pool) + .await?; + if ::anyhow::__private::not( + ids + == ::alloc::boxed::box_assume_init_into_vec_unsafe( + ::alloc::intrinsics::write_box_via_move( + ::alloc::boxed::Box::new_uninit(), + [expected_id], + ), + ), + ) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "expected exactly one row with plaintext = {0:?} at id {1}, got {2:?}", + probe, + expected_id, + ids, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_fixture_shape", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_fixture_shape; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ord_routes_through_ob"] + #[doc(hidden)] + pub const matrix_int4_ord_ord_routes_through_ob: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ord_routes_through_ob", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1444usize, + start_col: 22usize, + end_line: 1444usize, + end_col: 75usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ord_routes_through_ob()), + ), + }; + fn matrix_int4_ord_ord_routes_through_ob() -> anyhow::Result<()> { + async fn matrix_int4_ord_ord_routes_through_ob( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_no_hm"; + let index = "matrix_int4_ord_no_hm_idx"; + let fixture_table = ::fixture_table_name(); + let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot_lit = ::to_sql_literal( + pivot, + ); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {1} (plaintext {0}, value {2}) ON COMMIT DROP", + ::PG_TYPE, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, (payload - \'hm\')::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let with_hm: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE jsonb_exists(value::jsonb, \'hm\')", + table, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(with_hm == 0) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!("test rows must not carry hm"), + ); + error + }); + } + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE INDEX {0} ON {1} USING btree (eql_v2.ord_term(value))", + index, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("ANALYZE {0}", table)) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; + let pivot_payload: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT (payload - \'hm\')::text FROM {0} WHERE plaintext = {1}", + fixture_table, + pivot_lit, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let eq_count: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE value = $1::jsonb::{1}", + table, + d, + ), + ) + }), + ) + .bind(&pivot_payload) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(eq_count == 1) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "= must match exactly the pivot row via ob with no hm present (want 1, got {0})", + eq_count, + ), + ); + error + }); + } + let expected_neq = ::FIXTURE_VALUES + .len() as i64 - eq_count; + let neq_count: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE value <> $1::jsonb::{1}", + table, + d, + ), + ) + }), + ) + .bind(&pivot_payload) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(neq_count == expected_neq) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "<> must match every non-pivot fixture row (want {0}, got {1})", + expected_neq, + neq_count, + ), + ); + error + }); + } + let lit = pivot_payload.replace('\'', "''"); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {0} WHERE value = \'{1}\'::jsonb::{2}", + table, + lit, + d, + ), + ) + }), + index, + "= must engage the eql_v2.ord_term functional btree with no hm", + ) + .await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ord_routes_through_ob", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ord_routes_through_ob; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_ord_routes_through_ob"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_ord_routes_through_ob: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_ord_routes_through_ob", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1444usize, + start_col: 22usize, + end_line: 1444usize, + end_col: 75usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_ord_routes_through_ob()), + ), + }; + fn matrix_int4_ord_ore_ord_routes_through_ob() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_ord_routes_through_ob( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_ore_no_hm"; + let index = "matrix_int4_ord_ore_no_hm_idx"; + let fixture_table = ::fixture_table_name(); + let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot_lit = ::to_sql_literal( + pivot, + ); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {1} (plaintext {0}, value {2}) ON COMMIT DROP", + ::PG_TYPE, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, (payload - \'hm\')::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let with_hm: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE jsonb_exists(value::jsonb, \'hm\')", + table, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(with_hm == 0) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!("test rows must not carry hm"), + ); + error + }); + } + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE INDEX {0} ON {1} USING btree (eql_v2.ord_term(value))", + index, + table, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("ANALYZE {0}", table)) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; + let pivot_payload: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT (payload - \'hm\')::text FROM {0} WHERE plaintext = {1}", + fixture_table, + pivot_lit, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let eq_count: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE value = $1::jsonb::{1}", + table, + d, + ), + ) + }), + ) + .bind(&pivot_payload) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(eq_count == 1) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "= must match exactly the pivot row via ob with no hm present (want 1, got {0})", + eq_count, + ), + ); + error + }); + } + let expected_neq = ::FIXTURE_VALUES + .len() as i64 - eq_count; + let neq_count: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} WHERE value <> $1::jsonb::{1}", + table, + d, + ), + ) + }), + ) + .bind(&pivot_payload) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(neq_count == expected_neq) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "<> must match every non-pivot fixture row (want {0}, got {1})", + expected_neq, + neq_count, + ), + ); + error + }); + } + let lit = pivot_payload.replace('\'', "''"); + ::eql_tests::matrix::assert_index_scan_uses( + &mut *tx, + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT * FROM {0} WHERE value = \'{1}\'::jsonb::{2}", + table, + lit, + d, + ), + ) + }), + index, + "= must engage the eql_v2.ord_term functional btree with no hm", + ) + .await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_ord_routes_through_ob", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_ord_routes_through_ob; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_ore_injectivity"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_ore_injectivity: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_ore_injectivity", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1578usize, + start_col: 22usize, + end_line: 1578usize, + end_col: 69usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_ore_injectivity()), + ), + }; + fn matrix_int4_ord_ore_ore_injectivity() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_ore_injectivity( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture_table = ::fixture_table_name(); + let collisions: i64 = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT count(*) FROM {0} a JOIN {0} b ON a.id < b.id WHERE a.payload::{1} = b.payload::{1}", + fixture_table, + d, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(collisions == 0) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "no two distinct plaintexts may share an ORE term on {0}", + d, + ), + ); + error + }); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_ore_injectivity", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_ore_injectivity; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_min"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_min: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_ord_aggregate_min"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2087usize, + start_col: 22usize, + end_line: 2087usize, + end_col: 73usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_min()), + ), + }; + fn matrix_int4_ord_aggregate_min() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_min( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let extremum: i32 = ::FIXTURE_VALUES + .iter() + .copied() + .min() + .expect("FIXTURE_VALUES must be non-empty"); + let extremum_lit = ::to_sql_literal(extremum); + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + extremum_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + let actual: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "min", + d, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "min", + d, + extremum, + "min", + ), + ), + ); + } + } + }; + let ord_terms_match: bool = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "min", + d, + fixture, + ), + ) + }), + ) + .bind(&expected) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(ord_terms_match) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "min", + d, + extremum, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_min", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_min; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_min_empty"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_min_empty: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_min_empty", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2145usize, + start_col: 22usize, + end_line: 2145usize, + end_col: 80usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_min_empty()), + ), + }; + fn matrix_int4_ord_aggregate_min_empty() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_min_empty( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE empty_agg (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let result: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "min", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "min", + d, + result, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_min_empty", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_min_empty; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_min_all_null"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_min_all_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_min_all_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2170usize, + start_col: 22usize, + end_line: 2170usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_min_all_null()), + ), + }; + fn matrix_int4_ord_aggregate_min_all_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_min_all_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "min", + d, + ), + ) + }); + let result: Option = sqlx::query_scalar(&sql) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "min", + d, + result, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_min_all_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_min_all_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_min_mixed_null"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_min_mixed_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_min_mixed_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2196usize, + start_col: 22usize, + end_line: 2196usize, + end_col: 85usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_min_mixed_null()), + ), + }; + fn matrix_int4_ord_aggregate_min_mixed_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_min_mixed_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "mixed-NULL test needs >= 2 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let mut sorted: Vec = values.to_vec(); + sorted.sort(); + let low: i32 = *sorted.first().expect("non-empty after len check"); + let high: i32 = *sorted.last().expect("non-empty after len check"); + let expected_plaintext: i32 = low.min(high); + let low_lit = ::to_sql_literal(low); + let high_lit = ::to_sql_literal(high); + let expected_lit = ::to_sql_literal( + expected_plaintext, + ); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE mixed_null (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO mixed_null(value) SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {0} UNION ALL SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {1} UNION ALL SELECT NULL::{2}", + low_lit, + high_lit, + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + expected_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let actual: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "min", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not( + actual.as_deref() == Some(expected.as_str()), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "min", + "min", + expected_plaintext, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_min_mixed_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_min_mixed_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_max"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_max: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_ord_aggregate_max"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2087usize, + start_col: 22usize, + end_line: 2087usize, + end_col: 73usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_max()), + ), + }; + fn matrix_int4_ord_aggregate_max() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_max( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let extremum: i32 = ::FIXTURE_VALUES + .iter() + .copied() + .max() + .expect("FIXTURE_VALUES must be non-empty"); + let extremum_lit = ::to_sql_literal(extremum); + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + extremum_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + let actual: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "max", + d, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "max", + d, + extremum, + "max", + ), + ), + ); + } + } + }; + let ord_terms_match: bool = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "max", + d, + fixture, + ), + ) + }), + ) + .bind(&expected) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(ord_terms_match) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "max", + d, + extremum, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_max", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_max; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_max_empty"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_max_empty: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_max_empty", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2145usize, + start_col: 22usize, + end_line: 2145usize, + end_col: 80usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_max_empty()), + ), + }; + fn matrix_int4_ord_aggregate_max_empty() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_max_empty( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE empty_agg (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let result: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "max", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "max", + d, + result, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_max_empty", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_max_empty; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_max_all_null"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_max_all_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_max_all_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2170usize, + start_col: 22usize, + end_line: 2170usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_max_all_null()), + ), + }; + fn matrix_int4_ord_aggregate_max_all_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_max_all_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "max", + d, + ), + ) + }); + let result: Option = sqlx::query_scalar(&sql) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "max", + d, + result, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_max_all_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_max_all_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_max_mixed_null"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_max_mixed_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_max_mixed_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2196usize, + start_col: 22usize, + end_line: 2196usize, + end_col: 85usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_max_mixed_null()), + ), + }; + fn matrix_int4_ord_aggregate_max_mixed_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_max_mixed_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "mixed-NULL test needs >= 2 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let mut sorted: Vec = values.to_vec(); + sorted.sort(); + let low: i32 = *sorted.first().expect("non-empty after len check"); + let high: i32 = *sorted.last().expect("non-empty after len check"); + let expected_plaintext: i32 = low.max(high); + let low_lit = ::to_sql_literal(low); + let high_lit = ::to_sql_literal(high); + let expected_lit = ::to_sql_literal( + expected_plaintext, + ); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE mixed_null (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO mixed_null(value) SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {0} UNION ALL SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {1} UNION ALL SELECT NULL::{2}", + low_lit, + high_lit, + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + expected_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let actual: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "max", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not( + actual.as_deref() == Some(expected.as_str()), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "max", + "max", + expected_plaintext, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_max_mixed_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_max_mixed_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_min"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_min: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_min", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2087usize, + start_col: 22usize, + end_line: 2087usize, + end_col: 73usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_min()), + ), + }; + fn matrix_int4_ord_ore_aggregate_min() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_min( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let extremum: i32 = ::FIXTURE_VALUES + .iter() + .copied() + .min() + .expect("FIXTURE_VALUES must be non-empty"); + let extremum_lit = ::to_sql_literal(extremum); + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + extremum_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + let actual: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "min", + d, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "min", + d, + extremum, + "min", + ), + ), + ); + } + } + }; + let ord_terms_match: bool = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "min", + d, + fixture, + ), + ) + }), + ) + .bind(&expected) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(ord_terms_match) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "min", + d, + extremum, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_min", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_min; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_min_empty"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_min_empty: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_min_empty", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2145usize, + start_col: 22usize, + end_line: 2145usize, + end_col: 80usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_min_empty()), + ), + }; + fn matrix_int4_ord_ore_aggregate_min_empty() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_min_empty( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE empty_agg (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let result: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "min", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "min", + d, + result, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_min_empty", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_min_empty; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_min_all_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_min_all_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_min_all_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2170usize, + start_col: 22usize, + end_line: 2170usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_min_all_null()), + ), + }; + fn matrix_int4_ord_ore_aggregate_min_all_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_min_all_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "min", + d, + ), + ) + }); + let result: Option = sqlx::query_scalar(&sql) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "min", + d, + result, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_min_all_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_min_all_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_min_mixed_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_min_mixed_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_min_mixed_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2196usize, + start_col: 22usize, + end_line: 2196usize, + end_col: 85usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_min_mixed_null()), + ), + }; + fn matrix_int4_ord_ore_aggregate_min_mixed_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_min_mixed_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "mixed-NULL test needs >= 2 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let mut sorted: Vec = values.to_vec(); + sorted.sort(); + let low: i32 = *sorted.first().expect("non-empty after len check"); + let high: i32 = *sorted.last().expect("non-empty after len check"); + let expected_plaintext: i32 = low.min(high); + let low_lit = ::to_sql_literal(low); + let high_lit = ::to_sql_literal(high); + let expected_lit = ::to_sql_literal( + expected_plaintext, + ); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE mixed_null (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO mixed_null(value) SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {0} UNION ALL SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {1} UNION ALL SELECT NULL::{2}", + low_lit, + high_lit, + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + expected_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let actual: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "min", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not( + actual.as_deref() == Some(expected.as_str()), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "min", + "min", + expected_plaintext, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_min_mixed_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_min_mixed_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_max"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_max: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_max", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2087usize, + start_col: 22usize, + end_line: 2087usize, + end_col: 73usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_max()), + ), + }; + fn matrix_int4_ord_ore_aggregate_max() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_max( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let extremum: i32 = ::FIXTURE_VALUES + .iter() + .copied() + .max() + .expect("FIXTURE_VALUES must be non-empty"); + let extremum_lit = ::to_sql_literal(extremum); + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + extremum_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + let actual: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "max", + d, + fixture, + ), + ) + }), + ) + .fetch_one(&pool) + .await?; + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "max", + d, + extremum, + "max", + ), + ), + ); + } + } + }; + let ord_terms_match: bool = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "max", + d, + fixture, + ), + ) + }), + ) + .bind(&expected) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(ord_terms_match) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "max", + d, + extremum, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_max", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_max; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_max_empty"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_max_empty: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_max_empty", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2145usize, + start_col: 22usize, + end_line: 2145usize, + end_col: 80usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_max_empty()), + ), + }; + fn matrix_int4_ord_ore_aggregate_max_empty() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_max_empty( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE empty_agg (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let result: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "max", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "max", + d, + result, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_max_empty", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_max_empty; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_max_all_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_max_all_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_max_all_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2170usize, + start_col: 22usize, + end_line: 2170usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_max_all_null()), + ), + }; + fn matrix_int4_ord_ore_aggregate_max_all_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_max_all_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "max", + d, + ), + ) + }); + let result: Option = sqlx::query_scalar(&sql) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(result.is_none()) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "max", + d, + result, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_max_all_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_max_all_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_max_mixed_null"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_max_mixed_null: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_max_mixed_null", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2196usize, + start_col: 22usize, + end_line: 2196usize, + end_col: 85usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_max_mixed_null()), + ), + }; + fn matrix_int4_ord_ore_aggregate_max_mixed_null() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_max_mixed_null( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "mixed-NULL test needs >= 2 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let mut sorted: Vec = values.to_vec(); + sorted.sort(); + let low: i32 = *sorted.first().expect("non-empty after len check"); + let high: i32 = *sorted.last().expect("non-empty after len check"); + let expected_plaintext: i32 = low.max(high); + let low_lit = ::to_sql_literal(low); + let high_lit = ::to_sql_literal(high); + let expected_lit = ::to_sql_literal( + expected_plaintext, + ); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE mixed_null (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO mixed_null(value) SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {0} UNION ALL SELECT NULL::{2} UNION ALL SELECT payload::{2} FROM {3} WHERE plaintext = {1} UNION ALL SELECT NULL::{2}", + low_lit, + high_lit, + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + expected_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let actual: Option = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "max", + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not( + actual.as_deref() == Some(expected.as_str()), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "max", + "max", + expected_plaintext, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_max_mixed_null", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_max_mixed_null; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_group_by_min"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_group_by_min: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_group_by_min", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2375usize, + start_col: 22usize, + end_line: 2375usize, + end_col: 82usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_group_by_min()), + ), + }; + fn matrix_int4_ord_aggregate_group_by_min() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_group_by_min( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 5) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY test needs >= 5 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let group1: &[i32] = &values[..3]; + let group2: &[i32] = &values[3..5]; + let group1_extremum: i32 = group1 + .iter() + .copied() + .min() + .expect("group 1 is non-empty"); + let group2_extremum: i32 = group2 + .iter() + .copied() + .min() + .expect("group 2 is non-empty"); + let g1_lit = ::to_sql_literal(group1_extremum); + let g2_lit = ::to_sql_literal(group2_extremum); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE group_test (group_key int, value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + for v in group1 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 1, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + for v in group2 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 2, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + let g1_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g1_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let g2_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g2_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let rows: Vec<(i32, String)> = sqlx::query_as( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "min", + ), + ) + }), + ) + .fetch_all(&mut *tx) + .await?; + if ::anyhow::__private::not(rows.len() == 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY must return 2 rows, got {0}", + rows.len(), + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[0].0 == 1 && rows[0].1 == g1_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "min", + d, + group1_extremum, + 1, + g1_expected, + rows[0], + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[1].0 == 2 && rows[1].1 == g2_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "min", + d, + group2_extremum, + 2, + g2_expected, + rows[1], + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_group_by_min", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_group_by_min; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_group_by_max"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_group_by_max: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_group_by_max", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2375usize, + start_col: 22usize, + end_line: 2375usize, + end_col: 82usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_group_by_max()), + ), + }; + fn matrix_int4_ord_aggregate_group_by_max() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_group_by_max( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 5) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY test needs >= 5 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let group1: &[i32] = &values[..3]; + let group2: &[i32] = &values[3..5]; + let group1_extremum: i32 = group1 + .iter() + .copied() + .max() + .expect("group 1 is non-empty"); + let group2_extremum: i32 = group2 + .iter() + .copied() + .max() + .expect("group 2 is non-empty"); + let g1_lit = ::to_sql_literal(group1_extremum); + let g2_lit = ::to_sql_literal(group2_extremum); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE group_test (group_key int, value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + for v in group1 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 1, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + for v in group2 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 2, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + let g1_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g1_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let g2_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g2_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let rows: Vec<(i32, String)> = sqlx::query_as( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "max", + ), + ) + }), + ) + .fetch_all(&mut *tx) + .await?; + if ::anyhow::__private::not(rows.len() == 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY must return 2 rows, got {0}", + rows.len(), + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[0].0 == 1 && rows[0].1 == g1_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "max", + d, + group1_extremum, + 1, + g1_expected, + rows[0], + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[1].0 == 2 && rows[1].1 == g2_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "max", + d, + group2_extremum, + 2, + g2_expected, + rows[1], + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_group_by_max", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_group_by_max; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_group_by_min"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_group_by_min: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_group_by_min", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2375usize, + start_col: 22usize, + end_line: 2375usize, + end_col: 82usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_group_by_min()), + ), + }; + fn matrix_int4_ord_ore_aggregate_group_by_min() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_group_by_min( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 5) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY test needs >= 5 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let group1: &[i32] = &values[..3]; + let group2: &[i32] = &values[3..5]; + let group1_extremum: i32 = group1 + .iter() + .copied() + .min() + .expect("group 1 is non-empty"); + let group2_extremum: i32 = group2 + .iter() + .copied() + .min() + .expect("group 2 is non-empty"); + let g1_lit = ::to_sql_literal(group1_extremum); + let g2_lit = ::to_sql_literal(group2_extremum); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE group_test (group_key int, value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + for v in group1 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 1, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + for v in group2 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 2, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + let g1_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g1_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let g2_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g2_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let rows: Vec<(i32, String)> = sqlx::query_as( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "min", + ), + ) + }), + ) + .fetch_all(&mut *tx) + .await?; + if ::anyhow::__private::not(rows.len() == 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY must return 2 rows, got {0}", + rows.len(), + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[0].0 == 1 && rows[0].1 == g1_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "min", + d, + group1_extremum, + 1, + g1_expected, + rows[0], + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[1].0 == 2 && rows[1].1 == g2_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "min", + d, + group2_extremum, + 2, + g2_expected, + rows[1], + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_group_by_min", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_group_by_min; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_group_by_max"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_group_by_max: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_group_by_max", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2375usize, + start_col: 22usize, + end_line: 2375usize, + end_col: 82usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_group_by_max()), + ), + }; + fn matrix_int4_ord_ore_aggregate_group_by_max() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_group_by_max( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let values: &[i32] = ::FIXTURE_VALUES; + if ::anyhow::__private::not(values.len() >= 5) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY test needs >= 5 fixture values; got {0}", + values.len(), + ), + ) + }), + ), + ); + } + let group1: &[i32] = &values[..3]; + let group2: &[i32] = &values[3..5]; + let group1_extremum: i32 = group1 + .iter() + .copied() + .max() + .expect("group 1 is non-empty"); + let group2_extremum: i32 = group2 + .iter() + .copied() + .max() + .expect("group 2 is non-empty"); + let g1_lit = ::to_sql_literal(group1_extremum); + let g2_lit = ::to_sql_literal(group2_extremum); + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE group_test (group_key int, value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + for v in group1 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 1, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + for v in group2 { + let lit = ::to_sql_literal(*v); + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO group_test(group_key, value) SELECT 2, payload::{0} FROM {1} WHERE plaintext = {2}", + d, + fixture, + lit, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + } + let g1_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g1_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let g2_expected: String = sqlx::query_scalar( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT payload::text FROM {1} WHERE plaintext = {0}", + g2_lit, + fixture, + ), + ) + }), + ) + .fetch_one(&mut *tx) + .await?; + let rows: Vec<(i32, String)> = sqlx::query_as( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "max", + ), + ) + }), + ) + .fetch_all(&mut *tx) + .await?; + if ::anyhow::__private::not(rows.len() == 2) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "GROUP BY must return 2 rows, got {0}", + rows.len(), + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[0].0 == 1 && rows[0].1 == g1_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "max", + d, + group1_extremum, + 1, + g1_expected, + rows[0], + ), + ) + }), + ), + ); + } + if ::anyhow::__private::not(rows[1].0 == 2 && rows[1].1 == g2_expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "max", + d, + group2_extremum, + 2, + g2_expected, + rows[1], + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_group_by_max", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_group_by_max; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_aggregate_parallel_safe"] + #[doc(hidden)] + pub const matrix_int4_ord_aggregate_parallel_safe: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_aggregate_parallel_safe", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2290usize, + start_col: 22usize, + end_line: 2290usize, + end_col: 77usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_aggregate_parallel_safe()), + ), + }; + fn matrix_int4_ord_aggregate_parallel_safe() -> anyhow::Result<()> { + async fn matrix_int4_ord_aggregate_parallel_safe( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + for agg in ["min", "max"] { + let (proparallel, has_combine): (String, bool) = sqlx::query_as( + "SELECT p.proparallel::text, a.aggcombinefn <> 0 \ + FROM pg_proc p \ + JOIN pg_aggregate a ON a.aggfnoid = p.oid \ + WHERE p.proname = $1 \ + AND p.pronamespace = 'eql_v2'::regnamespace \ + AND p.proargtypes[0]::regtype = $2::regtype", + ) + .bind(agg) + .bind(d) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(proparallel == "s") { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "eql_v2.{0}({1}) must be PARALLEL SAFE (proparallel=\'s\'), got {2:?}", + agg, + d, + proparallel, + ), + ); + error + }); + } + if ::anyhow::__private::not(has_combine) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "eql_v2.{0}({1}) must declare a combinefunc for partial aggregation", + agg, + d, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_aggregate_parallel_safe", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_aggregate_parallel_safe; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_aggregate_parallel_safe"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_aggregate_parallel_safe: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_aggregate_parallel_safe", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2290usize, + start_col: 22usize, + end_line: 2290usize, + end_col: 77usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_aggregate_parallel_safe()), + ), + }; + fn matrix_int4_ord_ore_aggregate_parallel_safe() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_aggregate_parallel_safe( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + for agg in ["min", "max"] { + let (proparallel, has_combine): (String, bool) = sqlx::query_as( + "SELECT p.proparallel::text, a.aggcombinefn <> 0 \ + FROM pg_proc p \ + JOIN pg_aggregate a ON a.aggfnoid = p.oid \ + WHERE p.proname = $1 \ + AND p.pronamespace = 'eql_v2'::regnamespace \ + AND p.proargtypes[0]::regtype = $2::regtype", + ) + .bind(agg) + .bind(d) + .fetch_one(&pool) + .await?; + if ::anyhow::__private::not(proparallel == "s") { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "eql_v2.{0}({1}) must be PARALLEL SAFE (proparallel=\'s\'), got {2:?}", + agg, + d, + proparallel, + ), + ); + error + }); + } + if ::anyhow::__private::not(has_combine) { + return ::anyhow::__private::Err({ + let error = ::anyhow::__private::format_err( + format_args!( + "eql_v2.{0}({1}) must declare a combinefunc for partial aggregation", + agg, + d, + ), + ); + error + }); + } + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_aggregate_parallel_safe", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_ord_ore_aggregate_parallel_safe; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_aggregate_typecheck_min"] + #[doc(hidden)] + pub const matrix_int4_storage_aggregate_typecheck_min: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_aggregate_typecheck_min", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2531usize, + start_col: 22usize, + end_line: 2531usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_aggregate_typecheck_min()), + ), + }; + fn matrix_int4_storage_aggregate_typecheck_min() -> anyhow::Result<()> { + async fn matrix_int4_storage_aggregate_typecheck_min( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typecheck_table (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typecheck_table(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }), + ) + .bind(payload) + .execute(&mut *tx) + .await?; + sqlx::query("SAVEPOINT probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value) FROM typecheck_table", + "min", + ), + ) + }); + let err = sqlx::query_scalar::<_, String>(&sql) + .fetch_one(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "min", + d, + ), + ) + }), + ); + let db_err = err + .as_database_error() + .expect("expected database error from typecheck probe"); + let code = db_err.code(); + if ::anyhow::__private::not( + code.as_deref() == Some("42883") || code.as_deref() == Some("42725"), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "min", + d, + code, + db_err.message(), + ), + ) + }), + ), + ); + } + sqlx::query("ROLLBACK TO SAVEPOINT probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_aggregate_typecheck_min", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_aggregate_typecheck_min; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_aggregate_typecheck_max"] + #[doc(hidden)] + pub const matrix_int4_storage_aggregate_typecheck_max: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_aggregate_typecheck_max", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2531usize, + start_col: 22usize, + end_line: 2531usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_aggregate_typecheck_max()), + ), + }; + fn matrix_int4_storage_aggregate_typecheck_max() -> anyhow::Result<()> { + async fn matrix_int4_storage_aggregate_typecheck_max( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typecheck_table (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typecheck_table(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }), + ) + .bind(payload) + .execute(&mut *tx) + .await?; + sqlx::query("SAVEPOINT probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value) FROM typecheck_table", + "max", + ), + ) + }); + let err = sqlx::query_scalar::<_, String>(&sql) + .fetch_one(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "max", + d, + ), + ) + }), + ); + let db_err = err + .as_database_error() + .expect("expected database error from typecheck probe"); + let code = db_err.code(); + if ::anyhow::__private::not( + code.as_deref() == Some("42883") || code.as_deref() == Some("42725"), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "max", + d, + code, + db_err.message(), + ), + ) + }), + ), + ); + } + sqlx::query("ROLLBACK TO SAVEPOINT probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_aggregate_typecheck_max", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_storage_aggregate_typecheck_max; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_aggregate_typecheck_min"] + #[doc(hidden)] + pub const matrix_int4_eq_aggregate_typecheck_min: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_aggregate_typecheck_min", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2531usize, + start_col: 22usize, + end_line: 2531usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_aggregate_typecheck_min()), + ), + }; + fn matrix_int4_eq_aggregate_typecheck_min() -> anyhow::Result<()> { + async fn matrix_int4_eq_aggregate_typecheck_min( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typecheck_table (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typecheck_table(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }), + ) + .bind(payload) + .execute(&mut *tx) + .await?; + sqlx::query("SAVEPOINT probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value) FROM typecheck_table", + "min", + ), + ) + }); + let err = sqlx::query_scalar::<_, String>(&sql) + .fetch_one(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "min", + d, + ), + ) + }), + ); + let db_err = err + .as_database_error() + .expect("expected database error from typecheck probe"); + let code = db_err.code(); + if ::anyhow::__private::not( + code.as_deref() == Some("42883") || code.as_deref() == Some("42725"), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "min", + d, + code, + db_err.message(), + ), + ) + }), + ), + ); + } + sqlx::query("ROLLBACK TO SAVEPOINT probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_aggregate_typecheck_min", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_aggregate_typecheck_min; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_aggregate_typecheck_max"] + #[doc(hidden)] + pub const matrix_int4_eq_aggregate_typecheck_max: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_aggregate_typecheck_max", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2531usize, + start_col: 22usize, + end_line: 2531usize, + end_col: 83usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_aggregate_typecheck_max()), + ), + }; + fn matrix_int4_eq_aggregate_typecheck_max() -> anyhow::Result<()> { + async fn matrix_int4_eq_aggregate_typecheck_max( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let payload = ::eql_tests::helpers::PLACEHOLDER_PAYLOAD; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typecheck_table (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typecheck_table(value) VALUES ($1::jsonb::{0})", + d, + ), + ) + }), + ) + .bind(payload) + .execute(&mut *tx) + .await?; + sqlx::query("SAVEPOINT probe").execute(&mut *tx).await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT eql_v2.{0}(value) FROM typecheck_table", + "max", + ), + ) + }); + let err = sqlx::query_scalar::<_, String>(&sql) + .fetch_one(&mut *tx) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "max", + d, + ), + ) + }), + ); + let db_err = err + .as_database_error() + .expect("expected database error from typecheck probe"); + let code = db_err.code(); + if ::anyhow::__private::not( + code.as_deref() == Some("42883") || code.as_deref() == Some("42725"), + ) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "max", + d, + code, + db_err.message(), + ), + ) + }), + ), + ); + } + sqlx::query("ROLLBACK TO SAVEPOINT probe").execute(&mut *tx).await?; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_aggregate_typecheck_max", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures(&[]); + let f: fn(_) -> _ = matrix_int4_eq_aggregate_typecheck_max; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_count_typed_column"] + #[doc(hidden)] + pub const matrix_int4_storage_count_typed_column: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_count_typed_column", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2632usize, + start_col: 22usize, + end_line: 2632usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_count_typed_column()), + ), + }; + fn matrix_int4_storage_count_typed_column() -> anyhow::Result<()> { + async fn matrix_int4_storage_count_typed_column( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_count (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_count(value) SELECT payload::{0} FROM {1}", + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let actual: i64 = sqlx::query_scalar( + "SELECT COUNT(value) FROM typed_count", + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(value) on typed {0} column: want {1}, got {2}", + d, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_count_typed_column", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_storage_count_typed_column; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_storage_count_path_cast"] + #[doc(hidden)] + pub const matrix_int4_storage_count_path_cast: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_storage_count_path_cast", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2665usize, + start_col: 22usize, + end_line: 2665usize, + end_col: 69usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_storage_count_path_cast()), + ), + }; + fn matrix_int4_storage_count_path_cast() -> anyhow::Result<()> { + async fn matrix_int4_storage_count_path_cast( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Storage); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), + ) + }); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(payload::{0}) on {1}: want {2}, got {3}; SQL={4}", + d, + fixture, + expected, + actual, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_storage_count_path_cast", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_storage_count_path_cast; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_count_typed_column"] + #[doc(hidden)] + pub const matrix_int4_eq_count_typed_column: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_count_typed_column", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2632usize, + start_col: 22usize, + end_line: 2632usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_count_typed_column()), + ), + }; + fn matrix_int4_eq_count_typed_column() -> anyhow::Result<()> { + async fn matrix_int4_eq_count_typed_column( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_count (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_count(value) SELECT payload::{0} FROM {1}", + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let actual: i64 = sqlx::query_scalar( + "SELECT COUNT(value) FROM typed_count", + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(value) on typed {0} column: want {1}, got {2}", + d, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_count_typed_column", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_count_typed_column; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_count_path_cast"] + #[doc(hidden)] + pub const matrix_int4_eq_count_path_cast: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_eq_count_path_cast"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2665usize, + start_col: 22usize, + end_line: 2665usize, + end_col: 69usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_count_path_cast()), + ), + }; + fn matrix_int4_eq_count_path_cast() -> anyhow::Result<()> { + async fn matrix_int4_eq_count_path_cast( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), + ) + }); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(payload::{0}) on {1}: want {2}, got {3}; SQL={4}", + d, + fixture, + expected, + actual, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_count_path_cast", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_count_path_cast; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_eq_count_distinct_extractor"] + #[doc(hidden)] + pub const matrix_int4_eq_count_distinct_extractor: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_eq_count_distinct_extractor", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2709usize, + start_col: 22usize, + end_line: 2709usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_eq_count_distinct_extractor()), + ), + }; + fn matrix_int4_eq_count_distinct_extractor() -> anyhow::Result<()> { + async fn matrix_int4_eq_count_distinct_extractor( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Eq); + let d = &spec.sql_domain; + let extractor_fn = spec + .extractor_fn() + .expect("non-Storage variant must expose an extractor"); + let extractor = ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("{0}(value)", extractor_fn)) + }); + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE distinct_count (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO distinct_count(value) SELECT payload::{0} FROM {1}", + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT COUNT(DISTINCT {0}) FROM distinct_count", + extractor, + ), + ) + }); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&mut *tx).await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(DISTINCT {0}) on {1}: want {2} (one per FIXTURE_VALUES row), got {3}; SQL={4}", + extractor, + d, + expected, + actual, + sql, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_eq_count_distinct_extractor", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_eq_count_distinct_extractor; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_count_typed_column"] + #[doc(hidden)] + pub const matrix_int4_ord_count_typed_column: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_count_typed_column", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2632usize, + start_col: 22usize, + end_line: 2632usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_count_typed_column()), + ), + }; + fn matrix_int4_ord_count_typed_column() -> anyhow::Result<()> { + async fn matrix_int4_ord_count_typed_column( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_count (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_count(value) SELECT payload::{0} FROM {1}", + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let actual: i64 = sqlx::query_scalar( + "SELECT COUNT(value) FROM typed_count", + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(value) on typed {0} column: want {1}, got {2}", + d, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_count_typed_column", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_count_typed_column; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_count_path_cast"] + #[doc(hidden)] + pub const matrix_int4_ord_count_path_cast: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("scalars::int4::matrix_int4_ord_count_path_cast"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2665usize, + start_col: 22usize, + end_line: 2665usize, + end_col: 69usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_count_path_cast()), + ), + }; + fn matrix_int4_ord_count_path_cast() -> anyhow::Result<()> { + async fn matrix_int4_ord_count_path_cast( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), + ) + }); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(payload::{0}) on {1}: want {2}, got {3}; SQL={4}", + d, + fixture, + expected, + actual, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_count_path_cast", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_count_path_cast; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_count_distinct_extractor"] + #[doc(hidden)] + pub const matrix_int4_ord_count_distinct_extractor: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_count_distinct_extractor", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2709usize, + start_col: 22usize, + end_line: 2709usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_count_distinct_extractor()), + ), + }; + fn matrix_int4_ord_count_distinct_extractor() -> anyhow::Result<()> { + async fn matrix_int4_ord_count_distinct_extractor( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let extractor_fn = spec + .extractor_fn() + .expect("non-Storage variant must expose an extractor"); + let extractor = ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("{0}(value)", extractor_fn)) + }); + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE distinct_count (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO distinct_count(value) SELECT payload::{0} FROM {1}", + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT COUNT(DISTINCT {0}) FROM distinct_count", + extractor, + ), + ) + }); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&mut *tx).await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(DISTINCT {0}) on {1}: want {2} (one per FIXTURE_VALUES row), got {3}; SQL={4}", + extractor, + d, + expected, + actual, + sql, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_count_distinct_extractor", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_count_distinct_extractor; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_count_typed_column"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_count_typed_column: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_count_typed_column", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2632usize, + start_col: 22usize, + end_line: 2632usize, + end_col: 72usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_count_typed_column()), + ), + }; + fn matrix_int4_ord_ore_count_typed_column() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_count_typed_column( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE typed_count (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO typed_count(value) SELECT payload::{0} FROM {1}", + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let actual: i64 = sqlx::query_scalar( + "SELECT COUNT(value) FROM typed_count", + ) + .fetch_one(&mut *tx) + .await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(value) on typed {0} column: want {1}, got {2}", + d, + expected, + actual, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_count_typed_column", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_count_typed_column; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_count_path_cast"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_count_path_cast: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_count_path_cast", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2665usize, + start_col: 22usize, + end_line: 2665usize, + end_col: 69usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_count_path_cast()), + ), + }; + fn matrix_int4_ord_ore_count_path_cast() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_count_path_cast( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), + ) + }); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(payload::{0}) on {1}: want {2}, got {3}; SQL={4}", + d, + fixture, + expected, + actual, + sql, + ), + ) + }), + ), + ); + } + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_count_path_cast", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_count_path_cast; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_count_distinct_extractor"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_count_distinct_extractor: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_count_distinct_extractor", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 2709usize, + start_col: 22usize, + end_line: 2709usize, + end_col: 78usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_count_distinct_extractor()), + ), + }; + fn matrix_int4_ord_ore_count_distinct_extractor() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_count_distinct_extractor( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + use ::eql_tests::scalar_domains::ScalarType; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let extractor_fn = spec + .extractor_fn() + .expect("non-Storage variant must expose an extractor"); + let extractor = ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("{0}(value)", extractor_fn)) + }); + let fixture = ::fixture_table_name(); + let expected = ::FIXTURE_VALUES.len() as i64; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE distinct_count (value {0}) ON COMMIT DROP", + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO distinct_count(value) SELECT payload::{0} FROM {1}", + d, + fixture, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT COUNT(DISTINCT {0}) FROM distinct_count", + extractor, + ), + ) + }); + let actual: i64 = sqlx::query_scalar(&sql).fetch_one(&mut *tx).await?; + if ::anyhow::__private::not(actual == expected) { + return ::anyhow::__private::Err( + ::anyhow::Error::msg( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "COUNT(DISTINCT {0}) on {1}: want {2} (one per FIXTURE_VALUES row), got {3}; SQL={4}", + extractor, + d, + expected, + actual, + sql, + ), + ) + }), + ), + ); + } + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_count_distinct_extractor", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_count_distinct_extractor; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_asc_no_where"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_asc_no_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_asc_no_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_asc_no_where()), + ), + }; + fn matrix_int4_ord_order_by_asc_no_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_asc_no_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + "", + &spec.sql_domain, + "ASC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if "".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "ASC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "asc_no_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_asc_no_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_asc_no_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_desc_no_where"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_desc_no_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_desc_no_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_desc_no_where()), + ), + }; + fn matrix_int4_ord_order_by_desc_no_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_desc_no_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + "", + &spec.sql_domain, + "DESC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if "".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "DESC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "desc_no_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_desc_no_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_desc_no_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_asc_with_where"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_asc_with_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_asc_with_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_asc_with_where()), + ), + }; + fn matrix_int4_ord_order_by_asc_with_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_asc_with_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + " WHERE plaintext > 0", + &spec.sql_domain, + "ASC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if " WHERE plaintext > 0".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "ASC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "asc_with_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_asc_with_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_asc_with_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_desc_with_where"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_desc_with_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_desc_with_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_desc_with_where()), + ), + }; + fn matrix_int4_ord_order_by_desc_with_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_desc_with_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + " WHERE plaintext > 0", + &spec.sql_domain, + "DESC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if " WHERE plaintext > 0".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "DESC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "desc_with_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_desc_with_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_desc_with_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_asc_no_where"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_asc_no_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_asc_no_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_asc_no_where()), + ), + }; + fn matrix_int4_ord_ore_order_by_asc_no_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_asc_no_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + "", + &spec.sql_domain, + "ASC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if "".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "ASC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "asc_no_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_asc_no_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_asc_no_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_desc_no_where"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_desc_no_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_desc_no_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_desc_no_where()), + ), + }; + fn matrix_int4_ord_ore_order_by_desc_no_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_desc_no_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + "", + &spec.sql_domain, + "DESC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if "".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "DESC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "desc_no_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_desc_no_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_desc_no_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_asc_with_where"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_asc_with_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_asc_with_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_asc_with_where()), + ), + }; + fn matrix_int4_ord_ore_order_by_asc_with_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_asc_with_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + " WHERE plaintext > 0", + &spec.sql_domain, + "ASC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if " WHERE plaintext > 0".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "ASC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "asc_with_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_asc_with_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_asc_with_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_desc_with_where"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_desc_with_where: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_desc_with_where", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1781usize, + start_col: 22usize, + end_line: 1781usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_desc_with_where()), + ), + }; + fn matrix_int4_ord_ore_order_by_desc_with_where() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_desc_with_where( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + fixture_table, + " WHERE plaintext > 0", + &spec.sql_domain, + "DESC", + ), + ) + }); + let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; + let zero: i32 = Default::default(); + let mut expected: Vec = ::FIXTURE_VALUES + .to_vec(); + expected.sort(); + if " WHERE plaintext > 0".contains("plaintext > 0") { + expected.retain(|v| *v > zero); + } + if "DESC" == "DESC" { + expected.reverse(); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + &spec.sql_domain, + "desc_with_where", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_desc_with_where", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_desc_with_where; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_asc_nulls_first"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_asc_nulls_first: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_asc_nulls_first", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_asc_nulls_first()), + ), + }; + fn matrix_int4_ord_order_by_asc_nulls_first() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_asc_nulls_first( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_order_by_asc_nulls_first"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "ASC", + "FIRST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "ASC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "FIRST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "asc_nulls_first", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_asc_nulls_first", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_asc_nulls_first; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_asc_nulls_last"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_asc_nulls_last: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_asc_nulls_last", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_asc_nulls_last()), + ), + }; + fn matrix_int4_ord_order_by_asc_nulls_last() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_asc_nulls_last( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_order_by_asc_nulls_last"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "ASC", + "LAST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "ASC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "LAST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "asc_nulls_last", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_asc_nulls_last", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_asc_nulls_last; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_desc_nulls_first"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_desc_nulls_first: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_desc_nulls_first", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_desc_nulls_first()), + ), + }; + fn matrix_int4_ord_order_by_desc_nulls_first() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_desc_nulls_first( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_order_by_desc_nulls_first"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "DESC", + "FIRST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "DESC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "FIRST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "desc_nulls_first", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_desc_nulls_first", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_desc_nulls_first; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_desc_nulls_last"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_desc_nulls_last: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_desc_nulls_last", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_desc_nulls_last()), + ), + }; + fn matrix_int4_ord_order_by_desc_nulls_last() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_desc_nulls_last( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_order_by_desc_nulls_last"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "DESC", + "LAST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "DESC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "LAST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "desc_nulls_last", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_desc_nulls_last", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_desc_nulls_last; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_first"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_asc_nulls_first: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_first", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_asc_nulls_first()), + ), + }; + fn matrix_int4_ord_ore_order_by_asc_nulls_first() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_asc_nulls_first( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_ore_order_by_asc_nulls_first"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "ASC", + "FIRST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "ASC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "FIRST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "asc_nulls_first", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_first", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_asc_nulls_first; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_last"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_asc_nulls_last: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_last", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_asc_nulls_last()), + ), + }; + fn matrix_int4_ord_ore_order_by_asc_nulls_last() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_asc_nulls_last( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_ore_order_by_asc_nulls_last"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "ASC", + "LAST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "ASC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "LAST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "asc_nulls_last", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_last", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_asc_nulls_last; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_first"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_desc_nulls_first: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_first", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_desc_nulls_first()), + ), + }; + fn matrix_int4_ord_ore_order_by_desc_nulls_first() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_desc_nulls_first( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_ore_order_by_desc_nulls_first"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "DESC", + "FIRST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "DESC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "FIRST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "desc_nulls_first", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_first", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_desc_nulls_first; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_last"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_desc_nulls_last: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_last", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1880usize, + start_col: 22usize, + end_line: 1880usize, + end_col: 74usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_desc_nulls_last()), + ), + }; + fn matrix_int4_ord_ore_order_by_desc_nulls_last() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_desc_nulls_last( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + const NULL_ROWS: usize = 3; + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let d = &spec.sql_domain; + let table = "matrix_int4_ord_ore_order_by_desc_nulls_last"; + let fixture_table = ::fixture_table_name(); + let pg = ::PG_TYPE; + let mut tx = pool.begin().await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "CREATE TEMP TABLE {0} (plaintext {1}, value {2}) ON COMMIT DROP", + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT plaintext, payload::{2} FROM {0}", + fixture_table, + table, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + sqlx::query( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "INSERT INTO {1}(plaintext, value) SELECT NULL::{2}, NULL::{3} FROM generate_series(1, {0})", + NULL_ROWS, + table, + pg, + d, + ), + ) + }), + ) + .execute(&mut *tx) + .await?; + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "DESC", + "LAST", + table, + ), + ) + }); + let actual: Vec> = sqlx::query_scalar(&sql) + .fetch_all(&mut *tx) + .await?; + let mut non_null: Vec = ::FIXTURE_VALUES + .to_vec(); + non_null.sort(); + if "DESC" == "DESC" { + non_null.reverse(); + } + let sorted = non_null.into_iter().map(Some); + let mut expected: Vec> = Vec::new(); + if "LAST" == "FIRST" { + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + expected.extend(sorted); + } else { + expected.extend(sorted); + expected.extend(std::iter::repeat(None).take(NULL_ROWS)); + } + match (&actual, &expected) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "domain={0} mode={1} SQL={2} expected {3:?}, got {4:?}", + d, + "desc_nulls_last", + sql, + expected, + actual, + ), + ), + ); + } + } + }; + tx.commit().await?; + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_last", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_desc_nulls_last; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_using_lt_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_using_lt_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_using_lt_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_using_lt_rejects()), + ), + }; + fn matrix_int4_ord_order_by_using_lt_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_using_lt_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + "<", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + "<", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_using_lt_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_using_lt_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_using_lte_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_using_lte_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_using_lte_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_using_lte_rejects()), + ), + }; + fn matrix_int4_ord_order_by_using_lte_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_using_lte_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + "<=", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + "<=", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_using_lte_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_using_lte_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_using_gt_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_using_gt_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_using_gt_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_using_gt_rejects()), + ), + }; + fn matrix_int4_ord_order_by_using_gt_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_using_gt_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + ">", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + ">", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_using_gt_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_using_gt_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_order_by_using_gte_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_order_by_using_gte_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_order_by_using_gte_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_order_by_using_gte_rejects()), + ), + }; + fn matrix_int4_ord_order_by_using_gte_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_order_by_using_gte_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::Ord); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + ">=", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + ">=", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_order_by_using_gte_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_order_by_using_gte_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_using_lt_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_using_lt_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_using_lt_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_using_lt_rejects()), + ), + }; + fn matrix_int4_ord_ore_order_by_using_lt_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_using_lt_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + "<", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + "<", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_using_lt_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_using_lt_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_using_lte_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_using_lte_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_using_lte_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_using_lte_rejects()), + ), + }; + fn matrix_int4_ord_ore_order_by_using_lte_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_using_lte_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + "<=", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + "<=", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_using_lte_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_using_lte_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_using_gt_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_using_gt_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_using_gt_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_using_gt_rejects()), + ), + }; + fn matrix_int4_ord_ore_order_by_using_gt_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_using_gt_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + ">", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + ">", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_using_gt_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_using_gt_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } + extern crate test; + #[rustc_test_marker = "scalars::int4::matrix_int4_ord_ore_order_by_using_gte_rejects"] + #[doc(hidden)] + pub const matrix_int4_ord_ore_order_by_using_gte_rejects: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName( + "scalars::int4::matrix_int4_ord_ore_order_by_using_gte_rejects", + ), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "src/matrix.rs", + start_line: 1997usize, + start_col: 22usize, + end_line: 1997usize, + end_col: 87usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(matrix_int4_ord_ore_order_by_using_gte_rejects()), + ), + }; + fn matrix_int4_ord_ore_order_by_using_gte_rejects() -> anyhow::Result<()> { + async fn matrix_int4_ord_ore_order_by_using_gte_rejects( + pool: sqlx::PgPool, + ) -> anyhow::Result<()> { + { + let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< + i32, + >(::eql_tests::scalar_domains::Variant::OrdOre); + let fixture_table = ::fixture_table_name(); + let sql = ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "SELECT plaintext FROM {0} ORDER BY payload::{1} USING {2}", + fixture_table, + &spec.sql_domain, + ">=", + ), + ) + }); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_all(&pool) + .await + .expect_err( + &::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + "domain={0} op={1} SQL={2} must reject ORDER BY USING (no opclass on domain by design) but succeeded", + &spec.sql_domain, + ">=", + sql, + ), + ) + }), + ); + ::eql_tests::assert_db_error(&err, "42809", None); + Ok(()) + } + } + let mut args = ::sqlx::testing::TestArgs::new( + "encrypted_domain::scalars::int4::matrix_int4_ord_ore_order_by_using_gte_rejects", + ); + args.migrator( + &::sqlx::migrate::Migrator { + migrations: ::std::borrow::Cow::Borrowed( + &[ + ::sqlx::migrate::Migration { + version: 1i64, + description: ::std::borrow::Cow::Borrowed("placeholder"), + migration_type: ::sqlx::migrate::MigrationType::Simple, + sql: ::std::borrow::Cow::Borrowed(""), + no_tx: false, + checksum: ::std::borrow::Cow::Borrowed( + &[ + 56u8, 176u8, 96u8, 167u8, 81u8, 172u8, 150u8, 56u8, 76u8, + 217u8, 50u8, 126u8, 177u8, 177u8, 227u8, 106u8, 33u8, 253u8, + 183u8, 17u8, 20u8, 190u8, 7u8, 67u8, 76u8, 12u8, 199u8, + 191u8, 99u8, 246u8, 225u8, 218u8, 39u8, 78u8, 222u8, 191u8, + 231u8, 111u8, 101u8, 251u8, 213u8, 26u8, 210u8, 241u8, 72u8, + 152u8, 185u8, 91u8, + ], + ), + }, + ], + ), + ..::sqlx::migrate::Migrator::DEFAULT + }, + ); + args.fixtures( + &[ + ::sqlx::testing::TestFixture { + path: "../../../fixtures/eql_v2_int4.sql", + contents: "", + }, + ], + ); + let f: fn(_) -> _ = matrix_int4_ord_ore_order_by_using_gte_rejects; + ::sqlx::testing::TestFn::run_test(f, args) + } +} From 5008c7db6ead29218247fb0faf3185c960004a38 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Fri, 29 May 2026 17:06:46 +1000 Subject: [PATCH 18/93] ci(macro-expand): pin cargo-expand, single-source nightly, harden regen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardens the non-blocking macro-expand snapshot lane: 1. Pin cargo-expand to 1.0.122 (was unpinned). cargo-expand drives the rustfmt pass, so an unpinned version can drift the snapshot even with a frozen macro + nightly. 2. Single-source the pinned nightly date in mise.toml. The workflow now greps `nightly-YYYY-MM-DD` from mise.toml instead of hardcoding it, so there is nothing to bump in lockstep — the date lives in one place. 3. Don't truncate the snapshot before a possibly-failing expand: the mise task now expands into a mktemp file and `mv`s on success, so a transient expand failure no longer leaves a 0-byte snapshot locally. 4. SHA-pin the third-party actions (checkout, mise-action, rust-cache), reusing the exact SHAs already pinned in test-eql.yml (commit 41c7496). 5. Document the intended body-only-change nightly gap (no pull_request trigger) in the workflow header comment. --- .github/workflows/macro-expand-eql.yml | 37 +++++++++++++++++--------- mise.toml | 30 ++++++++++++++++++--- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/.github/workflows/macro-expand-eql.yml b/.github/workflows/macro-expand-eql.yml index 5ba71543..f6411657 100644 --- a/.github/workflows/macro-expand-eql.yml +++ b/.github/workflows/macro-expand-eql.yml @@ -11,9 +11,16 @@ name: "Macro expand EQL" # - nightly schedule (the backstop that flags a forgotten local regen) # - manual workflow_dispatch # -# The toolchain is pinned (nightly-2026-05-01) in lockstep with the -# `test:matrix:expand` mise task so the snapshot only moves when the macro -# moves, not when nightly reformats its expansion. Bump both together. +# GAP (intended): there is no `pull_request` trigger, so a change that only +# touches macro *bodies* (no arm add/remove) can merge without ever running +# here and will first surface as a red nightly run afterwards. Accept this — the +# expand lane needs nightly and stays off the PR critical path by design. +# +# The pinned nightly date lives in ONE place: the `cargo +nightly-...` invocation +# in the `test:matrix:expand` mise task. The install step below DERIVES the date +# from mise.toml (grep), so there is nothing to keep in lockstep — bump it once in +# mise.toml. The snapshot then only moves when the macro moves, not when nightly +# reformats its expansion. on: schedule: # 03:00 UTC daily @@ -39,28 +46,34 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: jdx/mise-action@v4 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 with: version: 2026.4.0 install: true cache: true - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: tests/sqlx shared-key: sqlx-tests - # Pinned nightly — keep the date in lockstep with the `cargo +nightly-...` - # invocation in the `test:matrix:expand` mise task. rustfmt formats the - # expansion deterministically. - - name: Install pinned nightly + cargo-expand + # Derive the pinned nightly date from mise.toml (single source of truth — + # the `cargo +nightly-...` invocation in the `test:matrix:expand` task) so + # there is nothing to bump in lockstep here. cargo-expand is likewise pinned + # once in mise.toml's [tools] (`cargo:cargo-expand`) and installed by the + # mise-action step above, so its version is single-sourced too — no + # hardcoded version lives in this workflow. It drives the rustfmt pass, so + # an unpinned version could drift the snapshot even with a frozen macro + + # nightly. The snapshot then only moves when the macro moves. + - name: Install pinned nightly toolchain run: | - rustup toolchain install nightly-2026-05-01 --profile minimal --component rustfmt - cargo binstall -y cargo-expand + NIGHTLY=$(grep -oE 'nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}' mise.toml | head -1) + test -n "$NIGHTLY" || { echo "could not find pinned nightly in mise.toml"; exit 1; } + rustup toolchain install "$NIGHTLY" --profile minimal --component rustfmt - name: Regenerate and verify the matrix expansion snapshot run: | diff --git a/mise.toml b/mise.toml index 47e03ea4..7d1bb173 100644 --- a/mise.toml +++ b/mise.toml @@ -11,6 +11,13 @@ "rust" = { version = "latest", components = "rustc,rust-std,cargo,rustfmt,rust-docs,clippy" } "cargo:cargo-binstall" = "latest" "cargo:sqlx-cli" = "latest" +# Single source of truth for the cargo-expand version used by `test:matrix:expand`. +# Pinned in lockstep with the nightly date in that task: cargo-expand drives the +# rustfmt pass, so an unpinned version can drift the matrix snapshot even with a +# frozen macro + nightly. mise installs this for both local runs and CI (the +# macro-expand-eql.yml workflow's mise-action step), so there is no separate +# hardcoded version to keep in lockstep. +"cargo:cargo-expand" = "1.0.122" "python" = "3.13" [task_config] @@ -120,9 +127,11 @@ description = "Regenerate the int4 matrix cargo-expand snapshot (requires the pi dir = "{{config_root}}/tests/sqlx" run = """ # Body-level fidelity backstop for the macro: the expanded source of the int4 -# matrix arms. NIGHTLY is pinned to a known-good date so the snapshot only moves -# when *the macro* moves, not when nightly reformats — bump the date deliberately -# and in lockstep with .github/workflows/macro-expand-eql.yml. +# matrix arms. The `cargo +nightly-...` invocation below is the SINGLE source of +# the pinned nightly date — .github/workflows/macro-expand-eql.yml greps it from +# here rather than hardcoding, so there is nothing to keep in lockstep. The date +# is pinned to a known-good value so the snapshot only moves when *the macro* +# moves, not when nightly reformats — bump it deliberately, here, in one place. # # `#[sqlx::test]` embeds one `sqlx::migrate::Migration` per file in migrations/ # plus the fixture (via include_str) into EVERY generated test — ~477 MB of @@ -136,6 +145,12 @@ run = """ # Non-blocking lane (no Postgres, never compiled): the `.rs` name lives under # snapshots/, not tests/, so Cargo never treats it as a test target. set -euo pipefail +# Force the mise-pinned cargo-expand (mise.toml [tools]) to win over any stray +# global `cargo install cargo-expand` in ~/.cargo/bin, which otherwise sits +# ahead of mise's install dir on PATH and silently drifts the snapshot. The +# version is single-sourced in [tools]; this only fixes PATH precedence. +PATH="$(mise where cargo:cargo-expand)/bin:$PATH" +export PATH mkdir -p snapshots fixtures BK=$(mktemp -d) cp -a migrations "$BK/migrations" @@ -155,5 +170,12 @@ trap restore EXIT rm -rf migrations && mkdir migrations : > migrations/0001_placeholder.sql : > fixtures/eql_v2_int4.sql -cargo +nightly-2026-05-01 expand --test encrypted_domain scalars::int4 > snapshots/int4_expanded.rs +# Expand into a temp file and mv into place only on success — a redirect straight +# onto the snapshot would zero it before cargo runs, so a transient expand +# failure would leave a 0-byte snapshot locally. (Under `set -euo pipefail` a +# cargo failure aborts the script and the trap restores migrations/fixtures; the +# temp file is then orphaned in $TMPDIR, which is acceptable.) +OUT=$(mktemp) +cargo +nightly-2026-05-01 expand --test encrypted_domain scalars::int4 > "$OUT" +mv "$OUT" snapshots/int4_expanded.rs """ From 4098c695a91eb2ca729438eba49967872ba0db0d Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 14:26:24 +1000 Subject: [PATCH 19/93] refactor(fixtures): collapse scalar fixture wrappers behind scalar_fixture! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recovered from orphaned commit 0a60f71 (dropped by a reset during the stacked-PR shuffle). The eql_v2_int4 fixture file was ~95% boilerplate shared with future scalar types: the spec() builder, the fixture-gen generator test, and the property-test module differed only in name, the Rust plaintext type, and the value const. Add a scalar_fixture!(name, ty, values) macro that stamps out all three. MIN/MAX in the signed-extremes test derive from <$ty>. The int2 hunk from the original commit is dropped — int2 is not on this branch. Test-infra only; no caller-observable change. Fixture property tests pass (3/3); generate() still compiles under --features fixture-gen. --- tests/sqlx/src/fixtures/eql_v2_int4.rs | 47 +------------- tests/sqlx/src/fixtures/mod.rs | 3 + tests/sqlx/src/fixtures/scalar_fixture.rs | 74 +++++++++++++++++++++++ 3 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 tests/sqlx/src/fixtures/scalar_fixture.rs diff --git a/tests/sqlx/src/fixtures/eql_v2_int4.rs b/tests/sqlx/src/fixtures/eql_v2_int4.rs index 316eac48..429e47d9 100644 --- a/tests/sqlx/src/fixtures/eql_v2_int4.rs +++ b/tests/sqlx/src/fixtures/eql_v2_int4.rs @@ -6,51 +6,6 @@ //! no EQL dependency; #225 layers the `eql_v2_int4` domain on top by casting //! `payload` per query. -use super::index_kind::IndexKind; use super::int4_values::VALUES; -use super::spec::FixtureSpec; -/// The complete fixture definition. `IndexKind::Unique` drives `=` / `<>` -/// (HMAC); `IndexKind::Ore` drives `<` `<=` `>` `>=` (ORE block terms). -pub fn spec() -> FixtureSpec<'static, i32> { - FixtureSpec::new("eql_v2_int4") - .with_index(IndexKind::Unique) - .with_index(IndexKind::Ore) - .with_column_type("jsonb") - .with_values(VALUES) -} - -/// The generator. Gated by `fixture-gen` so `cargo test` never compiles it; -/// `#[ignore]` is a second guard. Run via `mise run fixture:generate eql_v2_int4`. -#[cfg(feature = "fixture-gen")] -#[tokio::test] -#[ignore = "generator — run via `mise run fixture:generate`"] -async fn generate() -> anyhow::Result<()> { - spec().run().await -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn spec_is_complete() { - assert!(spec().check_complete().is_ok()); - } - - #[test] - fn spec_includes_signed_extremes() { - // i32::MIN / MAX exercise ORE block-encoding sign-bit edges - // that the smaller earlier list did not cover. - let spec = spec(); - let values = spec.values(); - assert!(values.contains(&i32::MIN), "spec must include i32::MIN"); - assert!(values.contains(&i32::MAX), "spec must include i32::MAX"); - assert!(values.contains(&0), "spec must include 0"); - } - - #[test] - fn spec_includes_negative_values() { - assert!(spec().values().iter().any(|&v| v < 0)); - } -} +crate::scalar_fixture!("eql_v2_int4", i32, VALUES); diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index 416a3b02..ee087d1d 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -19,6 +19,9 @@ pub mod spec; pub use spec::FixtureSpec; +#[macro_use] +pub mod scalar_fixture; + pub mod cipherstash; pub mod driver; diff --git a/tests/sqlx/src/fixtures/scalar_fixture.rs b/tests/sqlx/src/fixtures/scalar_fixture.rs new file mode 100644 index 00000000..00956cd6 --- /dev/null +++ b/tests/sqlx/src/fixtures/scalar_fixture.rs @@ -0,0 +1,74 @@ +//! `scalar_fixture!` — collapse a scalar fixture wrapper to one invocation. +//! +//! Every `eql_v2_` scalar fixture file (`eql_v2_int2`, `eql_v2_int4`, …) is +//! the same three items differing only in the fixture name, the Rust plaintext +//! type, and the generated value list: the `spec()` builder, the `fixture-gen` +//! generator test, and a small property-test module. This macro stamps all +//! three out, so a new scalar fixture is one `use` of the value const plus one +//! `scalar_fixture!(…)`. +//! +//! The per-file `//!` module docs still belong in each fixture file — they +//! describe *that* type's value choices and are not boilerplate. + +/// Stamp out the `spec()` builder, the `fixture-gen` generator test, and the +/// property-test module for a scalar fixture. +/// +/// - `$name` — the fixture name (`"eql_v2_int2"`), drives every derived path. +/// - `$ty` — the Rust plaintext type (`i16`); `<$ty>::MIN`/`MAX` supply the +/// signed-extreme assertions. +/// - `$values` — the generated value const (`int2_values::VALUES`). +/// +/// Indexes are fixed to `Unique` (HMAC, drives `=` / `<>`) and `Ore` (ORE +/// block terms, drives `<` `<=` `>` `>=`) with a committed `jsonb` payload — +/// the shape shared by every ordered scalar domain. +#[macro_export] +macro_rules! scalar_fixture { + ($name:literal, $ty:ty, $values:expr $(,)?) => { + /// The complete fixture definition. `IndexKind::Unique` drives `=` / + /// `<>` (HMAC); `IndexKind::Ore` drives `<` `<=` `>` `>=` (ORE block + /// terms). + pub fn spec() -> $crate::fixtures::FixtureSpec<'static, $ty> { + $crate::fixtures::FixtureSpec::new($name) + .with_index($crate::fixtures::IndexKind::Unique) + .with_index($crate::fixtures::IndexKind::Ore) + .with_column_type("jsonb") + .with_values($values) + } + + /// The generator. Gated by `fixture-gen` so `cargo test` never compiles + /// it; `#[ignore]` is a second guard. Run via + /// `mise run fixture:generate`. + #[cfg(feature = "fixture-gen")] + #[tokio::test] + #[ignore = "generator — run via `mise run fixture:generate`"] + async fn generate() -> anyhow::Result<()> { + spec().run().await + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn spec_is_complete() { + assert!(spec().check_complete().is_ok()); + } + + #[test] + fn spec_includes_signed_extremes() { + // MIN / MAX exercise ORE block-encoding sign-bit edges that a + // smaller list would not cover. + let spec = spec(); + let values = spec.values(); + assert!(values.contains(&<$ty>::MIN), "spec must include {}::MIN", stringify!($ty)); + assert!(values.contains(&<$ty>::MAX), "spec must include {}::MAX", stringify!($ty)); + assert!(values.contains(&0), "spec must include 0"); + } + + #[test] + fn spec_includes_negative_values() { + assert!(spec().values().iter().any(|&v| v < 0)); + } + } + }; +} From fab74be0ec58a05b7f258030125a30fefdcc7569 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 14:35:29 +1000 Subject: [PATCH 20/93] style(fixtures): rustfmt scalar_fixture.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo fmt --check (tasks/test/lint.sh) flagged scalar_fixture.rs: two assert! lines in the generated property-test arm exceed the line width, so rustfmt wraps them. The file was committed unformatted in 4098c69 and CI lint catches it. Formatting only — the macro expands identically, no behaviour change. --- tests/sqlx/src/fixtures/scalar_fixture.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/sqlx/src/fixtures/scalar_fixture.rs b/tests/sqlx/src/fixtures/scalar_fixture.rs index 00956cd6..2394b049 100644 --- a/tests/sqlx/src/fixtures/scalar_fixture.rs +++ b/tests/sqlx/src/fixtures/scalar_fixture.rs @@ -60,8 +60,16 @@ macro_rules! scalar_fixture { // smaller list would not cover. let spec = spec(); let values = spec.values(); - assert!(values.contains(&<$ty>::MIN), "spec must include {}::MIN", stringify!($ty)); - assert!(values.contains(&<$ty>::MAX), "spec must include {}::MAX", stringify!($ty)); + assert!( + values.contains(&<$ty>::MIN), + "spec must include {}::MIN", + stringify!($ty) + ); + assert!( + values.contains(&<$ty>::MAX), + "spec must include {}::MAX", + stringify!($ty) + ); assert!(values.contains(&0), "spec must include 0"); } From 04b0f322e01695c24ad7ae1181e1de7cc938a48e Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 11:09:42 +1000 Subject: [PATCH 21/93] feat(encrypted-domain): move int4 domain family into a new eql_v3 schema Introduce a separate eql_v3 schema for the encrypted-domain type families and move the int4 family into it, dropping the redundant version prefix: eql_v3.int4{,_eq,_ord,_ord_ore}, with extractors/wrappers/aggregates (eql_v3.eq_term, ord_term, eq/neq/lt/lte/gt/gte, min/max) also in eql_v3. The core index-term types (eql_v2.hmac_256, eql_v2.ore_block_u64_8_256) stay in eql_v2 and are referenced cross-schema. eql_v2 is unchanged. - codegen: DOMAIN_SCHEMA/CORE_SCHEMA in templates.py; schema-qualified domain_name; schema-v3 REQUIRE edges in generate.py - new src/schema-v3.sql; blocker helper moved to eql_v3 - pin_search_path: pin loop + structural skip broadened to eql_v3 - lints: operator/blocker/domain_over_domain/domain_opclass recognisers extended to eql_v3 - splinter: scope + eql_v3 allowlist rows - SQLx harness + family/lint tests + codegen tests + reference baseline - uninstallers drop eql_v3; docs, CHANGELOG, CLAUDE.md updated Resolves the open PR #239 review thread asking to move the family to eql_v3. The generator/test-quality hardening that was previously bundled here (_sql_str, brief disambiguation, placeholder/aggregate rationale comments, assert_index_scan_uses) now lands separately on v3-domain-type-int4, since it is schema-independent and useful to every scalar type. --- CHANGELOG.md | 4 +- CLAUDE.md | 6 +- docs/reference/encrypted-domain-generator.md | 12 +- .../encrypted-domain-implementation-spec.md | 25 +- docs/reference/eql-functions.md | 40 +- docs/reference/sql-support.md | 18 +- src/encrypted_domain/functions.sql | 10 +- src/lint/lints.sql | 29 +- src/schema-v3.sql | 22 + tasks/codegen/generate.py | 7 +- tasks/codegen/templates.py | 53 ++- tasks/codegen/test_generate.py | 16 +- tasks/codegen/test_templates.py | 110 ++--- tasks/pin_search_path.sql | 17 +- tasks/test/splinter.sh | 33 +- tasks/uninstall-protect.sql | 1 + tasks/uninstall.sql | 5 + .../reference/int4/int4_eq_functions.sql | 389 +++++++++--------- .../reference/int4/int4_eq_operators.sql | 178 ++++---- .../codegen/reference/int4/int4_functions.sql | 383 ++++++++--------- .../codegen/reference/int4/int4_operators.sql | 178 ++++---- .../reference/int4/int4_ord_aggregates.sql | 54 +-- .../reference/int4/int4_ord_functions.sql | 389 +++++++++--------- .../reference/int4/int4_ord_operators.sql | 178 ++++---- .../int4/int4_ord_ore_aggregates.sql | 54 +-- .../reference/int4/int4_ord_ore_functions.sql | 389 +++++++++--------- .../reference/int4/int4_ord_ore_operators.sql | 178 ++++---- tests/codegen/reference/int4/int4_types.sql | 18 +- tests/sqlx/src/matrix.rs | 74 ++-- tests/sqlx/src/scalar_domains.rs | 13 +- .../encrypted_domain/family/inlinability.rs | 112 ++--- .../family/jsonb_operator_surface.rs | 2 +- .../encrypted_domain/family/mutations.rs | 54 +-- .../tests/encrypted_domain/family/support.rs | 76 ++-- tests/sqlx/tests/lint_tests.rs | 18 +- 35 files changed, 1621 insertions(+), 1524 deletions(-) create mode 100644 src/schema-v3.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d45c0a..00215fd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,8 @@ Each entry that ships in a published release links to the PR that introduced it. ### Added -- **`eql_v2_int4` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int4` columns: `eql_v2_int4` (storage-only), `eql_v2_int4_eq` (`=` / `<>` via HMAC), and `eql_v2_int4_ord` / `eql_v2_int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v2.eq_term` / `eql_v2.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) -- **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v2.min(eql_v2__ord)` / `eql_v2.max(eql_v2__ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) +- **`eql_v3` encrypted-domain schema, with the `int4` family as its first member.** Encrypted-domain type families now live in a new, additional `eql_v3` schema (the existing `eql_v2` schema is unchanged — it keeps the core types/operators and stays the documented public API). Four jsonb-backed domains for encrypted `int4` columns: `eql_v3.int4` (storage-only), `eql_v3.int4_eq` (`=` / `<>` via HMAC), and `eql_v3.int4_ord` / `eql_v3.int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. The extractors still return the core `eql_v2.hmac_256` / `eql_v2.ore_block_u64_8_256` index-term types, which remain in `eql_v2` and are referenced cross-schema. Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`, namespaced under its own schema. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) +- **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v3.min(eql_v3._ord)` / `eql_v3.max(eql_v3._ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) ## [2.3.1] — 2026-05-21 diff --git a/CLAUDE.md b/CLAUDE.md index 361fd31b..1328a8b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,7 @@ This project uses `mise` for task management. Common commands: This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for searchable encryption. Key architectural components: ### Core Structure -- **Schema**: All EQL functions/types are in `eql_v2` PostgreSQL schema +- **Schema**: Core EQL functions/types are in the `eql_v2` PostgreSQL schema. The encrypted-domain type families (`int4` and future scalar domains) live in a separate `eql_v3` schema (see below); they reuse the core `eql_v2` index-term types cross-schema. `eql_v2` is unchanged and remains the documented public API. - **Main Type**: `eql_v2_encrypted` - composite type for encrypted columns (stored as JSONB) - **Configuration**: `eql_v2_configuration` table tracks encryption configs - **Index Types**: Various encrypted index types (blake3, hmac_256, bloom_filter, ore variants) @@ -75,7 +75,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search ### Encrypted-Domain Types -`src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains, one domain per operator/index capability (`eql_v2_` storage-only, `eql_v2__eq`, `eql_v2__ord`). `eql_v2_int4` (PR #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, and `timestamp` follow this materializer pattern. `jsonb` needs a separate design and is out of scope for the scalar materializer. +`src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, and `timestamp` follow this materializer pattern. `jsonb` needs a separate design and is out of scope for the scalar materializer. Adding a scalar encrypted-domain type is generated from a minimal manifest at `tasks/codegen/types/.toml`: the filename supplies ``, and the `[domain]` table maps each generated domain name to the fixed index terms it carries. Example: `int4_eq = ["hm"]`, `int4_ord = ["ore"]`. Term capabilities are fixed in `tasks/codegen/terms.py`: `hm` provides equality, and `ore` provides equality plus ordering. `mise run build` regenerates the scalar SQL surface into `src/encrypted_domain//` from every manifest at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. Use `mise run codegen:domain ` to refresh a single type manually while iterating on its manifest, or `mise run codegen:domain:all` to regenerate every type at once (the same enumeration `mise run build` uses). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` files are gitignored and never committed — the TOML manifest plus `tasks/codegen/terms.py` are the source of truth. Generated files carry an `AUTO-GENERATED — DO NOT EDIT` header; change the manifest or term catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. @@ -260,7 +260,7 @@ The entry under `Changed` / `Deprecated` should cross-link to the `U-NNN`. See ` ### Versioning -The `eql_v2` PostgreSQL schema name is part of the public API and is **independent of the EQL release version**. Major-version bumps to EQL do not rename the schema. When deciding on a version bump: +The `eql_v2` PostgreSQL schema name is part of the public API and is **independent of the EQL release version**. Major-version bumps to EQL do not rename the schema. The `eql_v3` schema is **not** a rename of `eql_v2`: it is a separate, additional schema introduced to namespace the encrypted-domain type families. Both schemas coexist; `eql_v2` keeps the core types/operators and is unchanged. Adding a new schema for a new surface is additive, not a public-API break. When deciding on a version bump: - **Patch (`2.3.x`)** — bug fixes, no behaviour changes - **Minor (`2.x.0`)** — additive changes, behaviour changes that don't break the public API (signatures, schema name, payload format, operator names) diff --git a/docs/reference/encrypted-domain-generator.md b/docs/reference/encrypted-domain-generator.md index 9b502690..bb6252be 100644 --- a/docs/reference/encrypted-domain-generator.md +++ b/docs/reference/encrypted-domain-generator.md @@ -7,7 +7,7 @@ The contract those outputs must satisfy is in [`encrypted-domain-implementation-spec.md`](./encrypted-domain-implementation-spec.md); this file describes the machine that produces them. -The reference type is `eql_v2_int4` (PR #239). `text` and `jsonb` are +The reference type is `eql_v3.int4` (PR #239). `text` and `jsonb` are outside scope. ## 1. Why a generator @@ -245,7 +245,7 @@ operators are always blockers. The table above covers `_functions.sql` only. Ordered domains additionally emit `_aggregates.sql` — two state functions (`min_sfunc`, `max_sfunc`) and two `CREATE AGGREGATE` declarations -(`eql_v2.min`, `eql_v2.max`). Each aggregate declares +(`eql_v3.min`, `eql_v3.max`). Each aggregate declares `combinefunc = ` and `parallel = safe`: min/max are associative, so the state function doubles as the combine function, enabling partial and parallel aggregation on large `GROUP BY` ORE workloads with no decryption. @@ -280,13 +280,13 @@ incorrect SQL unreachable. Invariants encoded in code: (`templates.py:46`), which doubles embedded single quotes. Today's catalog strings are all quote-free so it is a no-op, but it guarantees a future quote-bearing catalog string cannot break out of its literal. -- **No domain-over-domain.** Every domain is `CREATE DOMAIN ... AS - jsonb`, never `AS ` (`templates.py:72`). PostgreSQL +- **No domain-over-domain.** Every domain is `CREATE DOMAIN eql_v3. + AS jsonb`, never `AS ` (`templates.py:72`). PostgreSQL resolves operators against the underlying base type; a derived domain would silently bypass the fixed operator surface. - **No operator class on a domain.** The generator emits operators, not operator classes. Callers index through the extractor function - (e.g. `USING btree (eql_v2.ord_term(col))`), whose return type + (e.g. `USING btree (eql_v3.ord_term(col))`), whose return type already carries a default opclass. - **Ownership boundary.** `writer.is_generated` recognises owned files by their header line and refuses to overwrite anything else @@ -323,7 +323,7 @@ output without per-type edits: - **`tasks/pin_search_path.sql:265-290`** — structural skip identifies encrypted-domain functions by language (`sql`), volatility (`IMMUTABLE`), and the presence of at least one argument typed as a - jsonb-backed `DOMAIN` in `public` named `eql_v2_*`. New scalar types + jsonb-backed `DOMAIN` in the `eql_v3` schema. New scalar types need no edit here. - **`tasks/test/splinter.sh`** — name-based allowlist. The converged wrapper names (`eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `eq_term`, diff --git a/docs/reference/encrypted-domain-implementation-spec.md b/docs/reference/encrypted-domain-implementation-spec.md index 4499c20d..c6bec96a 100644 --- a/docs/reference/encrypted-domain-implementation-spec.md +++ b/docs/reference/encrypted-domain-implementation-spec.md @@ -8,8 +8,9 @@ the fixed term catalog in `tasks/codegen/terms.py`. ## 1. Model -Each generated public domain is a concrete `jsonb` domain named -`public.eql_v2_`. The manifest is intentionally small: +Each generated domain is a concrete `jsonb` domain in the `eql_v3` +schema named `eql_v3.` (dropped by `DROP SCHEMA eql_v3 CASCADE`; +survives an `eql_v2` uninstall). The manifest is intentionally small: ```toml [domain] @@ -110,10 +111,10 @@ with `v` pinned to `2`. Beyond key presence and the version value, a malformed term can still fail later inside its extractor unless a future catalog design adds stronger validation. -Every generated domain is a concrete domain over `jsonb`. Do not define -one generated domain over another generated domain; PostgreSQL resolves -operators against the underlying base type in ways that bypass the fixed -operator surface. +Every generated domain is a concrete domain over `jsonb` in the `eql_v3` +schema. Do not define one generated domain over another generated domain; +PostgreSQL resolves operators against the underlying base type in ways +that bypass the fixed operator surface. ## 4. Extractors And Wrappers @@ -179,7 +180,7 @@ minimal metadata because they should never be planner-visible supported paths. PostgreSQL's operator resolver still prefers the built-in `jsonb` operator -for untyped string literals in forms such as `payload::eql_v2_int4 ? 'c'`. +for untyped string literals in forms such as `payload::eql_v3.int4 ? 'c'`. Use typed parameters or explicit casts (`'c'::text`) to route those forms to the generated blocker. The generated surface blocks the typed native operator shapes exposed by the catalog. @@ -189,8 +190,8 @@ operator shapes exposed by the catalog. Each ordered (ord-capable) domain additionally gets a generated `_aggregates.sql` file declaring `MIN` / `MAX`: -- two state functions, `eql_v2.min_sfunc` and `eql_v2.max_sfunc`, and -- two aggregates, `eql_v2.min()` and `eql_v2.max()`. +- two state functions, `eql_v3.min_sfunc` and `eql_v3.max_sfunc`, and +- two aggregates, `eql_v3.min()` and `eql_v3.max()`. Comparison routes through the domain's `<` / `>` operator (the ORE block term — no decryption). The state functions are `LANGUAGE plpgsql @@ -224,12 +225,12 @@ generated siblings, `_extensions.sql` IS committed. ## 7. Indexing -Do not create operator classes on generated public domains. Index through +Do not create operator classes on generated domains. Index through the extractor: ```sql -CREATE INDEX ... ON table_name USING btree (eql_v2.ord_term(col)); -CREATE INDEX ... ON table_name USING hash (eql_v2.eq_term(col)); +CREATE INDEX ... ON table_name USING btree (eql_v3.ord_term(col)); +CREATE INDEX ... ON table_name USING hash (eql_v3.eq_term(col)); ``` The extractor return type must already have the needed PostgreSQL access diff --git a/docs/reference/eql-functions.md b/docs/reference/eql-functions.md index 5ee40c77..e517e63e 100644 --- a/docs/reference/eql-functions.md +++ b/docs/reference/eql-functions.md @@ -422,31 +422,33 @@ eql_v2.ste_vec(val eql_v2_encrypted) RETURNS eql_v2_encrypted[] eql_v2.ste_vec(val jsonb) RETURNS eql_v2_encrypted[] ``` -### `eql_v2.eq_term()` / `eql_v2.ord_term()` (encrypted-domain) +### `eql_v3.eq_term()` / `eql_v3.ord_term()` (encrypted-domain) Extract the equality (`hm`) or ordering (`ob`) index term from a scalar encrypted-domain value. Generated per eq/ord-capable variant of every scalar type — see [Encrypted-Domain Code Generator](./encrypted-domain-generator.md). The argument type selects the overload, and both are inlinable so a -functional index built on the extractor engages. +functional index built on the extractor engages. The extractors live in +the `eql_v3` schema; their return types remain the core `eql_v2` +index-term types. ```sql -- int4 — generated for every scalar type's eq / ord variants. -eql_v2.eq_term(a eql_v2_int4_eq) RETURNS eql_v2.hmac_256 -eql_v2.ord_term(a eql_v2_int4_ord) RETURNS eql_v2.ore_block_u64_8_256 -eql_v2.ord_term(a eql_v2_int4_ord_ore) RETURNS eql_v2.ore_block_u64_8_256 +eql_v3.eq_term(a eql_v3.int4_eq) RETURNS eql_v2.hmac_256 +eql_v3.ord_term(a eql_v3.int4_ord) RETURNS eql_v2.ore_block_u64_8_256 +eql_v3.ord_term(a eql_v3.int4_ord_ore) RETURNS eql_v2.ore_block_u64_8_256 ``` **Example:** ```sql -- Functional indexes on the extracted terms (see Database Indexes) -CREATE INDEX ON users USING hash (eql_v2.eq_term(salary_encrypted)); -CREATE INDEX ON users USING btree (eql_v2.ord_term(salary_encrypted)); +CREATE INDEX ON users USING hash (eql_v3.eq_term(salary_encrypted)); +CREATE INDEX ON users USING btree (eql_v3.ord_term(salary_encrypted)); ``` > The full per-domain operator/wrapper/blocker surface (and the -> `eql_v2_` / `_eq` / `_ord` / `_ord_ore` domain types themselves) is -> documented in [SQL support](./sql-support.md#encrypted-domain-scalar-types-eql_v2_t) +> `eql_v3.` / `_eq` / `_ord` / `_ord_ore` domain types themselves) is +> documented in [SQL support](./sql-support.md#encrypted-domain-scalar-types-eql_v3t) > and the [generator reference](./encrypted-domain-generator.md). --- @@ -669,28 +671,28 @@ SELECT eql_v2.min(encrypted_date) FROM events; SELECT eql_v2.max(encrypted_price) FROM products WHERE category = 'electronics'; ``` -### `eql_v2.min()` / `eql_v2.max()` (per-domain) +### `eql_v3.min()` / `eql_v3.max()` (per-domain) -Returns the minimum or maximum encrypted value in a set on an ordered encrypted-domain column. Defined per ord-capable variant of every scalar type (`eql_v2__ord`, `eql_v2__ord_ore`); the input type selects the aggregate via PostgreSQL's overload resolution. These are type-safe alternatives to the composite-type aggregates above and coexist with them. +Returns the minimum or maximum encrypted value in a set on an ordered encrypted-domain column. Defined per ord-capable variant of every scalar type (`eql_v3._ord`, `eql_v3._ord_ore`); the input type selects the aggregate via PostgreSQL's overload resolution. These are type-safe alternatives to the composite-type aggregates above and coexist with them. ```sql -- int4 — generated for every ordered variant of every scalar type. -eql_v2.min(eql_v2_int4_ord) RETURNS eql_v2_int4_ord -eql_v2.max(eql_v2_int4_ord) RETURNS eql_v2_int4_ord -eql_v2.min(eql_v2_int4_ord_ore) RETURNS eql_v2_int4_ord_ore -eql_v2.max(eql_v2_int4_ord_ore) RETURNS eql_v2_int4_ord_ore +eql_v3.min(eql_v3.int4_ord) RETURNS eql_v3.int4_ord +eql_v3.max(eql_v3.int4_ord) RETURNS eql_v3.int4_ord +eql_v3.min(eql_v3.int4_ord_ore) RETURNS eql_v3.int4_ord_ore +eql_v3.max(eql_v3.int4_ord_ore) RETURNS eql_v3.int4_ord_ore ``` Comparison routes through the variant's `<` / `>` operator, which uses the ORE block term — no decryption. The state function is `STRICT`, so `NULL` inputs are skipped and an all-`NULL` input set returns `NULL`. **Example:** ```sql --- ord-capable column (e.g. price_encrypted typed as eql_v2_int4_ord) -SELECT eql_v2.min(price_encrypted) FROM products; -SELECT eql_v2.max(price_encrypted) FROM products WHERE category = 'electronics'; +-- ord-capable column (e.g. price_encrypted typed as eql_v3.int4_ord) +SELECT eql_v3.min(price_encrypted) FROM products; +SELECT eql_v3.max(price_encrypted) FROM products WHERE category = 'electronics'; -- Equivalent on a generic jsonb column (cast to the right domain) -SELECT eql_v2.min(price_jsonb::eql_v2_int4_ord) FROM products; +SELECT eql_v3.min(price_jsonb::eql_v3.int4_ord) FROM products; ``` `SUM` / `AVG` and other numeric aggregates are not supported on encrypted columns — decrypt at the application boundary. `MIN` / `MAX` only require comparator-revealing terms; arithmetic aggregates would require homomorphic encryption. diff --git a/docs/reference/sql-support.md b/docs/reference/sql-support.md index ff2de727..d15be2ee 100644 --- a/docs/reference/sql-support.md +++ b/docs/reference/sql-support.md @@ -59,22 +59,22 @@ Use the equivalent [`jsonb_path_query`](#jsonb-functions-and-selectors-enabled-b --- -## Encrypted-domain scalar types (`eql_v2_`) +## Encrypted-domain scalar types (`eql_v3.`) -Scalar encrypted-domain types (e.g. `eql_v2_int4`; see the [generator reference](./encrypted-domain-generator.md)) are a different access model from the matrix above. Instead of configuring a search index on an `eql_v2_encrypted` column, you type the column as a specific domain *variant* whose operator surface is fixed at generation time. The index terms travel in the payload; there is no `add_search_config` step. +Scalar encrypted-domain types (e.g. `eql_v3.int4`; see the [generator reference](./encrypted-domain-generator.md)) are a different access model from the matrix above. Instead of configuring a search index on an `eql_v2_encrypted` column, you type the column as a specific domain *variant* whose operator surface is fixed at generation time. The index terms travel in the payload; there is no `add_search_config` step. The domains and their operator surface live in the `eql_v3` schema (dropped by `DROP SCHEMA eql_v3 CASCADE`, and they survive an `eql_v2` uninstall); their extracted index-term types remain the core `eql_v2` types. Each scalar type `` generates one storage-only variant plus eq/ord query variants: | Domain variant | Term carried | `=` `<>` | `<` `<=` `>` `>=` | `MIN` / `MAX` | `LIKE`/`ILIKE`, JSONB / ste_vec ops | | ------------------------------- | ------------------- | :------: | :---------------: | :-----------: | :---------------------------------: | -| `eql_v2_` | none (storage only) | ❌ | ❌ | ❌ | ❌ | -| `eql_v2__eq` | `hm` (hmac_256) | ✅ | ❌ | ❌ | ❌ | -| `eql_v2__ord` / `_ord_ore` | `ob` (ore_block) | ✅ | ✅ | ✅ | ❌ | +| `eql_v3.` | none (storage only) | ❌ | ❌ | ❌ | ❌ | +| `eql_v3._eq` | `hm` (hmac_256) | ✅ | ❌ | ❌ | ❌ | +| `eql_v3._ord` / `_ord_ore` | `ob` (ore_block) | ✅ | ✅ | ✅ | ❌ | -- The bare `eql_v2_` variant carries no index term and **blocks every comparison operator** — it is storage / decryption only. Type the column as `_eq` or `_ord` (or cast at the call site) when you need to query. +- The bare `eql_v3.` variant carries no index term and **blocks every comparison operator** — it is storage / decryption only. Type the column as `_eq` or `_ord` (or cast at the call site) when you need to query. - Unsupported operators are not silent no-ops: they route to blocker functions that `RAISE` an "operator not supported" exception (a `NULL` operand still raises — the blockers are deliberately not `STRICT`). - `LIKE` / `ILIKE` and the native JSONB operators (`@>`, `<@`, `->`, `->>`, `?`, `?|`, `?&`, `@?`, `@@`, `#>`, `#>>`, `-`, `#-`, `||`) are blocked on **every** scalar domain variant — they are meaningless on a scalar payload. -- `MIN` / `MAX` are exposed only on the ordered variants as `eql_v2.min(eql_v2__ord)` / `eql_v2.max(...)` — see [EQL Functions Reference](./eql-functions.md#eql_v2min--eql_v2max-per-domain). +- `MIN` / `MAX` are exposed only on the ordered variants as `eql_v3.min(eql_v3._ord)` / `eql_v3.max(...)` — see [EQL Functions Reference](./eql-functions.md#eql_v3min--eql_v3max-per-domain). --- @@ -95,7 +95,7 @@ This matrix covers higher-level SQL constructs rather than individual operators. | `GROUP BY col` | requires `unique` on the whole column; `ore` / `ope` not yet supported (see note below). Extracted JSON paths have separate caveats — see [ste_vec section](#index-terms-by-json-node-type). | ✅ | ❌ | ❌ | ❌ | ❌ | | `DISTINCT` / `DISTINCT ON (col)` | `unique`, `ore`, or `ope` | ✅ | ✅ | ✅ | ❌ | ❌ | | `HAVING` | same index requirements as the predicates used in `HAVING` (see operator matrix) | varies | varies | varies | varies | varies | -| `MIN(col)` / `MAX(col)` | `eql_v2.min(eql_v2_encrypted)` / `max` work on any `eql_v2_encrypted` column with `ore` terms. The encrypted-domain family additionally exposes type-safe `eql_v2.min(eql_v2__ord)` / `max` (and the `_ord_ore` twin); `Storage` and `Eq` variants have no comparator and do not declare these aggregates. | ❌ | ✅ | ✅ | ❌ | ❌ | +| `MIN(col)` / `MAX(col)` | `eql_v2.min(eql_v2_encrypted)` / `max` work on any `eql_v2_encrypted` column with `ore` terms. The encrypted-domain family additionally exposes type-safe `eql_v3.min(eql_v3._ord)` / `max` (and the `_ord_ore` twin); `Storage` and `Eq` variants have no comparator and do not declare these aggregates. | ❌ | ✅ | ✅ | ❌ | ❌ | | `COUNT(col)` / `COUNT(DISTINCT col)` | `ore` / `ope` or `unique` for `DISTINCT`; none for plain `COUNT(col)` | ✅ | ✅ | ✅ | ✅ | ✅ | | `JOIN … ON lhs.col = rhs.col` | same index and keyset on both sides | ✅ | ✅ | ✅ | ❌ | ❌ | | `JOIN … ON lhs.col < rhs.col` etc. | same index and keyset on both sides | ❌ | ✅ | ✅ | ❌ | ❌ | @@ -108,7 +108,7 @@ Notes: - **Cross-column / cross-table comparisons** (joins, `IN (subquery)`, `UNION` dedup, etc.) require both sides to have been encrypted with the *same* keyset and the matching search index. Encrypted values from different `ste_vec` prefixes are deliberately incomparable. - **`GROUP BY`** on encrypted columns relies on an operator class which currently only supports encrypted values with a `unique` index term. This is a surprising limitation because it would be natural to expect `ore` / `ope` index terms to also work. This limitation will be lifted in the future. See [Database Indexes](./database-indexes.md#group-by) for performance considerations. - **`ORDER BY`** without an `ore` or `ope` index will still *run* (the EQL `compare` function has a deterministic literal fallback to avoid btree errors), but the resulting order is not meaningful. Configure `ore` (or `ope`) whenever ordering matters. -- **`MIN(col)` / `MAX(col)`** is available two ways. The composite-type aggregates `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` work on any `eql_v2_encrypted` column carrying `ore` terms. The encrypted-domain family additionally exposes type-safe per-variant aggregates — see `eql_v2.min(eql_v2__ord)` / `eql_v2.max(eql_v2__ord)` (and the `_ord_ore` twin) in [EQL Functions Reference](./eql-functions.md#eql_v2min--eql_v2max-per-domain). For a domain-typed column, type it as the appropriate `_ord` variant or cast at the call site (`eql_v2.min(col::eql_v2_int4_ord)`). +- **`MIN(col)` / `MAX(col)`** is available two ways. The composite-type aggregates `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` work on any `eql_v2_encrypted` column carrying `ore` terms. The encrypted-domain family additionally exposes type-safe per-variant aggregates — see `eql_v3.min(eql_v3._ord)` / `eql_v3.max(eql_v3._ord)` (and the `_ord_ore` twin) in [EQL Functions Reference](./eql-functions.md#eql_v3min--eql_v3max-per-domain). For a domain-typed column, type it as the appropriate `_ord` variant or cast at the call site (`eql_v3.min(col::eql_v3.int4_ord)`). - **Aggregates beyond `MIN`/`MAX`** (e.g. `SUM`, `AVG`) are not supported on encrypted values — they would require homomorphic encryption. Decrypt at the application boundary and perform those aggregates client-side. - **Parameter binding**: CipherStash Proxy rewrites bound parameters in `WHERE`, `JOIN`, and `RETURNING` clauses with `::JSONB::eql_v2_encrypted` casts so that the encrypted operator and any B-tree / GIN indexes are selected. Writing those casts yourself is only required when bypassing the proxy. diff --git a/src/encrypted_domain/functions.sql b/src/encrypted_domain/functions.sql index 24b75145..71a070a1 100644 --- a/src/encrypted_domain/functions.sql +++ b/src/encrypted_domain/functions.sql @@ -1,9 +1,9 @@ --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql --! @file encrypted_domain/functions.sql ---! @brief Shared blocker helper for the eql_v2_int4 domain family. +--! @brief Shared blocker helper for the eql_v3 encrypted-domain families. --! ---! Per-domain wrapper functions live in src/encrypted_domain/int4/. +--! Per-domain wrapper functions live in src/encrypted_domain//. --! Blockers in those files delegate to encrypted_domain_unsupported_bool --! so every domain raises a uniform domain-specific error rather than --! letting an unsupported operator fall through to native jsonb @@ -12,10 +12,10 @@ --! @brief Shared blocker helper. Raises 'operator X is not supported --! for TYPE' so unsupported domain operators surface a clear --! error rather than fall through to native jsonb behaviour. ---! @param type_name Domain type name (eql_v2_int4*) +--! @param type_name Domain type name (eql_v3.*) --! @param operator_name Operator symbol (=, <, @>, ->, etc.) --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.encrypted_domain_unsupported_bool(type_name text, operator_name text) +CREATE FUNCTION eql_v3.encrypted_domain_unsupported_bool(type_name text, operator_name text) RETURNS boolean IMMUTABLE PARALLEL SAFE SET search_path = pg_catalog, extensions, public diff --git a/src/lint/lints.sql b/src/lint/lints.sql index b378f1bb..bf38c1e6 100644 --- a/src/lint/lints.sql +++ b/src/lint/lints.sql @@ -1,4 +1,5 @@ -- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql --! @brief EQL lint: detect non-inlinable operator implementation functions --! @@ -92,7 +93,8 @@ AS $$ SELECT 1 FROM pg_type t WHERE t.oid IN (op.oprleft, op.oprright) AND (t.typname LIKE 'eql_v2%' - OR t.typnamespace = 'eql_v2'::regnamespace) + OR t.typnamespace = 'eql_v2'::regnamespace + OR t.typnamespace = 'eql_v3'::regnamespace) ) ), @@ -132,7 +134,7 @@ AS $$ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace JOIN pg_catalog.pg_language lang_l ON lang_l.oid = p.prolang - WHERE n.nspname = 'eql_v2' + WHERE n.nspname IN ('eql_v2', 'eql_v3') AND (p.prosrc LIKE '%encrypted_domain_unsupported_bool%' OR p.prosrc LIKE '%is not supported for%') AND EXISTS ( @@ -142,9 +144,11 @@ AS $$ JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype WHERE dt.typtype = 'd' - AND dn.nspname = 'public' - AND dt.typname LIKE 'eql_v2\_%' AND bt.typname = 'jsonb' + AND ( + dn.nspname = 'eql_v3' + OR (dn.nspname = 'public' AND dt.typname LIKE 'eql_v2\_%') + ) ) ) @@ -320,10 +324,15 @@ AS $$ JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype JOIN pg_catalog.pg_namespace bn ON bn.oid = bt.typnamespace WHERE dt.typtype = 'd' - AND dn.nspname = 'public' - AND dt.typname LIKE 'eql_v2\_%' + AND ( + dn.nspname = 'eql_v3' + OR (dn.nspname = 'public' AND dt.typname LIKE 'eql_v2\_%') + ) AND bt.typtype = 'd' - AND bt.typname LIKE 'eql_v2\_%' + AND ( + bn.nspname = 'eql_v3' + OR (bn.nspname = 'public' AND bt.typname LIKE 'eql_v2\_%') + ) -- ┌─────────────────────────────────────────────────────────────────┐ -- │ Domain opclass: an operator class declared FOR TYPE on an │ @@ -345,8 +354,10 @@ AS $$ JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace JOIN pg_catalog.pg_namespace cn ON cn.oid = oc.opcnamespace WHERE t.typtype = 'd' - AND tn.nspname = 'public' - AND t.typname LIKE 'eql_v2\_%' + AND ( + tn.nspname = 'eql_v3' + OR (tn.nspname = 'public' AND t.typname LIKE 'eql_v2\_%') + ) ORDER BY 1, 2, 3; $$; diff --git a/src/schema-v3.sql b/src/schema-v3.sql new file mode 100644 index 00000000..06df8d38 --- /dev/null +++ b/src/schema-v3.sql @@ -0,0 +1,22 @@ +--! @file schema-v3.sql +--! @brief EQL v3 schema creation +--! +--! Creates the eql_v3 schema, which houses the encrypted-domain type +--! families (eql_v3.int4 and future scalar domains): their domains, index-term +--! extractors, comparison wrappers, blockers, and aggregates. The core +--! index-term types these reuse (eql_v2.hmac_256, eql_v2.ore_block_u64_8_256) +--! remain in the eql_v2 schema and are referenced cross-schema. +--! +--! Drops existing schema if present to support clean reinstallation. +--! +--! @warning DROP SCHEMA CASCADE will remove all objects in the schema +--! @note eql_v3 is a new, additional schema for domain families; the eql_v2 +--! schema name is unchanged. + +--! @brief Drop existing EQL v3 schema +--! @warning CASCADE will drop all dependent objects +DROP SCHEMA IF EXISTS eql_v3 CASCADE; + +--! @brief Create EQL v3 schema +--! @note Houses the encrypted-domain type families +CREATE SCHEMA eql_v3; diff --git a/tasks/codegen/generate.py b/tasks/codegen/generate.py index cf30c598..03ac6be7 100644 --- a/tasks/codegen/generate.py +++ b/tasks/codegen/generate.py @@ -87,7 +87,7 @@ def render_types_file(spec: TypeSpec) -> str: """ blocks = [render_domain_block(domain, spec.token) for domain in spec.domains] return ( - "-- REQUIRE: src/schema.sql\n\n" + "-- REQUIRE: src/schema-v3.sql\n\n" f"--! @file encrypted_domain/{spec.token}/{spec.token}_types.sql\n" f"--! @brief Encrypted-domain type family for {spec.token}.\n\n" "DO $$\nBEGIN\n" @@ -99,6 +99,7 @@ def render_types_file(spec: TypeSpec) -> str: def _functions_requires(spec: TypeSpec, domain: DomainSpec) -> list[str]: reqs = [ "src/schema.sql", + "src/schema-v3.sql", _types_path(spec.token), "src/encrypted_domain/functions.sql", ] @@ -181,7 +182,7 @@ def render_operators_file(spec: TypeSpec, domain: DomainSpec) -> str: ) requires = ( - "-- REQUIRE: src/schema.sql\n" + "-- REQUIRE: src/schema-v3.sql\n" f"-- REQUIRE: {_types_path(spec.token)}\n" f"-- REQUIRE: src/encrypted_domain/{spec.token}/" f"{domain.name}_functions.sql\n" @@ -202,7 +203,7 @@ def render_aggregates_file(spec: TypeSpec, domain: DomainSpec) -> str | None: return None parts = [render_aggregate(domain, AGGREGATE_OPS[name]) for name in ("min", "max")] requires = ( - "-- REQUIRE: src/schema.sql\n" + "-- REQUIRE: src/schema-v3.sql\n" f"-- REQUIRE: {_types_path(spec.token)}\n" f"-- REQUIRE: src/encrypted_domain/{spec.token}/" f"{domain.name}_functions.sql\n" diff --git a/tasks/codegen/templates.py b/tasks/codegen/templates.py index 60833495..0c446fec 100644 --- a/tasks/codegen/templates.py +++ b/tasks/codegen/templates.py @@ -49,7 +49,8 @@ def _sql_str(s: str) -> str: Use this at every `'{...}'` interpolation boundary in the render_* helpers — payload keys, operator symbols, domain names rendered into - RAISE messages, etc. + RAISE messages, etc. NOT for schema-qualified identifiers like + ``eql_v3.foo``: those are emitted unquoted and must not be doubled. Today every catalog string (term keys, operator symbols) is quote-free, so this is a no-op on real input and output stays byte-identical. It @@ -58,6 +59,16 @@ def _sql_str(s: str) -> str: return s.replace("'", "''") +# Schema housing the encrypted-domain families: the domains themselves plus +# their index-term extractors, comparison wrappers, blockers, and aggregates. +# New in v3 and distinct from the core eql_v2 schema, which still owns the +# shared index-term types the extractors return and construct +# (eql_v2.hmac_256, eql_v2.ore_block_u64_8_256). +DOMAIN_SCHEMA = "eql_v3" +# Schema owning the core index-term types/constructors the extractors reuse. +CORE_SCHEMA = "eql_v2" + + def render_fixture_values_rs(spec: TypeSpec) -> str: """Body for tests/sqlx/src/fixtures/_values.rs. @@ -170,8 +181,8 @@ def brief_role_clause(domain: DomainSpec, token: str) -> str: def domain_name(domain: str) -> str: - """The public SQL domain type name.""" - return f"eql_v2_{domain}" + """The schema-qualified SQL domain type name, e.g. ``eql_v3.int4_eq``.""" + return f"{DOMAIN_SCHEMA}.{domain}" def _arg_label(dom: str, arg_type: str) -> str: @@ -203,10 +214,10 @@ def render_domain_block(domain: DomainSpec, token: str) -> str: f" --! @brief {phrase} encrypted {token} domain.{clause}\n" f" IF NOT EXISTS (\n" f" SELECT 1 FROM pg_type\n" - f" WHERE typname = '{_sql_str(dom)}' " - f"AND typnamespace = 'public'::regnamespace\n" + f" WHERE typname = '{_sql_str(domain.name)}' " + f"AND typnamespace = '{DOMAIN_SCHEMA}'::regnamespace\n" f" ) THEN\n" - f" CREATE DOMAIN public.{dom} AS jsonb\n" + f" CREATE DOMAIN {dom} AS jsonb\n" f" CHECK (\n" f" jsonb_typeof(VALUE) = 'object'\n" f" AND {checks}\n" @@ -224,18 +235,18 @@ def render_extractor(domain: DomainSpec, term: Term) -> str: f"--! @return {term.returns}\n" ) return doxy + ( - f"CREATE FUNCTION eql_v2.{term.extractor}(a {dom})\n" + f"CREATE FUNCTION {DOMAIN_SCHEMA}.{term.extractor}(a {dom})\n" f"RETURNS {term.returns}\n" f"LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" - f"AS $$ SELECT eql_v2.{term.ctor}(a::jsonb) $$;\n" + f"AS $$ SELECT {CORE_SCHEMA}.{term.ctor}(a::jsonb) $$;\n" ) def _extract_arg(arg_type: str, extractor: str, domain: str, arg: str) -> str: """The extractor-call SQL for one operand, casting jsonb to the domain first.""" if arg_type == "jsonb": - return f"eql_v2.{extractor}({arg}::{domain})" - return f"eql_v2.{extractor}({arg})" + return f"{DOMAIN_SCHEMA}.{extractor}({arg}::{domain})" + return f"{DOMAIN_SCHEMA}.{extractor}({arg})" def render_wrapper( @@ -254,7 +265,7 @@ def render_wrapper( f"--! @return boolean\n" ) return doxy + ( - f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, b {arg_b})\n" + f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, b {arg_b})\n" f"RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" f"AS $$ SELECT {call_a} {op} {call_b} $$;\n" ) @@ -276,9 +287,9 @@ def render_blocker_bool( f"--! @return boolean (never returns; always raises)\n" ) return doxy + ( - f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, b {arg_b})\n" + f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, b {arg_b})\n" f"RETURNS boolean IMMUTABLE PARALLEL SAFE\n" - f"AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool(" + f"AS $$ BEGIN RETURN {DOMAIN_SCHEMA}.encrypted_domain_unsupported_bool(" f"'{_sql_str(dom)}', '{_sql_str(op)}'); END; $$\n" f"LANGUAGE plpgsql;\n" ) @@ -301,7 +312,7 @@ def render_blocker_path( f"--! @return {returns} (never returns; always raises)\n" ) return doxy + ( - f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, selector {arg_b})\n" + f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, selector {arg_b})\n" f"RETURNS {returns} IMMUTABLE PARALLEL SAFE\n" f"AS $$ BEGIN RAISE EXCEPTION " f"'operator % is not supported for %', '{_sql_str(op)}', " @@ -328,7 +339,7 @@ def render_blocker_native( ) if returns == "boolean": body = ( - "BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool(" + f"BEGIN RETURN {DOMAIN_SCHEMA}.encrypted_domain_unsupported_bool(" f"'{_sql_str(dom)}', '{_sql_str(op)}'); END;" ) else: @@ -338,7 +349,7 @@ def render_blocker_native( f"'{_sql_str(dom)}'; END;" ) return doxy + ( - f"CREATE FUNCTION eql_v2.{backing}(a {arg_a}, b {arg_b})\n" + f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, b {arg_b})\n" f"RETURNS {returns} IMMUTABLE PARALLEL SAFE\n" f"AS $$ {body} $$\n" f"LANGUAGE plpgsql;\n" @@ -407,7 +418,7 @@ def render_aggregate(domain: DomainSpec, op: AggregateOp) -> str: "-- also work, but the procedural form mirrors the blocker convention.)\n" ) sfunc = sfunc_rationale + ( - f"CREATE FUNCTION eql_v2.{op.sfunc_name}(state {dom}, value {dom})\n" + f"CREATE FUNCTION {DOMAIN_SCHEMA}.{op.sfunc_name}(state {dom}, value {dom})\n" f"RETURNS {dom}\n" f"LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE\n" f"SET search_path = pg_catalog, extensions, public\n" @@ -442,10 +453,10 @@ def render_aggregate(domain: DomainSpec, op: AggregateOp) -> str: "-- combinefunc = sfunc: min/max are associative, so merging two partial\n" "-- extrema is the same comparison. PARALLEL SAFE enables partial and\n" "-- parallel aggregation on large GROUP BY workloads, with no decryption.\n" - f"CREATE AGGREGATE eql_v2.{op.name}({dom}) (\n" - f" sfunc = eql_v2.{op.sfunc_name},\n" + f"CREATE AGGREGATE {DOMAIN_SCHEMA}.{op.name}({dom}) (\n" + f" sfunc = {DOMAIN_SCHEMA}.{op.sfunc_name},\n" f" stype = {dom},\n" - f" combinefunc = eql_v2.{op.sfunc_name},\n" + f" combinefunc = {DOMAIN_SCHEMA}.{op.sfunc_name},\n" f" parallel = safe\n" f");\n" ) @@ -470,7 +481,7 @@ def render_operator( ) lines += [ f"CREATE OPERATOR {op} (", - f" FUNCTION = eql_v2.{backing},", + f" FUNCTION = {DOMAIN_SCHEMA}.{backing},", f" LEFTARG = {leftarg}, RIGHTARG = {rightarg}", ] if supported and meta.kind == "symmetric": diff --git a/tasks/codegen/test_generate.py b/tasks/codegen/test_generate.py index e92e2f2f..db93b989 100644 --- a/tasks/codegen/test_generate.py +++ b/tasks/codegen/test_generate.py @@ -54,10 +54,10 @@ def load(tmp_path): def test_types_file_has_all_four_domains(tmp_path): spec = load(tmp_path) sql = render_types_file(spec) - assert "-- REQUIRE: src/schema.sql" in sql - for dom in ("eql_v2_int4", "eql_v2_int4_eq", - "eql_v2_int4_ord", "eql_v2_int4_ord_ore"): - assert f"CREATE DOMAIN public.{dom} AS jsonb" in sql + assert "-- REQUIRE: src/schema-v3.sql" in sql + for dom in ("int4", "int4_eq", + "int4_ord", "int4_ord_ore"): + assert f"CREATE DOMAIN eql_v3.{dom} AS jsonb" in sql def test_storage_functions_file_is_all_blockers(tmp_path): @@ -75,7 +75,7 @@ def test_eq_functions_file_counts_and_extractor(tmp_path): eq = next(d for d in spec.domains if d.name == "int4_eq") sql = render_functions_file(spec, eq) assert sql.count("CREATE FUNCTION") == 45 - assert "CREATE FUNCTION eql_v2.eq_term(a eql_v2_int4_eq)" in sql + assert "CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq)" in sql assert "RETURNS eql_v2.hmac_256" in sql # 1 extractor + 6 wrappers (=, <> across 3 arg-shapes) inlined as SQL; # 38 blockers across the remaining native jsonb surface as plpgsql. @@ -89,7 +89,7 @@ def test_ore_functions_file_counts_and_extractor(tmp_path): ordered = next(d for d in spec.domains if d.name == "int4_ord") sql = render_functions_file(spec, ordered) assert sql.count("CREATE FUNCTION") == 45 - assert "CREATE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord)" in sql + assert "CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord)" in sql assert "RETURNS eql_v2.ore_block_u64_8_256" in sql # 1 extractor + 18 wrappers (=, <>, <, <=, >, >= across 3 shapes); # 26 blockers across containment/path/native-jsonb fallback ops. @@ -343,8 +343,8 @@ def test_render_aggregates_file_carries_both_min_and_max(tmp_path): assert sql is not None assert sql.count("CREATE FUNCTION") == 2 assert sql.count("CREATE AGGREGATE") == 2 - assert "eql_v2.min_sfunc" in sql - assert "eql_v2.max_sfunc" in sql + assert "eql_v3.min_sfunc" in sql + assert "eql_v3.max_sfunc" in sql # REQUIRE edges: types + functions + operators must all be declared. assert "-- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql" in sql assert "-- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql" in sql diff --git a/tasks/codegen/test_templates.py b/tasks/codegen/test_templates.py index ba1e3b7d..7f30e4cb 100644 --- a/tasks/codegen/test_templates.py +++ b/tasks/codegen/test_templates.py @@ -72,7 +72,7 @@ def test_render_fixture_values_rs_preserves_manifest_order(): def test_domain_block_storage_uses_fixed_envelope_only(): domain = DomainSpec(name="int4", terms=[]) sql = render_domain_block(domain, "int4") - assert "CREATE DOMAIN public.eql_v2_int4 AS jsonb" in sql + assert "CREATE DOMAIN eql_v3.int4 AS jsonb" in sql assert "VALUE ? 'v'" in sql assert "VALUE ? 'i'" in sql assert "VALUE ? 'c'" in sql @@ -83,7 +83,7 @@ def test_domain_block_storage_uses_fixed_envelope_only(): def test_domain_block_uses_catalog_json_keys(): domain = DomainSpec(name="int4_ord", terms=["ore"]) sql = render_domain_block(domain, "int4") - assert "CREATE DOMAIN public.eql_v2_int4_ord AS jsonb" in sql + assert "CREATE DOMAIN eql_v3.int4_ord AS jsonb" in sql assert "VALUE ? 'ob'" in sql assert "VALUE ? 'ore'" not in sql @@ -109,7 +109,7 @@ def test_domain_block_check_pins_envelope_version(): def test_extractor_is_catalog_derived_and_inlinable(): domain = DomainSpec(name="int4_eq", terms=["hm"]) sql = render_extractor(domain, TERM_CATALOG["hm"]) - assert "CREATE FUNCTION eql_v2.eq_term(a eql_v2_int4_eq)" in sql + assert "CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq)" in sql assert "RETURNS eql_v2.hmac_256" in sql assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" in sql assert "SELECT eql_v2.hmac_256(a::jsonb)" in sql @@ -121,12 +121,12 @@ def test_wrapper_uses_term_extractor_for_supported_operator(): sql = render_wrapper( domain, op="<", - arg_a="eql_v2_int4_ord", + arg_a="eql_v3.int4_ord", arg_b="jsonb", extractor="ord_term", ) - assert "CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b jsonb)" in sql - assert "SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b::eql_v2_int4_ord)" in sql + assert "CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord, b jsonb)" in sql + assert "SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b::eql_v3.int4_ord)" in sql def test_wrapper_is_inlinable_sql(): @@ -135,8 +135,8 @@ def test_wrapper_is_inlinable_sql(): sql = render_wrapper( domain, op="=", - arg_a="eql_v2_int4_eq", - arg_b="eql_v2_int4_eq", + arg_a="eql_v3.int4_eq", + arg_b="eql_v3.int4_eq", extractor="eq_term", ) assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" in sql @@ -161,10 +161,10 @@ def test_blocker_bool_is_not_strict(): attribute line so any future refactor that re-adds STRICT fails loudly.""" domain = DomainSpec(name="int4", terms=[]) sql = render_blocker_bool( - domain, op="<", arg_a="eql_v2_int4", arg_b="eql_v2_int4", + domain, op="<", arg_a="eql_v3.int4", arg_b="eql_v3.int4", ) - assert "CREATE FUNCTION eql_v2.lt(a eql_v2_int4, b eql_v2_int4)" in sql - assert "encrypted_domain_unsupported_bool('eql_v2_int4', '<')" in sql + assert "CREATE FUNCTION eql_v3.lt(a eql_v3.int4, b eql_v3.int4)" in sql + assert "encrypted_domain_unsupported_bool('eql_v3.int4', '<')" in sql assert "RETURNS boolean IMMUTABLE PARALLEL SAFE\n" in sql assert "LANGUAGE plpgsql" in sql assert "STRICT" not in sql @@ -174,9 +174,9 @@ def test_blocker_path_is_not_strict(): """Mirror of test_blocker_bool_is_not_strict for path blockers.""" domain = DomainSpec(name="int4", terms=[]) sql = render_blocker_path( - domain, op="->", arg_a="eql_v2_int4", arg_b="text", + domain, op="->", arg_a="eql_v3.int4", arg_b="text", ) - assert "RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE\n" in sql + assert "RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE\n" in sql assert "LANGUAGE plpgsql" in sql assert "STRICT" not in sql @@ -184,12 +184,12 @@ def test_blocker_path_is_not_strict(): def test_blocker_path_returns_domain_or_text(): domain = DomainSpec(name="int4", terms=[]) arrow = render_blocker_path( - domain, op="->", arg_a="eql_v2_int4", arg_b="text", + domain, op="->", arg_a="eql_v3.int4", arg_b="text", ) - assert 'CREATE FUNCTION eql_v2."->"(a eql_v2_int4, selector text)' in arrow - assert "RETURNS eql_v2_int4" in arrow + assert 'CREATE FUNCTION eql_v3."->"(a eql_v3.int4, selector text)' in arrow + assert "RETURNS eql_v3.int4" in arrow arrow2 = render_blocker_path( - domain, op="->>", arg_a="eql_v2_int4", arg_b="text", + domain, op="->>", arg_a="eql_v3.int4", arg_b="text", ) assert "RETURNS text" in arrow2 @@ -199,19 +199,19 @@ def test_blocker_path_for_jsonb_left_arg_returns_domain(): return type for `->` (only `->>` returns text).""" domain = DomainSpec(name="int4", terms=[]) sql = render_blocker_path( - domain, op="->", arg_a="jsonb", arg_b="eql_v2_int4", + domain, op="->", arg_a="jsonb", arg_b="eql_v3.int4", ) - assert 'CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4)' in sql - assert "RETURNS eql_v2_int4" in sql + assert 'CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4)' in sql + assert "RETURNS eql_v3.int4" in sql def test_blocker_native_bool_uses_helper_and_is_not_strict(): domain = DomainSpec(name="int4", terms=[]) sql = render_blocker_native( - domain, op="?", arg_a="eql_v2_int4", arg_b="text", returns="boolean", + domain, op="?", arg_a="eql_v3.int4", arg_b="text", returns="boolean", ) - assert 'CREATE FUNCTION eql_v2."?"(a eql_v2_int4, b text)' in sql - assert "encrypted_domain_unsupported_bool('eql_v2_int4', '?')" in sql + assert 'CREATE FUNCTION eql_v3."?"(a eql_v3.int4, b text)' in sql + assert "encrypted_domain_unsupported_bool('eql_v3.int4', '?')" in sql assert "RETURNS boolean IMMUTABLE PARALLEL SAFE\n" in sql assert "LANGUAGE plpgsql" in sql assert "STRICT" not in sql @@ -220,11 +220,11 @@ def test_blocker_native_bool_uses_helper_and_is_not_strict(): def test_blocker_native_jsonb_result_raises_and_is_not_strict(): domain = DomainSpec(name="int4", terms=[]) sql = render_blocker_native( - domain, op="#>", arg_a="eql_v2_int4", arg_b="text[]", returns="jsonb", + domain, op="#>", arg_a="eql_v3.int4", arg_b="text[]", returns="jsonb", ) - assert 'CREATE FUNCTION eql_v2."#>"(a eql_v2_int4, b text[])' in sql + assert 'CREATE FUNCTION eql_v3."#>"(a eql_v3.int4, b text[])' in sql assert "RETURNS jsonb IMMUTABLE PARALLEL SAFE\n" in sql - assert "RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4'" in sql + assert "RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4'" in sql assert "LANGUAGE plpgsql" in sql assert "STRICT" not in sql @@ -232,9 +232,9 @@ def test_blocker_native_jsonb_result_raises_and_is_not_strict(): def test_blocker_native_text_result_raises_and_is_not_strict(): domain = DomainSpec(name="int4", terms=[]) sql = render_blocker_native( - domain, op="#>>", arg_a="eql_v2_int4", arg_b="text[]", returns="text", + domain, op="#>>", arg_a="eql_v3.int4", arg_b="text[]", returns="text", ) - assert 'CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4, b text[])' in sql + assert 'CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4, b text[])' in sql assert "RETURNS text IMMUTABLE PARALLEL SAFE\n" in sql assert "LANGUAGE plpgsql" in sql assert "STRICT" not in sql @@ -243,21 +243,21 @@ def test_blocker_native_text_result_raises_and_is_not_strict(): def test_blocker_native_concat_cross_shape(): domain = DomainSpec(name="int4", terms=[]) sql = render_blocker_native( - domain, op="||", arg_a="jsonb", arg_b="eql_v2_int4", returns="jsonb", + domain, op="||", arg_a="jsonb", arg_b="eql_v3.int4", returns="jsonb", ) - assert 'CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4)' in sql + assert 'CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4)' in sql assert "RETURNS jsonb" in sql def test_operator_symmetric_metadata(): sql = render_operator( op="=", backing="eq", - leftarg="eql_v2_int4_eq", rightarg="eql_v2_int4_eq", + leftarg="eql_v3.int4_eq", rightarg="eql_v3.int4_eq", supported=True, ) assert "CREATE OPERATOR = (" in sql - assert "FUNCTION = eql_v2.eq" in sql - assert "LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq" in sql + assert "FUNCTION = eql_v3.eq" in sql + assert "LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq" in sql assert "NEGATOR = <>" in sql assert "RESTRICT = eqsel" in sql @@ -267,12 +267,12 @@ def test_render_operator_unsupported_emits_only_function_and_args(): (those would lie about selectivity for a function that always raises).""" sql = render_operator( op="=", backing="eq", - leftarg="eql_v2_int4", rightarg="eql_v2_int4", + leftarg="eql_v3.int4", rightarg="eql_v3.int4", supported=False, ) assert "CREATE OPERATOR = (" in sql - assert "FUNCTION = eql_v2.eq" in sql - assert "LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4" in sql + assert "FUNCTION = eql_v3.eq" in sql + assert "LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4" in sql assert "NEGATOR" not in sql assert "RESTRICT" not in sql assert "JOIN" not in sql @@ -283,23 +283,23 @@ def test_render_aggregate_min_int4_ord_emits_state_function_and_aggregate(): """Pin the rendered shape for the canonical (int4_ord, min) case.""" domain = DomainSpec(name="int4_ord", terms=["ore"]) sql = render_aggregate(domain, AGGREGATE_OPS["min"]) - assert "CREATE FUNCTION eql_v2.min_sfunc(state eql_v2_int4_ord, value eql_v2_int4_ord)" in sql - assert "RETURNS eql_v2_int4_ord" in sql + assert "CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.int4_ord, value eql_v3.int4_ord)" in sql + assert "RETURNS eql_v3.int4_ord" in sql assert "LANGUAGE plpgsql IMMUTABLE STRICT" in sql assert "SET search_path = pg_catalog, extensions, public" in sql assert "IF value < state THEN" in sql - assert "CREATE AGGREGATE eql_v2.min(eql_v2_int4_ord) (" in sql - assert "sfunc = eql_v2.min_sfunc" in sql - assert "stype = eql_v2_int4_ord" in sql + assert "CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord) (" in sql + assert "sfunc = eql_v3.min_sfunc" in sql + assert "stype = eql_v3.int4_ord" in sql def test_render_aggregate_max_uses_greater_than_comparator(): """Symmetric pin: max uses `>` not `<`.""" domain = DomainSpec(name="int4_ord_ore", terms=["ore"]) sql = render_aggregate(domain, AGGREGATE_OPS["max"]) - assert "CREATE FUNCTION eql_v2.max_sfunc(state eql_v2_int4_ord_ore, value eql_v2_int4_ord_ore)" in sql + assert "CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.int4_ord_ore, value eql_v3.int4_ord_ore)" in sql assert "IF value > state THEN" in sql - assert "CREATE AGGREGATE eql_v2.max(eql_v2_int4_ord_ore) (" in sql + assert "CREATE AGGREGATE eql_v3.max(eql_v3.int4_ord_ore) (" in sql def test_render_aggregate_state_function_is_not_inlinable(): @@ -326,11 +326,11 @@ def test_render_operator_for_containment_omits_commutator(): must still omit those clauses.""" sql = render_operator( op="@>", backing="contains", - leftarg="eql_v2_int4_ord", rightarg="eql_v2_int4_ord", + leftarg="eql_v3.int4_ord", rightarg="eql_v3.int4_ord", supported=True, ) assert "CREATE OPERATOR @> (" in sql - assert "FUNCTION = eql_v2.contains" in sql + assert "FUNCTION = eql_v3.contains" in sql assert "COMMUTATOR" not in sql assert "NEGATOR" not in sql assert "RESTRICT" not in sql @@ -347,7 +347,7 @@ def test_render_operator_unsupported_emits_placeholder_comment(): domain.""" sql = render_operator( op="<", backing="lt", - leftarg="eql_v2_int4_eq", rightarg="eql_v2_int4_eq", + leftarg="eql_v3.int4_eq", rightarg="eql_v3.int4_eq", supported=False, ) assert sql.startswith("-- Placeholder:") @@ -361,7 +361,7 @@ def test_render_operator_supported_has_no_placeholder_comment(): """Supported operators route to real wrappers — no placeholder comment.""" sql = render_operator( op="=", backing="eq", - leftarg="eql_v2_int4_eq", rightarg="eql_v2_int4_eq", + leftarg="eql_v3.int4_eq", rightarg="eql_v3.int4_eq", supported=True, ) assert "Placeholder" not in sql @@ -380,7 +380,7 @@ def test_render_aggregate_state_function_emits_plpgsql_rationale_comment(): assert "not index" in sql # The rationale precedes the state-function definition. assert sql.index("-- LANGUAGE plpgsql, not sql:") < sql.index( - "CREATE FUNCTION eql_v2.min_sfunc" + "CREATE FUNCTION eql_v3.min_sfunc" ) @@ -396,8 +396,8 @@ def test_render_aggregate_enables_parallel_and_combinefunc(): assert "LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE" in sql # ...and the aggregate must declare the combinefunc + parallel safety # inside the CREATE AGGREGATE option list (not merely in prose). - aggregate_body = sql[sql.index(f"CREATE AGGREGATE eql_v2.{op_name}"):] - assert f"combinefunc = eql_v2.{sfunc}" in aggregate_body + aggregate_body = sql[sql.index(f"CREATE AGGREGATE eql_v3.{op_name}"):] + assert f"combinefunc = eql_v3.{sfunc}" in aggregate_body assert "parallel = safe" in aggregate_body # The stale "intentionally disabled" omission note must be gone. assert "intentionally disabled" not in sql @@ -479,13 +479,13 @@ def test_blocker_escapes_quote_bearing_domain_in_rendered_sql(): string literals.)""" domain = DomainSpec(name="o'dom", terms=[]) sql = render_blocker_bool( - domain, op="<", arg_a="eql_v2_o'dom", arg_b="eql_v2_o'dom", + domain, op="<", arg_a="eql_v3.o'dom", arg_b="eql_v3.o'dom", ) # The dom flows into encrypted_domain_unsupported_bool('', '') # as a single-quoted literal — the quote must be doubled. - assert "encrypted_domain_unsupported_bool('eql_v2_o''dom', '<')" in sql + assert "encrypted_domain_unsupported_bool('eql_v3.o''dom', '<')" in sql # The raw, unescaped single-quoted form must not appear. - assert "'eql_v2_o'dom'" not in sql + assert "'eql_v3.o'dom'" not in sql def test_domain_block_escapes_quote_bearing_key_in_check(): @@ -495,5 +495,5 @@ def test_domain_block_escapes_quote_bearing_key_in_check(): # literal escaping in the IF NOT EXISTS guard. quoted = DomainSpec(name="we'ird", terms=[]) sql = render_domain_block(quoted, "int4") - assert "typname = 'eql_v2_we''ird'" in sql - assert "typname = 'eql_v2_we'ird'" not in sql + assert "typname = 'we''ird'" in sql + assert "typname = 'we'ird'" not in sql diff --git a/tasks/pin_search_path.sql b/tasks/pin_search_path.sql index 168a9478..774be740 100644 --- a/tasks/pin_search_path.sql +++ b/tasks/pin_search_path.sql @@ -250,7 +250,7 @@ BEGIN SELECT p.oid FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - WHERE n.nspname = 'eql_v2' + WHERE n.nspname IN ('eql_v2', 'eql_v3') -- Only normal functions ('f') and window functions ('w') accept -- ALTER FUNCTION ... SET. Aggregates ('a') would be rejected by -- ALTER ROUTINE/FUNCTION, and procedures ('p') would need ALTER @@ -266,13 +266,16 @@ BEGIN -- A new encrypted-domain type needs NO edit here: its inline-critical -- extractors and comparison wrappers are recognised by the identity -- predicate — LANGUAGE sql, IMMUTABLE, and taking at least one argument - -- typed as a jsonb-backed DOMAIN in `public` named `eql_v2_*`. The + -- typed as a jsonb-backed DOMAIN of the encrypted-domain families. The + -- families live in the `eql_v3` schema (e.g. `eql_v3.int4_eq`); the + -- legacy `public.eql_v2_*` form is kept for any pre-v3 domain. The -- predicate is proconfig-independent: the outer loop has already -- excluded any function with a pinned `search_path`, so the only -- functions reaching here are unpinned. This catches no core function: -- `eql_v2_encrypted` is a composite type (not a domain), `ste_vec_entry` - -- is a domain in `eql_v2` (not `public`), and `hmac_256` is a domain - -- over `text` (not `jsonb`). + -- is a domain in `eql_v2` (not `eql_v3`/`public`), and `hmac_256` is a + -- domain over `text` (not `jsonb`). The eql_v3 blockers are plpgsql, so + -- the LANGUAGE-sql guard leaves them to be pinned as intended. AND NOT ( p.prolang = (SELECT l.oid FROM pg_catalog.pg_language l WHERE l.lanname = 'sql') @@ -283,9 +286,11 @@ BEGIN JOIN pg_catalog.pg_type dt ON dt.oid = arg.typ JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace WHERE dt.typtype = 'd' - AND dn.nspname = 'public' - AND dt.typname LIKE 'eql_v2\_%' AND dt.typbasetype = jsonb_oid + AND ( + dn.nspname = 'eql_v3' + OR (dn.nspname = 'public' AND dt.typname LIKE 'eql_v2\_%') + ) ) ) -- Encrypted-domain family — comment-marker fallback. Covers a diff --git a/tasks/test/splinter.sh b/tasks/test/splinter.sh index 6c203233..a01de51a 100755 --- a/tasks/test/splinter.sh +++ b/tasks/test/splinter.sh @@ -10,7 +10,7 @@ set -euo pipefail # Scope: only findings in EQL-owned schemas are gated. -EQL_OWNED_SCHEMAS="('eql_v2')" +EQL_OWNED_SCHEMAS="('eql_v2', 'eql_v3')" # Pinned to splinter main as of 2026-04-27. Bump intentionally. SPLINTER_SHA="55db5b1f28e58d816f7d9136eed87eabcd95868d" @@ -84,12 +84,12 @@ function_search_path_mutable eql_v2 jsonb_contained_by function GIN-inlining: sa function_search_path_mutable eql_v2 ore_cllw function Consolidated ORE-CLLW extractor (U-006): inlinable SQL so the planner can fold `eql_v2.ore_cllw(col -> 'sel')` calls into the calling query. SET search_path would silently undo the inlining and prevent functional-index match through the extractor form. Two overloads: (jsonb), (eql_v2.ste_vec_entry). function_search_path_mutable eql_v2 has_ore_cllw function Consolidated ORE-CLLW presence check (U-006): inlinable SQL counterpart to `eql_v2.ore_cllw`. Same rationale as `ore_cllw` — must stay unpinned to inline into the calling query. Two overloads: (jsonb), (eql_v2.ste_vec_entry). function_search_path_mutable eql_v2 selector function STE-vec entry selector extractor (#219): typed (eql_v2.ste_vec_entry) overload, inlinable so the planner can fold `eql_v2.selector(col -> 'sel')` into the calling query. -function_search_path_mutable eql_v2 eq function Equality backing function for `eql_v2.ste_vec_entry × eql_v2.ste_vec_entry` (#219). Inlines to `hmac_256(a) = hmac_256(b)`; the `=` operator must reach the functional hash index on `eql_v2.hmac_256(col -> 'sel')` for bare-form field equality to engage Index Scan. Splinter matches by name only, so this row also covers the converged eql_v2.eq wrappers on eql_v2_int4_eq / _ord / _ord_ore (PR #225). -function_search_path_mutable eql_v2 neq function Inequality backing function for `eql_v2.ste_vec_entry`. Same rationale as `eq`. Also covers the converged eql_v2.neq wrappers on eql_v2_int4_eq / _ord / _ord_ore (PR #225). -function_search_path_mutable eql_v2 lt function Less-than backing function for `eql_v2.ste_vec_entry`. Inlines to `ore_cllw(a) < ore_cllw(b)`; must reach the functional btree opclass on `eql_v2.ore_cllw` for ordered field queries to engage Index Scan. Splinter matches by name only, so this row also covers the converged eql_v2.lt wrappers on eql_v2_int4_ord / _ord_ore (PR #225). -function_search_path_mutable eql_v2 lte function Less-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. Also covers the converged eql_v2.lte wrappers on eql_v2_int4_ord / _ord_ore (PR #225). -function_search_path_mutable eql_v2 gt function Greater-than backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. Also covers the converged eql_v2.gt wrappers on eql_v2_int4_ord / _ord_ore (PR #225). -function_search_path_mutable eql_v2 gte function Greater-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. Also covers the converged eql_v2.gte wrappers on eql_v2_int4_ord / _ord_ore (PR #225). +function_search_path_mutable eql_v2 eq function Equality backing function for `eql_v2.ste_vec_entry × eql_v2.ste_vec_entry` (#219). Inlines to `hmac_256(a) = hmac_256(b)`; the `=` operator must reach the functional hash index on `eql_v2.hmac_256(col -> 'sel')` for bare-form field equality to engage Index Scan. (The converged int4 wrappers moved to the eql_v3 schema — see the eql_v3 rows below.) +function_search_path_mutable eql_v2 neq function Inequality backing function for `eql_v2.ste_vec_entry`. Same rationale as `eq`. +function_search_path_mutable eql_v2 lt function Less-than backing function for `eql_v2.ste_vec_entry`. Inlines to `ore_cllw(a) < ore_cllw(b)`; must reach the functional btree opclass on `eql_v2.ore_cllw` for ordered field queries to engage Index Scan. +function_search_path_mutable eql_v2 lte function Less-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. +function_search_path_mutable eql_v2 gt function Greater-than backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. +function_search_path_mutable eql_v2 gte function Greater-than-or-equal backing function for `eql_v2.ste_vec_entry`. Same rationale as `lt`. function_search_path_mutable eql_v2 ore_cllw_eq function Inner comparator for the `eql_v2.ore_cllw` type's `=` operator (#221). The outer same-type operators back the btree opclass on `eql_v2.ore_cllw`; the planner only carries the inlined form through to functional-index match if this inner function is also inlinable (no SET, IMMUTABLE). Mirrors ore_block_u64_8_256_eq. function_search_path_mutable eql_v2 ore_cllw_neq function Inner comparator for the `eql_v2.ore_cllw` type's `<>` operator (#221). Same rationale as `ore_cllw_eq`. function_search_path_mutable eql_v2 ore_cllw_lt function Inner comparator for the `eql_v2.ore_cllw` type's `<` operator (#221). Same rationale as `ore_cllw_eq`. @@ -97,11 +97,26 @@ function_search_path_mutable eql_v2 ore_cllw_lte function Inner comparator for t function_search_path_mutable eql_v2 ore_cllw_gt function Inner comparator for the `eql_v2.ore_cllw` type's `>` operator (#221). Same rationale as `ore_cllw_eq`. function_search_path_mutable eql_v2 ore_cllw_gte function Inner comparator for the `eql_v2.ore_cllw` type's `>=` operator (#221). Same rationale as `ore_cllw_eq`. function_search_path_mutable eql_v2 -> function Typed sv-element selector lookup (U-007): inlinable SQL so the planner can fold `col -> ''` into the calling query, preserving functional-index match for the chained recipes `WHERE col -> 'sel' = $1::ste_vec_entry` (via eq_term) and `ORDER BY eql_v2.ore_cllw(col -> 'sel')`. Three overloads: (enc, text), (enc, enc), (enc, int). -function_search_path_mutable eql_v2 eq_term function XOR-aware equality term extractor on a ste_vec entry (U-007): coalesces hm and oc as bytea. Must inline so `eql_v2.eq_term(col -> 'sel')` folds into the calling query and matches a functional hash index built on the same expression — same precedent as ore_cllw / hmac_256 extractors on ste_vec_entry. Also covers the eql_v2_int4_eq eq_term overload (PR #225). +function_search_path_mutable eql_v2 eq_term function XOR-aware equality term extractor on a ste_vec entry (U-007): coalesces hm and oc as bytea. Must inline so `eql_v2.eq_term(col -> 'sel')` folds into the calling query and matches a functional hash index built on the same expression — same precedent as ore_cllw / hmac_256 extractors on ste_vec_entry. (The eql_v3.int4_eq eq_term extractor is a separate overload in the eql_v3 schema — see the eql_v3 rows below.) function_search_path_mutable eql_v2 min function Aggregate (splinter labels these type=function): ALTER AGGREGATE has no SET configuration_parameter syntax, and ALTER ROUTINE/FUNCTION reject aggregates. The aggregate's SFUNC has a pinned search_path. function_search_path_mutable eql_v2 max function Aggregate: same as min. function_search_path_mutable eql_v2 grouped_value function Aggregate: same as min. -function_search_path_mutable eql_v2 ord_term function eql_v2_int4 ordered-variant index extractor: returns eql_v2.ore_block_u64_8_256 (carrying main DEFAULT btree opclass). Used inside the inlinable comparison wrappers and as the functional-index expression USING btree (eql_v2.ord_term(col)); must inline. SET search_path would disable SQL function inlining (see PostgreSQL inline_function). Covers both ord_term overloads (eql_v2_int4_ord_ore, eql_v2_int4_ord). +# Encrypted-domain families live in the eql_v3 schema (the int4 family and +# future scalar domains). Their inlinable extractors and comparison wrappers +# must stay unpinned for functional-index matching, exactly as the eql_v2 +# encrypted-type operators above; splinter matches by (schema, name, type), so +# they need their own rows. The plpgsql blockers are pinned by +# tasks/pin_search_path.sql and do not surface here. +function_search_path_mutable eql_v3 eq_term function HMAC equality term extractor for the eql_v3 *_eq domains: returns eql_v2.hmac_256. Must inline so `eql_v3.eq_term(col)` folds into the calling query and matches the functional hash/btree index built on the same expression. SET search_path would disable SQL function inlining (see PostgreSQL inline_function). +function_search_path_mutable eql_v3 ord_term function ORE-block order term extractor for the eql_v3 ordered domains: returns eql_v2.ore_block_u64_8_256 (carrying the main DEFAULT btree opclass). Used inside the inlinable comparison wrappers and as the functional-index expression USING btree (eql_v3.ord_term(col)); must inline. Covers both ord_term overloads (eql_v3.int4_ord, eql_v3.int4_ord_ore). +function_search_path_mutable eql_v3 eq function Equality comparison wrapper on the eql_v3 domains. Inlines to `eq_term(a) = eq_term(b)`; must reach the functional index on eql_v3.eq_term(col) for bare-form equality to engage Index Scan. Covers the converged eq wrappers on the eql_v3 int4 variants. +function_search_path_mutable eql_v3 neq function Inequality comparison wrapper on the eql_v3 domains. Same rationale as eql_v3.eq. +function_search_path_mutable eql_v3 lt function Less-than comparison wrapper on the eql_v3 ordered domains. Inlines to `ord_term(a) < ord_term(b)`; must reach the functional btree index on eql_v3.ord_term(col) for range queries to engage Index Scan. +function_search_path_mutable eql_v3 lte function Less-than-or-equal comparison wrapper on the eql_v3 ordered domains. Same rationale as eql_v3.lt. +function_search_path_mutable eql_v3 gt function Greater-than comparison wrapper on the eql_v3 ordered domains. Same rationale as eql_v3.lt. +function_search_path_mutable eql_v3 gte function Greater-than-or-equal comparison wrapper on the eql_v3 ordered domains. Same rationale as eql_v3.lt. +function_search_path_mutable eql_v3 min function Per-domain MIN aggregate on the eql_v3 ordered domains (splinter labels aggregates type=function): ALTER AGGREGATE has no SET configuration_parameter syntax, and ALTER ROUTINE/FUNCTION reject aggregates. The aggregate's SFUNC carries a pinned search_path. +function_search_path_mutable eql_v3 max function Per-domain MAX aggregate on the eql_v3 ordered domains. Same as eql_v3.min. ALLOW # Wrap splinter (a single bare SELECT expression) into a subquery we can diff --git a/tasks/uninstall-protect.sql b/tasks/uninstall-protect.sql index 83fddc7d..eb48602e 100644 --- a/tasks/uninstall-protect.sql +++ b/tasks/uninstall-protect.sql @@ -1 +1,2 @@ DROP SCHEMA IF EXISTS eql_v2 CASCADE; +DROP SCHEMA IF EXISTS eql_v3 CASCADE; diff --git a/tasks/uninstall.sql b/tasks/uninstall.sql index d7777870..e087e2a8 100644 --- a/tasks/uninstall.sql +++ b/tasks/uninstall.sql @@ -10,3 +10,8 @@ END $$; DROP SCHEMA IF EXISTS eql_v2 CASCADE; + +-- Encrypted-domain families (eql_v3.int4 and future scalar domains) live in +-- their own schema; drop it too. CASCADE removes the domains and any columns +-- typed with them. +DROP SCHEMA IF EXISTS eql_v3 CASCADE; diff --git a/tests/codegen/reference/int4/int4_eq_functions.sql b/tests/codegen/reference/int4/int4_eq_functions.sql index f1fe0d70..14344ba2 100644 --- a/tests/codegen/reference/int4/int4_eq_functions.sql +++ b/tests/codegen/reference/int4/int4_eq_functions.sql @@ -1,5 +1,6 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md -- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/functions.sql -- REQUIRE: src/hmac_256/functions.sql @@ -7,400 +8,400 @@ --! @file encrypted_domain/int4/int4_eq_functions.sql --! @brief Equality-only domain of the int4 encrypted-domain family — comparison/path functions. ---! @brief Index extractor for the eql_v2_int4_eq variant. ---! @param a eql_v2_int4_eq +--! @brief Index extractor for the eql_v3.int4_eq variant. +--! @param a eql_v3.int4_eq --! @return eql_v2.hmac_256 -CREATE FUNCTION eql_v2.eq_term(a eql_v2_int4_eq) +CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq) RETURNS eql_v2.hmac_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v2.hmac_256(a::jsonb) $$; ---! @brief Equality wrapper for eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Equality wrapper for eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean -CREATE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.eq_term(a) = eql_v2.eq_term(b) $$; +AS $$ SELECT eql_v3.eq_term(a) = eql_v3.eq_term(b) $$; ---! @brief Equality wrapper for eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Equality wrapper for eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4_eq, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.eq_term(a) = eql_v2.eq_term(b::eql_v2_int4_eq) $$; +AS $$ SELECT eql_v3.eq_term(a) = eql_v3.eq_term(b::eql_v3.int4_eq) $$; ---! @brief Equality wrapper for eql_v2_int4_eq (jsonb, domain). +--! @brief Equality wrapper for eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean -CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.eq_term(a::eql_v2_int4_eq) = eql_v2.eq_term(b) $$; +AS $$ SELECT eql_v3.eq_term(a::eql_v3.int4_eq) = eql_v3.eq_term(b) $$; ---! @brief Inequality wrapper for eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Inequality wrapper for eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean -CREATE FUNCTION eql_v2.neq(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.eq_term(a) <> eql_v2.eq_term(b) $$; +AS $$ SELECT eql_v3.eq_term(a) <> eql_v3.eq_term(b) $$; ---! @brief Inequality wrapper for eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Inequality wrapper for eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.neq(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4_eq, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.eq_term(a) <> eql_v2.eq_term(b::eql_v2_int4_eq) $$; +AS $$ SELECT eql_v3.eq_term(a) <> eql_v3.eq_term(b::eql_v3.int4_eq) $$; ---! @brief Inequality wrapper for eql_v2_int4_eq (jsonb, domain). +--! @brief Inequality wrapper for eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean -CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.eq_term(a::eql_v2_int4_eq) <> eql_v2.eq_term(b) $$; +AS $$ SELECT eql_v3.eq_term(a::eql_v3.int4_eq) <> eql_v3.eq_term(b) $$; ---! @brief Blocker for < on eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Blocker for < on eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lt(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Blocker for < on eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lt(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for < on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Blocker for <= on eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lte(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Blocker for <= on eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lte(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for <= on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Blocker for > on eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gt(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Blocker for > on eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gt(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for > on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Blocker for >= on eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gte(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Blocker for >= on eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gte(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for >= on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '>='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Blocker for @> on eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Blocker for @> on eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for @> on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Blocker for <@ on eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Blocker for <@ on eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for <@ on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_eq (domain, text). ---! @param a eql_v2_int4_eq +--! @brief Blocker for -> on eql_v3.int4_eq (domain, text). +--! @param a eql_v3.int4_eq --! @param selector text ---! @return eql_v2_int4_eq (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4_eq, selector text) -RETURNS eql_v2_int4_eq IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_eq'; END; $$ +--! @return eql_v3.int4_eq (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4_eq, selector text) +RETURNS eql_v3.int4_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_eq (domain, integer). ---! @param a eql_v2_int4_eq +--! @brief Blocker for -> on eql_v3.int4_eq (domain, integer). +--! @param a eql_v3.int4_eq --! @param selector integer ---! @return eql_v2_int4_eq (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4_eq, selector integer) -RETURNS eql_v2_int4_eq IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_eq'; END; $$ +--! @return eql_v3.int4_eq (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4_eq, selector integer) +RETURNS eql_v3.int4_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for -> on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4_eq ---! @return eql_v2_int4_eq (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4_eq) -RETURNS eql_v2_int4_eq IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_eq'; END; $$ +--! @param selector eql_v3.int4_eq +--! @return eql_v3.int4_eq (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4_eq) +RETURNS eql_v3.int4_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_eq (domain, text). ---! @param a eql_v2_int4_eq +--! @brief Blocker for ->> on eql_v3.int4_eq (domain, text). +--! @param a eql_v3.int4_eq --! @param selector text --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_eq, selector text) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_eq, selector text) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_eq (domain, integer). ---! @param a eql_v2_int4_eq +--! @brief Blocker for ->> on eql_v3.int4_eq (domain, integer). +--! @param a eql_v3.int4_eq --! @param selector integer --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_eq, selector integer) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_eq, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for ->> on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4_eq +--! @param selector eql_v3.int4_eq --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4_eq) +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4_eq) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v2_int4_eq (domain, text). ---! @param a eql_v2_int4_eq +--! @brief Blocker for ? on eql_v3.int4_eq (domain, text). +--! @param a eql_v3.int4_eq --! @param b text --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?"(a eql_v2_int4_eq, b text) +CREATE FUNCTION eql_v3."?"(a eql_v3.int4_eq, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v2_int4_eq (domain, text[]). ---! @param a eql_v2_int4_eq +--! @brief Blocker for ?| on eql_v3.int4_eq (domain, text[]). +--! @param a eql_v3.int4_eq --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?|"(a eql_v2_int4_eq, b text[]) +CREATE FUNCTION eql_v3."?|"(a eql_v3.int4_eq, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '?|'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '?|'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v2_int4_eq (domain, text[]). ---! @param a eql_v2_int4_eq +--! @brief Blocker for ?& on eql_v3.int4_eq (domain, text[]). +--! @param a eql_v3.int4_eq --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?&"(a eql_v2_int4_eq, b text[]) +CREATE FUNCTION eql_v3."?&"(a eql_v3.int4_eq, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '?&'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '?&'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v2_int4_eq (domain, jsonpath). ---! @param a eql_v2_int4_eq +--! @brief Blocker for @? on eql_v3.int4_eq (domain, jsonpath). +--! @param a eql_v3.int4_eq --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@?"(a eql_v2_int4_eq, b jsonpath) +CREATE FUNCTION eql_v3."@?"(a eql_v3.int4_eq, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v2_int4_eq (domain, jsonpath). ---! @param a eql_v2_int4_eq +--! @brief Blocker for @@ on eql_v3.int4_eq (domain, jsonpath). +--! @param a eql_v3.int4_eq --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@@"(a eql_v2_int4_eq, b jsonpath) +CREATE FUNCTION eql_v3."@@"(a eql_v3.int4_eq, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_eq', '@@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v2_int4_eq (domain, text[]). ---! @param a eql_v2_int4_eq +--! @brief Blocker for #> on eql_v3.int4_eq (domain, text[]). +--! @param a eql_v3.int4_eq --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#>"(a eql_v2_int4_eq, b text[]) +CREATE FUNCTION eql_v3."#>"(a eql_v3.int4_eq, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v2_int4_eq (domain, text[]). ---! @param a eql_v2_int4_eq +--! @brief Blocker for #>> on eql_v3.int4_eq (domain, text[]). +--! @param a eql_v3.int4_eq --! @param b text[] --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4_eq, b text[]) +CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4_eq, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_eq (domain, text). ---! @param a eql_v2_int4_eq +--! @brief Blocker for - on eql_v3.int4_eq (domain, text). +--! @param a eql_v3.int4_eq --! @param b text --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_eq, b text) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_eq, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_eq (domain, integer). ---! @param a eql_v2_int4_eq +--! @brief Blocker for - on eql_v3.int4_eq (domain, integer). +--! @param a eql_v3.int4_eq --! @param b integer --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_eq, b integer) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_eq, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_eq (domain, text[]). ---! @param a eql_v2_int4_eq +--! @brief Blocker for - on eql_v3.int4_eq (domain, text[]). +--! @param a eql_v3.int4_eq --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_eq, b text[]) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_eq, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v2_int4_eq (domain, text[]). ---! @param a eql_v2_int4_eq +--! @brief Blocker for #- on eql_v3.int4_eq (domain, text[]). +--! @param a eql_v3.int4_eq --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#-"(a eql_v2_int4_eq, b text[]) +CREATE FUNCTION eql_v3."#-"(a eql_v3.int4_eq, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_eq. ---! @param a eql_v2_int4_eq ---! @param b eql_v2_int4_eq +--! @brief Blocker for || on eql_v3.int4_eq. +--! @param a eql_v3.int4_eq +--! @param b eql_v3.int4_eq --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4_eq, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_eq (domain, jsonb). ---! @param a eql_v2_int4_eq +--! @brief Blocker for || on eql_v3.int4_eq (domain, jsonb). +--! @param a eql_v3.int4_eq --! @param b jsonb --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4_eq, b jsonb) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4_eq, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_eq (jsonb, domain). +--! @brief Blocker for || on eql_v3.int4_eq (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_eq +--! @param b eql_v3.int4_eq --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4_eq) +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4_eq) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_eq'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_eq_operators.sql b/tests/codegen/reference/int4/int4_eq_operators.sql index 85d8353c..a39951f6 100644 --- a/tests/codegen/reference/int4/int4_eq_operators.sql +++ b/tests/codegen/reference/int4/int4_eq_operators.sql @@ -1,5 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_eq_functions.sql @@ -7,265 +7,265 @@ --! @brief Equality-only domain of the int4 encrypted-domain family — operator declarations. CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq, + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb, + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq, + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq, + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb, + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq, + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); -- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4_eq, RIGHTARG = integer + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4_eq, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4_eq, RIGHTARG = integer + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4_eq, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( - FUNCTION = eql_v2."?", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( - FUNCTION = eql_v2."?|", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( - FUNCTION = eql_v2."?&", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( - FUNCTION = eql_v2."@?", - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonpath + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( - FUNCTION = eql_v2."@@", - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonpath + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( - FUNCTION = eql_v2."#>", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( - FUNCTION = eql_v2."#>>", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_eq, RIGHTARG = integer + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_eq, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( - FUNCTION = eql_v2."#-", - LEFTARG = eql_v2_int4_eq, RIGHTARG = text[] + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4_eq, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4_eq, RIGHTARG = jsonb + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_eq + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); diff --git a/tests/codegen/reference/int4/int4_functions.sql b/tests/codegen/reference/int4/int4_functions.sql index 27936e1d..a60bc7b1 100644 --- a/tests/codegen/reference/int4/int4_functions.sql +++ b/tests/codegen/reference/int4/int4_functions.sql @@ -1,403 +1,404 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md -- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/functions.sql --! @file encrypted_domain/int4/int4_functions.sql --! @brief Storage-only domain of the int4 encrypted-domain family — comparison/path functions. ---! @brief Blocker for = on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for = on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.eq(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for = on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for = on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.eq(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for = on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for = on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <> on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for <> on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.neq(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <> on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for <> on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.neq(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <> on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for <> on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for < on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lt(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for < on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lt(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for < on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for <= on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lte(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for <= on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lte(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for <= on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for > on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gt(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for > on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gt(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for > on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for >= on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gte(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for >= on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gte(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for >= on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '>='); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>='); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for @> on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for @> on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for @> on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for <@ on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for <@ on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for <@ on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4 (domain, text). ---! @param a eql_v2_int4 +--! @brief Blocker for -> on eql_v3.int4 (domain, text). +--! @param a eql_v3.int4 --! @param selector text ---! @return eql_v2_int4 (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4, selector text) -RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4'; END; $$ +--! @return eql_v3.int4 (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4, selector text) +RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4 (domain, integer). ---! @param a eql_v2_int4 +--! @brief Blocker for -> on eql_v3.int4 (domain, integer). +--! @param a eql_v3.int4 --! @param selector integer ---! @return eql_v2_int4 (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4, selector integer) -RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4'; END; $$ +--! @return eql_v3.int4 (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4, selector integer) +RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for -> on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4 ---! @return eql_v2_int4 (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4) -RETURNS eql_v2_int4 IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4'; END; $$ +--! @param selector eql_v3.int4 +--! @return eql_v3.int4 (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4) +RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4 (domain, text). ---! @param a eql_v2_int4 +--! @brief Blocker for ->> on eql_v3.int4 (domain, text). +--! @param a eql_v3.int4 --! @param selector text --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4, selector text) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4, selector text) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4 (domain, integer). ---! @param a eql_v2_int4 +--! @brief Blocker for ->> on eql_v3.int4 (domain, integer). +--! @param a eql_v3.int4 --! @param selector integer --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4, selector integer) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for ->> on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4 +--! @param selector eql_v3.int4 --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4) +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v2_int4 (domain, text). ---! @param a eql_v2_int4 +--! @brief Blocker for ? on eql_v3.int4 (domain, text). +--! @param a eql_v3.int4 --! @param b text --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?"(a eql_v2_int4, b text) +CREATE FUNCTION eql_v3."?"(a eql_v3.int4, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v2_int4 (domain, text[]). ---! @param a eql_v2_int4 +--! @brief Blocker for ?| on eql_v3.int4 (domain, text[]). +--! @param a eql_v3.int4 --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?|"(a eql_v2_int4, b text[]) +CREATE FUNCTION eql_v3."?|"(a eql_v3.int4, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '?|'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '?|'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v2_int4 (domain, text[]). ---! @param a eql_v2_int4 +--! @brief Blocker for ?& on eql_v3.int4 (domain, text[]). +--! @param a eql_v3.int4 --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?&"(a eql_v2_int4, b text[]) +CREATE FUNCTION eql_v3."?&"(a eql_v3.int4, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '?&'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '?&'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v2_int4 (domain, jsonpath). ---! @param a eql_v2_int4 +--! @brief Blocker for @? on eql_v3.int4 (domain, jsonpath). +--! @param a eql_v3.int4 --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@?"(a eql_v2_int4, b jsonpath) +CREATE FUNCTION eql_v3."@?"(a eql_v3.int4, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v2_int4 (domain, jsonpath). ---! @param a eql_v2_int4 +--! @brief Blocker for @@ on eql_v3.int4 (domain, jsonpath). +--! @param a eql_v3.int4 --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@@"(a eql_v2_int4, b jsonpath) +CREATE FUNCTION eql_v3."@@"(a eql_v3.int4, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '@@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v2_int4 (domain, text[]). ---! @param a eql_v2_int4 +--! @brief Blocker for #> on eql_v3.int4 (domain, text[]). +--! @param a eql_v3.int4 --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#>"(a eql_v2_int4, b text[]) +CREATE FUNCTION eql_v3."#>"(a eql_v3.int4, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v2_int4 (domain, text[]). ---! @param a eql_v2_int4 +--! @brief Blocker for #>> on eql_v3.int4 (domain, text[]). +--! @param a eql_v3.int4 --! @param b text[] --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4, b text[]) +CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4 (domain, text). ---! @param a eql_v2_int4 +--! @brief Blocker for - on eql_v3.int4 (domain, text). +--! @param a eql_v3.int4 --! @param b text --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4, b text) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4 (domain, integer). ---! @param a eql_v2_int4 +--! @brief Blocker for - on eql_v3.int4 (domain, integer). +--! @param a eql_v3.int4 --! @param b integer --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4, b integer) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4 (domain, text[]). ---! @param a eql_v2_int4 +--! @brief Blocker for - on eql_v3.int4 (domain, text[]). +--! @param a eql_v3.int4 --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4, b text[]) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v2_int4 (domain, text[]). ---! @param a eql_v2_int4 +--! @brief Blocker for #- on eql_v3.int4 (domain, text[]). +--! @param a eql_v3.int4 --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#-"(a eql_v2_int4, b text[]) +CREATE FUNCTION eql_v3."#-"(a eql_v3.int4, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4. ---! @param a eql_v2_int4 ---! @param b eql_v2_int4 +--! @brief Blocker for || on eql_v3.int4. +--! @param a eql_v3.int4 +--! @param b eql_v3.int4 --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4, b eql_v2_int4) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4, b eql_v3.int4) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4 (domain, jsonb). ---! @param a eql_v2_int4 +--! @brief Blocker for || on eql_v3.int4 (domain, jsonb). +--! @param a eql_v3.int4 --! @param b jsonb --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4, b jsonb) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4 (jsonb, domain). +--! @brief Blocker for || on eql_v3.int4 (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4 +--! @param b eql_v3.int4 --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4) +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_operators.sql b/tests/codegen/reference/int4/int4_operators.sql index fc3dd7cf..24ff1607 100644 --- a/tests/codegen/reference/int4/int4_operators.sql +++ b/tests/codegen/reference/int4/int4_operators.sql @@ -1,5 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_functions.sql @@ -8,264 +8,264 @@ -- Placeholder: this domain's term set does not support =; the backing function always raises. CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support =; the backing function always raises. CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support =; the backing function always raises. CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <>; the backing function always raises. CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <>; the backing function always raises. CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <>; the backing function always raises. CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4, RIGHTARG = text + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4, RIGHTARG = integer + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4, RIGHTARG = text + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4, RIGHTARG = integer + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( - FUNCTION = eql_v2."?", - LEFTARG = eql_v2_int4, RIGHTARG = text + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.int4, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( - FUNCTION = eql_v2."?|", - LEFTARG = eql_v2_int4, RIGHTARG = text[] + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.int4, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( - FUNCTION = eql_v2."?&", - LEFTARG = eql_v2_int4, RIGHTARG = text[] + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.int4, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( - FUNCTION = eql_v2."@?", - LEFTARG = eql_v2_int4, RIGHTARG = jsonpath + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.int4, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( - FUNCTION = eql_v2."@@", - LEFTARG = eql_v2_int4, RIGHTARG = jsonpath + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.int4, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( - FUNCTION = eql_v2."#>", - LEFTARG = eql_v2_int4, RIGHTARG = text[] + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.int4, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( - FUNCTION = eql_v2."#>>", - LEFTARG = eql_v2_int4, RIGHTARG = text[] + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.int4, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4, RIGHTARG = text + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4, RIGHTARG = text ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4, RIGHTARG = integer + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4, RIGHTARG = text[] + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( - FUNCTION = eql_v2."#-", - LEFTARG = eql_v2_int4, RIGHTARG = text[] + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.int4, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4, RIGHTARG = jsonb + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4 + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); diff --git a/tests/codegen/reference/int4/int4_ord_aggregates.sql b/tests/codegen/reference/int4/int4_ord_aggregates.sql index 52a64ec1..12f5efcc 100644 --- a/tests/codegen/reference/int4/int4_ord_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_aggregates.sql @@ -1,5 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql @@ -7,18 +7,18 @@ --! @file encrypted_domain/int4/int4_ord_aggregates.sql --! @brief Ordered domain of the int4 encrypted-domain family — MIN/MAX aggregates. ---! @brief State function for min aggregate on eql_v2_int4_ord. +--! @brief State function for min aggregate on eql_v3.int4_ord. --! @internal --! ---! @param state eql_v2_int4_ord running extremum ---! @param value eql_v2_int4_ord next non-NULL value ---! @return eql_v2_int4_ord the minimum of state and value +--! @param state eql_v3.int4_ord running extremum +--! @param value eql_v3.int4_ord next non-NULL value +--! @return eql_v3.int4_ord the minimum of state and value -- LANGUAGE plpgsql, not sql: aggregate state functions are not index -- expressions, so opacity to the planner is fine, and a multi-statement -- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would -- also work, but the procedural form mirrors the blocker convention.) -CREATE FUNCTION eql_v2.min_sfunc(state eql_v2_int4_ord, value eql_v2_int4_ord) -RETURNS eql_v2_int4_ord +CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.int4_ord, value eql_v3.int4_ord) +RETURNS eql_v3.int4_ord LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE SET search_path = pg_catalog, extensions, public AS $$ @@ -30,34 +30,34 @@ BEGIN END; $$; ---! @brief Find the minimum encrypted value in a group of eql_v2_int4_ord values. +--! @brief Find the minimum encrypted value in a group of eql_v3.int4_ord values. --! --! Comparison routes through the domain's `<` operator, which uses the ORE block term — no decryption. --! ---! @param input eql_v2_int4_ord encrypted values to aggregate ---! @return eql_v2_int4_ord minimum of the group, or NULL if all inputs are NULL +--! @param input eql_v3.int4_ord encrypted values to aggregate +--! @return eql_v3.int4_ord minimum of the group, or NULL if all inputs are NULL -- combinefunc = sfunc: min/max are associative, so merging two partial -- extrema is the same comparison. PARALLEL SAFE enables partial and -- parallel aggregation on large GROUP BY workloads, with no decryption. -CREATE AGGREGATE eql_v2.min(eql_v2_int4_ord) ( - sfunc = eql_v2.min_sfunc, - stype = eql_v2_int4_ord, - combinefunc = eql_v2.min_sfunc, +CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord) ( + sfunc = eql_v3.min_sfunc, + stype = eql_v3.int4_ord, + combinefunc = eql_v3.min_sfunc, parallel = safe ); ---! @brief State function for max aggregate on eql_v2_int4_ord. +--! @brief State function for max aggregate on eql_v3.int4_ord. --! @internal --! ---! @param state eql_v2_int4_ord running extremum ---! @param value eql_v2_int4_ord next non-NULL value ---! @return eql_v2_int4_ord the maximum of state and value +--! @param state eql_v3.int4_ord running extremum +--! @param value eql_v3.int4_ord next non-NULL value +--! @return eql_v3.int4_ord the maximum of state and value -- LANGUAGE plpgsql, not sql: aggregate state functions are not index -- expressions, so opacity to the planner is fine, and a multi-statement -- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would -- also work, but the procedural form mirrors the blocker convention.) -CREATE FUNCTION eql_v2.max_sfunc(state eql_v2_int4_ord, value eql_v2_int4_ord) -RETURNS eql_v2_int4_ord +CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.int4_ord, value eql_v3.int4_ord) +RETURNS eql_v3.int4_ord LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE SET search_path = pg_catalog, extensions, public AS $$ @@ -69,18 +69,18 @@ BEGIN END; $$; ---! @brief Find the maximum encrypted value in a group of eql_v2_int4_ord values. +--! @brief Find the maximum encrypted value in a group of eql_v3.int4_ord values. --! --! Comparison routes through the domain's `>` operator, which uses the ORE block term — no decryption. --! ---! @param input eql_v2_int4_ord encrypted values to aggregate ---! @return eql_v2_int4_ord maximum of the group, or NULL if all inputs are NULL +--! @param input eql_v3.int4_ord encrypted values to aggregate +--! @return eql_v3.int4_ord maximum of the group, or NULL if all inputs are NULL -- combinefunc = sfunc: min/max are associative, so merging two partial -- extrema is the same comparison. PARALLEL SAFE enables partial and -- parallel aggregation on large GROUP BY workloads, with no decryption. -CREATE AGGREGATE eql_v2.max(eql_v2_int4_ord) ( - sfunc = eql_v2.max_sfunc, - stype = eql_v2_int4_ord, - combinefunc = eql_v2.max_sfunc, +CREATE AGGREGATE eql_v3.max(eql_v3.int4_ord) ( + sfunc = eql_v3.max_sfunc, + stype = eql_v3.int4_ord, + combinefunc = eql_v3.max_sfunc, parallel = safe ); diff --git a/tests/codegen/reference/int4/int4_ord_functions.sql b/tests/codegen/reference/int4/int4_ord_functions.sql index 9d3ba2a2..a49bb1e9 100644 --- a/tests/codegen/reference/int4/int4_ord_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_functions.sql @@ -1,5 +1,6 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md -- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/functions.sql -- REQUIRE: src/ore_block_u64_8_256/functions.sql @@ -8,388 +9,388 @@ --! @file encrypted_domain/int4/int4_ord_functions.sql --! @brief Ordered domain of the int4 encrypted-domain family — comparison/path functions. ---! @brief Index extractor for the eql_v2_int4_ord variant. ---! @param a eql_v2_int4_ord +--! @brief Index extractor for the eql_v3.int4_ord variant. +--! @param a eql_v3.int4_ord --! @return eql_v2.ore_block_u64_8_256 -CREATE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord) +CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord) RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; ---! @brief Equality wrapper for eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Equality wrapper for eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; ---! @brief Equality wrapper for eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Equality wrapper for eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b::eql_v2_int4_ord) $$; +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Equality wrapper for eql_v2_int4_ord (jsonb, domain). +--! @brief Equality wrapper for eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) = eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) = eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Inequality wrapper for eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Inequality wrapper for eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b::eql_v2_int4_ord) $$; +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Inequality wrapper for eql_v2_int4_ord (jsonb, domain). +--! @brief Inequality wrapper for eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) <> eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) <> eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Less-than wrapper for eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Less-than wrapper for eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b::eql_v2_int4_ord) $$; +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Less-than wrapper for eql_v2_int4_ord (jsonb, domain). +--! @brief Less-than wrapper for eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) < eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) < eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Less-than-or-equal wrapper for eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Less-than-or-equal wrapper for eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b::eql_v2_int4_ord) $$; +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Less-than-or-equal wrapper for eql_v2_int4_ord (jsonb, domain). +--! @brief Less-than-or-equal wrapper for eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) <= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) <= eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Greater-than wrapper for eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Greater-than wrapper for eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b::eql_v2_int4_ord) $$; +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Greater-than wrapper for eql_v2_int4_ord (jsonb, domain). +--! @brief Greater-than wrapper for eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) > eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) > eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b::eql_v2_int4_ord) $$; +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord (jsonb, domain). +--! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean -CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord) >= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) >= eql_v3.ord_term(b) $$; ---! @brief Blocker for @> on eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Blocker for @> on eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Blocker for @> on eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4_ord (jsonb, domain). +--! @brief Blocker for @> on eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Blocker for <@ on eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Blocker for <@ on eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_ord (jsonb, domain). +--! @brief Blocker for <@ on eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_ord (domain, text). ---! @param a eql_v2_int4_ord +--! @brief Blocker for -> on eql_v3.int4_ord (domain, text). +--! @param a eql_v3.int4_ord --! @param selector text ---! @return eql_v2_int4_ord (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord, selector text) -RETURNS eql_v2_int4_ord IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord'; END; $$ +--! @return eql_v3.int4_ord (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord, selector text) +RETURNS eql_v3.int4_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_ord (domain, integer). ---! @param a eql_v2_int4_ord +--! @brief Blocker for -> on eql_v3.int4_ord (domain, integer). +--! @param a eql_v3.int4_ord --! @param selector integer ---! @return eql_v2_int4_ord (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord, selector integer) -RETURNS eql_v2_int4_ord IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord'; END; $$ +--! @return eql_v3.int4_ord (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord, selector integer) +RETURNS eql_v3.int4_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_ord (jsonb, domain). +--! @brief Blocker for -> on eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4_ord ---! @return eql_v2_int4_ord (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4_ord) -RETURNS eql_v2_int4_ord IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord'; END; $$ +--! @param selector eql_v3.int4_ord +--! @return eql_v3.int4_ord (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4_ord) +RETURNS eql_v3.int4_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_ord (domain, text). ---! @param a eql_v2_int4_ord +--! @brief Blocker for ->> on eql_v3.int4_ord (domain, text). +--! @param a eql_v3.int4_ord --! @param selector text --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord, selector text) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord, selector text) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_ord (domain, integer). ---! @param a eql_v2_int4_ord +--! @brief Blocker for ->> on eql_v3.int4_ord (domain, integer). +--! @param a eql_v3.int4_ord --! @param selector integer --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord, selector integer) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_ord (jsonb, domain). +--! @brief Blocker for ->> on eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4_ord +--! @param selector eql_v3.int4_ord --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4_ord) +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4_ord) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v2_int4_ord (domain, text). ---! @param a eql_v2_int4_ord +--! @brief Blocker for ? on eql_v3.int4_ord (domain, text). +--! @param a eql_v3.int4_ord --! @param b text --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?"(a eql_v2_int4_ord, b text) +CREATE FUNCTION eql_v3."?"(a eql_v3.int4_ord, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v2_int4_ord (domain, text[]). ---! @param a eql_v2_int4_ord +--! @brief Blocker for ?| on eql_v3.int4_ord (domain, text[]). +--! @param a eql_v3.int4_ord --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?|"(a eql_v2_int4_ord, b text[]) +CREATE FUNCTION eql_v3."?|"(a eql_v3.int4_ord, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '?|'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '?|'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v2_int4_ord (domain, text[]). ---! @param a eql_v2_int4_ord +--! @brief Blocker for ?& on eql_v3.int4_ord (domain, text[]). +--! @param a eql_v3.int4_ord --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?&"(a eql_v2_int4_ord, b text[]) +CREATE FUNCTION eql_v3."?&"(a eql_v3.int4_ord, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '?&'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '?&'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v2_int4_ord (domain, jsonpath). ---! @param a eql_v2_int4_ord +--! @brief Blocker for @? on eql_v3.int4_ord (domain, jsonpath). +--! @param a eql_v3.int4_ord --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@?"(a eql_v2_int4_ord, b jsonpath) +CREATE FUNCTION eql_v3."@?"(a eql_v3.int4_ord, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v2_int4_ord (domain, jsonpath). ---! @param a eql_v2_int4_ord +--! @brief Blocker for @@ on eql_v3.int4_ord (domain, jsonpath). +--! @param a eql_v3.int4_ord --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@@"(a eql_v2_int4_ord, b jsonpath) +CREATE FUNCTION eql_v3."@@"(a eql_v3.int4_ord, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '@@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v2_int4_ord (domain, text[]). ---! @param a eql_v2_int4_ord +--! @brief Blocker for #> on eql_v3.int4_ord (domain, text[]). +--! @param a eql_v3.int4_ord --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#>"(a eql_v2_int4_ord, b text[]) +CREATE FUNCTION eql_v3."#>"(a eql_v3.int4_ord, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v2_int4_ord (domain, text[]). ---! @param a eql_v2_int4_ord +--! @brief Blocker for #>> on eql_v3.int4_ord (domain, text[]). +--! @param a eql_v3.int4_ord --! @param b text[] --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4_ord, b text[]) +CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4_ord, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_ord (domain, text). ---! @param a eql_v2_int4_ord +--! @brief Blocker for - on eql_v3.int4_ord (domain, text). +--! @param a eql_v3.int4_ord --! @param b text --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord, b text) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_ord (domain, integer). ---! @param a eql_v2_int4_ord +--! @brief Blocker for - on eql_v3.int4_ord (domain, integer). +--! @param a eql_v3.int4_ord --! @param b integer --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord, b integer) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_ord (domain, text[]). ---! @param a eql_v2_int4_ord +--! @brief Blocker for - on eql_v3.int4_ord (domain, text[]). +--! @param a eql_v3.int4_ord --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord, b text[]) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v2_int4_ord (domain, text[]). ---! @param a eql_v2_int4_ord +--! @brief Blocker for #- on eql_v3.int4_ord (domain, text[]). +--! @param a eql_v3.int4_ord --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#-"(a eql_v2_int4_ord, b text[]) +CREATE FUNCTION eql_v3."#-"(a eql_v3.int4_ord, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_ord. ---! @param a eql_v2_int4_ord ---! @param b eql_v2_int4_ord +--! @brief Blocker for || on eql_v3.int4_ord. +--! @param a eql_v3.int4_ord +--! @param b eql_v3.int4_ord --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_ord (domain, jsonb). ---! @param a eql_v2_int4_ord +--! @brief Blocker for || on eql_v3.int4_ord (domain, jsonb). +--! @param a eql_v3.int4_ord --! @param b jsonb --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord, b jsonb) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_ord (jsonb, domain). +--! @brief Blocker for || on eql_v3.int4_ord (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord +--! @param b eql_v3.int4_ord --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4_ord) +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4_ord) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_ord_operators.sql b/tests/codegen/reference/int4/int4_ord_operators.sql index 3e3657f9..f52ecebb 100644 --- a/tests/codegen/reference/int4/int4_ord_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_operators.sql @@ -1,5 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql @@ -7,265 +7,265 @@ --! @brief Ordered domain of the int4 encrypted-domain family — operator declarations. CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel ); CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb, COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel ); CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel ); CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb, COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb, + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb, COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord, + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord, COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4_ord, RIGHTARG = integer + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4_ord, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4_ord, RIGHTARG = integer + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4_ord, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); -- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( - FUNCTION = eql_v2."?", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( - FUNCTION = eql_v2."?|", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( - FUNCTION = eql_v2."?&", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( - FUNCTION = eql_v2."@?", - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonpath + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( - FUNCTION = eql_v2."@@", - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonpath + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( - FUNCTION = eql_v2."#>", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( - FUNCTION = eql_v2."#>>", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_ord, RIGHTARG = integer + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_ord, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( - FUNCTION = eql_v2."#-", - LEFTARG = eql_v2_int4_ord, RIGHTARG = text[] + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4_ord, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4_ord, RIGHTARG = jsonb + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); diff --git a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql index f2f1e81e..26396459 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql @@ -1,5 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_operators.sql @@ -7,18 +7,18 @@ --! @file encrypted_domain/int4/int4_ord_ore_aggregates.sql --! @brief Ordered domain of the int4 encrypted-domain family — MIN/MAX aggregates. ---! @brief State function for min aggregate on eql_v2_int4_ord_ore. +--! @brief State function for min aggregate on eql_v3.int4_ord_ore. --! @internal --! ---! @param state eql_v2_int4_ord_ore running extremum ---! @param value eql_v2_int4_ord_ore next non-NULL value ---! @return eql_v2_int4_ord_ore the minimum of state and value +--! @param state eql_v3.int4_ord_ore running extremum +--! @param value eql_v3.int4_ord_ore next non-NULL value +--! @return eql_v3.int4_ord_ore the minimum of state and value -- LANGUAGE plpgsql, not sql: aggregate state functions are not index -- expressions, so opacity to the planner is fine, and a multi-statement -- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would -- also work, but the procedural form mirrors the blocker convention.) -CREATE FUNCTION eql_v2.min_sfunc(state eql_v2_int4_ord_ore, value eql_v2_int4_ord_ore) -RETURNS eql_v2_int4_ord_ore +CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.int4_ord_ore, value eql_v3.int4_ord_ore) +RETURNS eql_v3.int4_ord_ore LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE SET search_path = pg_catalog, extensions, public AS $$ @@ -30,34 +30,34 @@ BEGIN END; $$; ---! @brief Find the minimum encrypted value in a group of eql_v2_int4_ord_ore values. +--! @brief Find the minimum encrypted value in a group of eql_v3.int4_ord_ore values. --! --! Comparison routes through the domain's `<` operator, which uses the ORE block term — no decryption. --! ---! @param input eql_v2_int4_ord_ore encrypted values to aggregate ---! @return eql_v2_int4_ord_ore minimum of the group, or NULL if all inputs are NULL +--! @param input eql_v3.int4_ord_ore encrypted values to aggregate +--! @return eql_v3.int4_ord_ore minimum of the group, or NULL if all inputs are NULL -- combinefunc = sfunc: min/max are associative, so merging two partial -- extrema is the same comparison. PARALLEL SAFE enables partial and -- parallel aggregation on large GROUP BY workloads, with no decryption. -CREATE AGGREGATE eql_v2.min(eql_v2_int4_ord_ore) ( - sfunc = eql_v2.min_sfunc, - stype = eql_v2_int4_ord_ore, - combinefunc = eql_v2.min_sfunc, +CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord_ore) ( + sfunc = eql_v3.min_sfunc, + stype = eql_v3.int4_ord_ore, + combinefunc = eql_v3.min_sfunc, parallel = safe ); ---! @brief State function for max aggregate on eql_v2_int4_ord_ore. +--! @brief State function for max aggregate on eql_v3.int4_ord_ore. --! @internal --! ---! @param state eql_v2_int4_ord_ore running extremum ---! @param value eql_v2_int4_ord_ore next non-NULL value ---! @return eql_v2_int4_ord_ore the maximum of state and value +--! @param state eql_v3.int4_ord_ore running extremum +--! @param value eql_v3.int4_ord_ore next non-NULL value +--! @return eql_v3.int4_ord_ore the maximum of state and value -- LANGUAGE plpgsql, not sql: aggregate state functions are not index -- expressions, so opacity to the planner is fine, and a multi-statement -- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would -- also work, but the procedural form mirrors the blocker convention.) -CREATE FUNCTION eql_v2.max_sfunc(state eql_v2_int4_ord_ore, value eql_v2_int4_ord_ore) -RETURNS eql_v2_int4_ord_ore +CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.int4_ord_ore, value eql_v3.int4_ord_ore) +RETURNS eql_v3.int4_ord_ore LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE SET search_path = pg_catalog, extensions, public AS $$ @@ -69,18 +69,18 @@ BEGIN END; $$; ---! @brief Find the maximum encrypted value in a group of eql_v2_int4_ord_ore values. +--! @brief Find the maximum encrypted value in a group of eql_v3.int4_ord_ore values. --! --! Comparison routes through the domain's `>` operator, which uses the ORE block term — no decryption. --! ---! @param input eql_v2_int4_ord_ore encrypted values to aggregate ---! @return eql_v2_int4_ord_ore maximum of the group, or NULL if all inputs are NULL +--! @param input eql_v3.int4_ord_ore encrypted values to aggregate +--! @return eql_v3.int4_ord_ore maximum of the group, or NULL if all inputs are NULL -- combinefunc = sfunc: min/max are associative, so merging two partial -- extrema is the same comparison. PARALLEL SAFE enables partial and -- parallel aggregation on large GROUP BY workloads, with no decryption. -CREATE AGGREGATE eql_v2.max(eql_v2_int4_ord_ore) ( - sfunc = eql_v2.max_sfunc, - stype = eql_v2_int4_ord_ore, - combinefunc = eql_v2.max_sfunc, +CREATE AGGREGATE eql_v3.max(eql_v3.int4_ord_ore) ( + sfunc = eql_v3.max_sfunc, + stype = eql_v3.int4_ord_ore, + combinefunc = eql_v3.max_sfunc, parallel = safe ); diff --git a/tests/codegen/reference/int4/int4_ord_ore_functions.sql b/tests/codegen/reference/int4/int4_ord_ore_functions.sql index bd6fe8b4..005bf672 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_functions.sql @@ -1,5 +1,6 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md -- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/functions.sql -- REQUIRE: src/ore_block_u64_8_256/functions.sql @@ -8,388 +9,388 @@ --! @file encrypted_domain/int4/int4_ord_ore_functions.sql --! @brief Ordered domain of the int4 encrypted-domain family — comparison/path functions. ---! @brief Index extractor for the eql_v2_int4_ord_ore variant. ---! @param a eql_v2_int4_ord_ore +--! @brief Index extractor for the eql_v3.int4_ord_ore variant. +--! @param a eql_v3.int4_ord_ore --! @return eql_v2.ore_block_u64_8_256 -CREATE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord_ore) RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; ---! @brief Equality wrapper for eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Equality wrapper for eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; ---! @brief Equality wrapper for eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Equality wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.eq(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) = eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Equality wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Equality wrapper for eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.eq(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) = eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) = eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Inequality wrapper for eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Inequality wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.neq(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <> eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Inequality wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Inequality wrapper for eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.neq(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) <> eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) <> eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Less-than wrapper for eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Less-than wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.lt(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) < eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Less-than wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Less-than wrapper for eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.lt(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) < eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) < eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Less-than-or-equal wrapper for eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Less-than-or-equal wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.lte(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) <= eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Less-than-or-equal wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Less-than-or-equal wrapper for eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.lte(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) <= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) <= eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Greater-than wrapper for eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Greater-than wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.gt(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) > eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Greater-than wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Greater-than wrapper for eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.gt(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) > eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) > eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean -CREATE FUNCTION eql_v2.gte(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a) >= eql_v2.ord_term(b::eql_v2_int4_ord_ore) $$; +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Greater-than-or-equal wrapper for eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean -CREATE FUNCTION eql_v2.gte(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ord_term(a::eql_v2_int4_ord_ore) >= eql_v2.ord_term(b) $$; +AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) >= eql_v3.ord_term(b) $$; ---! @brief Blocker for @> on eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Blocker for @> on eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for @> on eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Blocker for @> on eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contains(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@>'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@>'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Blocker for <@ on eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for <@ on eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Blocker for <@ on eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2.contained_by(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '<@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '<@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_ord_ore (domain, text). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for -> on eql_v3.int4_ord_ore (domain, text). +--! @param a eql_v3.int4_ord_ore --! @param selector text ---! @return eql_v2_int4_ord_ore (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord_ore, selector text) -RETURNS eql_v2_int4_ord_ore IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord_ore'; END; $$ +--! @return eql_v3.int4_ord_ore (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord_ore, selector text) +RETURNS eql_v3.int4_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_ord_ore (domain, integer). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for -> on eql_v3.int4_ord_ore (domain, integer). +--! @param a eql_v3.int4_ord_ore --! @param selector integer ---! @return eql_v2_int4_ord_ore (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a eql_v2_int4_ord_ore, selector integer) -RETURNS eql_v2_int4_ord_ore IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord_ore'; END; $$ +--! @return eql_v3.int4_ord_ore (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord_ore, selector integer) +RETURNS eql_v3.int4_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Blocker for -> on eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4_ord_ore ---! @return eql_v2_int4_ord_ore (never returns; always raises) -CREATE FUNCTION eql_v2."->"(a jsonb, selector eql_v2_int4_ord_ore) -RETURNS eql_v2_int4_ord_ore IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v2_int4_ord_ore'; END; $$ +--! @param selector eql_v3.int4_ord_ore +--! @return eql_v3.int4_ord_ore (never returns; always raises) +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4_ord_ore) +RETURNS eql_v3.int4_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_ord_ore (domain, text). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for ->> on eql_v3.int4_ord_ore (domain, text). +--! @param a eql_v3.int4_ord_ore --! @param selector text --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord_ore, selector text) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord_ore, selector text) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_ord_ore (domain, integer). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for ->> on eql_v3.int4_ord_ore (domain, integer). +--! @param a eql_v3.int4_ord_ore --! @param selector integer --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a eql_v2_int4_ord_ore, selector integer) +CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord_ore, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Blocker for ->> on eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param selector eql_v2_int4_ord_ore +--! @param selector eql_v3.int4_ord_ore --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."->>"(a jsonb, selector eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4_ord_ore) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v2_int4_ord_ore (domain, text). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for ? on eql_v3.int4_ord_ore (domain, text). +--! @param a eql_v3.int4_ord_ore --! @param b text --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?"(a eql_v2_int4_ord_ore, b text) +CREATE FUNCTION eql_v3."?"(a eql_v3.int4_ord_ore, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v2_int4_ord_ore (domain, text[]). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for ?| on eql_v3.int4_ord_ore (domain, text[]). +--! @param a eql_v3.int4_ord_ore --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?|"(a eql_v2_int4_ord_ore, b text[]) +CREATE FUNCTION eql_v3."?|"(a eql_v3.int4_ord_ore, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '?|'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '?|'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v2_int4_ord_ore (domain, text[]). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for ?& on eql_v3.int4_ord_ore (domain, text[]). +--! @param a eql_v3.int4_ord_ore --! @param b text[] --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."?&"(a eql_v2_int4_ord_ore, b text[]) +CREATE FUNCTION eql_v3."?&"(a eql_v3.int4_ord_ore, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '?&'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '?&'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v2_int4_ord_ore (domain, jsonpath). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for @? on eql_v3.int4_ord_ore (domain, jsonpath). +--! @param a eql_v3.int4_ord_ore --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@?"(a eql_v2_int4_ord_ore, b jsonpath) +CREATE FUNCTION eql_v3."@?"(a eql_v3.int4_ord_ore, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@?'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@?'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v2_int4_ord_ore (domain, jsonpath). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for @@ on eql_v3.int4_ord_ore (domain, jsonpath). +--! @param a eql_v3.int4_ord_ore --! @param b jsonpath --! @return boolean (never returns; always raises) -CREATE FUNCTION eql_v2."@@"(a eql_v2_int4_ord_ore, b jsonpath) +CREATE FUNCTION eql_v3."@@"(a eql_v3.int4_ord_ore, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord_ore', '@@'); END; $$ +AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@@'); END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v2_int4_ord_ore (domain, text[]). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for #> on eql_v3.int4_ord_ore (domain, text[]). +--! @param a eql_v3.int4_ord_ore --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#>"(a eql_v2_int4_ord_ore, b text[]) +CREATE FUNCTION eql_v3."#>"(a eql_v3.int4_ord_ore, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v2_int4_ord_ore (domain, text[]). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for #>> on eql_v3.int4_ord_ore (domain, text[]). +--! @param a eql_v3.int4_ord_ore --! @param b text[] --! @return text (never returns; always raises) -CREATE FUNCTION eql_v2."#>>"(a eql_v2_int4_ord_ore, b text[]) +CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4_ord_ore, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_ord_ore (domain, text). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for - on eql_v3.int4_ord_ore (domain, text). +--! @param a eql_v3.int4_ord_ore --! @param b text --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord_ore, b text) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord_ore, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_ord_ore (domain, integer). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for - on eql_v3.int4_ord_ore (domain, integer). +--! @param a eql_v3.int4_ord_ore --! @param b integer --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord_ore, b integer) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord_ore, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v2_int4_ord_ore (domain, text[]). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for - on eql_v3.int4_ord_ore (domain, text[]). +--! @param a eql_v3.int4_ord_ore --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."-"(a eql_v2_int4_ord_ore, b text[]) +CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord_ore, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v2_int4_ord_ore (domain, text[]). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for #- on eql_v3.int4_ord_ore (domain, text[]). +--! @param a eql_v3.int4_ord_ore --! @param b text[] --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."#-"(a eql_v2_int4_ord_ore, b text[]) +CREATE FUNCTION eql_v3."#-"(a eql_v3.int4_ord_ore, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_ord_ore. ---! @param a eql_v2_int4_ord_ore ---! @param b eql_v2_int4_ord_ore +--! @brief Blocker for || on eql_v3.int4_ord_ore. +--! @param a eql_v3.int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord_ore, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_ord_ore (domain, jsonb). ---! @param a eql_v2_int4_ord_ore +--! @brief Blocker for || on eql_v3.int4_ord_ore (domain, jsonb). +--! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a eql_v2_int4_ord_ore, b jsonb) +CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord_ore, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v2_int4_ord_ore (jsonb, domain). +--! @brief Blocker for || on eql_v3.int4_ord_ore (jsonb, domain). --! @param a jsonb ---! @param b eql_v2_int4_ord_ore +--! @param b eql_v3.int4_ord_ore --! @return jsonb (never returns; always raises) -CREATE FUNCTION eql_v2."||"(a jsonb, b eql_v2_int4_ord_ore) +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4_ord_ore) RETURNS jsonb IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v2_int4_ord_ore'; END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; diff --git a/tests/codegen/reference/int4/int4_ord_ore_operators.sql b/tests/codegen/reference/int4/int4_ord_ore_operators.sql index ee1f84cf..e6dc27e9 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_operators.sql @@ -1,5 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql @@ -7,265 +7,265 @@ --! @brief Ordered domain of the int4 encrypted-domain family — operator declarations. CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR = ( - FUNCTION = eql_v2.eq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR <> ( - FUNCTION = eql_v2.neq, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR < ( - FUNCTION = eql_v2.lt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel ); CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb, COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel ); CREATE OPERATOR <= ( - FUNCTION = eql_v2.lte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel ); CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb, COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR > ( - FUNCTION = eql_v2.gt, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb, + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb, COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); CREATE OPERATOR >= ( - FUNCTION = eql_v2.gte, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore, + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore, COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( - FUNCTION = eql_v2.contains, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( - FUNCTION = eql_v2.contained_by, - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = integer + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( - FUNCTION = eql_v2."->", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = integer + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( - FUNCTION = eql_v2."->>", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); -- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( - FUNCTION = eql_v2."?", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); -- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( - FUNCTION = eql_v2."?|", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( - FUNCTION = eql_v2."?&", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( - FUNCTION = eql_v2."@?", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonpath + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( - FUNCTION = eql_v2."@@", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonpath + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonpath ); -- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( - FUNCTION = eql_v2."#>", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( - FUNCTION = eql_v2."#>>", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = integer + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = integer ); -- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( - FUNCTION = eql_v2."-", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( - FUNCTION = eql_v2."#-", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = text[] + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = eql_v2_int4_ord_ore, RIGHTARG = jsonb + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb ); -- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( - FUNCTION = eql_v2."||", - LEFTARG = jsonb, RIGHTARG = eql_v2_int4_ord_ore + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); diff --git a/tests/codegen/reference/int4/int4_types.sql b/tests/codegen/reference/int4/int4_types.sql index f7616539..0b33e740 100644 --- a/tests/codegen/reference/int4/int4_types.sql +++ b/tests/codegen/reference/int4/int4_types.sql @@ -1,5 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md --- REQUIRE: src/schema.sql +-- REQUIRE: src/schema-v3.sql --! @file encrypted_domain/int4/int4_types.sql --! @brief Encrypted-domain type family for int4. @@ -9,9 +9,9 @@ BEGIN --! @brief Storage-only encrypted int4 domain. IF NOT EXISTS ( SELECT 1 FROM pg_type - WHERE typname = 'eql_v2_int4' AND typnamespace = 'public'::regnamespace + WHERE typname = 'int4' AND typnamespace = 'eql_v3'::regnamespace ) THEN - CREATE DOMAIN public.eql_v2_int4 AS jsonb + CREATE DOMAIN eql_v3.int4 AS jsonb CHECK ( jsonb_typeof(VALUE) = 'object' AND VALUE ? 'v' @@ -24,9 +24,9 @@ BEGIN --! @brief Equality-only encrypted int4 domain. IF NOT EXISTS ( SELECT 1 FROM pg_type - WHERE typname = 'eql_v2_int4_eq' AND typnamespace = 'public'::regnamespace + WHERE typname = 'int4_eq' AND typnamespace = 'eql_v3'::regnamespace ) THEN - CREATE DOMAIN public.eql_v2_int4_eq AS jsonb + CREATE DOMAIN eql_v3.int4_eq AS jsonb CHECK ( jsonb_typeof(VALUE) = 'object' AND VALUE ? 'v' @@ -40,9 +40,9 @@ BEGIN --! @brief Ordered encrypted int4 domain. Scheme-explicit twin pinning the ore scheme; prefer the converged int4_ord name. IF NOT EXISTS ( SELECT 1 FROM pg_type - WHERE typname = 'eql_v2_int4_ord_ore' AND typnamespace = 'public'::regnamespace + WHERE typname = 'int4_ord_ore' AND typnamespace = 'eql_v3'::regnamespace ) THEN - CREATE DOMAIN public.eql_v2_int4_ord_ore AS jsonb + CREATE DOMAIN eql_v3.int4_ord_ore AS jsonb CHECK ( jsonb_typeof(VALUE) = 'object' AND VALUE ? 'v' @@ -56,9 +56,9 @@ BEGIN --! @brief Ordered encrypted int4 domain. Recommended converged name for this role. IF NOT EXISTS ( SELECT 1 FROM pg_type - WHERE typname = 'eql_v2_int4_ord' AND typnamespace = 'public'::regnamespace + WHERE typname = 'int4_ord' AND typnamespace = 'eql_v3'::regnamespace ) THEN - CREATE DOMAIN public.eql_v2_int4_ord AS jsonb + CREATE DOMAIN eql_v3.int4_ord AS jsonb CHECK ( jsonb_typeof(VALUE) = 'object' AND VALUE ? 'v' diff --git a/tests/sqlx/src/matrix.rs b/tests/sqlx/src/matrix.rs index 0d277af9..8b8c0923 100644 --- a/tests/sqlx/src/matrix.rs +++ b/tests/sqlx/src/matrix.rs @@ -180,11 +180,11 @@ macro_rules! ordered_numeric_matrix { eq_ops = [(eq, "="), (neq, "<>")], ord_ops = [(lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")], index_combos = [ - (eq, Eq, "eql_v2.eq_term", "btree", [(eq, "=")]), - (eq, Eq, "eql_v2.eq_term", "hash", [(eq, "=")]), - (ord, Ord, "eql_v2.ord_term", "btree", + (eq, Eq, "eql_v3.eq_term", "btree", [(eq, "=")]), + (eq, Eq, "eql_v3.eq_term", "hash", [(eq, "=")]), + (ord, Ord, "eql_v3.ord_term", "btree", [(eq, "="), (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")]), - (ord_ore, OrdOre, "eql_v2.ord_term", "btree", + (ord_ore, OrdOre, "eql_v3.ord_term", "btree", [(eq, "="), (lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")]), ], blocker_combos = [ @@ -246,8 +246,8 @@ macro_rules! eq_only_scalar_matrix { eq_ops = [(eq, "="), (neq, "<>")], ord_ops = [(lt, "<"), (lte, "<="), (gt, ">"), (gte, ">=")], index_combos = [ - (eq, Eq, "eql_v2.eq_term", "btree", [(eq, "=")]), - (eq, Eq, "eql_v2.eq_term", "hash", [(eq, "=")]), + (eq, Eq, "eql_v3.eq_term", "btree", [(eq, "=")]), + (eq, Eq, "eql_v3.eq_term", "hash", [(eq, "=")]), ], blocker_combos = [ (storage, Storage, [ @@ -1095,7 +1095,7 @@ macro_rules! __scalar_matrix_planner_metadata_case { JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright WHERE o.oprname IN ({op_list}) - AND (lt.typname = '{d}' OR rt.typname = '{d}') + AND ('{d}'::regtype = o.oprleft OR '{d}'::regtype = o.oprright) "# ); let rows: Vec<(String, String, String, bool, bool, bool, bool)> = @@ -1411,7 +1411,7 @@ macro_rules! __scalar_matrix_fixture_shape { // ============================================================================ // Ord-routes-through-ob category — ordered variants carry `c + ob` and // drop `hm`. Equality on an ord variant must therefore route through -// `eql_v2.ord_term` (the `ob` term), never HMAC. Strip `hm` from every +// `eql_v3.ord_term` (the `ob` term), never HMAC. Strip `hm` from every // fixture payload so an accidental regression to HMAC equality fails // rather than passing on the hm-carrying fixture. // ============================================================================ @@ -1474,7 +1474,7 @@ macro_rules! __scalar_matrix_ord_routes_case { anyhow::ensure!(with_hm == 0, "test rows must not carry hm"); sqlx::query(&format!( - "CREATE INDEX {index} ON {table} USING btree (eql_v2.ord_term(value))", + "CREATE INDEX {index} ON {table} USING btree (eql_v3.ord_term(value))", )).execute(&mut *tx).await?; sqlx::query(&format!("ANALYZE {table}")) .execute(&mut *tx).await?; @@ -1518,7 +1518,7 @@ macro_rules! __scalar_matrix_ord_routes_case { // VALIDITY, NOT PREFERENCE: this runs with // `enable_seqscan = off` (set above) on the ~17-row fixture, // so the planner picks the only usable alternative. A green - // assertion proves the `eql_v2.ord_term` functional btree is + // assertion proves the `eql_v3.ord_term` functional btree is // *usable* for `=` with no hm present, NOT that the planner // would *prefer* it at realistic scale. Cost-preference lives // in the `*_scale_preference_*` tests @@ -1534,7 +1534,7 @@ macro_rules! __scalar_matrix_ord_routes_case { &mut *tx, &format!("SELECT * FROM {table} WHERE value = '{lit}'::jsonb::{d}"), index, - "= must engage the eql_v2.ord_term functional btree with no hm", + "= must engage the eql_v3.ord_term functional btree with no hm", ).await?; tx.commit().await?; @@ -1786,7 +1786,7 @@ macro_rules! __scalar_matrix_order_by_case { <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); let sql = format!( "SELECT plaintext FROM {fixture}{where_clause} \ -ORDER BY eql_v2.ord_term(payload::{d}) {dir}", +ORDER BY eql_v3.ord_term(payload::{d}) {dir}", fixture = fixture_table, where_clause = $where_clause, d = &spec.sql_domain, dir = $direction, ); @@ -1816,7 +1816,7 @@ ORDER BY eql_v2.ord_term(payload::{d}) {dir}", // which has no NULL rows, so NULLS placement goes untested there. This arm // builds an isolated temp table mixing NULL-valued rows with the fixture rows // and pins that the NULL sort keys land at the requested end while the -// non-NULL rows stay in plaintext order. `eql_v2.ord_term` is STRICT, so a +// non-NULL rows stay in plaintext order. `eql_v3.ord_term` is STRICT, so a // NULL domain value yields a NULL sort key; a regression making it non-STRICT // would let NULL rows interleave — see the `family::mutations` negative // control for that dimension. @@ -1911,7 +1911,7 @@ SELECT NULL::{pg}, NULL::{d} FROM generate_series(1, {n})", n = NULL_ROWS, let sql = format!( "SELECT plaintext FROM {table} \ -ORDER BY eql_v2.ord_term(value) {dir} NULLS {nulls}", +ORDER BY eql_v3.ord_term(value) {dir} NULLS {nulls}", dir = $direction, nulls = $nulls, ); let actual: Vec> = @@ -2029,7 +2029,7 @@ domain by design) but succeeded", // Aggregate category — per (ord domain, op ∈ {min, max}), three tests: // extremum identity (payload of the min/max FIXTURE_VALUES row), all-NULL // returns NULL, and mixed NULL/non-NULL returns the correct extremum from -// the non-NULL subset. Pins that `eql_v2.min` / `eql_v2.max` aggregates +// the non-NULL subset. Pins that `eql_v3.min` / `eql_v3.max` aggregates // route through the domain's `<` / `>` and that the STRICT state function // correctly seeds + skips NULLs. Emits zero tests when ord_domains is // empty — eq-only umbrellas pick that up naturally. @@ -2103,13 +2103,13 @@ macro_rules! __scalar_matrix_aggregate_case { )).fetch_one(&pool).await?; let actual: String = sqlx::query_scalar(&format!( - "SELECT eql_v2.{agg}(payload::{d})::text FROM {fixture}", + "SELECT eql_v3.{agg}(payload::{d})::text FROM {fixture}", agg = $agg_fn, )).fetch_one(&pool).await?; assert_eq!( actual, expected, - "eql_v2.{}({}) must return the payload of plaintext={:?} (the fixture {})", + "eql_v3.{}({}) must return the payload of plaintext={:?} (the fixture {})", $agg_fn, d, extremum, $agg_fn, ); @@ -2120,8 +2120,8 @@ macro_rules! __scalar_matrix_aggregate_case { // where payload text matches but `ord_term` resolves to a // different value (e.g. due to payload-key reordering). let ord_terms_match: bool = sqlx::query_scalar(&format!( - "SELECT eql_v2.ord_term(eql_v2.{agg}(payload::{d})) \ - = eql_v2.ord_term($1::jsonb::{d}) \ + "SELECT eql_v3.ord_term(eql_v3.{agg}(payload::{d})) \ + = eql_v3.ord_term($1::jsonb::{d}) \ FROM {fixture}", agg = $agg_fn, )) @@ -2130,7 +2130,7 @@ macro_rules! __scalar_matrix_aggregate_case { .await?; anyhow::ensure!( ord_terms_match, - "eql_v2.ord_term(eql_v2.{}({})) must equal eql_v2.ord_term() \ + "eql_v3.ord_term(eql_v3.{}({})) must equal eql_v3.ord_term() \ for plaintext={:?}", $agg_fn, d, extremum, ); @@ -2152,12 +2152,12 @@ macro_rules! __scalar_matrix_aggregate_case { "CREATE TEMP TABLE empty_agg (value {d}) ON COMMIT DROP", )).execute(&mut *tx).await?; let result: Option = sqlx::query_scalar(&format!( - "SELECT eql_v2.{agg}(value)::text FROM empty_agg", + "SELECT eql_v3.{agg}(value)::text FROM empty_agg", agg = $agg_fn, )).fetch_one(&mut *tx).await?; anyhow::ensure!( result.is_none(), - "empty rowset to eql_v2.{} on {} must return NULL, got {:?}", + "empty rowset to eql_v3.{} on {} must return NULL, got {:?}", $agg_fn, d, result, ); tx.commit().await?; @@ -2173,7 +2173,7 @@ macro_rules! __scalar_matrix_aggregate_case { let spec = $crate::__scalar_matrix_spec!($scalar, $variant); let d = &spec.sql_domain; let sql = format!( - "SELECT eql_v2.{agg}(NULL::{d})::text FROM generate_series(1, 3)", + "SELECT eql_v3.{agg}(NULL::{d})::text FROM generate_series(1, 3)", agg = $agg_fn, ); let result: Option = sqlx::query_scalar(&sql) @@ -2181,7 +2181,7 @@ macro_rules! __scalar_matrix_aggregate_case { .await?; anyhow::ensure!( result.is_none(), - "all-NULL input to eql_v2.{} on {} must return NULL, got {:?}; SQL={}", + "all-NULL input to eql_v3.{} on {} must return NULL, got {:?}; SQL={}", $agg_fn, d, result, sql, ); Ok(()) @@ -2237,13 +2237,13 @@ macro_rules! __scalar_matrix_aggregate_case { )).fetch_one(&mut *tx).await?; let actual: Option = sqlx::query_scalar(&format!( - "SELECT eql_v2.{agg}(value)::text FROM mixed_null", + "SELECT eql_v3.{agg}(value)::text FROM mixed_null", agg = $agg_fn, )).fetch_one(&mut *tx).await?; anyhow::ensure!( actual.as_deref() == Some(expected.as_str()), - "eql_v2.{} on mixed NULL/non-NULL must return the {} non-NULL value (plaintext={:?}); want {expected:?}, got {actual:?}", + "eql_v3.{} on mixed NULL/non-NULL must return the {} non-NULL value (plaintext={:?}); want {expected:?}, got {actual:?}", $agg_fn, $agg_fn, expected_plaintext, ); @@ -2320,7 +2320,7 @@ macro_rules! __scalar_matrix_aggregate_parallel_case { // Aggregate GROUP BY category — per (ord domain, op ∈ {min, max}), build a // temp table partitioned into two groups, populate each with a known // subset of fixture rows, GROUP BY the group key, and assert that -// `eql_v2.(value)` returns the correct extremum payload per group. +// `eql_v3.(value)` returns the correct extremum payload per group. // Pins that the aggregate composes correctly under GROUP BY (state is // reset between groups, the sfunc routes through the variant's // comparator inside each partition). @@ -2430,7 +2430,7 @@ SELECT 2, payload::{d} FROM {fixture} WHERE plaintext = {lit}", )).fetch_one(&mut *tx).await?; let rows: Vec<(i32, String)> = sqlx::query_as(&format!( - "SELECT group_key, eql_v2.{agg}(value)::text \ + "SELECT group_key, eql_v3.{agg}(value)::text \ FROM group_test GROUP BY group_key ORDER BY group_key", agg = $agg_fn, )).fetch_all(&mut *tx).await?; @@ -2442,13 +2442,13 @@ FROM group_test GROUP BY group_key ORDER BY group_key", ); anyhow::ensure!( rows[0].0 == 1 && rows[0].1 == g1_expected, - "group 1 eql_v2.{}({}) must yield payload for plaintext={:?}; \ + "group 1 eql_v3.{}({}) must yield payload for plaintext={:?}; \ want ({}, {:?}), got {:?}", $agg_fn, d, group1_extremum, 1, g1_expected, rows[0], ); anyhow::ensure!( rows[1].0 == 2 && rows[1].1 == g2_expected, - "group 2 eql_v2.{}({}) must yield payload for plaintext={:?}; \ + "group 2 eql_v3.{}({}) must yield payload for plaintext={:?}; \ want ({}, {:?}), got {:?}", $agg_fn, d, group2_extremum, 2, g2_expected, rows[1], ); @@ -2462,7 +2462,7 @@ want ({}, {:?}), got {:?}", // ============================================================================ // Aggregate type-safety category — for variants that do NOT support ord -// (Storage, Eq), `eql_v2.min()` / `eql_v2.max(...)` must +// (Storage, Eq), `eql_v3.min()` / `eql_v3.max(...)` must // resolve to "function does not exist" (SQLSTATE 42883). Pins that // codegen correctly omits MIN/MAX wrappers for these variants — a // SQL-level regression test complementing the codegen unit test. @@ -2548,14 +2548,14 @@ macro_rules! __scalar_matrix_aggregate_typecheck_case { // can succeed cleanly. sqlx::query("SAVEPOINT probe").execute(&mut *tx).await?; let sql = format!( - "SELECT eql_v2.{agg}(value) FROM typecheck_table", + "SELECT eql_v3.{agg}(value) FROM typecheck_table", agg = $agg_fn, ); let err = sqlx::query_scalar::<_, String>(&sql) .fetch_one(&mut *tx) .await .expect_err(&format!( - "eql_v2.{} on non-ord variant {} must raise but succeeded", + "eql_v3.{} on non-ord variant {} must raise but succeeded", $agg_fn, d, )); // 42883 = undefined_function (no overload defined at all); @@ -2571,7 +2571,7 @@ macro_rules! __scalar_matrix_aggregate_typecheck_case { anyhow::ensure!( code.as_deref() == Some("42883") || code.as_deref() == Some("42725"), "expected SQLSTATE 42883 (undefined_function) or 42725 \ -(ambiguous_function) for eql_v2.{}({}), got {:?} (message: {})", +(ambiguous_function) for eql_v3.{}({}), got {:?} (message: {})", $agg_fn, d, code, db_err.message(), ); sqlx::query("ROLLBACK TO SAVEPOINT probe").execute(&mut *tx).await?; @@ -2594,7 +2594,7 @@ macro_rules! __scalar_matrix_aggregate_typecheck_case { // which only covered plain COUNT and only against the eql_v2_encrypted // type. Pinning per-variant DISTINCT catches the breakage class where // picking the wrong extractor would fail at runtime ("function -// eql_v2.eq_term(eql_v2_int4_ord) does not exist") — exactly the kind of +// eql_v3.eq_term(eql_v3.int4_ord) does not exist") — exactly the kind of // thing the variant-aware matrix is meant to surface mechanically. // ============================================================================ @@ -2689,8 +2689,8 @@ macro_rules! __scalar_matrix_count_case { // Dispatch on variant ident: Storage has no discriminating extractor, so // emits no DISTINCT test. The other three (Eq, Ord, OrdOre) each emit one // test that reads the extractor function name from the runtime -// `ScalarDomainSpec::extractor_fn()` accessor (Eq -> `eql_v2.eq_term`, -// Ord/OrdOre -> `eql_v2.ord_term`) and appends `(value)` at the call site. +// `ScalarDomainSpec::extractor_fn()` accessor (Eq -> `eql_v3.eq_term`, +// Ord/OrdOre -> `eql_v3.ord_term`) and appends `(value)` at the call site. #[macro_export] #[doc(hidden)] macro_rules! __scalar_matrix_count_distinct_dispatch { diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index b31c1a55..e7567584 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -143,20 +143,21 @@ impl Variant { /// or `None` if the variant carries no extractor (`Storage`). Returns /// just the function name — call sites append `(column)` themselves so /// the accessor is decoupled from any specific column-naming - /// convention. `Eq` resolves to `eql_v2.eq_term`; `Ord` and `OrdOre` - /// both resolve to `eql_v2.ord_term`. + /// convention. `Eq` resolves to `eql_v3.eq_term`; `Ord` and `OrdOre` + /// both resolve to `eql_v3.ord_term`. pub const fn extractor_fn(self) -> Option<&'static str> { match self { Variant::Storage => None, - Variant::Eq => Some("eql_v2.eq_term"), - Variant::Ord | Variant::OrdOre => Some("eql_v2.ord_term"), + Variant::Eq => Some("eql_v3.eq_term"), + Variant::Ord | Variant::OrdOre => Some("eql_v3.ord_term"), } } } /// Runtime spec built from `(T, Variant)`. The matrix macro consumes /// this; nothing here is `const` because `sql_domain` is derived via -/// `format!` from `T::PG_TYPE`. +/// `format!` from `T::PG_TYPE`. The domains live in the `eql_v3` schema, +/// so `sql_domain` is schema-qualified (e.g. `eql_v3.int4_eq`). #[derive(Debug, Clone)] pub struct ScalarDomainSpec { pub sql_domain: String, @@ -166,7 +167,7 @@ pub struct ScalarDomainSpec { impl ScalarDomainSpec { pub fn new(variant: Variant) -> Self { Self { - sql_domain: format!("eql_v2_{}{}", T::PG_TYPE, variant.suffix()), + sql_domain: format!("eql_v3.{}{}", T::PG_TYPE, variant.suffix()), variant, } } diff --git a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs index 37347228..3cacb605 100644 --- a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs +++ b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs @@ -1,17 +1,18 @@ //! Global guard for the encrypted-domain inline-critical SQL surface. //! //! `tasks/pin_search_path.sql` runs after every build and pins a fixed -//! `search_path` on every `eql_v2` function — except the inline-critical -//! ones, which must stay unpinned so the planner can inline them and the -//! documented functional indexes (`eql_v2.eq_term(col)`, -//! `eql_v2.ord_term(col)`, …) engage. +//! `search_path` on every `eql_v2`/`eql_v3` function — except the +//! inline-critical ones, which must stay unpinned so the planner can +//! inline them and the documented functional indexes (`eql_v3.eq_term(col)`, +//! `eql_v3.ord_term(col)`, …) engage. //! //! The encrypted-domain family is skipped by a structural rule anchored //! on the *identity predicate*: a `LANGUAGE sql`, `IMMUTABLE` function -//! taking at least one argument typed as a jsonb-backed DOMAIN in -//! `public` named `eql_v2_*`. The identity predicate is -//! proconfig-independent — it describes what a function intrinsically -//! IS, not whether it has been pinned. +//! taking at least one argument typed as a jsonb-backed DOMAIN of the +//! encrypted-domain families — a domain in the `eql_v3` schema (e.g. +//! `eql_v3.int4_eq`) or the legacy `public.eql_v2_*` form. The identity +//! predicate is proconfig-independent — it describes what a function +//! intrinsically IS, not whether it has been pinned. //! //! This test is the global net for that rule. It uses the identity //! predicate VERBATIM and appends one offender filter: @@ -35,11 +36,12 @@ use sqlx::PgPool; async fn no_encrypted_domain_inline_critical_function_is_pinned(pool: PgPool) -> Result<()> { // The identity predicate is shared verbatim with the structural skip // clause in tasks/pin_search_path.sql: LANGUAGE sql, IMMUTABLE, and - // taking at least one argument typed as a `public.eql_v2_*` domain - // over jsonb. It is proconfig-independent. The ONLY addition here is - // the offender filter `p.proconfig IS NOT NULL` — a function that - // matches the identity predicate but DID get pinned. That set must be - // empty. + // taking at least one argument typed as an encrypted-domain-family + // domain over jsonb (an `eql_v3.*` domain or the legacy + // `public.eql_v2_*` form). It is proconfig-independent. The ONLY + // addition here is the offender filter `p.proconfig IS NOT NULL` — a + // function that matches the identity predicate but DID get pinned. + // That set must be empty. let offenders: Vec<(String, String)> = sqlx::query_as( r#" SELECT p.oid::regprocedure::text AS signature, @@ -47,7 +49,7 @@ async fn no_encrypted_domain_inline_critical_function_is_pinned(pool: PgPool) -> FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace JOIN pg_catalog.pg_language l ON l.oid = p.prolang - WHERE n.nspname = 'eql_v2' + WHERE n.nspname IN ('eql_v2', 'eql_v3') AND l.lanname = 'sql' AND p.provolatile = 'i' AND p.proconfig IS NOT NULL @@ -58,9 +60,11 @@ async fn no_encrypted_domain_inline_critical_function_is_pinned(pool: PgPool) -> JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype WHERE dt.typtype = 'd' - AND dn.nspname = 'public' - AND dt.typname LIKE 'eql_v2\_%' AND bt.typname = 'jsonb' + AND ( + dn.nspname = 'eql_v3' + OR (dn.nspname = 'public' AND dt.typname LIKE 'eql_v2\_%') + ) ) ORDER BY signature "#, @@ -88,17 +92,18 @@ async fn every_inline_critical_eligible_domain_has_inline_critical_functions( pool: PgPool, ) -> Result<()> { // Stronger than a bare `count > 0`: if a future change accidentally - // narrows the structural predicate (e.g. hard-codes `eql_v2_int4_%`), - // a `count > 0` assertion would still pass while int8/bool/date + // narrows the structural predicate (e.g. hard-codes `int4_%`), a + // `count > 0` assertion would still pass while int8/bool/date // domains silently lose inline-critical coverage. Instead, assert - // that EVERY inline-critical-eligible domain (any `public.eql_v2_*` - // domain over jsonb that carries a capability suffix — `_eq`, `_ord`, - // `_ord_ore`) appears as an argument type of at least one - // inline-critical function. + // that EVERY inline-critical-eligible domain (any encrypted-domain + // family domain over jsonb — `eql_v3.*` or legacy `public.eql_v2_*` — + // that carries a capability suffix — `_eq`, `_ord`, `_ord_ore`) + // appears as an argument type of at least one inline-critical + // function. // - // Storage-only variants (the bare `eql_v2_` domain, with no - // capability suffix) intentionally have NO inline-critical surface - // and are excluded from the eligibility set. + // Storage-only variants (the bare `eql_v3.` / `eql_v2_` domain, + // with no capability suffix) intentionally have NO inline-critical + // surface and are excluded from the eligibility set. let unbound: Vec = sqlx::query_scalar( r#" SELECT dt.typname @@ -106,9 +111,11 @@ async fn every_inline_critical_eligible_domain_has_inline_critical_functions( JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype WHERE dt.typtype = 'd' - AND dn.nspname = 'public' AND bt.typname = 'jsonb' - AND dt.typname LIKE 'eql_v2\_%' + AND ( + dn.nspname = 'eql_v3' + OR (dn.nspname = 'public' AND dt.typname LIKE 'eql_v2\_%') + ) AND ( dt.typname LIKE '%\_eq' OR dt.typname LIKE '%\_ord' @@ -119,7 +126,7 @@ async fn every_inline_critical_eligible_domain_has_inline_critical_functions( FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace JOIN pg_catalog.pg_language l ON l.oid = p.prolang - WHERE n.nspname = 'eql_v2' + WHERE n.nspname IN ('eql_v2', 'eql_v3') AND l.lanname = 'sql' AND p.provolatile = 'i' AND dt.oid = ANY(p.proargtypes::oid[]) @@ -158,7 +165,7 @@ async fn encrypted_domain_blockers_are_plpgsql_and_non_strict(pool: PgPool) -> R FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace JOIN pg_catalog.pg_language l ON l.oid = p.prolang - WHERE n.nspname = 'eql_v2' + WHERE n.nspname IN ('eql_v2', 'eql_v3') AND (p.prosrc LIKE '%encrypted_domain_unsupported_bool%' OR p.prosrc LIKE '%is not supported for%') AND EXISTS ( @@ -168,9 +175,11 @@ async fn encrypted_domain_blockers_are_plpgsql_and_non_strict(pool: PgPool) -> R JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype WHERE dt.typtype = 'd' - AND dn.nspname = 'public' - AND dt.typname LIKE 'eql_v2\_%' AND bt.typname = 'jsonb' + AND ( + dn.nspname = 'eql_v3' + OR (dn.nspname = 'public' AND dt.typname LIKE 'eql_v2\_%') + ) ) AND (l.lanname <> 'plpgsql' OR p.proisstrict) ORDER BY signature @@ -187,10 +196,10 @@ async fn encrypted_domain_blockers_are_plpgsql_and_non_strict(pool: PgPool) -> R Ok(()) } -/// No `eql_v2_*` domain may be derived from another `eql_v2_*` domain — -/// operators resolve against the ultimate base type, so a derived domain -/// inherits jsonb's operator surface and not the base domain's blockers. -/// All family domains must be defined directly over jsonb. +/// No encrypted-domain family domain may be derived from another family +/// domain — operators resolve against the ultimate base type, so a derived +/// domain inherits jsonb's operator surface and not the base domain's +/// blockers. All family domains must be defined directly over jsonb. #[sqlx::test] async fn no_eql_v2_domain_is_derived_from_another_eql_v2_domain(pool: PgPool) -> Result<()> { let offenders: Vec<(String, String)> = sqlx::query_as( @@ -202,10 +211,15 @@ async fn no_eql_v2_domain_is_derived_from_another_eql_v2_domain(pool: PgPool) -> JOIN pg_catalog.pg_type bt ON bt.oid = dt.typbasetype JOIN pg_catalog.pg_namespace bn ON bn.oid = bt.typnamespace WHERE dt.typtype = 'd' - AND dn.nspname = 'public' - AND dt.typname LIKE 'eql_v2\_%' + AND ( + dn.nspname = 'eql_v3' + OR (dn.nspname = 'public' AND dt.typname LIKE 'eql_v2\_%') + ) AND bt.typtype = 'd' - AND bt.typname LIKE 'eql_v2\_%' + AND ( + bn.nspname = 'eql_v3' + OR (bn.nspname = 'public' AND bt.typname LIKE 'eql_v2\_%') + ) ORDER BY derived "#, ) @@ -214,16 +228,16 @@ async fn no_eql_v2_domain_is_derived_from_another_eql_v2_domain(pool: PgPool) -> assert!( offenders.is_empty(), - "eql_v2_* domains must be defined directly over jsonb, not derived \ - from another eql_v2_* domain. Offenders (derived, base): {offenders:#?}" + "encrypted-domain family domains must be defined directly over jsonb, \ + not derived from another family domain. Offenders (derived, base): {offenders:#?}" ); Ok(()) } -/// No operator class may be declared `FOR TYPE` on an `eql_v2_*` domain. -/// Opclasses on domains bypass the operator-resolution that storage -/// blockers depend on. The recommended index pattern is a functional -/// index on the extractor (e.g. `eql_v2.eq_term(col)`). +/// No operator class may be declared `FOR TYPE` on an encrypted-domain +/// family domain. Opclasses on domains bypass the operator-resolution that +/// storage blockers depend on. The recommended index pattern is a functional +/// index on the extractor (e.g. `eql_v3.eq_term(col)`). #[sqlx::test] async fn no_opclass_targets_eql_v2_domain(pool: PgPool) -> Result<()> { let offenders: Vec<(String, String)> = sqlx::query_as( @@ -235,8 +249,10 @@ async fn no_opclass_targets_eql_v2_domain(pool: PgPool) -> Result<()> { JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace JOIN pg_catalog.pg_namespace cn ON cn.oid = oc.opcnamespace WHERE t.typtype = 'd' - AND tn.nspname = 'public' - AND t.typname LIKE 'eql_v2\_%' + AND ( + tn.nspname = 'eql_v3' + OR (tn.nspname = 'public' AND t.typname LIKE 'eql_v2\_%') + ) ORDER BY opclass "#, ) @@ -245,8 +261,8 @@ async fn no_opclass_targets_eql_v2_domain(pool: PgPool) -> Result<()> { assert!( offenders.is_empty(), - "no operator class may target an eql_v2_* domain — use a functional \ - index on the extractor instead. Offenders (opclass, for_type): {offenders:#?}" + "no operator class may target an encrypted-domain family domain — use a \ + functional index on the extractor instead. Offenders (opclass, for_type): {offenders:#?}" ); Ok(()) } diff --git a/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs b/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs index 1a2fb95e..b10a848c 100644 --- a/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs +++ b/tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs @@ -1,6 +1,6 @@ //! Structural guard for the blocked native-jsonb operator enumeration. //! -//! The storage-only domains (`eql_v2_int4`, future scalars) promise that +//! The storage-only domains (`eql_v3.int4`, future scalars) promise that //! *every* native jsonb operator is blocked, so an encrypted column can never //! fall through to plaintext-jsonb semantics. That promise rests on three //! hand-maintained lists in `tasks/codegen/operator_surface.py` diff --git a/tests/sqlx/tests/encrypted_domain/family/mutations.rs b/tests/sqlx/tests/encrypted_domain/family/mutations.rs index 2745e1ae..7be20853 100644 --- a/tests/sqlx/tests/encrypted_domain/family/mutations.rs +++ b/tests/sqlx/tests/encrypted_domain/family/mutations.rs @@ -36,23 +36,23 @@ async fn mutate(pool: &PgPool, ddl: &str) -> Result<()> { // catch a blocker that silently stopped raising. #[sqlx::test] async fn disabling_storage_eq_blocker_flips_blocker_arm(pool: PgPool) -> Result<()> { - let sql = "SELECT $1::jsonb::eql_v2_int4 = $2::jsonb::eql_v2_int4"; + let sql = "SELECT $1::jsonb::eql_v3.int4 = $2::jsonb::eql_v3.int4"; // Baseline: the storage `=` blocker raises. assert_raises( &pool, sql, &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], - &blocker_msg("eql_v2_int4", "="), + &blocker_msg("eql_v3.int4", "="), ) .await?; // Mutation: replace the plpgsql blocker with an inlinable SQL body that // returns true. CREATE OR REPLACE keeps the oid, so the `=` operator on - // (eql_v2_int4, eql_v2_int4) now resolves to this no-raise body. + // (eql_v3.int4, eql_v3.int4) now resolves to this no-raise body. mutate( &pool, - "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4, b eql_v2_int4) \ + "CREATE OR REPLACE FUNCTION eql_v3.eq(a eql_v3.int4, b eql_v3.int4) \ RETURNS boolean LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$ SELECT true $$", ) .await?; @@ -84,8 +84,8 @@ async fn unsetting_restrict_flips_planner_metadata_arm(pool: PgPool) -> Result<( JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright WHERE o.oprname = '=' - AND lt.typname = 'eql_v2_int4_ord' - AND rt.typname = 'eql_v2_int4_ord' + AND lt.typname = 'int4_ord' + AND rt.typname = 'int4_ord' "#, ) .fetch_one(pool) @@ -96,14 +96,14 @@ async fn unsetting_restrict_flips_planner_metadata_arm(pool: PgPool) -> Result<( // Baseline: `=` on (ord, ord) declares a RESTRICT estimator. ensure!( restrict_present(&pool).await?, - "baseline: `=` on eql_v2_int4_ord must declare a RESTRICT estimator" + "baseline: `=` on eql_v3.int4_ord must declare a RESTRICT estimator" ); // Mutation: unset RESTRICT. DROP OPERATOR would hit COMMUTATOR/NEGATOR // dependency links; ALTER ... SET (RESTRICT = NONE) avoids that. mutate( &pool, - "ALTER OPERATOR = (eql_v2_int4_ord, eql_v2_int4_ord) SET (RESTRICT = NONE)", + "ALTER OPERATOR = (eql_v3.int4_ord, eql_v3.int4_ord) SET (RESTRICT = NONE)", ) .await?; @@ -130,7 +130,7 @@ async fn rerouting_ord_eq_through_hm_flips_ord_routes_arm(pool: PgPool) -> Resul .await?; let count_sql = "SELECT count(*) FROM fixtures.eql_v2_int4 \ - WHERE (payload - 'hm')::eql_v2_int4_ord = $1::jsonb::eql_v2_int4_ord"; + WHERE (payload - 'hm')::eql_v3.int4_ord = $1::jsonb::eql_v3.int4_ord"; // Baseline: with `hm` stripped, `=` still matches the pivot via `ord_term` // (the `ob` term survives) — exactly one row. @@ -148,7 +148,7 @@ async fn rerouting_ord_eq_through_hm_flips_ord_routes_arm(pool: PgPool) -> Resul // nothing. mutate( &pool, - "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4_ord, b eql_v2_int4_ord) \ + "CREATE OR REPLACE FUNCTION eql_v3.eq(a eql_v3.int4_ord, b eql_v3.int4_ord) \ RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ AS $$ SELECT eql_v2.hmac_256(a::jsonb) = eql_v2.hmac_256(b::jsonb) $$", ) @@ -171,7 +171,7 @@ async fn rerouting_ord_eq_through_hm_flips_ord_routes_arm(pool: PgPool) -> Resul // the `supported_null` arm has teeth. #[sqlx::test] async fn dropping_strict_on_eq_flips_supported_null_arm(pool: PgPool) -> Result<()> { - let sql = "SELECT $1::jsonb::eql_v2_int4_eq = $2::jsonb::eql_v2_int4_eq"; + let sql = "SELECT $1::jsonb::eql_v3.int4_eq = $2::jsonb::eql_v3.int4_eq"; // Baseline: STRICT `=` propagates NULL when one side is NULL. assert_null(&pool, sql, &[Some(PLACEHOLDER_PAYLOAD), None]).await?; @@ -180,7 +180,7 @@ async fn dropping_strict_on_eq_flips_supported_null_arm(pool: PgPool) -> Result< // keeps the oid; the operator now ignores NULL semantics. mutate( &pool, - "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b eql_v2_int4_eq) \ + "CREATE OR REPLACE FUNCTION eql_v3.eq(a eql_v3.int4_eq, b eql_v3.int4_eq) \ RETURNS boolean LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$ SELECT true $$", ) .await?; @@ -205,9 +205,9 @@ async fn dropping_strict_on_eq_flips_supported_null_arm(pool: PgPool) -> Result< // sort key. Blocking `<` alone must not disturb ORDER BY. #[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] async fn blocking_lt_flips_lt_arm_but_not_order_by(pool: PgPool) -> Result<()> { - let lt_sql = "SELECT $1::jsonb::eql_v2_int4_ord < $2::jsonb::eql_v2_int4_ord"; + let lt_sql = "SELECT $1::jsonb::eql_v3.int4_ord < $2::jsonb::eql_v3.int4_ord"; let order_by_sql = "SELECT plaintext FROM fixtures.eql_v2_int4 \ - ORDER BY eql_v2.ord_term(payload::eql_v2_int4_ord) ASC"; + ORDER BY eql_v3.ord_term(payload::eql_v3.int4_ord) ASC"; let mut ascending: Vec = ::FIXTURE_VALUES.to_vec(); ascending.sort(); @@ -228,13 +228,13 @@ async fn blocking_lt_flips_lt_arm_but_not_order_by(pool: PgPool) -> Result<()> { "baseline: ORDER BY ord_term ASC must be plaintext-sorted" ); - // Mutation: turn `eql_v2.lt(_ord, _ord)` into a blocker. Must be + // Mutation: turn `eql_v3.lt(_ord, _ord)` into a blocker. Must be // LANGUAGE plpgsql and non-STRICT so the RAISE always fires. mutate( &pool, - "CREATE OR REPLACE FUNCTION eql_v2.lt(a eql_v2_int4_ord, b eql_v2_int4_ord) \ + "CREATE OR REPLACE FUNCTION eql_v3.lt(a eql_v3.int4_ord, b eql_v3.int4_ord) \ RETURNS boolean LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE \ - AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4_ord', '<'); END; $$", + AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '<'); END; $$", ) .await?; @@ -243,7 +243,7 @@ async fn blocking_lt_flips_lt_arm_but_not_order_by(pool: PgPool) -> Result<()> { &pool, lt_sql, &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], - &blocker_msg("eql_v2_int4_ord", "<"), + &blocker_msg("eql_v3.int4_ord", "<"), ) .await?; @@ -283,7 +283,7 @@ async fn rerouting_eq_eq_through_ob_flips_eq_arm(pool: PgPool) -> Result<()> { .await?; let count_sql = "SELECT count(*) FROM fixtures.eql_v2_int4 \ - WHERE (payload - 'ob')::eql_v2_int4_eq = $1::jsonb::eql_v2_int4_eq"; + WHERE (payload - 'ob')::eql_v3.int4_eq = $1::jsonb::eql_v3.int4_eq"; // Baseline: with `ob` stripped, `=` still matches the pivot via `eq_term` // (the `hm` term survives) — exactly one row. @@ -300,7 +300,7 @@ async fn rerouting_eq_eq_through_ob_flips_eq_arm(pool: PgPool) -> Result<()> { // `eql_v2.ore_block_u64_8_256(jsonb)` raises rather than matching. mutate( &pool, - "CREATE OR REPLACE FUNCTION eql_v2.eq(a eql_v2_int4_eq, b eql_v2_int4_eq) \ + "CREATE OR REPLACE FUNCTION eql_v3.eq(a eql_v3.int4_eq, b eql_v3.int4_eq) \ RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) = eql_v2.ore_block_u64_8_256(b::jsonb) $$", ) @@ -334,7 +334,7 @@ async fn rerouting_eq_eq_through_ob_flips_eq_arm(pool: PgPool) -> Result<()> { #[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] async fn collapsing_ord_term_flips_order_by_arm(pool: PgPool) -> Result<()> { let order_by_desc = "SELECT plaintext FROM fixtures.eql_v2_int4 \ - ORDER BY eql_v2.ord_term(payload::eql_v2_int4_ord) DESC"; + ORDER BY eql_v3.ord_term(payload::eql_v3.int4_ord) DESC"; let mut descending: Vec = ::FIXTURE_VALUES.to_vec(); descending.sort(); @@ -353,7 +353,7 @@ async fn collapsing_ord_term_flips_order_by_arm(pool: PgPool) -> Result<()> { // function body. let const_payload = fetch_fixture_payload::(&pool, 0).await?; let ddl = format!( - "CREATE OR REPLACE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord) \ + "CREATE OR REPLACE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord) \ RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ AS $mutbody$ SELECT eql_v2.ore_block_u64_8_256('{esc}'::jsonb) $mutbody$", esc = const_payload.replace('\'', "''"), @@ -384,11 +384,11 @@ async fn making_ord_term_non_strict_flips_order_by_nulls_arm(pool: PgPool) -> Re const NULL_ROWS: usize = 3; let order_by = format!( "SELECT plaintext FROM ( \ - SELECT plaintext, payload::eql_v2_int4_ord AS value FROM fixtures.eql_v2_int4 \ + SELECT plaintext, payload::eql_v3.int4_ord AS value FROM fixtures.eql_v2_int4 \ UNION ALL \ - SELECT NULL::int4, NULL::eql_v2_int4_ord FROM generate_series(1, {NULL_ROWS}) \ + SELECT NULL::int4, NULL::eql_v3.int4_ord FROM generate_series(1, {NULL_ROWS}) \ ) s \ - ORDER BY eql_v2.ord_term(value) ASC NULLS LAST" + ORDER BY eql_v3.ord_term(value) ASC NULLS LAST" ); let tail_all_none = @@ -408,10 +408,10 @@ async fn making_ord_term_non_strict_flips_order_by_nulls_arm(pool: PgPool) -> Re // unchanged. Unique dollar-quote tag guards the embedded jsonb literal. let const_payload = fetch_fixture_payload::(&pool, 0).await?; let ddl = format!( - "CREATE OR REPLACE FUNCTION eql_v2.ord_term(a eql_v2_int4_ord) \ + "CREATE OR REPLACE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord) \ RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE PARALLEL SAFE \ AS $mutbody$ SELECT eql_v2.ore_block_u64_8_256(\ - coalesce(a, '{esc}'::jsonb::eql_v2_int4_ord)::jsonb) $mutbody$", + coalesce(a, '{esc}'::jsonb::eql_v3.int4_ord)::jsonb) $mutbody$", esc = const_payload.replace('\'', "''"), ); mutate(&pool, &ddl).await?; diff --git a/tests/sqlx/tests/encrypted_domain/family/support.rs b/tests/sqlx/tests/encrypted_domain/family/support.rs index b062b9ce..08b19c04 100644 --- a/tests/sqlx/tests/encrypted_domain/family/support.rs +++ b/tests/sqlx/tests/encrypted_domain/family/support.rs @@ -13,29 +13,29 @@ use sqlx::PgPool; #[test] fn variant_derives_consistent_sql_domain_and_capabilities() { let storage = ScalarDomainSpec::new::(Variant::Storage); - assert_eq!(storage.sql_domain, "eql_v2_int4"); + assert_eq!(storage.sql_domain, "eql_v3.int4"); assert!(!storage.supports_eq()); assert!(!storage.supports_ord()); assert_eq!(storage.extractor_fn(), None); assert_eq!(Variant::Storage.required_term(), None); let eq = ScalarDomainSpec::new::(Variant::Eq); - assert_eq!(eq.sql_domain, "eql_v2_int4_eq"); + assert_eq!(eq.sql_domain, "eql_v3.int4_eq"); assert!(eq.supports_eq()); assert!(!eq.supports_ord()); - assert_eq!(eq.extractor_fn(), Some("eql_v2.eq_term")); + assert_eq!(eq.extractor_fn(), Some("eql_v3.eq_term")); assert_eq!(Variant::Eq.required_term(), Some("hm")); let ord = ScalarDomainSpec::new::(Variant::Ord); - assert_eq!(ord.sql_domain, "eql_v2_int4_ord"); + assert_eq!(ord.sql_domain, "eql_v3.int4_ord"); assert!(ord.supports_ord()); - assert_eq!(ord.extractor_fn(), Some("eql_v2.ord_term")); + assert_eq!(ord.extractor_fn(), Some("eql_v3.ord_term")); assert_eq!(Variant::Ord.required_term(), Some("ob")); let ord_ore = ScalarDomainSpec::new::(Variant::OrdOre); - assert_eq!(ord_ore.sql_domain, "eql_v2_int4_ord_ore"); + assert_eq!(ord_ore.sql_domain, "eql_v3.int4_ord_ore"); assert!(ord_ore.supports_ord()); - assert_eq!(ord_ore.extractor_fn(), Some("eql_v2.ord_term")); + assert_eq!(ord_ore.extractor_fn(), Some("eql_v3.ord_term")); } #[test] @@ -103,8 +103,8 @@ async fn fetch_fixture_payload_returns_keyed_row(pool: PgPool) -> Result<()> { #[sqlx::test(fixtures(path = "../../../fixtures", scripts("eql_v2_int4")))] async fn assert_scalar_plaintexts_reports_sql_context(pool: PgPool) -> Result<()> { let lit = sql_string_literal(&fetch_fixture_payload::(&pool, 42).await?); - let predicate = format!("payload::eql_v2_int4_ord_ore = {lit}::jsonb::eql_v2_int4_ord_ore"); - assert_scalar_plaintexts::(&pool, "eql_v2_int4_ord_ore", "=", &predicate, &[42]).await?; + let predicate = format!("payload::eql_v3.int4_ord_ore = {lit}::jsonb::eql_v3.int4_ord_ore"); + assert_scalar_plaintexts::(&pool, "eql_v3.int4_ord_ore", "=", &predicate, &[42]).await?; Ok(()) } @@ -134,10 +134,10 @@ async fn placeholder_payload_satisfies_every_variant_check(pool: PgPool) -> Resu #[sqlx::test] async fn assert_raises_two_bind_blocker(pool: PgPool) -> Result<()> { - let msg = blocker_msg("eql_v2_int4", "="); + let msg = blocker_msg("eql_v3.int4", "="); assert_raises( &pool, - "SELECT $1::jsonb::eql_v2_int4 = $2::jsonb::eql_v2_int4", + "SELECT $1::jsonb::eql_v3.int4 = $2::jsonb::eql_v3.int4", &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], &msg, ) @@ -146,10 +146,10 @@ async fn assert_raises_two_bind_blocker(pool: PgPool) -> Result<()> { #[sqlx::test] async fn assert_raises_one_bind_path_blocker(pool: PgPool) -> Result<()> { - let msg = blocker_msg("eql_v2_int4", "->"); + let msg = blocker_msg("eql_v3.int4", "->"); assert_raises( &pool, - "SELECT $1::jsonb::eql_v2_int4 -> 'field'::text", + "SELECT $1::jsonb::eql_v3.int4 -> 'field'::text", &[Some(PLACEHOLDER_PAYLOAD)], &msg, ) @@ -162,7 +162,7 @@ async fn assert_raises_native_operator_absent(pool: PgPool) -> Result<()> { // "operator does not exist", not an EQL blocker message. assert_raises( &pool, - "SELECT $1::jsonb::eql_v2_int4 ~~ $2::jsonb::eql_v2_int4", + "SELECT $1::jsonb::eql_v3.int4 ~~ $2::jsonb::eql_v3.int4", &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], "operator does not exist", ) @@ -173,79 +173,79 @@ async fn assert_raises_native_operator_absent(pool: PgPool) -> Result<()> { async fn omitted_native_jsonb_operators_raise_eql_blockers(pool: PgPool) -> Result<()> { let cases: &[(&str, &[Option<&str>], &str)] = &[ ( - "SELECT $1::jsonb::eql_v2_int4 ? 'c'::text", + "SELECT $1::jsonb::eql_v3.int4 ? 'c'::text", &[Some(PLACEHOLDER_PAYLOAD)], "?", ), ( - "SELECT $1::jsonb::eql_v2_int4 ?| ARRAY['c']", + "SELECT $1::jsonb::eql_v3.int4 ?| ARRAY['c']", &[Some(PLACEHOLDER_PAYLOAD)], "?|", ), ( - "SELECT $1::jsonb::eql_v2_int4 ?& ARRAY['c']", + "SELECT $1::jsonb::eql_v3.int4 ?& ARRAY['c']", &[Some(PLACEHOLDER_PAYLOAD)], "?&", ), ( - "SELECT $1::jsonb::eql_v2_int4 #> ARRAY['i']", + "SELECT $1::jsonb::eql_v3.int4 #> ARRAY['i']", &[Some(PLACEHOLDER_PAYLOAD)], "#>", ), ( - "SELECT $1::jsonb::eql_v2_int4 #>> ARRAY['i', 'c']", + "SELECT $1::jsonb::eql_v3.int4 #>> ARRAY['i', 'c']", &[Some(PLACEHOLDER_PAYLOAD)], "#>>", ), ( - "SELECT $1::jsonb::eql_v2_int4 @? '$.c'::jsonpath", + "SELECT $1::jsonb::eql_v3.int4 @? '$.c'::jsonpath", &[Some(PLACEHOLDER_PAYLOAD)], "@?", ), ( - "SELECT $1::jsonb::eql_v2_int4 @@ '$.c == \"placeholder\"'::jsonpath", + "SELECT $1::jsonb::eql_v3.int4 @@ '$.c == \"placeholder\"'::jsonpath", &[Some(PLACEHOLDER_PAYLOAD)], "@@", ), ( - "SELECT $1::jsonb::eql_v2_int4 - 'c'::text", + "SELECT $1::jsonb::eql_v3.int4 - 'c'::text", &[Some(PLACEHOLDER_PAYLOAD)], "-", ), ( - "SELECT $1::jsonb::eql_v2_int4 - 0", + "SELECT $1::jsonb::eql_v3.int4 - 0", &[Some(PLACEHOLDER_PAYLOAD)], "-", ), ( - "SELECT $1::jsonb::eql_v2_int4 - ARRAY['c']", + "SELECT $1::jsonb::eql_v3.int4 - ARRAY['c']", &[Some(PLACEHOLDER_PAYLOAD)], "-", ), ( - "SELECT $1::jsonb::eql_v2_int4 #- ARRAY['i']", + "SELECT $1::jsonb::eql_v3.int4 #- ARRAY['i']", &[Some(PLACEHOLDER_PAYLOAD)], "#-", ), ( - "SELECT $1::jsonb::eql_v2_int4 || $2::jsonb", + "SELECT $1::jsonb::eql_v3.int4 || $2::jsonb", &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], "||", ), ( - "SELECT $1::jsonb || $2::jsonb::eql_v2_int4", + "SELECT $1::jsonb || $2::jsonb::eql_v3.int4", &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], "||", ), ( - "SELECT $1::jsonb::eql_v2_int4 || $2::jsonb::eql_v2_int4", + "SELECT $1::jsonb::eql_v3.int4 || $2::jsonb::eql_v3.int4", &[Some(PLACEHOLDER_PAYLOAD), Some(PLACEHOLDER_PAYLOAD)], "||", ), ]; for (sql, binds, op) in cases { - assert_raises(&pool, sql, binds, &blocker_msg("eql_v2_int4", op)).await?; + assert_raises(&pool, sql, binds, &blocker_msg("eql_v3.int4", op)).await?; } Ok(()) } @@ -253,10 +253,10 @@ async fn omitted_native_jsonb_operators_raise_eql_blockers(pool: PgPool) -> Resu #[sqlx::test] async fn assert_raises_engages_on_all_null(pool: PgPool) -> Result<()> { // Non-STRICT blocker proof — must raise even with NULL on both sides. - let msg = blocker_msg("eql_v2_int4", "="); + let msg = blocker_msg("eql_v3.int4", "="); assert_raises( &pool, - "SELECT $1::jsonb::eql_v2_int4 = $2::jsonb::eql_v2_int4", + "SELECT $1::jsonb::eql_v3.int4 = $2::jsonb::eql_v3.int4", &[None, None], &msg, ) @@ -268,7 +268,7 @@ async fn assert_null_propagates_through_supported_op(pool: PgPool) -> Result<()> // STRICT supported op with one NULL operand yields NULL. assert_null( &pool, - "SELECT $1::jsonb::eql_v2_int4_eq = $2::jsonb::eql_v2_int4_eq", + "SELECT $1::jsonb::eql_v3.int4_eq = $2::jsonb::eql_v3.int4_eq", &[Some(PLACEHOLDER_PAYLOAD), None], ) .await @@ -286,7 +286,7 @@ async fn neq_propagates_null_under_three_valued_logic(pool: PgPool) -> Result<() ] { assert_null( &pool, - "SELECT $1::jsonb::eql_v2_int4_eq <> $2::jsonb::eql_v2_int4_eq", + "SELECT $1::jsonb::eql_v3.int4_eq <> $2::jsonb::eql_v3.int4_eq", binds, ) .await?; @@ -297,7 +297,7 @@ async fn neq_propagates_null_under_three_valued_logic(pool: PgPool) -> Result<() #[sqlx::test] async fn no_cross_variant_equality_operator_is_declared(pool: PgPool) -> Result<()> { // The family deliberately does NOT define operators that mix two - // different capability variants — `eql_v2_int4_eq = eql_v2_int4_ord` + // different capability variants — `eql_v3.int4_eq = eql_v3.int4_ord` // would resolve against jsonb (the ultimate base type) and silently // bypass the per-variant blockers. If someone accidentally adds such // an operator, this test fails. @@ -311,9 +311,11 @@ async fn no_cross_variant_equality_operator_is_declared(pool: PgPool) -> Result< o.oprname, lt.typname, rt.typname) FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft + JOIN pg_catalog.pg_namespace ln ON ln.oid = lt.typnamespace JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright - WHERE lt.typname LIKE 'eql_v2\_%' - AND rt.typname LIKE 'eql_v2\_%' + JOIN pg_catalog.pg_namespace rn ON rn.oid = rt.typnamespace + WHERE ln.nspname = 'eql_v3' + AND rn.nspname = 'eql_v3' AND lt.typname <> rt.typname ORDER BY 1 "#, @@ -323,7 +325,7 @@ async fn no_cross_variant_equality_operator_is_declared(pool: PgPool) -> Result< assert!( cross_variant.is_empty(), - "no operator should mix two different eql_v2_* domain types, but found: {cross_variant:#?}" + "no operator should mix two different eql_v3 domain types, but found: {cross_variant:#?}" ); Ok(()) } diff --git a/tests/sqlx/tests/lint_tests.rs b/tests/sqlx/tests/lint_tests.rs index f9194b82..0387e396 100644 --- a/tests/sqlx/tests/lint_tests.rs +++ b/tests/sqlx/tests/lint_tests.rs @@ -96,15 +96,15 @@ async fn lint_categories_are_well_known(pool: PgPool) -> Result<()> { /// planner can fold or elide the call when the result is provably unused /// (a dead CASE branch, a folded predicate), silently bypassing the RAISE /// and re-enabling the operator. See CLAUDE.md footguns. This test plants -/// a fake LANGUAGE sql blocker on `eql_v2_int4` and asserts the lint +/// a fake LANGUAGE sql blocker on `eql_v3.int4` and asserts the lint /// surfaces it under category `blocker_language`. #[sqlx::test] async fn lint_flags_blocker_in_language_sql(pool: PgPool) -> Result<()> { sqlx::query( r#" - CREATE FUNCTION eql_v2.test_bad_blocker_sql(a eql_v2_int4, b eql_v2_int4) + CREATE FUNCTION eql_v2.test_bad_blocker_sql(a eql_v3.int4, b eql_v3.int4) RETURNS boolean LANGUAGE sql IMMUTABLE - AS $$ SELECT eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '=') $$; + AS $$ SELECT eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '=') $$; "#, ) .execute(&pool) @@ -134,15 +134,15 @@ async fn lint_flags_blocker_in_language_sql(pool: PgPool) -> Result<()> { /// A blocker marked `STRICT` lets PostgreSQL skip the body and return NULL /// on a NULL argument — silently bypassing the "operator not supported" /// RAISE. See CLAUDE.md footguns. This test plants a fake STRICT plpgsql -/// blocker on `eql_v2_int4` and asserts the lint surfaces it under +/// blocker on `eql_v3.int4` and asserts the lint surfaces it under /// `blocker_strict`. #[sqlx::test] async fn lint_flags_strict_blocker(pool: PgPool) -> Result<()> { sqlx::query( r#" - CREATE FUNCTION eql_v2.test_bad_blocker_strict(a eql_v2_int4, b eql_v2_int4) + CREATE FUNCTION eql_v2.test_bad_blocker_strict(a eql_v3.int4, b eql_v3.int4) RETURNS boolean LANGUAGE plpgsql IMMUTABLE STRICT - AS $$ BEGIN RETURN eql_v2.encrypted_domain_unsupported_bool('eql_v2_int4', '='); END; $$; + AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '='); END; $$; "#, ) .execute(&pool) @@ -186,7 +186,7 @@ async fn lint_does_not_report_generated_blockers_as_inlinability_errors( | "inlinability_volatility" | "inlinability_set_clause" | "inlinability_secdef" - ) && r.object_name.contains("eql_v2_int4") + ) && r.object_name.contains("eql_v3.int4") && (r.object_name.contains("operator =(") || r.object_name.contains("operator ->(") || r.object_name.contains("operator ?(")) @@ -209,7 +209,7 @@ async fn lint_does_not_report_generated_blockers_as_inlinability_errors( /// surfaces it under `domain_over_domain`. #[sqlx::test] async fn lint_flags_domain_over_domain(pool: PgPool) -> Result<()> { - sqlx::query(r#"CREATE DOMAIN public.eql_v2_test_baddom AS public.eql_v2_int4;"#) + sqlx::query(r#"CREATE DOMAIN public.eql_v2_test_baddom AS eql_v3.int4;"#) .execute(&pool) .await?; @@ -330,7 +330,7 @@ async fn scalar_family_inlinable_operators_are_clean(pool: PgPool) -> Result<()> if matches!(variant, Variant::Storage) { continue; } - let domain = format!("eql_v2_{pg_type}{}", variant.suffix()); + let domain = format!("eql_v3.{pg_type}{}", variant.suffix()); let supported_ops: &[&str] = if variant.supports_ord() { &["=", "<>", "<", "<=", ">", ">="] } else { From da300da33f24b5a00b2317b9a0d0776536638efc Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 15:21:31 +1000 Subject: [PATCH 22/93] test(encrypted-domain): point int4 matrix tests at eql_v3 schema The int4 domain family moved from eql_v2 to eql_v3, but three generated matrix tests still hardcoded eql_v2. Fixes the CI failures on PG 15/16/17: - scale_default_combos used eql_v2.ord_term as the index extractor, but the ord column is eql_v3.int4_ord (extractor is eql_v3.ord_term). - the aggregate parallel-safety introspection query filtered pg_proc on 'eql_v2'::regnamespace, finding no min/max aggregate (now in eql_v3). Test-only; no production SQL change. --- tests/sqlx/src/matrix.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/sqlx/src/matrix.rs b/tests/sqlx/src/matrix.rs index 8b8c0923..6227de53 100644 --- a/tests/sqlx/src/matrix.rs +++ b/tests/sqlx/src/matrix.rs @@ -204,7 +204,7 @@ macro_rules! ordered_numeric_matrix { // converged ordered domain, ord_term btree. One curated combo keeps // PR CI cost bounded. scale_default_combos = [ - (ord, Ord, "eql_v2.ord_term", "btree"), + (ord, Ord, "eql_v3.ord_term", "btree"), ], } }; @@ -2298,7 +2298,7 @@ macro_rules! __scalar_matrix_aggregate_parallel_case { FROM pg_proc p \ JOIN pg_aggregate a ON a.aggfnoid = p.oid \ WHERE p.proname = $1 \ - AND p.pronamespace = 'eql_v2'::regnamespace \ + AND p.pronamespace = 'eql_v3'::regnamespace \ AND p.proargtypes[0]::regtype = $2::regtype", ) .bind(agg) @@ -2306,9 +2306,9 @@ macro_rules! __scalar_matrix_aggregate_parallel_case { .fetch_one(&pool) .await?; anyhow::ensure!(proparallel == "s", - "eql_v2.{agg}({d}) must be PARALLEL SAFE (proparallel='s'), got {proparallel:?}"); + "eql_v3.{agg}({d}) must be PARALLEL SAFE (proparallel='s'), got {proparallel:?}"); anyhow::ensure!(has_combine, - "eql_v2.{agg}({d}) must declare a combinefunc for partial aggregation"); + "eql_v3.{agg}({d}) must declare a combinefunc for partial aggregation"); } Ok(()) } From 59f3a35bd5bde0e86e811c35802c6e53c222fab8 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 16:01:45 +1000 Subject: [PATCH 23/93] fix(lint): describe encrypted-domains namespace-neutrally The domain_over_domain and domain_opclass lint messages hard-coded 'eql_v2_*' and recommended eql_v2.eq_term/ord_term. Now that these lints also target eql_v3 domains, a message firing on an eql_v3 domain pointed at the wrong surface. Drop the version-specific label ('another encrypted-domain', 'an encrypted-domain type') and build the extractor recommendation from the offending domain's own schema (%s.eq_term / %s.ord_term via tn.nspname) so remediation is correct in either namespace. Addresses CodeRabbit review feedback on #247. --- src/lint/lints.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lint/lints.sql b/src/lint/lints.sql index bf38c1e6..cf66f7d4 100644 --- a/src/lint/lints.sql +++ b/src/lint/lints.sql @@ -304,7 +304,7 @@ AS $$ WHERE isstrict -- ┌─────────────────────────────────────────────────────────────────┐ - -- │ Domain identity: an eql_v2_* domain must be defined directly │ + -- │ Domain identity: an encrypted-domain must be defined directly │ -- │ over jsonb. Operators resolve against the ultimate base type, │ -- │ so domain-over-domain inherits jsonb's operator surface and not │ -- │ the base domain's blockers. │ @@ -317,7 +317,7 @@ AS $$ 'domain_over_domain', format('domain %I.%I', dn.nspname, dt.typname), format( - 'Domain `%s.%s` is derived from another eql_v2_* domain `%s.%s` rather than jsonb. Operators resolve against the ultimate base type, so the derived domain does not inherit the base domain''s operator surface and storage blockers do not engage. Define this domain directly over jsonb.', + 'Domain `%s.%s` is derived from another encrypted-domain `%s.%s` rather than jsonb. Operators resolve against the ultimate base type, so the derived domain does not inherit the base domain''s operator surface and storage blockers do not engage. Define this domain directly over jsonb.', dn.nspname, dt.typname, bn.nspname, bt.typname) FROM pg_catalog.pg_type dt JOIN pg_catalog.pg_namespace dn ON dn.oid = dt.typnamespace @@ -336,7 +336,7 @@ AS $$ -- ┌─────────────────────────────────────────────────────────────────┐ -- │ Domain opclass: an operator class declared FOR TYPE on an │ - -- │ eql_v2_* domain bypasses operator resolution at index time. │ + -- │ encrypted-domain bypasses operator resolution at index time. │ -- │ Use a functional index on the extractor instead. │ -- └─────────────────────────────────────────────────────────────────┘ @@ -347,8 +347,8 @@ AS $$ 'domain_opclass', format('opclass %I.%I FOR TYPE %s.%s', cn.nspname, oc.opcname, tn.nspname, t.typname), format( - 'Operator class `%s.%s` is declared FOR TYPE `%s.%s`, which is an eql_v2_* domain. Opclasses on domains bypass operator resolution. Use a functional index on the extractor (e.g. `eql_v2.eq_term(col)`, `eql_v2.ord_term(col)`) instead.', - cn.nspname, oc.opcname, tn.nspname, t.typname) + 'Operator class `%s.%s` is declared FOR TYPE `%s.%s`, which is an encrypted-domain type. Opclasses on domains bypass operator resolution. Use a functional index on the extractor (e.g. `%s.eq_term(col)`, `%s.ord_term(col)`) instead.', + cn.nspname, oc.opcname, tn.nspname, t.typname, tn.nspname, tn.nspname) FROM pg_catalog.pg_opclass oc JOIN pg_catalog.pg_type t ON t.oid = oc.opcintype JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace From 5576136b8b5cd8adc7a8916a3b15c57d9935fdc8 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 16:01:45 +1000 Subject: [PATCH 24/93] test(matrix): assert real index-scan node in scale-preference sweep The feature-gated scale-preference sweep arm asserted plan_text.contains(index) on raw EXPLAIN text, which can false-pass on an incidental mention of the index name. Switch it to the JSON-plan helper assert_index_scan_uses, matching the always-on default-category arm and every other EXPLAIN assertion in the file, so the sweep proves a genuine Index/Index-Only/Bitmap-Index scan node rather than a substring. Addresses CodeRabbit review feedback on #247. --- tests/sqlx/src/matrix.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/sqlx/src/matrix.rs b/tests/sqlx/src/matrix.rs index 6227de53..ac690a16 100644 --- a/tests/sqlx/src/matrix.rs +++ b/tests/sqlx/src/matrix.rs @@ -1205,15 +1205,15 @@ SELECT $1::jsonb::{d} FROM generate_series(1, 5000)", .execute(&mut *tx).await?; let lit = pivot_payload.replace('\'', "''"); - let plan: Vec = sqlx::query_scalar(&format!( - "EXPLAIN SELECT * FROM {table} WHERE value = '{lit}'::jsonb::{d}", - )).fetch_all(&mut *tx).await?; - let plan_text = plan.join("\n"); - anyhow::ensure!(plan_text.contains(index), - "with seqscan enabled the planner must prefer the {extractor} \ -{using} index for a selective = ; plan:\n{plan_text}", - extractor = $extractor, using = $using, - ); + $crate::matrix::assert_index_scan_uses( + &mut *tx, + &format!("SELECT * FROM {table} WHERE value = '{lit}'::jsonb::{d}"), + index, + &format!( + "with seqscan enabled the planner must prefer the {extractor} {using} index for a selective =", + extractor = $extractor, using = $using, + ), + ).await?; tx.commit().await?; Ok(()) From 268181170c31db7e01864b1d9693a7e0f3503cee Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 14:16:51 +1000 Subject: [PATCH 25/93] feat(encrypted-domain): add int2 scalar domain family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the int2 ordered numeric scalar to the generated encrypted-domain family, stacked on the int4 reference. Four jsonb-backed domains (eql_v2_int2{,_eq,_ord,_ord_ore}) are generated from the new tasks/codegen/types/int2.toml manifest by the existing type-generic materializer — no generator behaviour changes. - Register the int2 ScalarKind (i16, MIN -32768 / MAX 32767 / zero) in tasks/codegen/scalars.py, with test_scalars.py coverage. - Commit the generated fixture-value const tests/sqlx/src/fixtures/int2_values.rs (single source of truth shared by the fixture generator and the matrix oracle). - Wire the SQLx matrix oracle: impl ScalarType for i16, the eql_v2_int2 fixture via the scalar_fixture! macro (mirroring eql_v2_int4), and the sealed EqlPlaintext impl for i16 (small_int cast, Plaintext::SmallInt, smallint oracle column). - Add the ordered_numeric_matrix! invocation and the int2 matrix test-name inventory snapshot. - Record the new family in CHANGELOG.md. Keep the codegen reference int4-only: it is a golden master for the type-generic generator, so one anchor detects all template/term drift. New scalar types add no per-type baseline; documented in the spec and tests/codegen/reference/README.md. int2 is guaranteed by the int4 reference, the int2_values.rs staleness guard + test_scalars.py, and the SQLx matrix. --- .github/workflows/test-eql.yml | 1 + CHANGELOG.md | 1 + CLAUDE.md | 1 + docs/reference/encrypted-domain-generator.md | 23 +- .../encrypted-domain-implementation-spec.md | 45 +++- mise.toml | 14 +- tasks/codegen/scalars.py | 9 + tasks/codegen/test_scalars.py | 18 ++ tasks/codegen/types/int2.toml | 19 ++ tests/codegen/reference/README.md | 12 +- tests/sqlx/snapshots/README.md | 53 +++++ tests/sqlx/snapshots/int2_matrix_tests.txt | 211 ++++++++++++++++++ tests/sqlx/src/fixtures/eql_plaintext.rs | 34 +++ tests/sqlx/src/fixtures/eql_v2_int2.rs | 12 + tests/sqlx/src/fixtures/int2_values.rs | 33 +++ tests/sqlx/src/fixtures/mod.rs | 6 + tests/sqlx/src/scalar_domains.rs | 9 + .../tests/encrypted_domain/scalars/int2.rs | 14 ++ .../tests/encrypted_domain/scalars/mod.rs | 2 + 19 files changed, 495 insertions(+), 22 deletions(-) create mode 100644 tasks/codegen/types/int2.toml create mode 100644 tests/sqlx/snapshots/README.md create mode 100644 tests/sqlx/snapshots/int2_matrix_tests.txt create mode 100644 tests/sqlx/src/fixtures/eql_v2_int2.rs create mode 100644 tests/sqlx/src/fixtures/int2_values.rs create mode 100644 tests/sqlx/tests/encrypted_domain/scalars/int2.rs diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 436df886..ff82bb42 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -121,6 +121,7 @@ jobs: run: | mise run test:matrix:inventory git diff --exit-code -- tests/sqlx/snapshots/int4_matrix_tests.txt \ + tests/sqlx/snapshots/int2_matrix_tests.txt \ || { echo "Coverage inventory stale — run 'mise run test:matrix:inventory' and commit."; exit 1; } test: diff --git a/CHANGELOG.md b/CHANGELOG.md index 00215fd2..c75b70db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Each entry that ships in a published release links to the PR that introduced it. ### Added - **`eql_v3` encrypted-domain schema, with the `int4` family as its first member.** Encrypted-domain type families now live in a new, additional `eql_v3` schema (the existing `eql_v2` schema is unchanged — it keeps the core types/operators and stays the documented public API). Four jsonb-backed domains for encrypted `int4` columns: `eql_v3.int4` (storage-only), `eql_v3.int4_eq` (`=` / `<>` via HMAC), and `eql_v3.int4_ord` / `eql_v3.int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. The extractors still return the core `eql_v2.hmac_256` / `eql_v2.ore_block_u64_8_256` index-term types, which remain in `eql_v2` and are referenced cross-schema. Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`, namespaced under its own schema. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) +- **`eql_v3.int2` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int2` columns — `eql_v3.int2` (storage-only), `eql_v3.int2_eq` (`=` / `<>` via HMAC), and `eql_v3.int2_ord` / `eql_v3.int2_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from `tasks/codegen/types/int2.toml` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `smallint` column, proving the scalar generator generalizes beyond the `int4` reference. ([#243](https://github.com/cipherstash/encrypt-query-language/pull/243)) - **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v3.min(eql_v3._ord)` / `eql_v3.max(eql_v3._ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) ## [2.3.1] — 2026-05-21 diff --git a/CLAUDE.md b/CLAUDE.md index 1328a8b0..4b489123 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,6 +29,7 @@ This project uses `mise` for task management. Common commands: - Run SQLx tests directly: `mise run test:sqlx` - Run SQLx tests in watch mode: `mise run test:sqlx:watch` - Tests are located in `tests/sqlx/` using Rust and SQLx framework +- Regenerate the scalar matrix coverage snapshots: `mise run test:matrix:inventory` (no database required). These committed `tests/sqlx/snapshots/_matrix_tests.txt` baselines pin the set of `scalars::::*` test names so a silently dropped/renamed/`#[cfg]`-gated test fails CI's `matrix-coverage` job. When you add or remove matrix tests (or add a scalar type), regenerate and commit the affected snapshot in the same change. See `tests/sqlx/snapshots/README.md`. ### Build System - Dependencies are resolved using `-- REQUIRE:` comments in SQL files diff --git a/docs/reference/encrypted-domain-generator.md b/docs/reference/encrypted-domain-generator.md index bb6252be..9b2d6a07 100644 --- a/docs/reference/encrypted-domain-generator.md +++ b/docs/reference/encrypted-domain-generator.md @@ -377,12 +377,23 @@ The end-to-end shape from a generator perspective: 4. **Build picks it up automatically** — `tasks/build.sh` regenerates before computing the `tsort` graph, so the new files appear in the dependency walk via the `-- REQUIRE:` edges the generator emits. -5. **Baseline & test.** Create a hand-reviewed byte-parity baseline under - `tests/codegen/reference//` (each file marked `-- REFERENCE:` / - `// REFERENCE:`) so `test_against_reference.py` guards the new type — it - only covers types that have a baseline directory. Then run - `mise run test:codegen`, the relevant SQLx suites, and the PostgreSQL - matrix. +5. **Test.** Do **not** add a `tests/codegen/reference//` baseline. + `int4` is the sole golden master for the type-generic generator: the SQL + templates are pure token substitution and the only type-specific rendering + is `_values.rs`, so a per-type baseline can only fail where `int4`'s + already would. Drift protection for the new type comes from the `int4` + reference (shared templates + `terms.py`), the committed `_values.rs` + const guarded by the codegen staleness check, the `` cases in + `test_scalars.py`, and the `ordered_numeric_matrix!` SQLx suite (behaviour, + not bytes). Run `mise run test:codegen`, the relevant SQLx suites, and the + PostgreSQL matrix. +6. **Snapshot the matrix inventory.** Run `mise run test:matrix:inventory` + and commit the new `tests/sqlx/snapshots/_matrix_tests.txt` — the + sorted list of the type's `scalars::::*` test names. CI's + `matrix-coverage` job `git diff --exit-code`s it (like `_values.rs`) + to catch a silently dropped or renamed matrix test. The snapshot is a + committed test baseline, not gitignored generated SQL. See + `tests/sqlx/snapshots/README.md`. Adding a new **term** is a bigger move — edit `terms.py`, add tests, audit `splinter.sh` for a name collision, and update the reference diff --git a/docs/reference/encrypted-domain-implementation-spec.md b/docs/reference/encrypted-domain-implementation-spec.md index c6bec96a..e21c8d6a 100644 --- a/docs/reference/encrypted-domain-implementation-spec.md +++ b/docs/reference/encrypted-domain-implementation-spec.md @@ -83,13 +83,21 @@ future migration. - [ ] Put optional hand-written SQL in `src/encrypted_domain//_extensions.sql` with explicit `-- REQUIRE:` edges. This file IS committed. -- [ ] Create a hand-reviewed byte-parity baseline under - `tests/codegen/reference//` — one file per generated SQL output plus - `_values.rs`, each headed with the `-- REFERENCE:` / `// REFERENCE:` - marker. `tasks/codegen/test_against_reference.py` only guards types that - have a baseline directory, so without it the new type gets no - drift protection. The committed-fixture parity assertion is currently - `int4`-only; extend it to cover ``. +- [ ] Do **not** add a `tests/codegen/reference//` baseline. `int4` is the + single golden master for the type-generic generator: the SQL templates are + pure token substitution and the only type-specific rendering is + `_values.rs`, so a per-type baseline can only fail when `int4`'s already + would. Drift protection for the new type comes from the `int4` reference + (shared templates + `terms.py`), the committed `_values.rs` const guarded + by the CI staleness check (`mise run codegen:domain ` + `git diff + --exit-code`) and the `` cases in `tasks/codegen/test_scalars.py`, and + the `ordered_numeric_matrix!` SQLx suite (behaviour, not bytes). +- [ ] Run `mise run test:matrix:inventory` and commit the regenerated + `tests/sqlx/snapshots/_matrix_tests.txt` — the sorted inventory of every + `scalars::::*` test name in the `encrypted_domain` binary. CI diffs it + (same as `_values.rs`); a stale snapshot fails the `matrix-coverage` + job with "Coverage inventory stale". This baseline is what catches a + silently dropped, renamed, or `#[cfg]`-gated matrix test. See §8. - [ ] Run `mise run test:codegen`, the relevant SQLx suites, and the PostgreSQL matrix before merging. @@ -252,8 +260,9 @@ Cover each generated domain with SQLx tests appropriate to its terms: - domain `CHECK` rejects non-object and under-populated payloads; - real typed columns are tested, not only cast literals; - generated ordered-domain twins remain byte-identical modulo type name - (verified by `tasks/codegen/test_against_reference.py` against the - hand-reviewed baseline in `tests/codegen/reference//`). + (the shared generator is anchored by the `int4` golden master in + `tests/codegen/reference/int4/` via `tasks/codegen/test_against_reference.py`; + new types add no baseline of their own — see §2). For ordered numeric scalars this coverage is generated by the `ordered_numeric_matrix!` convention wrapper in `tests/sqlx/src/matrix.rs`: @@ -272,6 +281,24 @@ For ordered `int4`, keep the assertion that distinct plaintext values produce distinct ORE blocks. Do not add assertions for term behavior that the catalog does not promise. +### Matrix coverage inventory snapshot + +The *set of test names* the matrix emits is itself guarded. `mise run +test:matrix:inventory` lists every test in the `encrypted_domain` binary +under a pinned feature set (`--no-default-features`, which deliberately +excludes the `scale` arm — see the task comment in `mise.toml`), greps it to +each `scalars::::*` matrix, `LC_ALL=C sort`s for byte-stable ordering, and +writes one committed snapshot per scalar at +`tests/sqlx/snapshots/_matrix_tests.txt`. The CI `matrix-coverage` job +regenerates with the same feature set and `git diff --exit-code`s every +snapshot; a divergence fails with "Coverage inventory stale". This is the +guard that catches a silently dropped, renamed, or `#[cfg]`-gated matrix +test — a behaviour the SQLx assertions above cannot see, because a deleted +test simply stops running. When you add a scalar you add a new snapshot; +when you add or remove matrix tests you regenerate and commit the affected +snapshot in the same change. The files are a committed test baseline, **not** +gitignored generated SQL. See `tests/sqlx/snapshots/README.md`. + ## 9. Fixtures Fixture generation should use real encrypted payloads produced through diff --git a/mise.toml b/mise.toml index 270fab25..c513202f 100644 --- a/mise.toml +++ b/mise.toml @@ -96,7 +96,7 @@ mise exec python -- python -m pytest tasks/codegen -q """ [tasks."test:matrix:inventory"] -description = "Regenerate the int4 matrix test-name inventory snapshot (no database required)" +description = "Regenerate the int4/int2 matrix test-name inventory snapshots (no database required)" dir = "{{config_root}}/tests/sqlx" run = """ # Pin an explicit feature set so the inventory is deterministic regardless of @@ -105,14 +105,18 @@ run = """ # of this default-feature inventory, covered instead by the scale gate + the # family::mutations negative controls. `--list` enumerates the whole # encrypted_domain binary (family::support, family::inlinability, -# family::mutations, scalars::int4); `grep '^scalars::int4'` scopes the -# snapshot to the matrix only, so landing other family tests never dirties it. -# `LC_ALL=C sort` makes ordering byte-stable across locales (a bare `sort` is -# locale-dependent and yields spurious CI diffs). +# family::mutations, scalars::int4, scalars::int2); the per-scalar `grep` +# scopes each snapshot to that matrix only, so landing other family tests +# never dirties it. `LC_ALL=C sort` makes ordering byte-stable across locales +# (a bare `sort` is locale-dependent and yields spurious CI diffs). set -euo pipefail mkdir -p snapshots cargo test --no-default-features --test encrypted_domain -- --list | sed -n 's/: test$//p' | grep '^scalars::int4' | LC_ALL=C sort > snapshots/int4_matrix_tests.txt +cargo test --no-default-features --test encrypted_domain -- --list | + sed -n 's/: test$//p' | + grep '^scalars::int2' | + LC_ALL=C sort > snapshots/int2_matrix_tests.txt """ diff --git a/tasks/codegen/scalars.py b/tasks/codegen/scalars.py index a93df905..eee01dc4 100644 --- a/tasks/codegen/scalars.py +++ b/tasks/codegen/scalars.py @@ -79,6 +79,15 @@ def render_literal(self, value: str) -> str: min_value=-2147483648, max_value=2147483647, ), + "int2": ScalarKind( + token="int2", + rust_type="i16", + min_symbol="i16::MIN", + max_symbol="i16::MAX", + zero_symbol="0", + min_value=-32768, + max_value=32767, + ), } diff --git a/tasks/codegen/test_scalars.py b/tasks/codegen/test_scalars.py index 3ef7d0f0..1f15f1c3 100644 --- a/tasks/codegen/test_scalars.py +++ b/tasks/codegen/test_scalars.py @@ -62,3 +62,21 @@ def test_require_scalar_unknown_raises(): def test_int4_registered_in_catalog(): assert "int4" in SCALAR_KINDS + + +def test_int2_kind_resolves_and_renders(): + kind = require_scalar("int2") + assert kind.rust_type == "i16" + assert kind.numeric_value("MIN") == -32768 + assert kind.numeric_value("MAX") == 32767 + assert kind.numeric_value("ZERO") == 0 + assert kind.render_literal("MIN") == "i16::MIN" + assert kind.render_literal("MAX") == "i16::MAX" + assert kind.render_literal("ZERO") == "0" + assert kind.render_literal("30000") == "30000" + + +def test_int2_kind_rejects_out_of_range(): + kind = require_scalar("int2") + with pytest.raises(ScalarError, match="out of range"): + kind.numeric_value("40000") diff --git a/tasks/codegen/types/int2.toml b/tasks/codegen/types/int2.toml new file mode 100644 index 00000000..314bc698 --- /dev/null +++ b/tasks/codegen/types/int2.toml @@ -0,0 +1,19 @@ +# Encrypted-domain scalar manifest for int2. +# The filename supplies the type token. Each domain lists the index terms +# it carries; term capabilities are fixed in tasks/codegen/terms.py. + +[domain] +int2 = [] +int2_eq = ["hm"] +int2_ord_ore = ["ore"] +int2_ord = ["ore"] + +# Single source of truth for the int2 fixture plaintext list. Drives the +# generated tests/sqlx/src/fixtures/int2_values.rs const, shared by the fixture +# generator and the matrix oracle. Sentinels MIN/MAX/ZERO map to i16 named +# consts; the set MUST include MIN, MAX, and zero (matrix comparison pivots). +[fixture] +values = [ + "MIN", "-30000", "-100", "-1", "ZERO", "1", "2", "5", "10", "17", "25", + "42", "50", "100", "250", "1000", "9999", "30000", "MAX", +] diff --git a/tests/codegen/reference/README.md b/tests/codegen/reference/README.md index c1fa5118..58f01cc1 100644 --- a/tests/codegen/reference/README.md +++ b/tests/codegen/reference/README.md @@ -1,5 +1,13 @@ # Codegen reference -The SQL files under `/` are the original, hand-written reference implementation for each encrypted-domain scalar type. +The SQL files under `int4/` are the original, hand-written reference implementation for the encrypted-domain scalar generator. `int4` is the **single golden master**: the generator in `tasks/codegen/` is type-generic — its SQL templates are pure token substitution, and the only type-specific rendering is the `_values.rs` const — so one anchored type detects all template/term drift for every current and future scalar. -They are the parity baseline for the generator in `tasks/codegen/`. `tasks/codegen/test_against_reference.py` renders the generator's output and asserts it matches these files byte-for-byte. If the generator diverges, either it regressed (fix `tasks/codegen/`) or the reference is being updated deliberately (commit the new reference in the same PR). +`tasks/codegen/test_against_reference.py` renders the generator's output for `int4` and asserts it matches these files byte-for-byte. If the generator diverges, either it regressed (fix `tasks/codegen/`) or the reference is being updated deliberately (commit the new `int4` reference in the same PR). + +## New scalar types do not add a reference + +Adding a scalar type (`int2`, `int8`, …) does **not** add a `tests/codegen/reference//` directory. A per-type baseline would be redundant: the SQL is byte-identical to `int4` modulo the type token, so it can only fail when `int4`'s baseline already would. New types are guaranteed three other ways: + +- the `int4` reference here anchors the shared generator (templates + `terms.py`); +- the committed `tests/sqlx/src/fixtures/_values.rs` const is pinned by the CI staleness guard (`git diff --exit-code` after `mise run codegen:domain `) and by the `` cases in `tasks/codegen/test_scalars.py` (the only type-specific rendering, `i16::MIN` vs `i32::MIN`); +- the SQLx `ordered_numeric_matrix!` suite exercises the generated SQL's *behaviour* against a real database — a far stronger guarantee than a byte comparison. diff --git a/tests/sqlx/snapshots/README.md b/tests/sqlx/snapshots/README.md new file mode 100644 index 00000000..a4ce5ae9 --- /dev/null +++ b/tests/sqlx/snapshots/README.md @@ -0,0 +1,53 @@ +# Matrix coverage inventory snapshots + +This directory holds one committed snapshot per scalar encrypted-domain type: + +- `int4_matrix_tests.txt` +- `int2_matrix_tests.txt` + +Each file is a sorted, byte-stable list of every `scalars::::*` test name in +the `encrypted_domain` SQLx binary. They are a **committed test baseline**, not +gitignored generated SQL — keep them in version control. + +## What they guard + +The SQLx assertions verify that the tests which run produce the right results. +They cannot see a test that *stops running* — a matrix test that is deleted, +renamed, or hidden behind a `#[cfg]` gate simply vanishes silently, quietly +shrinking coverage. These snapshots close that gap: they pin the *set of test +names* so any such change shows up as an added/removed line in the PR diff. + +## How they are generated + +Run: + +```bash +mise run test:matrix:inventory +``` + +The task (`mise.toml`, `[tasks."test:matrix:inventory"]`) enumerates the binary +with `cargo test --test encrypted_domain -- --list`, greps each +`scalars::` matrix into its own file, and `LC_ALL=C sort`s for ordering +that is byte-stable across locales. No database is required — `--list` only +enumerates; the suite uses runtime queries. + +It pins `--no-default-features` so the inventory is deterministic regardless of +the caller's local flags. That deliberately excludes the `scale` feature arm +(`#[cfg(feature = "scale")]`) — a known blind spot of this inventory, covered +instead by the scale gate plus the `family::mutations` negative controls. + +## CI enforcement + +The `matrix-coverage` job in `.github/workflows/test-eql.yml` regenerates with +the same pinned feature set and runs `git diff --exit-code` against every +snapshot in this directory. A divergence fails the job with: + +> Coverage inventory stale — run 'mise run test:matrix:inventory' and commit. + +## When you must update these + +- **Adding a new scalar type** → a new `_matrix_tests.txt` appears; commit it. +- **Adding / removing / renaming matrix tests** → regenerate and commit the + affected snapshot in the same change. + +See `docs/reference/encrypted-domain-implementation-spec.md` §2 and §8. diff --git a/tests/sqlx/snapshots/int2_matrix_tests.txt b/tests/sqlx/snapshots/int2_matrix_tests.txt new file mode 100644 index 00000000..3b6ed674 --- /dev/null +++ b/tests/sqlx/snapshots/int2_matrix_tests.txt @@ -0,0 +1,211 @@ +scalars::int2::matrix_int2_eq_aggregate_typecheck_max +scalars::int2::matrix_int2_eq_aggregate_typecheck_min +scalars::int2::matrix_int2_eq_contained_by_blocker +scalars::int2::matrix_int2_eq_contains_blocker +scalars::int2::matrix_int2_eq_count_distinct_extractor +scalars::int2::matrix_int2_eq_count_path_cast +scalars::int2::matrix_int2_eq_count_typed_column +scalars::int2::matrix_int2_eq_eq_pivot_max_correctness +scalars::int2::matrix_int2_eq_eq_pivot_max_cross_shape +scalars::int2::matrix_int2_eq_eq_pivot_min_correctness +scalars::int2::matrix_int2_eq_eq_pivot_min_cross_shape +scalars::int2::matrix_int2_eq_eq_pivot_zero_correctness +scalars::int2::matrix_int2_eq_eq_pivot_zero_cross_shape +scalars::int2::matrix_int2_eq_eq_supported_null +scalars::int2::matrix_int2_eq_gt_blocker +scalars::int2::matrix_int2_eq_gte_blocker +scalars::int2::matrix_int2_eq_index_engages_btree +scalars::int2::matrix_int2_eq_index_engages_hash +scalars::int2::matrix_int2_eq_lt_blocker +scalars::int2::matrix_int2_eq_lte_blocker +scalars::int2::matrix_int2_eq_native_absent_ops +scalars::int2::matrix_int2_eq_neq_pivot_max_correctness +scalars::int2::matrix_int2_eq_neq_pivot_max_cross_shape +scalars::int2::matrix_int2_eq_neq_pivot_min_correctness +scalars::int2::matrix_int2_eq_neq_pivot_min_cross_shape +scalars::int2::matrix_int2_eq_neq_pivot_zero_correctness +scalars::int2::matrix_int2_eq_neq_pivot_zero_cross_shape +scalars::int2::matrix_int2_eq_neq_supported_null +scalars::int2::matrix_int2_eq_path_op_blockers +scalars::int2::matrix_int2_eq_payload_check +scalars::int2::matrix_int2_eq_planner_metadata_eq +scalars::int2::matrix_int2_eq_sanity +scalars::int2::matrix_int2_eq_typed_column_blocker +scalars::int2::matrix_int2_fixture_shape +scalars::int2::matrix_int2_ord_aggregate_group_by_max +scalars::int2::matrix_int2_ord_aggregate_group_by_min +scalars::int2::matrix_int2_ord_aggregate_max +scalars::int2::matrix_int2_ord_aggregate_max_all_null +scalars::int2::matrix_int2_ord_aggregate_max_empty +scalars::int2::matrix_int2_ord_aggregate_max_mixed_null +scalars::int2::matrix_int2_ord_aggregate_min +scalars::int2::matrix_int2_ord_aggregate_min_all_null +scalars::int2::matrix_int2_ord_aggregate_min_empty +scalars::int2::matrix_int2_ord_aggregate_min_mixed_null +scalars::int2::matrix_int2_ord_aggregate_parallel_safe +scalars::int2::matrix_int2_ord_contained_by_blocker +scalars::int2::matrix_int2_ord_contains_blocker +scalars::int2::matrix_int2_ord_count_distinct_extractor +scalars::int2::matrix_int2_ord_count_path_cast +scalars::int2::matrix_int2_ord_count_typed_column +scalars::int2::matrix_int2_ord_eq_pivot_max_correctness +scalars::int2::matrix_int2_ord_eq_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_eq_pivot_min_correctness +scalars::int2::matrix_int2_ord_eq_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_eq_pivot_zero_correctness +scalars::int2::matrix_int2_ord_eq_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_eq_supported_null +scalars::int2::matrix_int2_ord_gt_pivot_max_correctness +scalars::int2::matrix_int2_ord_gt_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_gt_pivot_min_correctness +scalars::int2::matrix_int2_ord_gt_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_gt_pivot_zero_correctness +scalars::int2::matrix_int2_ord_gt_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_gt_supported_null +scalars::int2::matrix_int2_ord_gte_pivot_max_correctness +scalars::int2::matrix_int2_ord_gte_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_gte_pivot_min_correctness +scalars::int2::matrix_int2_ord_gte_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_gte_pivot_zero_correctness +scalars::int2::matrix_int2_ord_gte_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_gte_supported_null +scalars::int2::matrix_int2_ord_index_engages_btree +scalars::int2::matrix_int2_ord_lt_pivot_max_correctness +scalars::int2::matrix_int2_ord_lt_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_lt_pivot_min_correctness +scalars::int2::matrix_int2_ord_lt_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_lt_pivot_zero_correctness +scalars::int2::matrix_int2_ord_lt_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_lt_supported_null +scalars::int2::matrix_int2_ord_lte_pivot_max_correctness +scalars::int2::matrix_int2_ord_lte_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_lte_pivot_min_correctness +scalars::int2::matrix_int2_ord_lte_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_lte_pivot_zero_correctness +scalars::int2::matrix_int2_ord_lte_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_lte_supported_null +scalars::int2::matrix_int2_ord_native_absent_ops +scalars::int2::matrix_int2_ord_neq_pivot_max_correctness +scalars::int2::matrix_int2_ord_neq_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_neq_pivot_min_correctness +scalars::int2::matrix_int2_ord_neq_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_neq_pivot_zero_correctness +scalars::int2::matrix_int2_ord_neq_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_neq_supported_null +scalars::int2::matrix_int2_ord_ord_routes_through_ob +scalars::int2::matrix_int2_ord_order_by_asc_no_where +scalars::int2::matrix_int2_ord_order_by_asc_nulls_first +scalars::int2::matrix_int2_ord_order_by_asc_nulls_last +scalars::int2::matrix_int2_ord_order_by_asc_with_where +scalars::int2::matrix_int2_ord_order_by_desc_no_where +scalars::int2::matrix_int2_ord_order_by_desc_nulls_first +scalars::int2::matrix_int2_ord_order_by_desc_nulls_last +scalars::int2::matrix_int2_ord_order_by_desc_with_where +scalars::int2::matrix_int2_ord_order_by_using_gt_rejects +scalars::int2::matrix_int2_ord_order_by_using_gte_rejects +scalars::int2::matrix_int2_ord_order_by_using_lt_rejects +scalars::int2::matrix_int2_ord_order_by_using_lte_rejects +scalars::int2::matrix_int2_ord_ore_aggregate_group_by_max +scalars::int2::matrix_int2_ord_ore_aggregate_group_by_min +scalars::int2::matrix_int2_ord_ore_aggregate_max +scalars::int2::matrix_int2_ord_ore_aggregate_max_all_null +scalars::int2::matrix_int2_ord_ore_aggregate_max_empty +scalars::int2::matrix_int2_ord_ore_aggregate_max_mixed_null +scalars::int2::matrix_int2_ord_ore_aggregate_min +scalars::int2::matrix_int2_ord_ore_aggregate_min_all_null +scalars::int2::matrix_int2_ord_ore_aggregate_min_empty +scalars::int2::matrix_int2_ord_ore_aggregate_min_mixed_null +scalars::int2::matrix_int2_ord_ore_aggregate_parallel_safe +scalars::int2::matrix_int2_ord_ore_contained_by_blocker +scalars::int2::matrix_int2_ord_ore_contains_blocker +scalars::int2::matrix_int2_ord_ore_count_distinct_extractor +scalars::int2::matrix_int2_ord_ore_count_path_cast +scalars::int2::matrix_int2_ord_ore_count_typed_column +scalars::int2::matrix_int2_ord_ore_eq_pivot_max_correctness +scalars::int2::matrix_int2_ord_ore_eq_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_ore_eq_pivot_min_correctness +scalars::int2::matrix_int2_ord_ore_eq_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_ore_eq_pivot_zero_correctness +scalars::int2::matrix_int2_ord_ore_eq_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_ore_eq_supported_null +scalars::int2::matrix_int2_ord_ore_gt_pivot_max_correctness +scalars::int2::matrix_int2_ord_ore_gt_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_ore_gt_pivot_min_correctness +scalars::int2::matrix_int2_ord_ore_gt_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_ore_gt_pivot_zero_correctness +scalars::int2::matrix_int2_ord_ore_gt_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_ore_gt_supported_null +scalars::int2::matrix_int2_ord_ore_gte_pivot_max_correctness +scalars::int2::matrix_int2_ord_ore_gte_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_ore_gte_pivot_min_correctness +scalars::int2::matrix_int2_ord_ore_gte_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_ore_gte_pivot_zero_correctness +scalars::int2::matrix_int2_ord_ore_gte_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_ore_gte_supported_null +scalars::int2::matrix_int2_ord_ore_index_engages_btree +scalars::int2::matrix_int2_ord_ore_lt_pivot_max_correctness +scalars::int2::matrix_int2_ord_ore_lt_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_ore_lt_pivot_min_correctness +scalars::int2::matrix_int2_ord_ore_lt_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_ore_lt_pivot_zero_correctness +scalars::int2::matrix_int2_ord_ore_lt_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_ore_lt_supported_null +scalars::int2::matrix_int2_ord_ore_lte_pivot_max_correctness +scalars::int2::matrix_int2_ord_ore_lte_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_ore_lte_pivot_min_correctness +scalars::int2::matrix_int2_ord_ore_lte_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_ore_lte_pivot_zero_correctness +scalars::int2::matrix_int2_ord_ore_lte_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_ore_lte_supported_null +scalars::int2::matrix_int2_ord_ore_native_absent_ops +scalars::int2::matrix_int2_ord_ore_neq_pivot_max_correctness +scalars::int2::matrix_int2_ord_ore_neq_pivot_max_cross_shape +scalars::int2::matrix_int2_ord_ore_neq_pivot_min_correctness +scalars::int2::matrix_int2_ord_ore_neq_pivot_min_cross_shape +scalars::int2::matrix_int2_ord_ore_neq_pivot_zero_correctness +scalars::int2::matrix_int2_ord_ore_neq_pivot_zero_cross_shape +scalars::int2::matrix_int2_ord_ore_neq_supported_null +scalars::int2::matrix_int2_ord_ore_ord_routes_through_ob +scalars::int2::matrix_int2_ord_ore_order_by_asc_no_where +scalars::int2::matrix_int2_ord_ore_order_by_asc_nulls_first +scalars::int2::matrix_int2_ord_ore_order_by_asc_nulls_last +scalars::int2::matrix_int2_ord_ore_order_by_asc_with_where +scalars::int2::matrix_int2_ord_ore_order_by_desc_no_where +scalars::int2::matrix_int2_ord_ore_order_by_desc_nulls_first +scalars::int2::matrix_int2_ord_ore_order_by_desc_nulls_last +scalars::int2::matrix_int2_ord_ore_order_by_desc_with_where +scalars::int2::matrix_int2_ord_ore_order_by_using_gt_rejects +scalars::int2::matrix_int2_ord_ore_order_by_using_gte_rejects +scalars::int2::matrix_int2_ord_ore_order_by_using_lt_rejects +scalars::int2::matrix_int2_ord_ore_order_by_using_lte_rejects +scalars::int2::matrix_int2_ord_ore_ore_injectivity +scalars::int2::matrix_int2_ord_ore_path_op_blockers +scalars::int2::matrix_int2_ord_ore_payload_check +scalars::int2::matrix_int2_ord_ore_planner_metadata_eq +scalars::int2::matrix_int2_ord_ore_planner_metadata_ord +scalars::int2::matrix_int2_ord_ore_sanity +scalars::int2::matrix_int2_ord_ore_typed_column_blocker +scalars::int2::matrix_int2_ord_path_op_blockers +scalars::int2::matrix_int2_ord_payload_check +scalars::int2::matrix_int2_ord_planner_metadata_eq +scalars::int2::matrix_int2_ord_planner_metadata_ord +scalars::int2::matrix_int2_ord_sanity +scalars::int2::matrix_int2_ord_scale_preference_default_btree +scalars::int2::matrix_int2_ord_typed_column_blocker +scalars::int2::matrix_int2_storage_aggregate_typecheck_max +scalars::int2::matrix_int2_storage_aggregate_typecheck_min +scalars::int2::matrix_int2_storage_contained_by_blocker +scalars::int2::matrix_int2_storage_contains_blocker +scalars::int2::matrix_int2_storage_count_path_cast +scalars::int2::matrix_int2_storage_count_typed_column +scalars::int2::matrix_int2_storage_eq_blocker +scalars::int2::matrix_int2_storage_gt_blocker +scalars::int2::matrix_int2_storage_gte_blocker +scalars::int2::matrix_int2_storage_lt_blocker +scalars::int2::matrix_int2_storage_lte_blocker +scalars::int2::matrix_int2_storage_native_absent_ops +scalars::int2::matrix_int2_storage_neq_blocker +scalars::int2::matrix_int2_storage_path_op_blockers +scalars::int2::matrix_int2_storage_payload_check +scalars::int2::matrix_int2_storage_sanity +scalars::int2::matrix_int2_storage_typed_column_blocker diff --git a/tests/sqlx/src/fixtures/eql_plaintext.rs b/tests/sqlx/src/fixtures/eql_plaintext.rs index 0db9482a..36b348fd 100644 --- a/tests/sqlx/src/fixtures/eql_plaintext.rs +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -53,6 +53,7 @@ pub struct PlaintextSqlType(&'static str); impl PlaintextSqlType { pub const INTEGER: PlaintextSqlType = PlaintextSqlType("integer"); + pub const SMALLINT: PlaintextSqlType = PlaintextSqlType("smallint"); pub fn as_str(&self) -> &'static str { self.0 @@ -68,6 +69,7 @@ impl fmt::Display for PlaintextSqlType { mod sealed { pub trait Sealed {} impl Sealed for i32 {} + impl Sealed for i16 {} } /// A Rust type usable as a fixture `plaintext` value, carrying its EQL cast @@ -96,6 +98,15 @@ impl EqlPlaintext for i32 { } } +impl EqlPlaintext for i16 { + const CAST: Cast = Cast::SMALL_INT; + const PLAINTEXT_SQL_TYPE: PlaintextSqlType = PlaintextSqlType::SMALLINT; + + fn to_plaintext(&self) -> Plaintext { + Plaintext::SmallInt(Some(*self)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -122,4 +133,27 @@ mod tests { other => panic!("expected Plaintext::Int(Some(42)), got {other:?}"), } } + + #[test] + fn i16_casts_to_small_int() { + assert_eq!(::CAST.as_str(), "small_int"); + } + + #[test] + fn i16_plaintext_sql_type_is_smallint() { + assert_eq!( + ::PLAINTEXT_SQL_TYPE.as_str(), + "smallint" + ); + } + + #[test] + fn i16_to_plaintext_wraps_in_small_int_variant() { + // i16 must lift into the SmallInt variant so the fixture driver + // encrypts it under the `small_int` cast, not `int`. + match 42_i16.to_plaintext() { + Plaintext::SmallInt(Some(value)) => assert_eq!(value, 42), + other => panic!("expected Plaintext::SmallInt(Some(42)), got {other:?}"), + } + } } diff --git a/tests/sqlx/src/fixtures/eql_v2_int2.rs b/tests/sqlx/src/fixtures/eql_v2_int2.rs new file mode 100644 index 00000000..ec4a1333 --- /dev/null +++ b/tests/sqlx/src/fixtures/eql_v2_int2.rs @@ -0,0 +1,12 @@ +//! The `eql_v2_int2` fixture — the int4 reference, clamped to 16 bits. +//! +//! 19 integers spanning a negative boundary, the i16 signed extremes +//! (`MIN`/`MAX`), zero, a pair near the ±32767 boundary, and +//! small/medium/large magnitudes. The generated +//! `tests/sqlx/fixtures/eql_v2_int2.sql` is a plain `jsonb`-payload table with +//! no EQL dependency; the `eql_v2_int2` domain is layered on top by casting +//! `payload` per query. + +use super::int2_values::VALUES; + +crate::scalar_fixture!("eql_v2_int2", i16, VALUES); diff --git a/tests/sqlx/src/fixtures/int2_values.rs b/tests/sqlx/src/fixtures/int2_values.rs new file mode 100644 index 00000000..74aff1c4 --- /dev/null +++ b/tests/sqlx/src/fixtures/int2_values.rs @@ -0,0 +1,33 @@ +// AUTO-GENERATED — DO NOT EDIT. +// Regenerated by `mise run build` (or `mise run codegen:domain `). +// Source of truth: tasks/codegen/types/.toml `[fixture] values`. +// This file IS committed and verified in CI (git diff --exit-code). +//! Fixture plaintext values for the int2 encrypted-domain family. +//! +//! Generated from tasks/codegen/types/int2.toml `[fixture] values` — +//! the single source of truth shared by the fixture generator +//! (`fixtures::eql_v2_int2`) and the matrix oracle +//! (`ScalarType::FIXTURE_VALUES`). + +/// Distinct plaintext values present in the `eql_v2_int2` fixture. +pub const VALUES: &[i16] = &[ + i16::MIN, + -30000, + -100, + -1, + 0, + 1, + 2, + 5, + 10, + 17, + 25, + 42, + 50, + 100, + 250, + 1000, + 9999, + 30000, + i16::MAX, +]; diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index ee087d1d..ac363a49 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -31,3 +31,9 @@ pub mod driver; pub mod int4_values; pub mod eql_v2_int4; + +/// Generated from tasks/codegen/types/int2.toml `[fixture] values`. +/// Committed and verified by CI; never hand-edit (`mise run codegen:domain int2`). +pub mod int2_values; + +pub mod eql_v2_int2; diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index e7567584..c3acc428 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -81,6 +81,15 @@ impl ScalarType for i32 { const FIXTURE_VALUES: &'static [i32] = crate::fixtures::int4_values::VALUES; } +impl ScalarType for i16 { + const PG_TYPE: &'static str = "int2"; + /// Single-sourced from `tasks/codegen/types/int2.toml` `[fixture] values` + /// via the generated `fixtures::int2_values::VALUES` const — the same list + /// the fixture generator encrypts, so the oracle cannot drift from the + /// fixture. Spans the negative boundary, the i16 signed extremes, and zero. + const FIXTURE_VALUES: &'static [i16] = crate::fixtures::int2_values::VALUES; +} + /// Per-domain capability + payload shape. Storage carries no terms, `Eq` /// adds `hm`, `Ord`/`OrdOre` add `ob`. `Ord` and `OrdOre` are deliberate /// twins — same operator surface, different SQL domain names — for the diff --git a/tests/sqlx/tests/encrypted_domain/scalars/int2.rs b/tests/sqlx/tests/encrypted_domain/scalars/int2.rs new file mode 100644 index 00000000..7a8e9331 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/scalars/int2.rs @@ -0,0 +1,14 @@ +//! `eql_v2_int2` — the int4 reference scalar, clamped to 16 bits. +//! +//! Adding a new ordered numeric scalar (i64, f64, date, ...) is one +//! `impl ScalarType` in `tests/sqlx/src/scalar_domains.rs` plus an +//! `ordered_numeric_matrix!` invocation like this one. The matrix covers +//! everything generic over `T: ScalarType`. + +use eql_tests::ordered_numeric_matrix; + +ordered_numeric_matrix! { + suite = int2, + scalar = i16, + eql_type = "eql_v2_int2", +} diff --git a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs index 8abc1857..f42cfb5f 100644 --- a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs +++ b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs @@ -2,3 +2,5 @@ //! additions (`int8`, `bool`, `date`, …) become sibling modules here. pub mod int4; + +pub mod int2; From 1d1dcc5f69db933b2175ac3ed303a8e63a796239 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 1 Jun 2026 15:17:44 +1000 Subject: [PATCH 26/93] ci(fixtures): regenerate all scalar fixtures via fixture:generate:all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test task hand-listed only eql_v2_int4 for fixture regeneration, so the int2 matrix test's compile-time include_str! of eql_v2_int2.sql failed in CI (fixtures are gitignored and regenerated each run). Add a fixture:generate:all task that enumerates tasks/codegen/types/*.toml — the same manifests codegen:domain:all drives — and regenerates the fixture for every type declaring a [fixture] table, then call it from the test task. New scalar types are now picked up automatically. --- mise.toml | 5 ++++- tasks/fixtures.toml | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mise.toml b/mise.toml index c513202f..8eecd6b6 100644 --- a/mise.toml +++ b/mise.toml @@ -51,6 +51,9 @@ cd tests/sqlx sqlx migrate run # Regenerate fixtures every run — they are not committed (see .gitignore). +# fixture:generate:all enumerates every scalar manifest in +# tasks/codegen/types/ that declares a [fixture] table, so new scalar types +# are picked up automatically without editing this task. # Generator encrypts via cipherstash-client directly, which needs BOTH a # ZeroKMS auth credential (CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN, via # AutoStrategy) AND a client key (CS_CLIENT_ID + CS_CLIENT_KEY, via @@ -58,7 +61,7 @@ sqlx migrate run # separate roles — the two pairs are not alternatives. echo "Regenerating SQLx fixtures..." cd "{{config_root}}" -mise run fixture:generate eql_v2_int4 +mise run fixture:generate:all echo "Running Rust tests..." cd tests/sqlx diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml index acce7495..ecfe3ad9 100644 --- a/tasks/fixtures.toml +++ b/tasks/fixtures.toml @@ -27,3 +27,32 @@ cargo test --features fixture-gen --lib \ "fixtures::${fixture}::generate" \ -- --ignored --exact --nocapture """ + +["fixture:generate:all"] +description = "Regenerate every scalar SQLx fixture declared by a type manifest" +# Enumerates tasks/codegen/types/*.toml — the SAME manifests that +# `codegen:domain:all` drives — and regenerates the SQLx fixture for each type +# whose manifest declares a [fixture] table. This keeps the test fixtures in +# lockstep with the declared scalar types: adding a new scalar type (a new +# .toml with a [fixture] table) is picked up automatically, so the test +# task never has to hand-list each fixture. Same prerequisites as +# `fixture:generate` (Postgres up + CS_* credentials). +dir = "{{config_root}}" +run = """ +generated=0 +for manifest in tasks/codegen/types/*.toml; do + # Guard the no-match case (glob stays literal under POSIX sh). + [ -e "$manifest" ] || continue + # Only types that declare a [fixture] table have a SQLx fixture generator. + grep -qE '^\\[fixture\\]' "$manifest" || continue + token=$(basename "$manifest" .toml) + echo "Generating fixture eql_v2_${token}..." + mise run fixture:generate "eql_v2_${token}" + generated=$((generated + 1)) +done +if [ "$generated" -eq 0 ]; then + echo "No scalar manifests with a [fixture] table found in tasks/codegen/types/" >&2 + exit 1 +fi +echo "Regenerated ${generated} scalar fixture(s)." +""" From 1a42fddf29c256a8f79a95706c5e2be4cea3c8c7 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 08:19:47 +1000 Subject: [PATCH 27/93] feat(codegen): add Cargo workspace with eql-scalars catalog + eql-codegen stub Introduce a root Cargo workspace (resolver 2, default-members = tests/sqlx so a bare `cargo test` behaves as before) with two new members: - crates/eql-scalars: a std-only, dependency-free catalog encoding the scalar and index-term facts as Rust enums/consts (ScalarKind, Term, Fixture, DomainSpec, ScalarSpec, const CATALOG) with the spec.py/terms.py/scalars.py validations ported as #[test]s. The CATALOG registry carries int4 + int2 only (matching the manifests on this branch); int8 is intentionally absent. The capability layer (ScalarKind::I64) already supports it, so adding int8 later is a pure CATALOG append. - crates/eql-codegen: a stub binary (filled in by a later plan). The root Cargo.lock supersedes tests/sqlx/Cargo.lock (removed) and /target/ is gitignored. No consumers yet; this is the foundation layer only. --- .gitignore | 3 + tests/sqlx/Cargo.lock => Cargo.lock | 8 + Cargo.toml | 22 + crates/eql-codegen/Cargo.toml | 8 + crates/eql-codegen/src/main.rs | 2 + crates/eql-scalars/Cargo.toml | 10 + crates/eql-scalars/src/lib.rs | 745 ++++++++++++++++++++++++++++ 7 files changed, 798 insertions(+) rename tests/sqlx/Cargo.lock => Cargo.lock (99%) create mode 100644 Cargo.toml create mode 100644 crates/eql-codegen/Cargo.toml create mode 100644 crates/eql-codegen/src/main.rs create mode 100644 crates/eql-scalars/Cargo.toml create mode 100644 crates/eql-scalars/src/lib.rs diff --git a/.gitignore b/.gitignore index 05b0ae00..bdc8e444 100644 --- a/.gitignore +++ b/.gitignore @@ -246,3 +246,6 @@ docs/superpowers/ # Build variants - protect variant deps src/deps-protect.txt src/deps-ordered-protect.txt + +# Cargo workspace root build artifacts +/target/ diff --git a/tests/sqlx/Cargo.lock b/Cargo.lock similarity index 99% rename from tests/sqlx/Cargo.lock rename to Cargo.lock index 18dd84e0..f7bdda95 100644 --- a/tests/sqlx/Cargo.lock +++ b/Cargo.lock @@ -1155,6 +1155,14 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "eql-codegen" +version = "0.1.0" + +[[package]] +name = "eql-scalars" +version = "0.1.0" + [[package]] name = "eql_tests" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..0baa1aad --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +# Cargo workspace root. +# +# Members: +# crates/eql-scalars — the scalar/term catalog (std-only, no deps). Source of +# truth for the Rust generator (Plan 2) and the SQLx test +# harness (Plan 3). +# crates/eql-codegen — the SQL generator binary (stub here; Plan 2 fills it in). +# tests/sqlx — the existing `eql_tests` SQLx integration crate. +# +# resolver = "2" keeps the heavy test-crate feature set (sqlx/tokio/cipherstash- +# client) from unifying into the lean catalog/generator crates. +# +# default-members = ["tests/sqlx"] keeps a bare `cargo test` / `cargo build` at the +# root building only the test crate, exactly as the pre-workspace layout did. +[workspace] +resolver = "2" +members = [ + "crates/eql-scalars", + "crates/eql-codegen", + "tests/sqlx", +] +default-members = ["tests/sqlx"] diff --git a/crates/eql-codegen/Cargo.toml b/crates/eql-codegen/Cargo.toml new file mode 100644 index 00000000..0c4e9b25 --- /dev/null +++ b/crates/eql-codegen/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "eql-codegen" +version = "0.1.0" +edition = "2021" +description = "SQL generator for EQL encrypted-domain types (stub; implemented in Plan 2)." + +# Stub: Plan 2 adds `eql-scalars` as a path dependency and implements the binary. +[dependencies] diff --git a/crates/eql-codegen/src/main.rs b/crates/eql-codegen/src/main.rs new file mode 100644 index 00000000..e51f0ccf --- /dev/null +++ b/crates/eql-codegen/src/main.rs @@ -0,0 +1,2 @@ +//! Stub entry point. Plan 2 replaces this with the catalog-driven SQL generator. +fn main() {} diff --git a/crates/eql-scalars/Cargo.toml b/crates/eql-scalars/Cargo.toml new file mode 100644 index 00000000..093b1a12 --- /dev/null +++ b/crates/eql-scalars/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "eql-scalars" +version = "0.1.0" +edition = "2021" +description = "Scalar/term catalog for EQL encrypted-domain codegen (std-only, no deps)." + +# INTENTIONALLY no dependencies. This crate must stay std-only so the future +# generator (eql-codegen) compiles in ~1-2s and never drags serde/toml onto the +# SQL build's critical path. Do not add deps here. +[dependencies] diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs new file mode 100644 index 00000000..a9c7cba6 --- /dev/null +++ b/crates/eql-scalars/src/lib.rs @@ -0,0 +1,745 @@ +//! Scalar/term catalog for EQL encrypted-domain codegen. +//! +//! Replaces the Python `tasks/codegen/scalars.py`, `terms.py`, and `spec.py` +//! plus the `tasks/codegen/types/*.toml` manifests. Plain Rust data + enums; +//! std-only, no dependencies. +//! +//! Plans 2 and 3 depend on the public names here verbatim — do not rename. + +/// The native Rust scalar a domain type maps onto. +/// +/// Mirrors `scalars.py`'s `ScalarKind` rendering facts. `min_value`/`max_value` +/// are widened to `i128` so a single accessor type covers `i16`..`i64` bounds. +/// This is the fixed capability layer: a variant being present here only means +/// the generator *can* render that Rust kind; the `CATALOG` registry below is +/// what declares which scalar types actually exist. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScalarKind { + I16, + I32, + I64, +} + +impl ScalarKind { + /// The Rust type name as it appears in generated source (e.g. `"i32"`). + pub const fn rust_type(self) -> &'static str { + match self { + ScalarKind::I16 => "i16", + ScalarKind::I32 => "i32", + ScalarKind::I64 => "i64", + } + } + + /// The `MIN` named-constant symbol (e.g. `"i32::MIN"`). + pub const fn min_symbol(self) -> &'static str { + match self { + ScalarKind::I16 => "i16::MIN", + ScalarKind::I32 => "i32::MIN", + ScalarKind::I64 => "i64::MIN", + } + } + + /// The `MAX` named-constant symbol (e.g. `"i32::MAX"`). + pub const fn max_symbol(self) -> &'static str { + match self { + ScalarKind::I16 => "i16::MAX", + ScalarKind::I32 => "i32::MAX", + ScalarKind::I64 => "i64::MAX", + } + } + + /// The zero literal symbol (always `"0"`). + pub const fn zero_symbol(self) -> &'static str { + "0" + } + + /// Inclusive lower bound of the representable range, widened to `i128`. + pub const fn min_value(self) -> i128 { + match self { + ScalarKind::I16 => i16::MIN as i128, + ScalarKind::I32 => i32::MIN as i128, + ScalarKind::I64 => i64::MIN as i128, + } + } + + /// Inclusive upper bound of the representable range, widened to `i128`. + pub const fn max_value(self) -> i128 { + match self { + ScalarKind::I16 => i16::MAX as i128, + ScalarKind::I32 => i32::MAX as i128, + ScalarKind::I64 => i64::MAX as i128, + } + } +} + +/// A fixed index term known to the scalar materializer. +/// +/// Mirrors `terms.py`'s `TERM_CATALOG`. `Hm` provides equality; `Ore` provides +/// equality plus ordering. The `json_key`/`extractor`/`returns`/`ctor` values +/// are the cross-schema SQL contract and are copied verbatim from `terms.py` — +/// changing one is a generated-SQL behaviour change, not a refactor. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Term { + Hm, + Ore, +} + +impl Term { + /// JSON payload key carrying this term (`"hm"` / `"ob"`). + pub const fn json_key(self) -> &'static str { + match self { + Term::Hm => "hm", + Term::Ore => "ob", + } + } + + /// The generated extractor function name (`"eq_term"` / `"ord_term"`). + pub const fn extractor(self) -> &'static str { + match self { + Term::Hm => "eq_term", + Term::Ore => "ord_term", + } + } + + /// Cross-schema return type of the extractor (in `eql_v2`). + pub const fn returns(self) -> &'static str { + match self { + Term::Hm => "eql_v2.hmac_256", + Term::Ore => "eql_v2.ore_block_u64_8_256", + } + } + + /// Constructor name for the index-term type (unqualified). + pub const fn ctor(self) -> &'static str { + match self { + Term::Hm => "hmac_256", + Term::Ore => "ore_block_u64_8_256", + } + } + + /// Generated-file role label for a domain whose first term is this one. + pub const fn role(self) -> &'static str { + match self { + Term::Hm => "eq", + Term::Ore => "ord", + } + } + + /// SQL operators this term supports, in catalog order. + pub const fn operators(self) -> &'static [&'static str] { + match self { + Term::Hm => &["=", "<>"], + Term::Ore => &["=", "<>", "<", "<=", ">", ">="], + } + } + + /// SQL `-- REQUIRE:` edges this term pulls in, in catalog order. + pub const fn requires(self) -> &'static [&'static str] { + match self { + Term::Hm => &["src/hmac_256/functions.sql"], + Term::Ore => &[ + "src/ore_block_u64_8_256/functions.sql", + "src/ore_block_u64_8_256/operators.sql", + ], + } + } +} + +impl Term { + /// Stable dedupe — first occurrence wins. The Rust analogue of + /// `terms.py`'s `dict.fromkeys` ordering contract. + fn dedupe_preserving_order<'a>( + items: impl IntoIterator, + ) -> Vec<&'a str> { + let mut out: Vec<&'a str> = Vec::new(); + for item in items { + if !out.contains(&item) { + out.push(item); + } + } + out + } + + /// Supported operators for the union of a domain's terms (catalog order, + /// deduped). Mirrors `terms.py::operators_for_terms`. + pub fn operators_for_terms(terms: &[Term]) -> Vec<&'static str> { + Self::dedupe_preserving_order( + terms.iter().flat_map(|t| t.operators().iter().copied()), + ) + } + + /// JSON payload keys required by these terms (deduped, in order). + /// Mirrors `terms.py::term_json_keys`. + pub fn term_json_keys(terms: &[Term]) -> Vec<&'static str> { + Self::dedupe_preserving_order(terms.iter().map(|t| t.json_key())) + } + + /// SQL `-- REQUIRE:` edges needed by these terms (deduped, in order). + /// Mirrors `terms.py::term_requires`. + pub fn term_requires(terms: &[Term]) -> Vec<&'static str> { + Self::dedupe_preserving_order( + terms.iter().flat_map(|t| t.requires().iter().copied()), + ) + } + + /// The extractor that supports `op` for a domain carrying `terms`, or + /// `None`. First supporting term wins. Mirrors + /// `terms.py::extractor_for_operator`. + pub fn extractor_for_operator(terms: &[Term], op: &str) -> Option<&'static str> { + terms + .iter() + .find(|t| t.operators().contains(&op)) + .map(|t| t.extractor()) + } + + /// Generated-file role label for a domain with these terms. No terms => + /// `"storage"`; otherwise the first term's role. Mirrors + /// `terms.py::role_for_terms`. + pub fn role_for_terms(terms: &[Term]) -> &'static str { + match terms.first() { + None => "storage", + Some(t) => t.role(), + } + } +} + +/// A single fixture plaintext value for a scalar type. +/// +/// Mirrors `scalars.py`'s fixture-token handling, but typed: the sentinels +/// `MIN`/`MAX`/`ZERO` (the matrix comparison pivots) are dedicated variants, +/// and `N(i128)` is any explicit numeric literal. Range validity of committed +/// catalog data is enforced by the invariant `#[test]`s, not here. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Fixture { + Min, + Max, + Zero, + N(i128), +} + +impl Fixture { + /// Resolve this fixture to its numeric value for the given scalar kind. + /// Mirrors `scalars.py::numeric_value`. Infallible: `Min`/`Max` resolve to + /// the kind's bounds, `Zero` to `0`, and `N(n)` to `n` verbatim. It does + /// NOT range-check — for committed catalog data the range is statically + /// un-failable, and the bounds guard the old `Result` encoded lives in the + /// invariant test `every_fixture_value_is_within_kind_bounds` + /// (which compares this value against `[min_value(), max_value()]`). + pub fn numeric_value(self, kind: ScalarKind) -> i128 { + match self { + Fixture::Min => kind.min_value(), + Fixture::Max => kind.max_value(), + Fixture::Zero => 0, + Fixture::N(n) => n, + } + } + + /// Render this fixture as a Rust source literal of the given scalar kind. + /// Sentinels render to their named constant; `N` renders the integer. + /// Mirrors `scalars.py::render_literal`. + pub fn render_literal(self, kind: ScalarKind) -> String { + match self { + Fixture::Min => kind.min_symbol().to_string(), + Fixture::Max => kind.max_symbol().to_string(), + Fixture::Zero => kind.zero_symbol().to_string(), + Fixture::N(n) => n.to_string(), + } + } +} + +/// One generated public domain: a suffix appended to the type token and the +/// fixed index terms it carries. Suffix `""` is the storage-only domain. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DomainSpec { + pub suffix: &'static str, + pub terms: &'static [Term], +} + +/// A scalar encrypted-domain type: its SQL token, native Rust type, generated +/// domains, and fixture plaintext list. The Rust analogue of one `*.toml`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ScalarSpec { + pub token: &'static str, + pub kind: ScalarKind, + pub domains: &'static [DomainSpec], + pub fixtures: &'static [Fixture], +} + +impl ScalarSpec { + /// The fully-qualified domain name: `token` + `suffix`. Makes the old + /// "domain name must start with the token" validation structural. + pub fn domain_name(&self, domain: &DomainSpec) -> String { + format!("{}{}", self.token, domain.suffix) + } +} + +/// Domains shared by every ordered-integer scalar, in manifest file order: +/// storage (no terms), `_eq` (hm), `_ord_ore` (ore), `_ord` (ore). +const ORDERED_INT_DOMAINS: &[DomainSpec] = &[ + DomainSpec { suffix: "", terms: &[] }, + DomainSpec { suffix: "_eq", terms: &[Term::Hm] }, + DomainSpec { suffix: "_ord_ore", terms: &[Term::Ore] }, + DomainSpec { suffix: "_ord", terms: &[Term::Ore] }, +]; + +/// int4 fixture plaintexts — verbatim from `tasks/codegen/types/int4.toml`. +const INT4_FIXTURES: &[Fixture] = &[ + Fixture::Min, + Fixture::N(-100), + Fixture::N(-1), + Fixture::Zero, + Fixture::N(1), + Fixture::N(2), + Fixture::N(5), + Fixture::N(10), + Fixture::N(17), + Fixture::N(25), + Fixture::N(42), + Fixture::N(50), + Fixture::N(100), + Fixture::N(250), + Fixture::N(1000), + Fixture::N(9999), + Fixture::Max, +]; + +/// int2 fixture plaintexts — verbatim from `tasks/codegen/types/int2.toml`. +const INT2_FIXTURES: &[Fixture] = &[ + Fixture::Min, + Fixture::N(-30000), + Fixture::N(-100), + Fixture::N(-1), + Fixture::Zero, + Fixture::N(1), + Fixture::N(2), + Fixture::N(5), + Fixture::N(10), + Fixture::N(17), + Fixture::N(25), + Fixture::N(42), + Fixture::N(50), + Fixture::N(100), + Fixture::N(250), + Fixture::N(1000), + Fixture::N(9999), + Fixture::N(30000), + Fixture::Max, +]; + +const INT4: ScalarSpec = ScalarSpec { + token: "int4", + kind: ScalarKind::I32, + domains: ORDERED_INT_DOMAINS, + fixtures: INT4_FIXTURES, +}; + +const INT2: ScalarSpec = ScalarSpec { + token: "int2", + kind: ScalarKind::I16, + domains: ORDERED_INT_DOMAINS, + fixtures: INT2_FIXTURES, +}; + +/// The scalar catalog: the single source of truth replacing the TOML manifests +/// present on this branch (`int4`, `int2`). Order is significant (it drives +/// generation/enumeration order). `int8` is intentionally absent here — it is +/// added on the branch that introduces the int8 SQL surface by appending its +/// `ScalarSpec`; the capability layer above (`ScalarKind::I64`) already supports +/// it, so that addition is a pure append. +pub const CATALOG: &[ScalarSpec] = &[INT4, INT2]; + +#[cfg(test)] +mod rust_tests { + use super::*; + + #[test] + fn i32_facts_match_int4() { + assert_eq!(ScalarKind::I32.rust_type(), "i32"); + assert_eq!(ScalarKind::I32.min_symbol(), "i32::MIN"); + assert_eq!(ScalarKind::I32.max_symbol(), "i32::MAX"); + assert_eq!(ScalarKind::I32.zero_symbol(), "0"); + assert_eq!(ScalarKind::I32.min_value(), -2_147_483_648_i128); + assert_eq!(ScalarKind::I32.max_value(), 2_147_483_647_i128); + } + + #[test] + fn i16_facts_match_int2() { + assert_eq!(ScalarKind::I16.rust_type(), "i16"); + assert_eq!(ScalarKind::I16.min_symbol(), "i16::MIN"); + assert_eq!(ScalarKind::I16.max_symbol(), "i16::MAX"); + assert_eq!(ScalarKind::I16.zero_symbol(), "0"); + assert_eq!(ScalarKind::I16.min_value(), -32_768_i128); + assert_eq!(ScalarKind::I16.max_value(), 32_767_i128); + } + + #[test] + fn i64_facts() { + // Capability-layer fact: i64 is the Rust kind a future int8 maps onto. + // Present here so adding int8 later is a pure `CATALOG` append. + assert_eq!(ScalarKind::I64.rust_type(), "i64"); + assert_eq!(ScalarKind::I64.min_symbol(), "i64::MIN"); + assert_eq!(ScalarKind::I64.max_symbol(), "i64::MAX"); + assert_eq!(ScalarKind::I64.zero_symbol(), "0"); + assert_eq!(ScalarKind::I64.min_value(), -9_223_372_036_854_775_808_i128); + assert_eq!(ScalarKind::I64.max_value(), 9_223_372_036_854_775_807_i128); + } +} + +#[cfg(test)] +mod term_tests { + use super::*; + + #[test] + fn hm_term_provides_equality() { + let hm = Term::Hm; + assert_eq!(hm.json_key(), "hm"); + assert_eq!(hm.extractor(), "eq_term"); + assert_eq!(hm.returns(), "eql_v2.hmac_256"); + assert_eq!(hm.ctor(), "hmac_256"); + assert_eq!(hm.role(), "eq"); + assert_eq!(hm.operators(), &["=", "<>"]); + assert_eq!(hm.requires(), &["src/hmac_256/functions.sql"]); + } + + #[test] + fn ore_term_preserves_int4_sql_contract() { + let ore = Term::Ore; + assert_eq!(ore.json_key(), "ob"); + assert_eq!(ore.extractor(), "ord_term"); + assert_eq!(ore.returns(), "eql_v2.ore_block_u64_8_256"); + assert_eq!(ore.ctor(), "ore_block_u64_8_256"); + assert_eq!(ore.role(), "ord"); + assert_eq!(ore.operators(), &["=", "<>", "<", "<=", ">", ">="]); + assert_eq!( + ore.requires(), + &[ + "src/ore_block_u64_8_256/functions.sql", + "src/ore_block_u64_8_256/operators.sql", + ] + ); + } +} + +#[cfg(test)] +mod term_helper_tests { + use super::*; + + #[test] + fn operators_are_union_in_catalog_order() { + // ore then hm: ore's six ops first, hm adds nothing new. + assert_eq!( + Term::operators_for_terms(&[Term::Ore, Term::Hm]), + vec!["=", "<>", "<", "<=", ">", ">="] + ); + } + + #[test] + fn operators_for_terms_handles_empty() { + assert!(Term::operators_for_terms(&[]).is_empty()); + } + + #[test] + fn json_keys_come_from_catalog() { + assert_eq!( + Term::term_json_keys(&[Term::Hm, Term::Ore]), + vec!["hm", "ob"] + ); + assert!(Term::term_json_keys(&[]).is_empty()); + } + + #[test] + fn requires_are_deduplicated_in_order() { + assert_eq!( + Term::term_requires(&[Term::Ore, Term::Ore, Term::Hm]), + vec![ + "src/ore_block_u64_8_256/functions.sql", + "src/ore_block_u64_8_256/operators.sql", + "src/hmac_256/functions.sql", + ] + ); + assert!(Term::term_requires(&[]).is_empty()); + } + + #[test] + fn role_for_terms_handles_storage_eq_ord() { + assert_eq!(Term::role_for_terms(&[]), "storage"); + assert_eq!(Term::role_for_terms(&[Term::Hm]), "eq"); + assert_eq!(Term::role_for_terms(&[Term::Ore]), "ord"); + } + + #[test] + fn extractor_for_operator_picks_first_supporting_term() { + assert_eq!(Term::extractor_for_operator(&[Term::Hm], "="), Some("eq_term")); + assert_eq!(Term::extractor_for_operator(&[Term::Ore], "<"), Some("ord_term")); + assert_eq!( + Term::extractor_for_operator(&[Term::Hm, Term::Ore], "="), + Some("eq_term") + ); + assert_eq!( + Term::extractor_for_operator(&[Term::Hm, Term::Ore], "<"), + Some("ord_term") + ); + } + + #[test] + fn extractor_for_operator_none_when_unsupported() { + assert_eq!(Term::extractor_for_operator(&[Term::Hm], "<"), None); + assert_eq!(Term::extractor_for_operator(&[], "="), None); + } +} + +#[cfg(test)] +mod fixture_tests { + use super::*; + + #[test] + fn numeric_value_resolves_sentinels_and_literals_for_i32() { + assert_eq!(Fixture::Min.numeric_value(ScalarKind::I32), -2_147_483_648); + assert_eq!(Fixture::Max.numeric_value(ScalarKind::I32), 2_147_483_647); + assert_eq!(Fixture::Zero.numeric_value(ScalarKind::I32), 0); + assert_eq!(Fixture::N(42).numeric_value(ScalarKind::I32), 42); + assert_eq!(Fixture::N(-1).numeric_value(ScalarKind::I32), -1); + } + + #[test] + fn numeric_value_resolves_sentinels_per_kind() { + // Sentinels resolve to the kind's bounds; zero is always 0. + assert_eq!(Fixture::Min.numeric_value(ScalarKind::I16), -32_768); + assert_eq!(Fixture::Max.numeric_value(ScalarKind::I16), 32_767); + assert_eq!(Fixture::Min.numeric_value(ScalarKind::I64), -9_223_372_036_854_775_808); + assert_eq!(Fixture::Max.numeric_value(ScalarKind::I64), 9_223_372_036_854_775_807); + assert_eq!(Fixture::Zero.numeric_value(ScalarKind::I64), 0); + // `numeric_value` is infallible: it resolves a literal verbatim and does + // NOT range-check. Range validity of committed catalog data is enforced + // by the invariant test `every_fixture_value_is_within_kind_bounds`, + // which compares `numeric_value` against `[min_value(), max_value()]`. + assert_eq!(Fixture::N(5_000_000_000).numeric_value(ScalarKind::I64), 5_000_000_000); + } + + #[test] + fn render_literal_maps_sentinels() { + assert_eq!(Fixture::Min.render_literal(ScalarKind::I32), "i32::MIN"); + assert_eq!(Fixture::Max.render_literal(ScalarKind::I32), "i32::MAX"); + assert_eq!(Fixture::Zero.render_literal(ScalarKind::I32), "0"); + assert_eq!(Fixture::Min.render_literal(ScalarKind::I16), "i16::MIN"); + assert_eq!(Fixture::Max.render_literal(ScalarKind::I64), "i64::MAX"); + } + + #[test] + fn render_literal_passes_through_numeric() { + assert_eq!(Fixture::N(-100).render_literal(ScalarKind::I32), "-100"); + assert_eq!(Fixture::N(9999).render_literal(ScalarKind::I32), "9999"); + assert_eq!(Fixture::N(5_000_000_000).render_literal(ScalarKind::I64), "5000000000"); + } +} + +#[cfg(test)] +mod catalog_tests { + use super::*; + + fn scalar(token: &str) -> &'static ScalarSpec { + CATALOG + .iter() + .find(|s| s.token == token) + .unwrap_or_else(|| panic!("{token} missing from CATALOG")) + } + + #[test] + fn catalog_has_int4_int2_in_order() { + let tokens: Vec<&str> = CATALOG.iter().map(|s| s.token).collect(); + assert_eq!(tokens, vec!["int4", "int2"]); + } + + #[test] + fn int4_maps_to_i32_with_four_domains() { + let s = scalar("int4"); + assert_eq!(s.kind, ScalarKind::I32); + let suffixes: Vec<&str> = s.domains.iter().map(|d| d.suffix).collect(); + // File order from int4.toml: storage, _eq, _ord_ore, _ord. + assert_eq!(suffixes, vec!["", "_eq", "_ord_ore", "_ord"]); + } + + #[test] + fn int4_domain_terms_match_manifest() { + let s = scalar("int4"); + assert_eq!(s.domains[0].terms, &[] as &[Term]); // storage + assert_eq!(s.domains[1].terms, &[Term::Hm]); // _eq + assert_eq!(s.domains[2].terms, &[Term::Ore]); // _ord_ore + assert_eq!(s.domains[3].terms, &[Term::Ore]); // _ord + } + + #[test] + fn int2_rust_type() { + assert_eq!(scalar("int2").kind, ScalarKind::I16); + } + + #[test] + fn all_types_share_the_same_domain_shape() { + // Every scalar declares the same four domains with the same terms; + // only the token differs (the matrix-snapshot collapse depends on this). + for s in CATALOG { + let suffixes: Vec<&str> = s.domains.iter().map(|d| d.suffix).collect(); + assert_eq!( + suffixes, + vec!["", "_eq", "_ord_ore", "_ord"], + "{} has unexpected domain set", + s.token + ); + } + } + + #[test] + fn domain_name_concatenates_token_and_suffix() { + let s = scalar("int4"); + assert_eq!(s.domain_name(&s.domains[0]), "int4"); // storage + assert_eq!(s.domain_name(&s.domains[1]), "int4_eq"); + assert_eq!(s.domain_name(&s.domains[3]), "int4_ord"); + } + + #[test] + fn int4_fixtures_match_manifest() { + let s = scalar("int4"); + // From int4.toml [fixture] values, in order. + let expected = vec![ + Fixture::Min, + Fixture::N(-100), + Fixture::N(-1), + Fixture::Zero, + Fixture::N(1), + Fixture::N(2), + Fixture::N(5), + Fixture::N(10), + Fixture::N(17), + Fixture::N(25), + Fixture::N(42), + Fixture::N(50), + Fixture::N(100), + Fixture::N(250), + Fixture::N(1000), + Fixture::N(9999), + Fixture::Max, + ]; + assert_eq!(s.fixtures, expected.as_slice()); + } + + #[test] + fn int2_fixtures_match_manifest() { + let s = scalar("int2"); + // From int2.toml [fixture] values, in order — includes the wide + // ±30000 values that exercise the i16 bounds. + assert!(s.fixtures.contains(&Fixture::N(-30000))); + assert!(s.fixtures.contains(&Fixture::N(30000))); + assert_eq!(s.fixtures.first(), Some(&Fixture::Min)); + assert_eq!(s.fixtures.last(), Some(&Fixture::Max)); + } +} + +#[cfg(test)] +mod invariant_tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn every_domain_name_starts_with_its_token() { + for s in CATALOG { + for d in s.domains { + let name = s.domain_name(d); + assert!( + name == s.token || name.starts_with(&format!("{}_", s.token)), + "{name} does not start with token {}", + s.token + ); + } + } + } + + #[test] + fn every_type_has_at_least_one_domain() { + for s in CATALOG { + assert!(!s.domains.is_empty(), "{} has no domains", s.token); + } + } + + #[test] + fn fixtures_include_min_max_and_zero() { + for s in CATALOG { + let resolved: Vec = s + .fixtures + .iter() + .map(|f| f.numeric_value(s.kind)) + .collect(); + assert!( + resolved.contains(&s.kind.min_value()), + "{} fixtures missing MIN", + s.token + ); + assert!( + resolved.contains(&s.kind.max_value()), + "{} fixtures missing MAX", + s.token + ); + assert!(resolved.contains(&0), "{} fixtures missing zero", s.token); + } + } + + #[test] + fn fixture_values_are_distinct_by_resolved_number() { + for s in CATALOG { + let mut seen: HashMap = HashMap::new(); + for f in s.fixtures { + let n = f.numeric_value(s.kind); + if let Some(prev) = seen.insert(n, *f) { + panic!( + "{}: {f:?} duplicates {prev:?} (both resolve to {n})", + s.token + ); + } + } + } + } + + #[test] + fn every_fixture_value_is_within_kind_bounds() { + // `numeric_value` is infallible, so the range guarantee the old + // `Result` encoded is asserted explicitly here: this is the ONLY guard + // against an out-of-range `N` in committed catalog data. + for s in CATALOG { + let (lo, hi) = (s.kind.min_value(), s.kind.max_value()); + for f in s.fixtures { + let n = f.numeric_value(s.kind); + assert!( + n >= lo && n <= hi, + "{}: fixture {f:?} resolves to {n}, out of range [{lo}, {hi}]", + s.token + ); + } + } + } + + #[test] + fn helper_outputs_match_for_known_domains() { + // Cross-check the Term helpers against a known domain shape on int4. + let s = CATALOG.iter().find(|s| s.token == "int4").unwrap(); + // storage domain: no terms. + assert_eq!(Term::role_for_terms(s.domains[0].terms), "storage"); + assert!(Term::operators_for_terms(s.domains[0].terms).is_empty()); + // _eq domain: hm => equality only. + assert_eq!(Term::role_for_terms(s.domains[1].terms), "eq"); + assert_eq!( + Term::operators_for_terms(s.domains[1].terms), + vec!["=", "<>"] + ); + assert_eq!(Term::term_json_keys(s.domains[1].terms), vec!["hm"]); + // _ord domain: ore => full ordering. + assert_eq!(Term::role_for_terms(s.domains[3].terms), "ord"); + assert_eq!( + Term::operators_for_terms(s.domains[3].terms), + vec!["=", "<>", "<", "<=", ">", ">="] + ); + assert_eq!(Term::term_json_keys(s.domains[3].terms), vec!["ob"]); + assert_eq!( + Term::extractor_for_operator(s.domains[3].terms, "<"), + Some("ord_term") + ); + } +} From a12c729bb1395b5e5b76419cb5ec5598c06379b9 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 08:19:56 +1000 Subject: [PATCH 28/93] ci: move rust-cache workspaces key to root for the Cargo workspace --- .github/workflows/bench-eql.yml | 2 +- .github/workflows/test-eql.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bench-eql.yml b/.github/workflows/bench-eql.yml index cee01ca3..5b302c08 100644 --- a/.github/workflows/bench-eql.yml +++ b/.github/workflows/bench-eql.yml @@ -50,7 +50,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: - workspaces: tests/sqlx + workspaces: . shared-key: sqlx-tests - name: Setup database diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index ff82bb42..6dbc62e8 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -50,7 +50,7 @@ jobs: - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: - workspaces: tests/sqlx + workspaces: . shared-key: sqlx-tests - name: Validate v2.2 / v2.3 payload schemas @@ -108,7 +108,7 @@ jobs: - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: - workspaces: tests/sqlx + workspaces: . shared-key: sqlx-tests # Regenerate the matrix test-name inventory with the SAME pinned feature @@ -159,7 +159,7 @@ jobs: - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: - workspaces: tests/sqlx + workspaces: . shared-key: sqlx-tests - name: Setup database (Postgres ${{ matrix.postgres-version }}) From 49675dca8466396f919975e80f3e9c3f9d6ac118 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:07:08 +1000 Subject: [PATCH 29/93] feat(codegen): value-kind Fixture + fixtures! macro in eql-scalars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the integer-only `Fixture::N(i128)` with a value-kind-tagged enum (`Int`/`Numeric`/`Text`/`Jsonb` + Min/Max/Zero sentinels) so one non-generic CATALOG spans every scalar kind. A new `fixtures!` macro builds each type's fixture array and range-checks integer literals at the definition site (`const _RANGE_CHECK`), so an out-of-range literal (e.g. N(-40000) for i16) fails to compile — the compile-time guarantee from the PR review, delivered where it applies without primitive-generic `Fixture` (which would force a heterogeneous catalog and only help integer kinds). - ScalarKind gains Numeric/Text/Jsonb + is_int(); bounded-numeric accessors panic on non-int kinds (gated by is_int(); explicit arms so a future integer variant breaks the build rather than silently hitting the panic). - numeric_value -> Option (None for string kinds); render_literal quotes string-backed kinds via Debug. - Tests: per-kind DistinctKey; #[should_panic] coverage for the accessors; macro edge cases (empty/trailing-comma/sentinels-only); string-variant render/None. 41 pass (was 30). - CLAUDE.md: text/jsonb are "planned, no SQL surface yet", not "out of scope". Comments note text/numeric are ORE-orderable (only jsonb isn't). --- CLAUDE.md | 4 +- crates/eql-scalars/src/lib.rs | 501 ++++++++++++++++++++++++---------- 2 files changed, 357 insertions(+), 148 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4b489123..c15fa0e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,9 +76,9 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search ### Encrypted-Domain Types -`src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, and `timestamp` follow this materializer pattern. `jsonb` needs a separate design and is out of scope for the scalar materializer. +`src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, `text`, and `jsonb` follow this materializer pattern. `text`, `numeric`, and `jsonb` are planned but have no generated SQL surface yet — `jsonb` in particular needs a separate SQL design beyond the ordered-scalar materializer. The `eql-scalars` fixture catalog (`crates/eql-scalars`) already models their fixture values ahead of the SQL surface. -Adding a scalar encrypted-domain type is generated from a minimal manifest at `tasks/codegen/types/.toml`: the filename supplies ``, and the `[domain]` table maps each generated domain name to the fixed index terms it carries. Example: `int4_eq = ["hm"]`, `int4_ord = ["ore"]`. Term capabilities are fixed in `tasks/codegen/terms.py`: `hm` provides equality, and `ore` provides equality plus ordering. `mise run build` regenerates the scalar SQL surface into `src/encrypted_domain//` from every manifest at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. Use `mise run codegen:domain ` to refresh a single type manually while iterating on its manifest, or `mise run codegen:domain:all` to regenerate every type at once (the same enumeration `mise run build` uses). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` files are gitignored and never committed — the TOML manifest plus `tasks/codegen/terms.py` are the source of truth. Generated files carry an `AUTO-GENERATED — DO NOT EDIT` header; change the manifest or term catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. +Adding a scalar encrypted-domain type is generated from a minimal manifest at `tasks/codegen/types/.toml`: the filename supplies ``, and the `[domain]` table maps each generated domain name to the fixed index terms it carries. Example: `int4_eq = ["hm"]`, `int4_ord = ["ore"]`. Term capabilities are fixed in `tasks/codegen/terms.py`: `hm` provides equality, and `ore` provides equality plus ordering. `mise run build` regenerates the scalar SQL surface into `src/encrypted_domain//` from every manifest at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. Use `mise run codegen:domain ` to refresh a single type manually while iterating on its manifest, or `mise run codegen:domain:all` to regenerate every type at once (the same enumeration `mise run build` uses). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` files are gitignored and never committed — the TOML manifest plus `tasks/codegen/terms.py` are the source of truth. Generated files carry an `AUTO-GENERATED — DO NOT EDIT` header; change the manifest or term catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` have no generated SQL surface yet (they are planned, not out of scope); `jsonb` needs a separate SQL design beyond this ordered-scalar materializer. **Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the manifest only declares domain names and terms. New term behavior belongs in `tasks/codegen/terms.py` with tests, not in free-form TOML fields. diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index a9c7cba6..91cfae3d 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -1,73 +1,117 @@ -//! Scalar/term catalog for EQL encrypted-domain codegen. +//! Scalar/term catalog for EQL encrypted-domain codegen — the Rust source of +//! truth replacing `tasks/codegen/{scalars,terms,spec}.py` and the +//! `types/*.toml` manifests. Std-only, no dependencies. //! -//! Replaces the Python `tasks/codegen/scalars.py`, `terms.py`, and `spec.py` -//! plus the `tasks/codegen/types/*.toml` manifests. Plain Rust data + enums; -//! std-only, no dependencies. +//! `Fixture` is value-kind tagged (one non-generic enum, variant = value kind), +//! so a single `CATALOG` spans every scalar kind. Integer literals are +//! range-checked at their definition site by `fixtures!` (`N(-40000)` for `i16` +//! does not compile). //! -//! Plans 2 and 3 depend on the public names here verbatim — do not rename. +//! Capability axes are independent: equality covers every kind; order covers +//! every kind except `jsonb` (ORE compares ciphertext, so it is +//! plaintext-agnostic — `text`/`date` order like integers); only the integer +//! kinds have an i128 range with `Min`/`Max`/`Zero` sentinels. `numeric_value` +//! cannot yet express the order of a non-integer fixture set. +//! +//! Public names are consumed verbatim by the later codegen plans — do not rename. -/// The native Rust scalar a domain type maps onto. +/// The native scalar a domain type maps onto. Integer kinds carry i128 bounds; +/// the others (`Numeric`/`Text`/`Jsonb`) have string fixtures and no numeric +/// range — though `Numeric`/`Text` are still ORE-orderable, only `Jsonb` is not. +/// Capability layer only: `CATALOG` declares which kinds actually exist. /// -/// Mirrors `scalars.py`'s `ScalarKind` rendering facts. `min_value`/`max_value` -/// are widened to `i128` so a single accessor type covers `i16`..`i64` bounds. -/// This is the fixed capability layer: a variant being present here only means -/// the generator *can* render that Rust kind; the `CATALOG` registry below is -/// what declares which scalar types actually exist. +/// The bounded-numeric accessors below `panic!` on non-integer kinds; callers +/// gate with `is_int()`, so the panic guards against misuse rather than being a +/// reachable path (kept over `Option` to spare every integer caller an unwrap). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ScalarKind { I16, I32, I64, + Numeric, + Text, + Jsonb, } impl ScalarKind { + /// Fixed-width integer kinds — those with i128 bounds and `Min`/`Max`/`Zero` + /// sentinels. Gates the bounded-numeric accessors and invariants. NOT an + /// orderability test: `Numeric`/`Text` are ORE-orderable yet not integers. + pub const fn is_int(self) -> bool { + matches!(self, ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64) + } + /// The Rust type name as it appears in generated source (e.g. `"i32"`). pub const fn rust_type(self) -> &'static str { match self { ScalarKind::I16 => "i16", ScalarKind::I32 => "i32", ScalarKind::I64 => "i64", + ScalarKind::Numeric => "numeric", + ScalarKind::Text => "text", + ScalarKind::Jsonb => "jsonb", } } - /// The `MIN` named-constant symbol (e.g. `"i32::MIN"`). + /// The `MIN` named-constant symbol (e.g. `"i32::MIN"`). Integer kinds only. pub const fn min_symbol(self) -> &'static str { match self { ScalarKind::I16 => "i16::MIN", ScalarKind::I32 => "i32::MIN", ScalarKind::I64 => "i64::MIN", + // Explicit (not `_`) so a future integer variant is a compile + // error here rather than silently hitting the panic. + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + panic!("min_symbol is only defined for integer kinds") + } } } - /// The `MAX` named-constant symbol (e.g. `"i32::MAX"`). + /// The `MAX` named-constant symbol (e.g. `"i32::MAX"`). Integer kinds only. pub const fn max_symbol(self) -> &'static str { match self { ScalarKind::I16 => "i16::MAX", ScalarKind::I32 => "i32::MAX", ScalarKind::I64 => "i64::MAX", + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + panic!("max_symbol is only defined for integer kinds") + } } } - /// The zero literal symbol (always `"0"`). + /// The zero literal symbol (always `"0"`). Integer kinds only. pub const fn zero_symbol(self) -> &'static str { - "0" + match self { + ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 => "0", + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + panic!("zero_symbol is only defined for integer kinds") + } + } } /// Inclusive lower bound of the representable range, widened to `i128`. + /// Integer kinds only. pub const fn min_value(self) -> i128 { match self { ScalarKind::I16 => i16::MIN as i128, ScalarKind::I32 => i32::MIN as i128, ScalarKind::I64 => i64::MIN as i128, + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + panic!("min_value is only defined for integer kinds") + } } } /// Inclusive upper bound of the representable range, widened to `i128`. + /// Integer kinds only. pub const fn max_value(self) -> i128 { match self { ScalarKind::I16 => i16::MAX as i128, ScalarKind::I32 => i32::MAX as i128, ScalarKind::I64 => i64::MAX as i128, + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + panic!("max_value is only defined for integer kinds") + } } } } @@ -148,9 +192,7 @@ impl Term { impl Term { /// Stable dedupe — first occurrence wins. The Rust analogue of /// `terms.py`'s `dict.fromkeys` ordering contract. - fn dedupe_preserving_order<'a>( - items: impl IntoIterator, - ) -> Vec<&'a str> { + fn dedupe_preserving_order<'a>(items: impl IntoIterator) -> Vec<&'a str> { let mut out: Vec<&'a str> = Vec::new(); for item in items { if !out.contains(&item) { @@ -163,9 +205,7 @@ impl Term { /// Supported operators for the union of a domain's terms (catalog order, /// deduped). Mirrors `terms.py::operators_for_terms`. pub fn operators_for_terms(terms: &[Term]) -> Vec<&'static str> { - Self::dedupe_preserving_order( - terms.iter().flat_map(|t| t.operators().iter().copied()), - ) + Self::dedupe_preserving_order(terms.iter().flat_map(|t| t.operators().iter().copied())) } /// JSON payload keys required by these terms (deduped, in order). @@ -177,9 +217,7 @@ impl Term { /// SQL `-- REQUIRE:` edges needed by these terms (deduped, in order). /// Mirrors `terms.py::term_requires`. pub fn term_requires(terms: &[Term]) -> Vec<&'static str> { - Self::dedupe_preserving_order( - terms.iter().flat_map(|t| t.requires().iter().copied()), - ) + Self::dedupe_preserving_order(terms.iter().flat_map(|t| t.requires().iter().copied())) } /// The extractor that supports `op` for a domain carrying `terms`, or @@ -203,46 +241,49 @@ impl Term { } } -/// A single fixture plaintext value for a scalar type. +/// A single fixture plaintext value, value-kind tagged: `Min`/`Max`/`Zero` are +/// the integer matrix pivots (resolved per-kind); `Int` is an integer literal; +/// `Numeric`/`Text`/`Jsonb` carry rendered string literals. /// -/// Mirrors `scalars.py`'s fixture-token handling, but typed: the sentinels -/// `MIN`/`MAX`/`ZERO` (the matrix comparison pivots) are dedicated variants, -/// and `N(i128)` is any explicit numeric literal. Range validity of committed -/// catalog data is enforced by the invariant `#[test]`s, not here. +/// `fixtures!` range-checks `Int` literals at compile time, but a hand-built +/// `Fixture::Int(n)` is not — hence the runtime invariant tests. `Int(MIN)` and +/// `Min` resolve equal but render differently (`"-32768"` vs `"i16::MIN"`). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Fixture { Min, Max, Zero, - N(i128), + Int(i128), + Numeric(&'static str), + Text(&'static str), + Jsonb(&'static str), } impl Fixture { - /// Resolve this fixture to its numeric value for the given scalar kind. - /// Mirrors `scalars.py::numeric_value`. Infallible: `Min`/`Max` resolve to - /// the kind's bounds, `Zero` to `0`, and `N(n)` to `n` verbatim. It does - /// NOT range-check — for committed catalog data the range is statically - /// un-failable, and the bounds guard the old `Result` encoded lives in the - /// invariant test `every_fixture_value_is_within_kind_bounds` - /// (which compares this value against `[min_value(), max_value()]`). - pub fn numeric_value(self, kind: ScalarKind) -> i128 { + /// The integer value for this fixture (`Min`/`Max` -> kind bounds, `Zero` -> + /// 0, `Int(n)` -> n), or `None` for the string-backed kinds. Does not + /// range-check; `every_fixture_value_is_within_kind_bounds` guards the bounds. + pub fn numeric_value(self, kind: ScalarKind) -> Option { match self { - Fixture::Min => kind.min_value(), - Fixture::Max => kind.max_value(), - Fixture::Zero => 0, - Fixture::N(n) => n, + Fixture::Min => Some(kind.min_value()), + Fixture::Max => Some(kind.max_value()), + Fixture::Zero => Some(0), + Fixture::Int(n) => Some(n), + Fixture::Numeric(_) | Fixture::Text(_) | Fixture::Jsonb(_) => None, } } - /// Render this fixture as a Rust source literal of the given scalar kind. - /// Sentinels render to their named constant; `N` renders the integer. - /// Mirrors `scalars.py::render_literal`. + /// Render as a Rust source literal: sentinels -> named constant, `Int` -> the + /// number, string kinds -> a `Debug`-quoted (Rust-escaped, not SQL) literal. pub fn render_literal(self, kind: ScalarKind) -> String { match self { Fixture::Min => kind.min_symbol().to_string(), Fixture::Max => kind.max_symbol().to_string(), Fixture::Zero => kind.zero_symbol().to_string(), - Fixture::N(n) => n.to_string(), + Fixture::Int(n) => n.to_string(), + Fixture::Numeric(s) | Fixture::Text(s) | Fixture::Jsonb(s) => { + format!("{s:?}") + } } } } @@ -276,55 +317,56 @@ impl ScalarSpec { /// Domains shared by every ordered-integer scalar, in manifest file order: /// storage (no terms), `_eq` (hm), `_ord_ore` (ore), `_ord` (ore). const ORDERED_INT_DOMAINS: &[DomainSpec] = &[ - DomainSpec { suffix: "", terms: &[] }, - DomainSpec { suffix: "_eq", terms: &[Term::Hm] }, - DomainSpec { suffix: "_ord_ore", terms: &[Term::Ore] }, - DomainSpec { suffix: "_ord", terms: &[Term::Ore] }, + DomainSpec { + suffix: "", + terms: &[], + }, + DomainSpec { + suffix: "_eq", + terms: &[Term::Hm], + }, + DomainSpec { + suffix: "_ord_ore", + terms: &[Term::Ore], + }, + DomainSpec { + suffix: "_ord", + terms: &[Term::Ore], + }, ]; +/// Builds a `&[Fixture]`. The `int ;` arm (a tt-muncher over `Min`/`Max`/ +/// `Zero` and `N()`) range-checks each literal against `` at compile +/// time via `const _RANGE_CHECK`, so out-of-range literals do not compile; +/// `text;`/`numeric;`/`jsonb;` wrap string literals. The reject case has no +/// in-crate test (macro isn't exported, no `trybuild` under zero-deps) — verify +/// by hand with a bad `N(..)`. +macro_rules! fixtures { + (int $t:ty; $($body:tt)*) => { fixtures!(@int $t; [] $($body)*) }; + (@int $t:ty; [$($acc:expr),*]) => { &[$($acc),*] }; + (@int $t:ty; [$($acc:expr),*] , $($r:tt)*) => { fixtures!(@int $t; [$($acc),*] $($r)*) }; + (@int $t:ty; [$($acc:expr),*] Min $($r:tt)*) => { fixtures!(@int $t; [$($acc,)* Fixture::Min ] $($r)*) }; + (@int $t:ty; [$($acc:expr),*] Max $($r:tt)*) => { fixtures!(@int $t; [$($acc,)* Fixture::Max ] $($r)*) }; + (@int $t:ty; [$($acc:expr),*] Zero $($r:tt)*) => { fixtures!(@int $t; [$($acc,)* Fixture::Zero] $($r)*) }; + (@int $t:ty; [$($acc:expr),*] N($v:literal) $($r:tt)*) => { + fixtures!(@int $t; [$($acc,)* Fixture::Int({ const _RANGE_CHECK: $t = $v; $v as i128 })] $($r)*) + }; + (text; $($s:literal),* $(,)?) => { &[$(Fixture::Text($s)),*] }; + (numeric; $($s:literal),* $(,)?) => { &[$(Fixture::Numeric($s)),*] }; + (jsonb; $($s:literal),* $(,)?) => { &[$(Fixture::Jsonb($s)),*] }; +} + /// int4 fixture plaintexts — verbatim from `tasks/codegen/types/int4.toml`. -const INT4_FIXTURES: &[Fixture] = &[ - Fixture::Min, - Fixture::N(-100), - Fixture::N(-1), - Fixture::Zero, - Fixture::N(1), - Fixture::N(2), - Fixture::N(5), - Fixture::N(10), - Fixture::N(17), - Fixture::N(25), - Fixture::N(42), - Fixture::N(50), - Fixture::N(100), - Fixture::N(250), - Fixture::N(1000), - Fixture::N(9999), - Fixture::Max, -]; +/// `N(..)` literals are range-checked against `i32` at compile time. +const INT4_FIXTURES: &[Fixture] = fixtures!(int i32; + Min, N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), N(25), + N(42), N(50), N(100), N(250), N(1000), N(9999), Max); /// int2 fixture plaintexts — verbatim from `tasks/codegen/types/int2.toml`. -const INT2_FIXTURES: &[Fixture] = &[ - Fixture::Min, - Fixture::N(-30000), - Fixture::N(-100), - Fixture::N(-1), - Fixture::Zero, - Fixture::N(1), - Fixture::N(2), - Fixture::N(5), - Fixture::N(10), - Fixture::N(17), - Fixture::N(25), - Fixture::N(42), - Fixture::N(50), - Fixture::N(100), - Fixture::N(250), - Fixture::N(1000), - Fixture::N(9999), - Fixture::N(30000), - Fixture::Max, -]; +/// `N(..)` literals are range-checked against `i16` at compile time. +const INT2_FIXTURES: &[Fixture] = fixtures!(int i16; + Min, N(-30000), N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), + N(25), N(42), N(50), N(100), N(250), N(1000), N(9999), N(30000), Max); const INT4: ScalarSpec = ScalarSpec { token: "int4", @@ -340,12 +382,8 @@ const INT2: ScalarSpec = ScalarSpec { fixtures: INT2_FIXTURES, }; -/// The scalar catalog: the single source of truth replacing the TOML manifests -/// present on this branch (`int4`, `int2`). Order is significant (it drives -/// generation/enumeration order). `int8` is intentionally absent here — it is -/// added on the branch that introduces the int8 SQL surface by appending its -/// `ScalarSpec`; the capability layer above (`ScalarKind::I64`) already supports -/// it, so that addition is a pure append. +/// The scalar catalog — the single source of truth. Order is significant (it +/// drives generation order). New types are appended as their SQL surface lands. pub const CATALOG: &[ScalarSpec] = &[INT4, INT2]; #[cfg(test)] @@ -372,6 +410,47 @@ mod rust_tests { assert_eq!(ScalarKind::I16.max_value(), 32_767_i128); } + #[test] + fn is_int_classifies_kinds() { + assert!(ScalarKind::I16.is_int()); + assert!(ScalarKind::I32.is_int()); + assert!(ScalarKind::I64.is_int()); + assert!(!ScalarKind::Numeric.is_int()); + assert!(!ScalarKind::Text.is_int()); + assert!(!ScalarKind::Jsonb.is_int()); + } + + // Pin that the bounded-numeric accessors panic (with message) on non-int kinds. + #[test] + #[should_panic(expected = "min_symbol is only defined for integer kinds")] + fn min_symbol_panics_on_non_int_kind() { + ScalarKind::Text.min_symbol(); + } + + #[test] + #[should_panic(expected = "max_symbol is only defined for integer kinds")] + fn max_symbol_panics_on_non_int_kind() { + ScalarKind::Numeric.max_symbol(); + } + + #[test] + #[should_panic(expected = "zero_symbol is only defined for integer kinds")] + fn zero_symbol_panics_on_non_int_kind() { + ScalarKind::Jsonb.zero_symbol(); + } + + #[test] + #[should_panic(expected = "min_value is only defined for integer kinds")] + fn min_value_panics_on_non_int_kind() { + ScalarKind::Text.min_value(); + } + + #[test] + #[should_panic(expected = "max_value is only defined for integer kinds")] + fn max_value_panics_on_non_int_kind() { + ScalarKind::Jsonb.max_value(); + } + #[test] fn i64_facts() { // Capability-layer fact: i64 is the Rust kind a future int8 maps onto. @@ -469,8 +548,14 @@ mod term_helper_tests { #[test] fn extractor_for_operator_picks_first_supporting_term() { - assert_eq!(Term::extractor_for_operator(&[Term::Hm], "="), Some("eq_term")); - assert_eq!(Term::extractor_for_operator(&[Term::Ore], "<"), Some("ord_term")); + assert_eq!( + Term::extractor_for_operator(&[Term::Hm], "="), + Some("eq_term") + ); + assert_eq!( + Term::extractor_for_operator(&[Term::Ore], "<"), + Some("ord_term") + ); assert_eq!( Term::extractor_for_operator(&[Term::Hm, Term::Ore], "="), Some("eq_term") @@ -494,26 +579,51 @@ mod fixture_tests { #[test] fn numeric_value_resolves_sentinels_and_literals_for_i32() { - assert_eq!(Fixture::Min.numeric_value(ScalarKind::I32), -2_147_483_648); - assert_eq!(Fixture::Max.numeric_value(ScalarKind::I32), 2_147_483_647); - assert_eq!(Fixture::Zero.numeric_value(ScalarKind::I32), 0); - assert_eq!(Fixture::N(42).numeric_value(ScalarKind::I32), 42); - assert_eq!(Fixture::N(-1).numeric_value(ScalarKind::I32), -1); + assert_eq!( + Fixture::Min.numeric_value(ScalarKind::I32), + Some(-2_147_483_648) + ); + assert_eq!( + Fixture::Max.numeric_value(ScalarKind::I32), + Some(2_147_483_647) + ); + assert_eq!(Fixture::Zero.numeric_value(ScalarKind::I32), Some(0)); + assert_eq!(Fixture::Int(42).numeric_value(ScalarKind::I32), Some(42)); + assert_eq!(Fixture::Int(-1).numeric_value(ScalarKind::I32), Some(-1)); } #[test] fn numeric_value_resolves_sentinels_per_kind() { // Sentinels resolve to the kind's bounds; zero is always 0. - assert_eq!(Fixture::Min.numeric_value(ScalarKind::I16), -32_768); - assert_eq!(Fixture::Max.numeric_value(ScalarKind::I16), 32_767); - assert_eq!(Fixture::Min.numeric_value(ScalarKind::I64), -9_223_372_036_854_775_808); - assert_eq!(Fixture::Max.numeric_value(ScalarKind::I64), 9_223_372_036_854_775_807); - assert_eq!(Fixture::Zero.numeric_value(ScalarKind::I64), 0); - // `numeric_value` is infallible: it resolves a literal verbatim and does - // NOT range-check. Range validity of committed catalog data is enforced - // by the invariant test `every_fixture_value_is_within_kind_bounds`, - // which compares `numeric_value` against `[min_value(), max_value()]`. - assert_eq!(Fixture::N(5_000_000_000).numeric_value(ScalarKind::I64), 5_000_000_000); + assert_eq!(Fixture::Min.numeric_value(ScalarKind::I16), Some(-32_768)); + assert_eq!(Fixture::Max.numeric_value(ScalarKind::I16), Some(32_767)); + assert_eq!( + Fixture::Min.numeric_value(ScalarKind::I64), + Some(-9_223_372_036_854_775_808) + ); + assert_eq!( + Fixture::Max.numeric_value(ScalarKind::I64), + Some(9_223_372_036_854_775_807) + ); + assert_eq!(Fixture::Zero.numeric_value(ScalarKind::I64), Some(0)); + // `Int` resolves verbatim; no runtime range-check here. + assert_eq!( + Fixture::Int(5_000_000_000).numeric_value(ScalarKind::I64), + Some(5_000_000_000) + ); + } + + #[test] + fn numeric_value_is_none_for_string_variants() { + assert_eq!(Fixture::Text("alice").numeric_value(ScalarKind::Text), None); + assert_eq!( + Fixture::Numeric("3.14").numeric_value(ScalarKind::Numeric), + None + ); + assert_eq!( + Fixture::Jsonb(r#"{"a":1}"#).numeric_value(ScalarKind::Jsonb), + None + ); } #[test] @@ -527,9 +637,69 @@ mod fixture_tests { #[test] fn render_literal_passes_through_numeric() { - assert_eq!(Fixture::N(-100).render_literal(ScalarKind::I32), "-100"); - assert_eq!(Fixture::N(9999).render_literal(ScalarKind::I32), "9999"); - assert_eq!(Fixture::N(5_000_000_000).render_literal(ScalarKind::I64), "5000000000"); + assert_eq!(Fixture::Int(-100).render_literal(ScalarKind::I32), "-100"); + assert_eq!(Fixture::Int(9999).render_literal(ScalarKind::I32), "9999"); + assert_eq!( + Fixture::Int(5_000_000_000).render_literal(ScalarKind::I64), + "5000000000" + ); + } + + #[test] + fn render_literal_quotes_string_variants() { + // String-backed kinds render a valid quoted Rust literal. + assert_eq!( + Fixture::Text("alice").render_literal(ScalarKind::Text), + "\"alice\"" + ); + assert_eq!( + Fixture::Numeric("3.14").render_literal(ScalarKind::Numeric), + "\"3.14\"" + ); + assert_eq!( + Fixture::Jsonb(r#"{"a":1}"#).render_literal(ScalarKind::Jsonb), + r#""{\"a\":1}""# + ); + } + + #[test] + fn fixtures_macro_builds_each_kind() { + // The int arm range-checks at compile time; sentinels + literals mix. + const INTS: &[Fixture] = fixtures!(int i16; Min, N(-1), Zero, N(30000), Max); + assert_eq!( + INTS, + &[ + Fixture::Min, + Fixture::Int(-1), + Fixture::Zero, + Fixture::Int(30000), + Fixture::Max + ] + ); + // The string arms wrap into the matching variant. + const TEXTS: &[Fixture] = fixtures!(text; "alice", "bob"); + assert_eq!(TEXTS, &[Fixture::Text("alice"), Fixture::Text("bob")]); + const NUMS: &[Fixture] = fixtures!(numeric; "0.1", "-2.5"); + assert_eq!(NUMS, &[Fixture::Numeric("0.1"), Fixture::Numeric("-2.5")]); + const JSONS: &[Fixture] = fixtures!(jsonb; r#"{"a":1}"#); + assert_eq!(JSONS, &[Fixture::Jsonb(r#"{"a":1}"#)]); + } + + #[test] + fn fixtures_macro_handles_degenerate_inputs() { + // Empty list — every arm accepts zero elements. + const NO_INT: &[Fixture] = fixtures!(int i32;); + const NO_TEXT: &[Fixture] = fixtures!(text;); + assert_eq!(NO_INT, &[] as &[Fixture]); + assert_eq!(NO_TEXT, &[] as &[Fixture]); + // Trailing comma — int muncher (leading-comma rule) and string arm `$(,)?`. + const TRAILING_INT: &[Fixture] = fixtures!(int i32; Min, N(1),); + const TRAILING_TEXT: &[Fixture] = fixtures!(text; "a",); + assert_eq!(TRAILING_INT, &[Fixture::Min, Fixture::Int(1)]); + assert_eq!(TRAILING_TEXT, &[Fixture::Text("a")]); + // Sentinels-only, no `N(..)`. + const SENTINELS: &[Fixture] = fixtures!(int i32; Min, Zero, Max); + assert_eq!(SENTINELS, &[Fixture::Min, Fixture::Zero, Fixture::Max]); } } @@ -602,21 +772,21 @@ mod catalog_tests { // From int4.toml [fixture] values, in order. let expected = vec![ Fixture::Min, - Fixture::N(-100), - Fixture::N(-1), + Fixture::Int(-100), + Fixture::Int(-1), Fixture::Zero, - Fixture::N(1), - Fixture::N(2), - Fixture::N(5), - Fixture::N(10), - Fixture::N(17), - Fixture::N(25), - Fixture::N(42), - Fixture::N(50), - Fixture::N(100), - Fixture::N(250), - Fixture::N(1000), - Fixture::N(9999), + Fixture::Int(1), + Fixture::Int(2), + Fixture::Int(5), + Fixture::Int(10), + Fixture::Int(17), + Fixture::Int(25), + Fixture::Int(42), + Fixture::Int(50), + Fixture::Int(100), + Fixture::Int(250), + Fixture::Int(1000), + Fixture::Int(9999), Fixture::Max, ]; assert_eq!(s.fixtures, expected.as_slice()); @@ -627,8 +797,8 @@ mod catalog_tests { let s = scalar("int2"); // From int2.toml [fixture] values, in order — includes the wide // ±30000 values that exercise the i16 bounds. - assert!(s.fixtures.contains(&Fixture::N(-30000))); - assert!(s.fixtures.contains(&Fixture::N(30000))); + assert!(s.fixtures.contains(&Fixture::Int(-30000))); + assert!(s.fixtures.contains(&Fixture::Int(30000))); assert_eq!(s.fixtures.first(), Some(&Fixture::Min)); assert_eq!(s.fixtures.last(), Some(&Fixture::Max)); } @@ -660,13 +830,34 @@ mod invariant_tests { } } + /// Cross-kind distinctness key: integer fixtures dedupe by their resolved + /// number, string-backed fixtures by their literal. Generalises the Python + /// distinct-plaintext contract to every scalar kind. + #[derive(Debug, PartialEq, Eq, Hash)] + enum DistinctKey { + Num(i128), + Str(&'static str), + } + + fn distinct_key(f: Fixture, kind: ScalarKind) -> DistinctKey { + match f { + Fixture::Numeric(s) | Fixture::Text(s) | Fixture::Jsonb(s) => DistinctKey::Str(s), + _ => DistinctKey::Num( + f.numeric_value(kind) + .expect("sentinel/Int fixtures resolve to a number"), + ), + } + } + #[test] fn fixtures_include_min_max_and_zero() { - for s in CATALOG { + // The MIN/MAX/ZERO pivots are an integer-kind invariant; non-integer + // kinds (text/numeric/jsonb) have no such pivots. + for s in CATALOG.iter().filter(|s| s.kind.is_int()) { let resolved: Vec = s .fixtures .iter() - .map(|f| f.numeric_value(s.kind)) + .filter_map(|f| f.numeric_value(s.kind)) .collect(); assert!( resolved.contains(&s.kind.min_value()), @@ -685,28 +876,46 @@ mod invariant_tests { #[test] fn fixture_values_are_distinct_by_resolved_number() { for s in CATALOG { - let mut seen: HashMap = HashMap::new(); + let mut seen: HashMap = HashMap::new(); for f in s.fixtures { - let n = f.numeric_value(s.kind); - if let Some(prev) = seen.insert(n, *f) { - panic!( - "{}: {f:?} duplicates {prev:?} (both resolve to {n})", - s.token - ); + if let Some(prev) = seen.insert(distinct_key(*f, s.kind), *f) { + panic!("{}: {f:?} duplicates {prev:?}", s.token); } } } } + #[test] + fn distinct_key_separates_string_fixtures() { + // CATALOG is int-only, so the `Str` path is otherwise unexercised. + assert_eq!( + distinct_key(Fixture::Text("a"), ScalarKind::Text), + distinct_key(Fixture::Text("a"), ScalarKind::Text) + ); + assert_ne!( + distinct_key(Fixture::Text("a"), ScalarKind::Text), + distinct_key(Fixture::Text("b"), ScalarKind::Text) + ); + assert_eq!( + distinct_key(Fixture::Numeric("x"), ScalarKind::Numeric), + distinct_key(Fixture::Jsonb("x"), ScalarKind::Jsonb) + ); + // Str and Num keys never collide. + assert_ne!( + distinct_key(Fixture::Text("0"), ScalarKind::Text), + distinct_key(Fixture::Zero, ScalarKind::I32) + ); + } + #[test] fn every_fixture_value_is_within_kind_bounds() { - // `numeric_value` is infallible, so the range guarantee the old - // `Result` encoded is asserted explicitly here: this is the ONLY guard - // against an out-of-range `N` in committed catalog data. - for s in CATALOG { + // Asserts the resolved sentinels stay within bounds (integer kinds only). + for s in CATALOG.iter().filter(|s| s.kind.is_int()) { let (lo, hi) = (s.kind.min_value(), s.kind.max_value()); for f in s.fixtures { - let n = f.numeric_value(s.kind); + let Some(n) = f.numeric_value(s.kind) else { + continue; + }; assert!( n >= lo && n <= hi, "{}: fixture {f:?} resolves to {n}, out of range [{lo}, {hi}]", From 7ba43a62c91e71c9088a32f0b5b9fdf78ed09840 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:26:18 +1000 Subject: [PATCH 30/93] ci(test): cover the Rust workspace crates in test-eql MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace members under crates/ were invisible to CI: the test-eql path filters only matched src/sql/tests/tasks, so a PR touching crates/** or the root Cargo.{toml,lock} skipped the workflow entirely, and no task ran the new crates' tests anyway (test:sqlx runs only tests/sqlx; the "codegen" job runs the Python generator, not the Rust eql-codegen crate; default-members is tests/sqlx). The eql-scalars catalog tests could break with CI green. - Add crates/**, Cargo.toml, Cargo.lock to the test-eql push + pull_request path filters (and mirror them in bench-eql). - New `test:crates` mise task: cargo fmt --check + clippy + test, scoped to -p eql-scalars -p eql-codegen (NOT --workspace — tests/sqlx needs Postgres + CS_* secrets and stays covered by the `test` job). - New `rust-crates` CI job running it — no database, fast, standalone. --- .github/workflows/bench-eql.yml | 3 +++ .github/workflows/test-eql.yml | 34 +++++++++++++++++++++++++++++++++ mise.toml | 17 +++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/.github/workflows/bench-eql.yml b/.github/workflows/bench-eql.yml index 5b302c08..782ed3c3 100644 --- a/.github/workflows/bench-eql.yml +++ b/.github/workflows/bench-eql.yml @@ -15,6 +15,9 @@ on: - "src/**/*.sql" - "tests/sqlx/**/*" - "tasks/**/*" + - "crates/**" + - "Cargo.toml" + - "Cargo.lock" schedule: # 02:00 UTC daily diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 6dbc62e8..4fa0c457 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -9,6 +9,9 @@ on: - "sql/**/*.sql" - "tests/**/*" - "tasks/**/*" + - "crates/**" + - "Cargo.toml" + - "Cargo.lock" pull_request: # run on all pull requests @@ -18,6 +21,9 @@ on: - "sql/**/*.sql" - "tests/**/*" - "tasks/**/*" + - "crates/**" + - "Cargo.toml" + - "Cargo.lock" workflow_dispatch: @@ -57,6 +63,34 @@ jobs: run: | mise run test:schema + rust-crates: + name: "Rust workspace crates" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 + with: + version: 2026.4.0 + install: true + cache: true + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + workspaces: . + shared-key: sqlx-tests + + # fmt + clippy + test for the std-only catalog/generator crates. No + # Postgres: these never touch a database, so they run standalone and fast. + - name: Compile, lint and test the Rust workspace crates + run: | + export active_rust_toolchain=$(rustup show active-toolchain | cut -d' ' -f1) + rustup component add --toolchain ${active_rust_toolchain} rustfmt clippy + mise run test:crates + codegen: name: "Encrypted-domain codegen" runs-on: ubuntu-latest diff --git a/mise.toml b/mise.toml index 8eecd6b6..9b19397b 100644 --- a/mise.toml +++ b/mise.toml @@ -98,6 +98,23 @@ mise exec python -- python -m pip install --quiet --disable-pip-version-check py mise exec python -- python -m pytest tasks/codegen -q """ +[tasks."test:crates"] +description = "Compile, lint and test the std-only Rust workspace crates (no database)" +dir = "{{config_root}}" +run = """ +# eql-scalars / eql-codegen are the lean workspace members. Scope explicitly to +# them (NOT --workspace): a workspace-wide test would drag in tests/sqlx, whose +# suite needs Postgres + CS_* secrets and is already covered by the `test` job. +# clippy is likewise scoped — a workspace clippy recompiles the heavy +# sqlx/tokio/cipherstash-client tree for no added coverage of these crates. +# `set -eu` only (no pipefail): mise runs tasks under `sh`, which is dash on the +# CI runners, and dash rejects `set -o pipefail`. There are no pipes here. +set -eu +cargo fmt --check +cargo clippy -p eql-scalars -p eql-codegen --all-targets -- -D warnings +cargo test -p eql-scalars -p eql-codegen +""" + [tasks."test:matrix:inventory"] description = "Regenerate the int4/int2 matrix test-name inventory snapshots (no database required)" dir = "{{config_root}}/tests/sqlx" From 2737d5f95d84e34fb59b45fe5c821501f00e3dd0 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:20:57 +1000 Subject: [PATCH 31/93] feat(codegen): port SQL generator to Rust with generator parity Port the scalar encrypted-domain SQL generator from the Python oracle to the `eql-codegen` Rust crate, achieving generator parity and wiring it into the build/test/CI flow. Generator: - Scaffold the eql-codegen crate: AUTO-GENERATED headers, schema consts, sql_str, operator-surface table, backing_function, role/brief/shape helpers, and the fixture-values renderer. - Port the domain-block, extractor, wrapper, three blocker renderers (LANGUAGE plpgsql, never STRICT), operator, aggregate (ord-capability), and types-file renderers; relocate derivation helpers + logic tests into the context layer. - Render the int4 SQL surface (types/functions/operators/aggregates) from minijinja templates; add the minijinja+serde template environment. - Port the ownership-guarded writer (std-only) and the generate_all orchestrator + CLI (generate / list-types). Tests & CI: - int4 golden-master reference tests; values.rs byte-exact, integration golden line-normalized; coarsened footgun + escaping guards as whole-file/context scans. - Retire the Python-oracle parity gate; codegen:parity now gates on golden + values. Drop the deprecated Python test:codegen drift check. Cleanup: - Standardize the generated-SQL marker on `-- AUTOMATICALLY GENERATED FILE` (the project-wide marker docs:validate greps on); regenerate int2/int4 values.rs fixtures and update the consts.rs assertion and CLAUDE.md. - Address review findings (operator-check rename, placeholder_payload comment, exit-code u8 clamp). --- .github/workflows/test-eql.yml | 22 +- CLAUDE.md | 2 +- Cargo.lock | 21 + crates/eql-codegen/Cargo.toml | 14 +- crates/eql-codegen/src/consts.rs | 62 ++ crates/eql-codegen/src/context.rs | 306 ++++++++ crates/eql-codegen/src/generate.rs | 709 ++++++++++++++++++ crates/eql-codegen/src/lib.rs | 11 + crates/eql-codegen/src/main.rs | 46 +- crates/eql-codegen/src/operator_surface.rs | 149 ++++ crates/eql-codegen/src/templates.rs | 72 ++ crates/eql-codegen/src/writer.rs | 291 +++++++ .../eql-codegen/templates/aggregates.sql.j2 | 34 + crates/eql-codegen/templates/functions.sql.j2 | 34 + crates/eql-codegen/templates/operators.sql.j2 | 13 + crates/eql-codegen/templates/types.sql.j2 | 26 + crates/eql-codegen/tests/parity.rs | 100 +++ mise.toml | 5 + tasks/codegen-parity.sh | 26 + tasks/test.sh | 15 +- .../reference/int4/int4_eq_functions.sql | 215 +++--- .../reference/int4/int4_eq_operators.sql | 41 +- .../codegen/reference/int4/int4_functions.sql | 237 +++--- .../codegen/reference/int4/int4_operators.sql | 47 +- .../reference/int4/int4_ord_aggregates.sql | 55 +- .../reference/int4/int4_ord_functions.sql | 167 +++-- .../reference/int4/int4_ord_operators.sql | 29 +- .../int4/int4_ord_ore_aggregates.sql | 55 +- .../reference/int4/int4_ord_ore_functions.sql | 167 +++-- .../reference/int4/int4_ord_ore_operators.sql | 29 +- tests/codegen/reference/int4/int4_types.sql | 11 +- tests/sqlx/src/fixtures/int2_values.rs | 5 +- tests/sqlx/src/fixtures/int4_values.rs | 5 +- .../tests/encrypted_domain/family/support.rs | 20 +- 34 files changed, 2395 insertions(+), 646 deletions(-) create mode 100644 crates/eql-codegen/src/consts.rs create mode 100644 crates/eql-codegen/src/context.rs create mode 100644 crates/eql-codegen/src/generate.rs create mode 100644 crates/eql-codegen/src/lib.rs create mode 100644 crates/eql-codegen/src/operator_surface.rs create mode 100644 crates/eql-codegen/src/templates.rs create mode 100644 crates/eql-codegen/src/writer.rs create mode 100644 crates/eql-codegen/templates/aggregates.sql.j2 create mode 100644 crates/eql-codegen/templates/functions.sql.j2 create mode 100644 crates/eql-codegen/templates/operators.sql.j2 create mode 100644 crates/eql-codegen/templates/types.sql.j2 create mode 100644 crates/eql-codegen/tests/parity.rs create mode 100755 tasks/codegen-parity.sh diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 4fa0c457..22a61d1c 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -106,9 +106,17 @@ jobs: install: true cache: true - - name: Run codegen generator + drift tests - run: | - mise run test:codegen + # Shared with the sibling Rust jobs so the eql-codegen build artifacts the + # parity gate needs are reused rather than rebuilt from scratch. + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + workspaces: . + shared-key: sqlx-tests + + # The Python `test:codegen` drift suite is intentionally not run here: the + # Python generator is deprecated and removed in the following PR. The + # hand-written reference (tests/codegen/reference/) is now gated against + # the Rust generator by the `mise run codegen:parity` step below. # Regenerate the committed Rust fixture-value consts for EVERY type from # their manifests and fail if any differ from / are missing in the tree. @@ -125,6 +133,14 @@ jobs: git diff --exit-code -- tests/sqlx/src/fixtures \ || { echo "Fixture value const(s) stale or uncommitted — run 'mise run codegen:domain:all' and commit tests/sqlx/src/fixtures."; exit 1; } + # Cross-generator parity: assert the Rust eql-codegen output is byte- + # identical to the Python oracle across all types, the committed + # _values.rs, and the int4 golden reference. No Postgres needed — both + # generators are deterministic and run offline. + - name: Verify Rust↔Python generator parity + run: | + mise run codegen:parity + matrix-coverage: name: "Matrix coverage inventory" runs-on: ubuntu-latest diff --git a/CLAUDE.md b/CLAUDE.md index c15fa0e1..10477062 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,7 +78,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search `src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, `text`, and `jsonb` follow this materializer pattern. `text`, `numeric`, and `jsonb` are planned but have no generated SQL surface yet — `jsonb` in particular needs a separate SQL design beyond the ordered-scalar materializer. The `eql-scalars` fixture catalog (`crates/eql-scalars`) already models their fixture values ahead of the SQL surface. -Adding a scalar encrypted-domain type is generated from a minimal manifest at `tasks/codegen/types/.toml`: the filename supplies ``, and the `[domain]` table maps each generated domain name to the fixed index terms it carries. Example: `int4_eq = ["hm"]`, `int4_ord = ["ore"]`. Term capabilities are fixed in `tasks/codegen/terms.py`: `hm` provides equality, and `ore` provides equality plus ordering. `mise run build` regenerates the scalar SQL surface into `src/encrypted_domain//` from every manifest at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. Use `mise run codegen:domain ` to refresh a single type manually while iterating on its manifest, or `mise run codegen:domain:all` to regenerate every type at once (the same enumeration `mise run build` uses). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` files are gitignored and never committed — the TOML manifest plus `tasks/codegen/terms.py` are the source of truth. Generated files carry an `AUTO-GENERATED — DO NOT EDIT` header; change the manifest or term catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` have no generated SQL surface yet (they are planned, not out of scope); `jsonb` needs a separate SQL design beyond this ordered-scalar materializer. +Adding a scalar encrypted-domain type is generated from a minimal manifest at `tasks/codegen/types/.toml`: the filename supplies ``, and the `[domain]` table maps each generated domain name to the fixed index terms it carries. Example: `int4_eq = ["hm"]`, `int4_ord = ["ore"]`. Term capabilities are fixed in `tasks/codegen/terms.py`: `hm` provides equality, and `ore` provides equality plus ordering. `mise run build` regenerates the scalar SQL surface into `src/encrypted_domain//` from every manifest at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. Use `mise run codegen:domain ` to refresh a single type manually while iterating on its manifest, or `mise run codegen:domain:all` to regenerate every type at once (the same enumeration `mise run build` uses). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` files are gitignored and never committed — the TOML manifest plus `tasks/codegen/terms.py` are the source of truth. Generated files carry an `AUTOMATICALLY GENERATED FILE — DO NOT EDIT` header (the project-wide marker that `docs:validate` greps on to skip generated SQL); change the manifest or term catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` have no generated SQL surface yet (they are planned, not out of scope); `jsonb` needs a separate SQL design beyond this ordered-scalar materializer. **Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the manifest only declares domain names and terms. New term behavior belongs in `tasks/codegen/terms.py` with tests, not in free-form TOML fields. diff --git a/Cargo.lock b/Cargo.lock index f7bdda95..ebc39165 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,11 @@ dependencies = [ [[package]] name = "eql-codegen" version = "0.1.0" +dependencies = [ + "eql-scalars", + "minijinja", + "serde", +] [[package]] name = "eql-scalars" @@ -2234,6 +2239,12 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memo-map" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + [[package]] name = "micromap" version = "0.3.0" @@ -2270,6 +2281,16 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "minijinja" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2929e494b2280e1e18959bb2e121da03347ae896896fdfaceaab43c88a02803f" +dependencies = [ + "memo-map", + "serde", +] + [[package]] name = "miniz_oxide" version = "0.8.9" diff --git a/crates/eql-codegen/Cargo.toml b/crates/eql-codegen/Cargo.toml index 0c4e9b25..25165397 100644 --- a/crates/eql-codegen/Cargo.toml +++ b/crates/eql-codegen/Cargo.toml @@ -2,7 +2,17 @@ name = "eql-codegen" version = "0.1.0" edition = "2021" -description = "SQL generator for EQL encrypted-domain types (stub; implemented in Plan 2)." +publish = false -# Stub: Plan 2 adds `eql-scalars` as a path dependency and implements the binary. [dependencies] +eql-scalars = { path = "../eql-scalars" } +minijinja = "2" +serde = { version = "1", features = ["derive"] } + +[[bin]] +name = "eql-codegen" +path = "src/main.rs" + +[lib] +name = "eql_codegen" +path = "src/lib.rs" diff --git a/crates/eql-codegen/src/consts.rs b/crates/eql-codegen/src/consts.rs new file mode 100644 index 00000000..ae318ac8 --- /dev/null +++ b/crates/eql-codegen/src/consts.rs @@ -0,0 +1,62 @@ +//! AUTO-GENERATED headers, schema constants, and SQL-string escaping. + +/// SQL generated-file marker. The SQL templates emit this as their first line; +/// the writer uses it only to recognise files it owns (overwrite/clean safety). +pub const AUTO_GENERATED_HEADER: &str = "-- AUTOMATICALLY GENERATED FILE.\n"; + +/// Rust generated-file marker, prepended to `_values.rs` (which has no +/// template). Rust comment syntax so the `.rs` file stays valid. +pub const AUTO_GENERATED_HEADER_RS: &str = "// AUTOMATICALLY GENERATED FILE.\n"; + +/// Schema housing the encrypted-domain families. +pub const DOMAIN_SCHEMA: &str = "eql_v3"; +/// Schema owning the core index-term types/constructors. +pub const CORE_SCHEMA: &str = "eql_v2"; + +/// Envelope keys checked for presence in every domain CHECK, in order. +pub const ENVELOPE_KEYS: &[&str] = &["v", "i"]; +/// Ciphertext payload key. +pub const CIPHERTEXT_KEY: &str = "c"; +/// Envelope-version key whose value is pinned. +pub const VERSION_KEY: &str = "v"; +/// EQL payload-format version pinned by the domain CHECK. +pub const ENVELOPE_VERSION: u32 = 2; + +/// Escape a string for use inside a single-quoted SQL literal by doubling +/// embedded single quotes. Port of templates.py `_sql_str`. +pub fn sql_str(s: &str) -> String { + s.replace('\'', "''") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sql_marker_is_grep_compatible_single_line() { + // The `^-- AUTOMATICALLY GENERATED FILE` marker is what + // tasks/docs/validate/{coverage,required-tags}.sh grep on to skip + // generated SQL — keep this assertion and that grep in lockstep. + assert_eq!(AUTO_GENERATED_HEADER, "-- AUTOMATICALLY GENERATED FILE.\n"); + assert!(AUTO_GENERATED_HEADER.contains("AUTOMATICALLY GENERATED FILE")); + } + + #[test] + fn rust_marker_is_a_rust_comment() { + assert_eq!(AUTO_GENERATED_HEADER_RS, "// AUTOMATICALLY GENERATED FILE.\n"); + for line in AUTO_GENERATED_HEADER_RS.lines() { + assert!( + !line.starts_with("--"), + "rust marker must not contain SQL comments" + ); + } + } + + #[test] + fn sql_str_doubles_single_quotes() { + assert_eq!(sql_str("o'brien"), "o''brien"); + assert_eq!(sql_str("a'b'c"), "a''b''c"); + assert_eq!(sql_str("int4_eq"), "int4_eq"); + assert_eq!(sql_str("<="), "<="); + } +} diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs new file mode 100644 index 00000000..6a104831 --- /dev/null +++ b/crates/eql-codegen/src/context.rs @@ -0,0 +1,306 @@ +//! minijinja environment + serde context structs + relocated logic helpers. + +use crate::consts::*; +use eql_scalars::{DomainSpec, Term}; + +/// Line-normalize SQL for best-effort byte-exact comparison: trim each line's +/// leading/trailing whitespace and drop blank lines; preserve intra-line +/// spacing. NOT used for `_values.rs` (which stays byte-exact). +pub fn normalize_sql(s: &str) -> String { + s.lines() + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .collect::>() + .join("\n") +} + +/// Build the minijinja environment with the four embedded whole-file templates. +/// Templates are compiled in via `include_str!` — no runtime file IO. +pub fn environment() -> minijinja::Environment<'static> { + let mut env = minijinja::Environment::new(); + // Preserve each template file's trailing newline so generated SQL files end + // with one (minijinja strips it by default). + env.set_keep_trailing_newline(true); + env.add_template("types.sql", include_str!("../templates/types.sql.j2")) + .expect("types.sql template"); + env.add_template("functions.sql", include_str!("../templates/functions.sql.j2")) + .expect("functions.sql template"); + env.add_template("operators.sql", include_str!("../templates/operators.sql.j2")) + .expect("operators.sql template"); + env.add_template("aggregates.sql", include_str!("../templates/aggregates.sql.j2")) + .expect("aggregates.sql template"); + env.add_global("domain_schema", DOMAIN_SCHEMA); + env.add_global("core_schema", CORE_SCHEMA); + env +} + +/// One idempotent CREATE DOMAIN block, with SQL-required values precomputed. +#[derive(serde::Serialize)] +pub struct DomainBlock { + pub typname: String, // sql_str-escaped bare name, e.g. int4_ord_ore + pub name: String, // raw bare name (unescaped), e.g. int4_ord_ore + pub keys: Vec, // ordered, sql_str-escaped key tokens (envelope + ciphertext + term keys) +} + +#[derive(serde::Serialize)] +pub struct TypesContext { + pub token: String, + pub domains: Vec, +} + +/// Build the per-domain block data (port of `render_domain_block`'s value logic, +/// minus comment prose and the CHECK skeleton — those are template-resident). +pub fn domain_block(token: &str, domain: &DomainSpec) -> DomainBlock { + let name = full_domain_name(token, domain.suffix); + + let mut keys: Vec = ENVELOPE_KEYS.iter().map(|k| sql_str(k)).collect(); + keys.push(sql_str(CIPHERTEXT_KEY)); + for k in Term::term_json_keys(domain.terms) { + keys.push(sql_str(k)); + } + + DomainBlock { + // typname is sql_str-escaped defensively: the escaping boundary stays + // Rust-side even though real catalog names carry no quotes. + typname: sql_str(&name), + name, + keys, + } +} + +/// One SQL parameter (name + SQL type), shared by wrapper/blocker signatures +/// and their `@param` docs tags. +#[derive(serde::Serialize)] +pub struct SqlParam { + pub name: &'static str, // "a", "b", or "selector" + pub ty: String, +} + +/// One generated function entry. The serde tag drives the template's three-way +/// switch; the blocker arm is never merged with the others (footgun separation). +#[derive(serde::Serialize)] +#[serde(tag = "kind")] +pub enum FnEntry { + Extractor { + ret: String, // e.g. eql_v2.hmac_256 (selection STAYS in Rust) + extractor: String, // e.g. eq_term + ctor: String, // e.g. hmac_256 (called as {{ core_schema }}.{{ ctor }}) + }, + Wrapper { + op: String, // SQL operator used in the body, e.g. = + function_name: String, // e.g. eq + args: [SqlParam; 2], + call_a: String, // e.g. eql_v3.eq_term(a) (embeds extract_arg cast logic) + call_b: String, // e.g. eql_v3.eq_term(b::eql_v3.int4_eq) + }, + Blocker { + operator_lit: String, // sql_str(op), escaped content for the RAISE literal + function_name: String, // e.g. lt / "->" / "#>" + args: [SqlParam; 2], + returns: String, // boolean / text / jsonb / domain (selection STAYS in Rust) + }, +} + +#[derive(serde::Serialize)] +pub struct FunctionsContext { + pub requires: Vec, // dependency paths only; template emits "-- REQUIRE:" + pub token: String, + pub name: String, // full domain name (token+suffix) + pub dom: String, // schema-qualified domain, e.g. eql_v3.int4_eq + pub domain_lit: String, // sql_str(dom), defensively escaped for the RAISE literal + pub entries: Vec, +} + +/// Build the inlinable index-extractor entry for a domain term. +pub fn extractor_entry(term: Term) -> FnEntry { + FnEntry::Extractor { + ret: term.returns().to_string(), + extractor: term.extractor().to_string(), + ctor: term.ctor().to_string(), + } +} + +/// Build an inlinable comparison-wrapper entry for a supported operator. +/// `dom` is the schema-qualified domain name. +pub fn wrapper_entry(dom: &str, op: &str, arg_a: &str, arg_b: &str, extractor: &str) -> FnEntry { + use crate::operator_surface::backing_function; + FnEntry::Wrapper { + op: op.to_string(), + function_name: backing_function(op).to_string(), + args: [ + SqlParam { name: "a", ty: arg_a.to_string() }, + SqlParam { name: "b", ty: arg_b.to_string() }, + ], + call_a: extract_arg(arg_a, extractor, dom, "a"), + call_b: extract_arg(arg_b, extractor, dom, "b"), + } +} + +/// Build an unsupported-operator blocker entry. Every blocker shares one +/// uniform `RAISE EXCEPTION` body; only signature facts vary. +pub fn blocker_entry(op: &str, args: [SqlParam; 2], returns: &str) -> FnEntry { + use crate::operator_surface::backing_function; + FnEntry::Blocker { + // operator_lit is sql_str-escaped defensively for the single-quoted RAISE literal. + operator_lit: sql_str(op), + function_name: backing_function(op).to_string(), + args, + returns: returns.to_string(), + } +} + +/// One CREATE OPERATOR declaration, with the optional metadata line precomputed. +#[derive(serde::Serialize)] +pub struct OpEntry { + pub symbol: String, + pub function_name: String, // unqualified; schema literal lives in the template + pub leftarg: String, + pub rightarg: String, + pub metadata: Option, // e.g. "COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel" +} + +#[derive(serde::Serialize)] +pub struct OperatorsContext { + pub requires: Vec, + pub token: String, + pub name: String, + pub dom: String, + pub operators: Vec, +} + +/// Build one CREATE OPERATOR entry. The metadata line exists only for supported +/// symmetric operators that carry at least one extra (the `@>`/`<@` symmetric- +/// but-empty trap collapses to `None`). +pub fn operator_entry( + op: &str, + function_name: &str, + leftarg: &str, + rightarg: &str, + supported: bool, +) -> OpEntry { + use crate::operator_surface::{operator, Kind}; + let meta = operator(op); + let metadata = if supported && meta.kind == Kind::Symmetric { + let mut extras = Vec::new(); + if let Some(c) = meta.commutator { + extras.push(format!("COMMUTATOR = {c}")); + } + if let Some(n) = meta.negator { + extras.push(format!("NEGATOR = {n}")); + } + if let Some(r) = meta.restrict { + extras.push(format!("RESTRICT = {r}")); + } + if let Some(j) = meta.join { + extras.push(format!("JOIN = {j}")); + } + (!extras.is_empty()).then(|| extras.join(", ")) // empty → None (the @>/<@ trap) + } else { + None + }; + OpEntry { + symbol: op.to_string(), + function_name: function_name.to_string(), + leftarg: leftarg.to_string(), + rightarg: rightarg.to_string(), + metadata, + } +} + +#[derive(serde::Serialize)] +pub struct AggregatesContext { + pub requires: Vec, // dependency paths only; template emits "-- REQUIRE:" + pub token: String, + pub name: String, + pub dom: String, // schema-qualified domain, hoisted + pub aggregates: &'static [AggregateOp], // == AGGREGATE_OPS +} + +/// The schema-qualified SQL domain type name, e.g. `eql_v3.int4_eq`. +/// Port of `domain_name`. +pub fn domain_name(name: &str) -> String { + format!("{DOMAIN_SCHEMA}.{name}") +} + +/// The full domain name from a token + suffix (suffix "" => bare token). +pub fn full_domain_name(token: &str, suffix: &str) -> String { + format!("{token}{suffix}") +} + +/// The extractor-call SQL for one operand, casting jsonb to the domain first. +/// Port of `_extract_arg`. `dom` is the schema-qualified domain name. +pub fn extract_arg(arg_type: &str, extractor: &str, dom: &str, arg: &str) -> String { + if arg_type == "jsonb" { + format!("{DOMAIN_SCHEMA}.{extractor}({arg}::{dom})") + } else { + format!("{DOMAIN_SCHEMA}.{extractor}({arg})") + } +} + +/// One aggregate operator definition (min or max). Only SQL-required facts: the +/// state-function name is the mechanical suffix `{{ a.name }}_sfunc` in the +/// template, and English comment phrases are template-resident. +#[derive(serde::Serialize)] +pub struct AggregateOp { + pub name: &'static str, // min / max + pub comparator: &'static str, // < / > +} + +/// The two aggregate ops in (min, max) order. Port of `AGGREGATE_OPS`. +pub const AGGREGATE_OPS: &[AggregateOp] = &[ + AggregateOp { name: "min", comparator: "<" }, + AggregateOp { name: "max", comparator: ">" }, +]; + +/// True if the domain carries a comparator term (supports `<`). +/// Port of `is_ord_capable`. +pub fn is_ord_capable(terms: &[Term]) -> bool { + Term::role_for_terms(terms) == "ord" +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn domain_name_qualifies_with_schema() { + assert_eq!(domain_name("int4_eq"), "eql_v3.int4_eq"); + } + + #[test] + fn is_ord_capable_matches_role() { + assert!(is_ord_capable(&[Term::Ore])); + assert!(!is_ord_capable(&[Term::Hm])); + assert!(!is_ord_capable(&[])); + } + + #[test] + fn normalize_trims_lines_and_drops_blanks() { + let input = " CREATE DOMAIN x\n\n CHECK (a) \n\n"; + assert_eq!(normalize_sql(input), "CREATE DOMAIN x\nCHECK (a)"); + } + + #[test] + fn normalize_preserves_intra_line_spacing() { + let input = "RAISE EXCEPTION 'operator % is not supported for %';"; + assert_eq!( + normalize_sql(input), + "RAISE EXCEPTION 'operator % is not supported for %';" + ); + } + + #[test] + fn normalize_equal_modulo_indentation_and_blank_lines() { + let a = "DO $$\nBEGIN\n IF NOT EXISTS (\n ) THEN\n END IF;\nEND\n$$;\n"; + let b = "DO $$\n\nBEGIN\n IF NOT EXISTS (\n ) THEN\nEND IF;\nEND\n$$;"; + assert_eq!(normalize_sql(a), normalize_sql(b)); + } + + #[test] + fn environment_has_four_templates() { + let env = environment(); + for name in ["types.sql", "functions.sql", "operators.sql", "aggregates.sql"] { + assert!(env.get_template(name).is_ok(), "missing template {name}"); + } + } +} diff --git a/crates/eql-codegen/src/generate.rs b/crates/eql-codegen/src/generate.rs new file mode 100644 index 00000000..a7e1340c --- /dev/null +++ b/crates/eql-codegen/src/generate.rs @@ -0,0 +1,709 @@ +//! File renderers and orchestrator (port of generate.py). + +use std::path::{Path, PathBuf}; + +use eql_scalars::{DomainSpec, ScalarSpec, Term}; + +use crate::operator_surface::{ + backing_function, BLOCKER_ONLY_OPERATORS, PATH_OPERATORS, SYMMETRIC_OPERATORS, +}; +use crate::context::{domain_name, is_ord_capable}; + +/// The full domain name (token + suffix). suffix "" => bare token. +fn full_name(token: &str, suffix: &str) -> String { + format!("{token}{suffix}") +} + +/// Symmetric-operator argument shapes. Port of `_symmetric_shapes`. +fn symmetric_shapes(dom: &str) -> Vec<(String, String)> { + vec![ + (dom.to_string(), dom.to_string()), + (dom.to_string(), "jsonb".to_string()), + ("jsonb".to_string(), dom.to_string()), + ] +} + +/// Path-operator argument shapes. Port of `_path_shapes`. +fn path_shapes(dom: &str) -> Vec<(String, String)> { + vec![ + (dom.to_string(), "text".to_string()), + (dom.to_string(), "integer".to_string()), + ("jsonb".to_string(), dom.to_string()), + ] +} + +/// Blocker-only argument shapes for a native jsonb operator. +/// Port of `_blocker_only_shapes`. Returns (arg_a, arg_b, returns). +fn blocker_only_shapes(dom: &str, op: &str) -> Vec<(String, String, String)> { + let d = dom.to_string(); + match op { + "?" => vec![(d, "text".into(), "boolean".into())], + "?|" | "?&" => vec![(d, "text[]".into(), "boolean".into())], + "@?" | "@@" => vec![(d, "jsonpath".into(), "boolean".into())], + "#>" => vec![(d, "text[]".into(), "jsonb".into())], + "#>>" => vec![(d, "text[]".into(), "text".into())], + "-" => vec![ + (d.clone(), "text".into(), "jsonb".into()), + (d.clone(), "integer".into(), "jsonb".into()), + (d, "text[]".into(), "jsonb".into()), + ], + "#-" => vec![(d, "text[]".into(), "jsonb".into())], + "||" => vec![ + (d.clone(), d.clone(), "jsonb".into()), + (d.clone(), "jsonb".into(), "jsonb".into()), + ("jsonb".into(), d, "jsonb".into()), + ], + other => panic!("unhandled blocker-only operator: {other}"), + } +} + +/// REQUIRE path for a type's _types.sql. Port of `_types_path`. +fn types_path(token: &str) -> String { + format!("src/encrypted_domain/{token}/{token}_types.sql") +} + +/// Committed Rust fixture-value const path. Port of `fixture_values_rs_path`. +pub fn fixture_values_rs_path(out_root: &Path, token: &str) -> PathBuf { + out_root + .join("tests") + .join("sqlx") + .join("src") + .join("fixtures") + .join(format!("{token}_values.rs")) +} + +/// Body for _types.sql: every domain in one idempotent DO block. +/// Port of `render_types_file`. +pub fn render_types_file(spec: &ScalarSpec) -> String { + use crate::context::{domain_block, environment, TypesContext}; + let ctx = TypesContext { + token: spec.token.to_string(), + domains: spec + .domains + .iter() + .map(|d| domain_block(spec.token, d)) + .collect(), + }; + environment() + .get_template("types.sql") + .unwrap() + .render(&ctx) + .expect("render types.sql") +} + +/// REQUIRE edges for a domain's _functions.sql. Port of `_functions_requires`. +fn functions_requires(token: &str, terms: &[Term]) -> Vec { + let mut reqs = vec![ + "src/schema.sql".to_string(), + "src/schema-v3.sql".to_string(), + types_path(token), + "src/encrypted_domain/functions.sql".to_string(), + ]; + for extra in Term::term_requires(terms) { + if !reqs.iter().any(|r| r == extra) { + reqs.push(extra.to_string()); + } + } + reqs +} + +/// Distinct extractor-bearing terms (first occurrence per extractor). +/// Port of `_extractor_terms`. +fn extractor_terms(terms: &[Term]) -> Vec { + let mut seen: Vec<&str> = Vec::new(); + let mut out: Vec = Vec::new(); + for &t in terms { + if !seen.contains(&t.extractor()) { + seen.push(t.extractor()); + out.push(t); + } + } + out +} + +/// Body for a domain's _functions.sql. Port of `render_functions_file`. +pub fn render_functions_file(token: &str, domain: &DomainSpec) -> String { + use crate::consts::sql_str; + use crate::context::{ + blocker_entry, environment, extractor_entry, wrapper_entry, FunctionsContext, SqlParam, + }; + let name = full_name(token, domain.suffix); + let dom = domain_name(&name); + let domain_lit = sql_str(&dom); + let supported = Term::operators_for_terms(domain.terms); + let is_supported = |op: &str| supported.iter().any(|s| *s == op); + + let mut entries = Vec::new(); + for term in extractor_terms(domain.terms) { + entries.push(extractor_entry(term)); + } + for &op in SYMMETRIC_OPERATORS { + let extractor = Term::extractor_for_operator(domain.terms, op); + for (arg_a, arg_b) in symmetric_shapes(&dom) { + if is_supported(op) { + if let Some(ex) = extractor { + entries.push(wrapper_entry(&dom, op, &arg_a, &arg_b, ex)); + continue; + } + } + let args = [ + SqlParam { name: "a", ty: arg_a }, + SqlParam { name: "b", ty: arg_b }, + ]; + entries.push(blocker_entry(op, args, "boolean")); + } + } + for &op in PATH_OPERATORS { + for (arg_a, arg_b) in path_shapes(&dom) { + let returns = if op == "->>" { "text".to_string() } else { dom.clone() }; + let args = [ + SqlParam { name: "a", ty: arg_a }, + SqlParam { name: "selector", ty: arg_b }, + ]; + entries.push(blocker_entry(op, args, &returns)); + } + } + for &op in BLOCKER_ONLY_OPERATORS { + for (arg_a, arg_b, returns) in blocker_only_shapes(&dom, op) { + let args = [ + SqlParam { name: "a", ty: arg_a }, + SqlParam { name: "b", ty: arg_b }, + ]; + entries.push(blocker_entry(op, args, &returns)); + } + } + + let ctx = FunctionsContext { + requires: functions_requires(token, domain.terms), + token: token.to_string(), + name, + dom, + domain_lit, + entries, + }; + environment() + .get_template("functions.sql") + .unwrap() + .render(&ctx) + .expect("render functions.sql") +} + +/// Body for a domain's _operators.sql. Port of `render_operators_file`. +pub fn render_operators_file(token: &str, domain: &DomainSpec) -> String { + use crate::context::{environment, operator_entry, OperatorsContext}; + let name = full_name(token, domain.suffix); + let dom = domain_name(&name); + let supported = Term::operators_for_terms(domain.terms); + let is_supported = |op: &str| supported.iter().any(|s| *s == op); + + let mut operators = Vec::new(); + for &op in SYMMETRIC_OPERATORS { + let function_name = backing_function(op); + for (l, r) in symmetric_shapes(&dom) { + operators.push(operator_entry(op, function_name, &l, &r, is_supported(op))); + } + } + for &op in PATH_OPERATORS { + let function_name = backing_function(op); + for (l, r) in path_shapes(&dom) { + operators.push(operator_entry(op, function_name, &l, &r, false)); + } + } + for &op in BLOCKER_ONLY_OPERATORS { + let function_name = backing_function(op); + for (l, r, _ret) in blocker_only_shapes(&dom, op) { + operators.push(operator_entry(op, function_name, &l, &r, false)); + } + } + + let ctx = OperatorsContext { + requires: vec![ + "src/schema-v3.sql".to_string(), + types_path(token), + format!("src/encrypted_domain/{token}/{name}_functions.sql"), + ], + token: token.to_string(), + name, + dom, + operators, + }; + environment() + .get_template("operators.sql") + .unwrap() + .render(&ctx) + .expect("render operators.sql") +} + +/// Body for a domain's _aggregates.sql, or None if not ord-capable. +/// Port of `render_aggregates_file`. +pub fn render_aggregates_file(token: &str, domain: &DomainSpec) -> Option { + use crate::context::{environment, AggregatesContext, AGGREGATE_OPS}; + if !is_ord_capable(domain.terms) { + return None; + } + let name = full_name(token, domain.suffix); + let dom = domain_name(&name); + let ctx = AggregatesContext { + requires: vec![ + "src/schema-v3.sql".to_string(), + types_path(token), + format!("src/encrypted_domain/{token}/{name}_functions.sql"), + format!("src/encrypted_domain/{token}/{name}_operators.sql"), + ], + token: token.to_string(), + name, + dom, // hoisted: one copy, template reads {{ dom }} + aggregates: AGGREGATE_OPS, // iterate the const directly (no per-entry wrapper) + }; + Some( + environment() + .get_template("aggregates.sql") + .unwrap() + .render(&ctx) + .expect("render aggregates.sql"), + ) +} + +use crate::templates::render_fixture_values_rs; +use crate::writer::{ + clean_generated_files, ensure_generated_paths_writable, write_generated_file, + write_generated_rs, WriteError, +}; + +/// Regenerate every generated file for one type into `out_dir`. +/// Port of `generate_type`. Returns the written paths. +pub fn generate_type(spec: &ScalarSpec, out_dir: &Path) -> Result, WriteError> { + let token = spec.token; + let mut targets = vec![out_dir.join(format!("{token}_types.sql"))]; + for d in spec.domains { + let name = full_name(token, d.suffix); + targets.push(out_dir.join(format!("{name}_functions.sql"))); + targets.push(out_dir.join(format!("{name}_operators.sql"))); + if is_ord_capable(d.terms) { + targets.push(out_dir.join(format!("{name}_aggregates.sql"))); + } + } + ensure_generated_paths_writable(&targets)?; + clean_generated_files(out_dir)?; + + let mut written: Vec = Vec::new(); + + let types_path = out_dir.join(format!("{token}_types.sql")); + write_generated_file(&types_path, &render_types_file(spec))?; + written.push(types_path); + + for d in spec.domains { + let name = full_name(token, d.suffix); + let fn_path = out_dir.join(format!("{name}_functions.sql")); + write_generated_file(&fn_path, &render_functions_file(token, d))?; + written.push(fn_path); + + let op_path = out_dir.join(format!("{name}_operators.sql")); + write_generated_file(&op_path, &render_operators_file(token, d))?; + written.push(op_path); + + if let Some(agg) = render_aggregates_file(token, d) { + let agg_path = out_dir.join(format!("{name}_aggregates.sql")); + write_generated_file(&agg_path, &agg)?; + written.push(agg_path); + } + } + Ok(written) +} + +/// Generate every catalog type's SQL + committed _values.rs under `out_root`. +/// The single entry point: replaces Python's per-type and --all forms. +pub fn generate_all(out_root: &Path) -> Result { + for spec in eql_scalars::CATALOG { + let token = spec.token; + let out_dir = out_root + .join("src") + .join("encrypted_domain") + .join(token); + let mut written = generate_type(spec, &out_dir)?; + + let rs_path = fixture_values_rs_path(out_root, token); + write_generated_rs(&rs_path, &render_fixture_values_rs(spec))?; + written.push(rs_path); + + for p in &written { + let rel = p.strip_prefix(out_root).unwrap_or(p); + println!("generated {}", rel.display()); + } + println!("generated {} files for {token}", written.len()); + } + let tokens: Vec<&str> = eql_scalars::CATALOG.iter().map(|s| s.token).collect(); + println!( + "codegen: ok ({} types: {})", + tokens.len(), + tokens.join(", ") + ); + Ok(0) +} + +#[cfg(test)] +mod tests { + use super::*; + use eql_scalars::CATALOG; + + fn spec(token: &str) -> &'static ScalarSpec { + CATALOG.iter().find(|s| s.token == token).expect("catalog token") + } + + fn domain<'a>(spec: &'a ScalarSpec, suffix: &str) -> &'a DomainSpec { + spec.domains.iter().find(|d| d.suffix == suffix).expect("domain suffix") + } + + use crate::templates::render_fixture_values_rs; + use std::fs; + + fn repo_root() -> PathBuf { + // crates/eql-codegen/ -> repo root is two parents up from CARGO_MANIFEST_DIR. + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .to_path_buf() + } + + fn strip_reference_marker(text: &str) -> String { + let mut lines: Vec<&str> = text.lines().collect(); + // .lines() drops the trailing newline; re-add per line and handle the + // first marker line(s). + while !lines.is_empty() + && (lines[0].starts_with("-- REFERENCE:") || lines[0].starts_with("// REFERENCE:")) + { + lines.remove(0); + } + let mut out = lines.join("\n"); + if text.ends_with('\n') { + out.push('\n'); + } + out + } + + fn rendered_for(token: &str, name: &str, spec: &ScalarSpec) -> String { + if name == format!("{token}_types.sql") { + return render_types_file(spec); + } + for d in spec.domains { + let full = full_name(token, d.suffix); + if name == format!("{full}_functions.sql") { + return render_functions_file(token, d); + } + if name == format!("{full}_operators.sql") { + return render_operators_file(token, d); + } + if name == format!("{full}_aggregates.sql") { + return render_aggregates_file(token, d) + .expect("reference exists but generator skipped (not ord-capable)"); + } + } + panic!("unrecognised reference filename: {name}"); + } + + #[test] + fn types_file_normalized_matches_golden() { + use crate::context::normalize_sql; + let root = repo_root(); + let path = root.join("tests/codegen/reference/int4/int4_types.sql"); + let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); + let actual = render_types_file(spec("int4")); + assert_eq!(normalize_sql(&actual), normalize_sql(&expected)); + } + + #[test] + fn functions_files_normalized_match_golden() { + use crate::context::normalize_sql; + let root = repo_root(); + let s = spec("int4"); + for d in s.domains { + let full = full_name("int4", d.suffix); + let path = root.join(format!("tests/codegen/reference/int4/{full}_functions.sql")); + let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); + let actual = render_functions_file("int4", d); + assert_eq!( + normalize_sql(&actual), + normalize_sql(&expected), + "{full}_functions.sql diverged" + ); + } + } + + #[test] + fn operators_files_normalized_match_golden() { + use crate::context::normalize_sql; + let root = repo_root(); + let s = spec("int4"); + for d in s.domains { + let full = full_name("int4", d.suffix); + let path = root.join(format!("tests/codegen/reference/int4/{full}_operators.sql")); + let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); + let actual = render_operators_file("int4", d); + assert_eq!(normalize_sql(&actual), normalize_sql(&expected), "{full}_operators.sql"); + } + } + + #[test] + fn aggregates_files_normalized_match_golden() { + use crate::context::normalize_sql; + let root = repo_root(); + let s = spec("int4"); + for d in s.domains { + if let Some(actual) = render_aggregates_file("int4", d) { + let full = full_name("int4", d.suffix); + let path = root.join(format!("tests/codegen/reference/int4/{full}_aggregates.sql")); + let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); + assert_eq!(normalize_sql(&actual), normalize_sql(&expected), "{full}_aggregates.sql"); + } + } + } + + #[test] + fn generator_matches_int4_reference_golden() { + let root = repo_root(); + let ref_dir = root.join("tests/codegen/reference/int4"); + let s = spec("int4"); + let mut checked = 0; + for entry in fs::read_dir(&ref_dir).expect("reference dir") { + let path = entry.unwrap().path(); + if path.extension().and_then(|e| e.to_str()) != Some("sql") { + continue; + } + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + use crate::context::normalize_sql; + let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); + let actual = rendered_for("int4", &name, s); + assert_eq!( + normalize_sql(&actual), + normalize_sql(&expected), + "{name}: generator diverged from golden reference (normalized)" + ); + checked += 1; + } + assert!(checked >= 11, "expected >=11 reference SQL files, checked {checked}"); + } + + #[test] + fn generator_matches_int4_values_rs_reference() { + let root = repo_root(); + let path = root.join("tests/codegen/reference/int4/int4_values.rs"); + let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); + let actual = render_fixture_values_rs(spec("int4")); + assert_eq!(actual, expected, "int4_values.rs: generator diverged from golden reference"); + } + + #[test] + fn generate_type_writes_expected_files() { + let d = crate::writer::test_support::tempdir(); + let s = spec("int4"); + let out = d.path().join("int4"); + let written = generate_type(s, &out).unwrap(); + let names: Vec = written + .iter() + .map(|p| p.file_name().unwrap().to_str().unwrap().to_string()) + .collect(); + assert!(names.contains(&"int4_types.sql".to_string())); + for dom in ["int4", "int4_eq", "int4_ord_ore", "int4_ord"] { + assert!(names.contains(&format!("{dom}_functions.sql"))); + assert!(names.contains(&format!("{dom}_operators.sql"))); + } + assert!(!names.contains(&"int4_aggregates.sql".to_string())); + assert!(!names.contains(&"int4_eq_aggregates.sql".to_string())); + assert!(names.contains(&"int4_ord_ore_aggregates.sql".to_string())); + assert!(names.contains(&"int4_ord_aggregates.sql".to_string())); + assert_eq!(written.len(), 11); + for p in &written { + assert!(fs::read_to_string(p).unwrap().starts_with(crate::consts::AUTO_GENERATED_HEADER)); + } + } + + #[test] + fn types_file_has_all_four_domains() { + let sql = render_types_file(spec("int4")); + assert!(sql.contains("-- REQUIRE: src/schema-v3.sql")); + for dom in ["int4", "int4_eq", "int4_ord_ore", "int4_ord"] { + assert!(sql.contains(&format!("CREATE DOMAIN eql_v3.{dom} AS jsonb")), "missing {dom}"); + } + } + + #[test] + fn storage_functions_file_is_all_blockers() { + let s = spec("int4"); + let sql = render_functions_file(s.token, domain(s, "")); + assert_eq!(sql.matches("CREATE FUNCTION").count(), 44); + assert!(!sql.contains("SET search_path")); + assert_eq!(sql.matches("LANGUAGE plpgsql").count(), 44); + assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 0); + } + + #[test] + fn eq_functions_file_counts() { + let s = spec("int4"); + let sql = render_functions_file(s.token, domain(s, "_eq")); + assert_eq!(sql.matches("CREATE FUNCTION").count(), 45); + assert!(sql.contains("CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq)")); + assert!(sql.contains("RETURNS eql_v2.hmac_256")); + assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 7); + assert_eq!(sql.matches("LANGUAGE plpgsql").count(), 38); + assert!(!sql.contains("SET search_path")); + } + + #[test] + fn ore_functions_file_counts() { + let s = spec("int4"); + let sql = render_functions_file(s.token, domain(s, "_ord")); + assert_eq!(sql.matches("CREATE FUNCTION").count(), 45); + assert!(sql.contains("CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord)")); + assert!(sql.contains("RETURNS eql_v2.ore_block_u64_8_256")); + assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 19); + assert_eq!(sql.matches("LANGUAGE plpgsql").count(), 26); + } + + #[test] + fn operators_file_has_forty_four() { + let s = spec("int4"); + let sql = render_operators_file(s.token, domain(s, "_eq")); + assert_eq!(sql.matches("CREATE OPERATOR").count(), 44); + } + + #[test] + fn aggregates_file_only_for_ord_variants() { + let s = spec("int4"); + assert!(render_aggregates_file(s.token, domain(s, "")).is_none()); + assert!(render_aggregates_file(s.token, domain(s, "_eq")).is_none()); + assert!(render_aggregates_file(s.token, domain(s, "_ord")).is_some()); + assert!(render_aggregates_file(s.token, domain(s, "_ord_ore")).is_some()); + } + + #[test] + fn aggregates_file_carries_min_and_max_and_requires() { + let s = spec("int4"); + let sql = render_aggregates_file(s.token, domain(s, "_ord")).unwrap(); + assert_eq!(sql.matches("CREATE FUNCTION").count(), 2); + assert_eq!(sql.matches("CREATE AGGREGATE").count(), 2); + assert!(sql.contains("eql_v3.min_sfunc")); + assert!(sql.contains("eql_v3.max_sfunc")); + assert!(sql.contains("-- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql")); + assert!(sql.contains("-- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql")); + assert!(sql.contains("-- REQUIRE: src/encrypted_domain/int4/int4_types.sql")); + } + + #[test] + fn ordered_files_byte_identical_modulo_typename() { + let s = spec("int4"); + let ord = domain(s, "_ord"); + let ore = domain(s, "_ord_ore"); + let norm = |sql: String| sql.replace("int4_ord_ore", "T").replace("int4_ord", "T"); + assert_eq!(norm(render_functions_file(s.token, ord)), norm(render_functions_file(s.token, ore))); + assert_eq!(norm(render_operators_file(s.token, ord)), norm(render_operators_file(s.token, ore))); + assert_eq!( + norm(render_aggregates_file(s.token, ord).unwrap()), + norm(render_aggregates_file(s.token, ore).unwrap()) + ); + } + + // --- Coarsened footgun invariant guards (whole-file scans) --- + + #[test] + fn blockers_are_never_strict_and_always_plpgsql() { + let s = spec("int4"); + // Storage domain functions file is all blockers. + let sql = render_functions_file("int4", domain(s, "")); + // Every CREATE FUNCTION here is a blocker: none may be STRICT, all plpgsql. + assert!(!sql.contains("STRICT"), "blocker marked STRICT"); + assert_eq!( + sql.matches("CREATE FUNCTION").count(), + sql.matches("LANGUAGE plpgsql").count(), + "every blocker must be LANGUAGE plpgsql" + ); + } + + #[test] + fn inlinable_functions_have_no_set_search_path() { + let s = spec("int4"); + // Extractors and wrappers (eq/ord functions files) are inlinable SQL. + for suffix in ["_eq", "_ord"] { + let sql = render_functions_file("int4", domain(s, suffix)); + // Inlinable rows are the LANGUAGE sql ones; none may pin search_path. + for block in sql.split("CREATE FUNCTION").skip(1) { + if block.contains("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") { + assert!( + !block.contains("SET search_path"), + "inlinable SQL function pins search_path" + ); + } + } + } + } + + #[test] + fn aggregate_state_functions_are_plpgsql_not_inlinable() { + let s = spec("int4"); + let sql = render_aggregates_file("int4", domain(s, "_ord")).unwrap(); + assert_eq!(sql.matches("CREATE FUNCTION").count(), 2); + assert_eq!(sql.matches("LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE").count(), 2); + assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 0); + } + + #[test] + fn generated_function_like_docs_keep_required_tags() { + let s = spec("int4"); + for d in s.domains { + let sql = render_functions_file("int4", d); + let functions = sql.matches("CREATE FUNCTION").count(); + assert_eq!(sql.matches("--! @return").count(), functions); + assert!( + sql.matches("--! @param").count() >= functions, + "each generated function must keep at least one @param tag" + ); + assert!( + sql.matches("--! @brief").count() >= functions, + "each generated function must keep @brief" + ); + } + + let sql = render_aggregates_file("int4", domain(s, "_ord")).unwrap(); + let function_like = + sql.matches("CREATE FUNCTION").count() + sql.matches("CREATE AGGREGATE").count(); + assert_eq!(sql.matches("--! @return").count(), function_like); + assert!(sql.matches("--! @param").count() >= function_like); + assert!(sql.matches("--! @brief").count() >= function_like); + } + + // --- Escaping guards over the context builders (synthetic inputs) --- + + #[test] + fn blocker_entry_preserves_operator_literal_and_domain_lit_is_escaped() { + use crate::consts::sql_str; + use crate::context::{blocker_entry, FnEntry, SqlParam}; + let dom = "eql_v3.o'dom"; + let domain_lit = sql_str(dom); + let entry = blocker_entry( + "<", + [ + SqlParam { name: "a", ty: dom.into() }, + SqlParam { name: "b", ty: dom.into() }, + ], + "boolean", + ); + match entry { + FnEntry::Blocker { operator_lit, .. } => { + assert_eq!(domain_lit, "eql_v3.o''dom"); // quote doubled by sql_str + assert_eq!(operator_lit, "<"); + } + _ => panic!("expected blocker"), + } + } + + #[test] + fn domain_block_escapes_quote_bearing_name() { + use crate::context::domain_block; + use eql_scalars::DomainSpec; + let block = domain_block("int4", &DomainSpec { suffix: "_q", terms: &[] }); + assert_eq!(block.typname, "int4_q"); // no quote present → unchanged + // keys are sql_str-escaped key tokens; none should carry a bare unescaped quote. + assert!(block.keys.iter().all(|k| !k.contains("o'"))); + } +} diff --git a/crates/eql-codegen/src/lib.rs b/crates/eql-codegen/src/lib.rs new file mode 100644 index 00000000..ebde05bf --- /dev/null +++ b/crates/eql-codegen/src/lib.rs @@ -0,0 +1,11 @@ +//! Scalar encrypted-domain SQL generator. Renders the `eql-scalars` catalog to +//! the gitignored SQL surface and the committed `_values.rs` consts. The SQL +//! surface is validated against the `tests/codegen/reference/int4` golden under +//! line-normalized comparison; `_values.rs` is validated byte-exact. + +pub mod consts; +pub mod context; +pub mod generate; +pub mod operator_surface; +pub mod templates; +pub mod writer; diff --git a/crates/eql-codegen/src/main.rs b/crates/eql-codegen/src/main.rs index e51f0ccf..6e88340e 100644 --- a/crates/eql-codegen/src/main.rs +++ b/crates/eql-codegen/src/main.rs @@ -1,2 +1,44 @@ -//! Stub entry point. Plan 2 replaces this with the catalog-driven SQL generator. -fn main() {} +use std::path::PathBuf; +use std::process::ExitCode; + +use eql_codegen::generate::generate_all; + +fn repo_root() -> PathBuf { + // The binary runs from the repo root via `cargo run`; CARGO_MANIFEST_DIR + // points at crates/eql-codegen, so the repo root is two parents up. + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .to_path_buf() +} + +fn main() -> ExitCode { + let args: Vec = std::env::args().collect(); + + // `list-types`: print catalog tokens, one per line. Consumed by Plan 3's + // fixtures-all and matrix-inventory enumeration. + if args.len() == 2 && args[1] == "list-types" { + for spec in eql_scalars::CATALOG { + println!("{}", spec.token); + } + return ExitCode::SUCCESS; + } + + if args.len() == 1 { + // No args: generate every type's SQL + _values.rs. + match generate_all(&repo_root()) { + Ok(0) => return ExitCode::SUCCESS, + Ok(code) => return ExitCode::from(code.clamp(0, 255) as u8), + Err(e) => { + eprintln!("error: {e}"); + return ExitCode::from(1); + } + } + } + + eprintln!("Usage: eql-codegen (generate all types)"); + eprintln!(" eql-codegen list-types (print catalog tokens)"); + ExitCode::from(2) +} diff --git a/crates/eql-codegen/src/operator_surface.rs b/crates/eql-codegen/src/operator_surface.rs new file mode 100644 index 00000000..eace7e06 --- /dev/null +++ b/crates/eql-codegen/src/operator_surface.rs @@ -0,0 +1,149 @@ +//! The generated operator surface (port of operator_surface.py). + +/// One operator in the generated surface. +#[derive(Clone, Copy)] +pub struct Operator { + pub symbol: &'static str, + pub backing: &'static str, + pub kind: Kind, + pub restrict: Option<&'static str>, + pub join: Option<&'static str>, + pub commutator: Option<&'static str>, + pub negator: Option<&'static str>, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Kind { + Symmetric, + Path, + BlockerOnly, +} + +pub const SYMMETRIC_OPERATORS: &[&str] = &["=", "<>", "<", "<=", ">", ">=", "@>", "<@"]; +pub const PATH_OPERATORS: &[&str] = &["->", "->>"]; +pub const BLOCKER_ONLY_OPERATORS: &[&str] = + &["?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||"]; + +/// Look up the operator metadata for a symbol. Panics on an unknown symbol — +/// the generator only ever passes catalog symbols, matching Python's KeyError. +pub fn operator(symbol: &str) -> Operator { + OPERATORS + .iter() + .copied() + .find(|o| o.symbol == symbol) + .unwrap_or_else(|| panic!("unknown operator symbol: {symbol}")) +} + +/// The eql_v2 backing function name for an operator symbol. +pub fn backing_function(symbol: &str) -> &'static str { + operator(symbol).backing +} + +/// The 20-operator table. Order matches the SYMMETRIC/PATH/BLOCKER_ONLY lists. +pub const OPERATORS: &[Operator] = &[ + Operator { symbol: "=", backing: "eq", kind: Kind::Symmetric, restrict: Some("eqsel"), join: Some("eqjoinsel"), commutator: Some("="), negator: Some("<>") }, + Operator { symbol: "<>", backing: "neq", kind: Kind::Symmetric, restrict: Some("neqsel"), join: Some("neqjoinsel"), commutator: Some("<>"), negator: Some("=") }, + Operator { symbol: "<", backing: "lt", kind: Kind::Symmetric, restrict: Some("scalarltsel"), join: Some("scalarltjoinsel"), commutator: Some(">"), negator: Some(">=") }, + Operator { symbol: "<=", backing: "lte", kind: Kind::Symmetric, restrict: Some("scalarlesel"), join: Some("scalarlejoinsel"), commutator: Some(">="), negator: Some(">") }, + Operator { symbol: ">", backing: "gt", kind: Kind::Symmetric, restrict: Some("scalargtsel"), join: Some("scalargtjoinsel"), commutator: Some("<"), negator: Some("<=") }, + Operator { symbol: ">=", backing: "gte", kind: Kind::Symmetric, restrict: Some("scalargesel"), join: Some("scalargejoinsel"), commutator: Some("<="), negator: Some("<") }, + Operator { symbol: "@>", backing: "contains", kind: Kind::Symmetric, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "<@", backing: "contained_by", kind: Kind::Symmetric, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "->", backing: "\"->\"", kind: Kind::Path, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "->>", backing: "\"->>\"", kind: Kind::Path, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "?", backing: "\"?\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "?|", backing: "\"?|\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "?&", backing: "\"?&\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "@?", backing: "\"@?\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "@@", backing: "\"@@\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "#>", backing: "\"#>\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "#>>", backing: "\"#>>\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "-", backing: "\"-\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "#-", backing: "\"#-\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { symbol: "||", backing: "\"||\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn twenty_operators_total() { + assert_eq!(OPERATORS.len(), 20); + } + + #[test] + fn operator_lists_match() { + assert_eq!(SYMMETRIC_OPERATORS, &["=", "<>", "<", "<=", ">", ">=", "@>", "<@"]); + assert_eq!(PATH_OPERATORS, &["->", "->>"]); + assert_eq!( + BLOCKER_ONLY_OPERATORS, + &["?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||"] + ); + } + + #[test] + fn no_like_operators() { + assert!(OPERATORS.iter().all(|o| o.symbol != "~~" && o.symbol != "~~*")); + } + + #[test] + fn backing_function_names() { + assert_eq!(backing_function("="), "eq"); + assert_eq!(backing_function("<>"), "neq"); + assert_eq!(backing_function("<"), "lt"); + assert_eq!(backing_function("<="), "lte"); + assert_eq!(backing_function(">"), "gt"); + assert_eq!(backing_function(">="), "gte"); + assert_eq!(backing_function("@>"), "contains"); + assert_eq!(backing_function("<@"), "contained_by"); + assert_eq!(backing_function("->"), "\"->\""); + assert_eq!(backing_function("->>"), "\"->>\""); + assert_eq!(backing_function("?"), "\"?\""); + assert_eq!(backing_function("?|"), "\"?|\""); + assert_eq!(backing_function("?&"), "\"?&\""); + assert_eq!(backing_function("@?"), "\"@?\""); + assert_eq!(backing_function("@@"), "\"@@\""); + assert_eq!(backing_function("#>"), "\"#>\""); + assert_eq!(backing_function("#>>"), "\"#>>\""); + assert_eq!(backing_function("-"), "\"-\""); + assert_eq!(backing_function("#-"), "\"#-\""); + assert_eq!(backing_function("||"), "\"||\""); + } + + #[test] + fn selectivity_estimators() { + assert_eq!(operator("=").restrict, Some("eqsel")); + assert_eq!(operator("=").join, Some("eqjoinsel")); + assert_eq!(operator("<>").restrict, Some("neqsel")); + assert_eq!(operator("<").restrict, Some("scalarltsel")); + assert_eq!(operator("<=").restrict, Some("scalarlesel")); + assert_eq!(operator(">").restrict, Some("scalargtsel")); + assert_eq!(operator(">=").restrict, Some("scalargesel")); + } + + #[test] + fn negators_and_commutators() { + assert_eq!(operator("=").negator, Some("<>")); + assert_eq!(operator("<>").negator, Some("=")); + assert_eq!(operator("<").commutator, Some(">")); + assert_eq!(operator("<").negator, Some(">=")); + assert_eq!(operator(">=").commutator, Some("<=")); + } + + #[test] + fn known_jsonb_operators_match_table_keys() { + let union: Vec<&str> = SYMMETRIC_OPERATORS + .iter() + .chain(PATH_OPERATORS) + .chain(BLOCKER_ONLY_OPERATORS) + .copied() + .collect(); + let keys: Vec<&str> = OPERATORS.iter().map(|o| o.symbol).collect(); + assert_eq!(union, keys); + assert_eq!( + union.len(), + SYMMETRIC_OPERATORS.len() + PATH_OPERATORS.len() + BLOCKER_ONLY_OPERATORS.len() + ); + } +} diff --git a/crates/eql-codegen/src/templates.rs b/crates/eql-codegen/src/templates.rs new file mode 100644 index 00000000..df4e3065 --- /dev/null +++ b/crates/eql-codegen/src/templates.rs @@ -0,0 +1,72 @@ +//! Rust fixture-const renderer. The SQL surface is rendered from minijinja +//! templates (see `context.rs`); this file emits only the committed +//! `_values.rs` consts, which stay byte-exact. + +use eql_scalars::ScalarSpec; + +/// Body for tests/sqlx/src/fixtures/_values.rs. The writer prepends the +/// AUTO-GENERATED Rust header, so the body carries none. +/// Port of templates.py `render_fixture_values_rs`. +pub fn render_fixture_values_rs(spec: &ScalarSpec) -> String { + let token = spec.token; + let rust_type = spec.kind.rust_type(); + let mut literals = String::new(); + for &f in spec.fixtures { + literals.push_str(&format!(" {},\n", f.render_literal(spec.kind))); + } + format!( + "//! Fixture plaintext values for the {token} encrypted-domain family.\n\ + //!\n\ + //! Generated from tasks/codegen/types/{token}.toml `[fixture] values` —\n\ + //! the single source of truth shared by the fixture generator\n\ + //! (`fixtures::eql_v2_{token}`) and the matrix oracle\n\ + //! (`ScalarType::FIXTURE_VALUES`).\n\n\ + /// Distinct plaintext values present in the `eql_v2_{token}` fixture.\n\ + pub const VALUES: &[{rust_type}] = &[\n\ + {literals}\ + ];\n" + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use eql_scalars::CATALOG; + + fn spec(token: &str) -> &'static ScalarSpec { + CATALOG.iter().find(|s| s.token == token).expect("catalog token") + } + + #[test] + fn fixture_values_rs_emits_typed_const_for_int4() { + let body = render_fixture_values_rs(spec("int4")); + assert!(body.contains("pub const VALUES: &[i32] = &[")); + assert!(body.contains("tasks/codegen/types/int4.toml")); + assert!(body.contains(" i32::MIN,\n")); + assert!(body.contains(" i32::MAX,\n")); + assert!(body.contains(" -1,\n")); + assert!(body.contains(" 0,\n")); + assert!(body.contains(" 1,\n")); + assert!(!body.contains("AUTO-GENERATED")); + } + + #[test] + fn fixture_values_rs_preserves_catalog_order() { + let body = render_fixture_values_rs(spec("int4")); + let min = body.find("i32::MIN").unwrap(); + let zero = body.find(" 0,").unwrap(); + let max = body.find("i32::MAX").unwrap(); + assert!(min < zero && zero < max); + } + + // Adapted from the plan's `fixture_values_rs_int8_uses_i64`: the shipped + // CATALOG has no int8 (deliberately reserved for a later branch), so this + // exercises the second committed non-i32 type — int2 (i16). + #[test] + fn fixture_values_rs_int2_uses_i16() { + let body = render_fixture_values_rs(spec("int2")); + assert!(body.contains("pub const VALUES: &[i16] = &[")); + assert!(body.contains(" i16::MIN,\n")); + assert!(body.contains(" -30000,\n")); + } +} diff --git a/crates/eql-codegen/src/writer.rs b/crates/eql-codegen/src/writer.rs new file mode 100644 index 00000000..172c336d --- /dev/null +++ b/crates/eql-codegen/src/writer.rs @@ -0,0 +1,291 @@ +//! Ownership-guarded file writer (port of writer.py). + +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +use crate::consts::{AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS}; + +/// First line of the SQL header — the ownership marker. +fn sql_marker() -> &'static str { + AUTO_GENERATED_HEADER.lines().next().unwrap() +} + +/// First line of the Rust header — the ownership marker. +fn rs_marker() -> &'static str { + AUTO_GENERATED_HEADER_RS.lines().next().unwrap() +} + +/// Raised when the generator would clobber a hand-written file. +#[derive(Debug)] +pub enum WriteError { + Ownership(String), + Io(io::Error), +} + +impl From for WriteError { + fn from(e: io::Error) -> Self { + WriteError::Io(e) + } +} + +impl std::fmt::Display for WriteError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WriteError::Ownership(m) => write!(f, "{m}"), + WriteError::Io(e) => write!(f, "io error: {e}"), + } + } +} + +fn first_line(path: &Path) -> io::Result { + let content = fs::read_to_string(path)?; + Ok(content + .lines() + .next() + .unwrap_or("") + .trim_end_matches(['\r', '\n']) + .to_string()) +} + +/// True if the file carries the SQL AUTO-GENERATED marker. Port of `is_generated`. +pub fn is_generated(path: &Path) -> bool { + path.is_file() && first_line(path).map(|l| l == sql_marker()).unwrap_or(false) +} + +/// True if the file carries the Rust AUTO-GENERATED marker. Port of `is_generated_rs`. +pub fn is_generated_rs(path: &Path) -> bool { + path.is_file() && first_line(path).map(|l| l == rs_marker()).unwrap_or(false) +} + +/// Delete every generated .sql file in `directory`, returning removed paths. +/// Port of `clean_generated_files`. +pub fn clean_generated_files(directory: &Path) -> io::Result> { + if !directory.is_dir() { + return Ok(Vec::new()); + } + let mut paths: Vec = fs::read_dir(directory)? + .filter_map(|e| e.ok().map(|e| e.path())) + .filter(|p| p.extension().and_then(|x| x.to_str()) == Some("sql")) + .collect(); + paths.sort(); + let mut removed = Vec::new(); + for p in paths { + if is_generated(&p) { + fs::remove_file(&p)?; + removed.push(p); + } + } + Ok(removed) +} + +/// Refuse a generation run if any target is hand-written. Port of +/// `ensure_generated_paths_writable`. +pub fn ensure_generated_paths_writable(paths: &[PathBuf]) -> Result<(), WriteError> { + for path in paths { + if path.exists() && !is_generated(path) { + return Err(WriteError::Ownership(format!( + "refusing to overwrite hand-written file: {} (no AUTO-GENERATED header). \ + Remove it by hand if it is a one-time generator-adoption target.", + path.display() + ))); + } + } + Ok(()) +} + +/// Write the rendered SQL `body` to `path`, after refusing to clobber a +/// hand-written file. The SQL templates emit the `-- AUTOMATICALLY GENERATED +/// FILE.` marker as their own first line, so the writer writes `body` verbatim +/// — it does not prepend a header. +pub fn write_generated_file(path: &Path, body: &str) -> Result<(), WriteError> { + ensure_generated_paths_writable(std::slice::from_ref(&path.to_path_buf()))?; + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(path, body)?; + Ok(()) +} + +/// Write `body` to a Rust file prefixed with the Rust header. Port of `write_generated_rs`. +pub fn write_generated_rs(path: &Path, body: &str) -> Result<(), WriteError> { + if path.exists() && !is_generated_rs(path) { + return Err(WriteError::Ownership(format!( + "refusing to overwrite hand-written file: {} (no AUTO-GENERATED header).", + path.display() + ))); + } + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(path, format!("{AUTO_GENERATED_HEADER_RS}{body}"))?; + Ok(()) +} + +#[cfg(test)] +pub(crate) mod test_support { + use std::fs; + use std::path::{Path, PathBuf}; + + pub struct TempDir(PathBuf); + impl TempDir { + pub fn path(&self) -> &Path { &self.0 } + } + impl Drop for TempDir { + fn drop(&mut self) { let _ = fs::remove_dir_all(&self.0); } + } + pub fn tempdir() -> TempDir { + let mut p = std::env::temp_dir(); + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(); + p.push(format!("eql-codegen-test-{nanos}-{:?}", std::thread::current().id())); + fs::create_dir_all(&p).unwrap(); + TempDir(p) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::test_support::tempdir as tmp; + + #[test] + fn is_generated_true_for_header() { + let d = tmp(); + let p = d.path().join("x.sql"); + fs::write(&p, format!("{AUTO_GENERATED_HEADER}SELECT 1;\n")).unwrap(); + assert!(is_generated(&p)); + } + + #[test] + fn is_generated_false_for_handwritten() { + let d = tmp(); + let p = d.path().join("x.sql"); + fs::write(&p, "-- REQUIRE: src/schema.sql\nSELECT 1;\n").unwrap(); + assert!(!is_generated(&p)); + } + + #[test] + fn is_generated_true_for_crlf_header() { + let d = tmp(); + let p = d.path().join("x.sql"); + let marker = sql_marker(); + fs::write(&p, format!("{marker}\r\nSELECT 1;\n")).unwrap(); + assert!(is_generated(&p)); + } + + #[test] + fn write_generated_file_writes_rendered_body_verbatim() { + let d = tmp(); + let p = d.path().join("int4_types.sql"); + // The template render carries the marker on line 1; the writer writes it + // through unchanged. + let body = format!("{AUTO_GENERATED_HEADER}DO $$ BEGIN END $$;\n"); + write_generated_file(&p, &body).unwrap(); + let text = fs::read_to_string(&p).unwrap(); + assert_eq!(text, body); + assert!(is_generated(&p)); + } + + #[test] + fn write_refuses_to_overwrite_handwritten() { + let d = tmp(); + let p = d.path().join("int4_types.sql"); + fs::write(&p, "-- REQUIRE: src/schema.sql\n-- hand-written\n").unwrap(); + let err = write_generated_file(&p, "DO $$ BEGIN END $$;\n").unwrap_err(); + assert!(matches!(err, WriteError::Ownership(_))); + assert!(err.to_string().contains("hand-written")); + } + + #[test] + fn preflight_refuses_handwritten_target() { + let d = tmp(); + let generated = d.path().join("int4_types.sql"); + let hand = d.path().join("int4_eq_functions.sql"); + fs::write(&generated, format!("{AUTO_GENERATED_HEADER}-- old generated\n")).unwrap(); + fs::write(&hand, "-- REQUIRE: src/schema.sql\n-- hand-written\n").unwrap(); + let err = ensure_generated_paths_writable(&[generated.clone(), hand.clone()]).unwrap_err(); + assert!(err.to_string().contains("int4_eq_functions.sql")); + assert!(generated.exists()); + assert!(hand.exists()); + } + + #[test] + fn write_overwrites_existing_generated_file() { + let d = tmp(); + let p = d.path().join("int4_types.sql"); + fs::write(&p, format!("{AUTO_GENERATED_HEADER}-- old content\n")).unwrap(); + write_generated_file(&p, &format!("{AUTO_GENERATED_HEADER}-- new content\n")).unwrap(); + let text = fs::read_to_string(&p).unwrap(); + assert!(text.contains("-- new content")); + assert!(!text.contains("-- old content")); + } + + #[test] + fn clean_removes_only_generated_files() { + let d = tmp(); + let gen1 = d.path().join("int4_eq_functions.sql"); + let gen2 = d.path().join("int4_old_domain_functions.sql"); + let hand = d.path().join("int4_jsonb_extra.sql"); + fs::write(&gen1, format!("{AUTO_GENERATED_HEADER}SELECT 1;\n")).unwrap(); + fs::write(&gen2, format!("{AUTO_GENERATED_HEADER}SELECT 2;\n")).unwrap(); + fs::write(&hand, "-- REQUIRE: src/schema.sql\n-- hand-written\n").unwrap(); + let removed = clean_generated_files(d.path()).unwrap(); + assert!(!gen1.exists()); + assert!(!gen2.exists()); + assert!(hand.exists()); + assert_eq!(removed.len(), 2); + } + + #[test] + fn clean_on_empty_directory() { + let d = tmp(); + assert!(clean_generated_files(d.path()).unwrap().is_empty()); + } + + #[test] + fn write_generated_rs_creates_with_rust_header() { + let d = tmp(); + let p = d.path().join("int4_values.rs"); + write_generated_rs(&p, "pub const VALUES: &[i32] = &[];\n").unwrap(); + let text = fs::read_to_string(&p).unwrap(); + assert!(text.starts_with(AUTO_GENERATED_HEADER_RS)); + assert!(text.contains("pub const VALUES")); + } + + #[test] + fn is_generated_rs_true_for_rust_header() { + let d = tmp(); + let p = d.path().join("int4_values.rs"); + fs::write(&p, format!("{AUTO_GENERATED_HEADER_RS}pub const VALUES: &[i32] = &[];\n")).unwrap(); + assert!(is_generated_rs(&p)); + } + + #[test] + fn is_generated_rs_false_for_handwritten() { + let d = tmp(); + let p = d.path().join("int4_values.rs"); + fs::write(&p, "//! hand-written\npub const VALUES: &[i32] = &[];\n").unwrap(); + assert!(!is_generated_rs(&p)); + } + + #[test] + fn write_generated_rs_refuses_handwritten() { + let d = tmp(); + let p = d.path().join("int4_values.rs"); + fs::write(&p, "//! hand-written\n").unwrap(); + let err = write_generated_rs(&p, "pub const VALUES: &[i32] = &[];\n").unwrap_err(); + assert!(err.to_string().contains("hand-written")); + } + + #[test] + fn write_generated_rs_overwrites_existing_generated() { + let d = tmp(); + let p = d.path().join("int4_values.rs"); + fs::write(&p, format!("{AUTO_GENERATED_HEADER_RS}// old\n")).unwrap(); + write_generated_rs(&p, "// new\n").unwrap(); + let text = fs::read_to_string(&p).unwrap(); + assert!(text.contains("// new")); + assert!(!text.contains("// old")); + } +} diff --git a/crates/eql-codegen/templates/aggregates.sql.j2 b/crates/eql-codegen/templates/aggregates.sql.j2 new file mode 100644 index 00000000..9660917d --- /dev/null +++ b/crates/eql-codegen/templates/aggregates.sql.j2 @@ -0,0 +1,34 @@ +-- AUTOMATICALLY GENERATED FILE. +{% for r in requires -%} +-- REQUIRE: {{ r }} +{% endfor %} +--! @file encrypted_domain/{{ token }}/{{ name }}_aggregates.sql +--! @brief Aggregates for {{ dom }}. +{% for a in aggregates %} +--! @brief State function for {{ a.name }} on {{ dom }}. +--! @param state {{ dom }} +--! @param value {{ dom }} +--! @return {{ dom }} +CREATE FUNCTION {{ domain_schema }}.{{ a.name }}_sfunc(state {{ dom }}, value {{ dom }}) +RETURNS {{ dom }} +LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE +SET search_path = pg_catalog, extensions, public +AS $$ +BEGIN + IF value {{ a.comparator }} state THEN + RETURN value; + END IF; + RETURN state; +END; +$$; + +--! @brief {{ a.name }} aggregate for {{ dom }}. +--! @param input {{ dom }} +--! @return {{ dom }} +CREATE AGGREGATE {{ domain_schema }}.{{ a.name }}({{ dom }}) ( + sfunc = {{ domain_schema }}.{{ a.name }}_sfunc, + stype = {{ dom }}, + combinefunc = {{ domain_schema }}.{{ a.name }}_sfunc, + parallel = safe +); +{% endfor -%} diff --git a/crates/eql-codegen/templates/functions.sql.j2 b/crates/eql-codegen/templates/functions.sql.j2 new file mode 100644 index 00000000..0b75cd3a --- /dev/null +++ b/crates/eql-codegen/templates/functions.sql.j2 @@ -0,0 +1,34 @@ +-- AUTOMATICALLY GENERATED FILE. +{% for r in requires -%} +-- REQUIRE: {{ r }} +{% endfor %} +--! @file encrypted_domain/{{ token }}/{{ name }}_functions.sql +--! @brief Functions for {{ dom }}. +{% for e in entries %} +{% if e.kind == "Extractor" -%} +--! @brief Index extractor for {{ dom }}. +--! @param a {{ dom }} +--! @return {{ e.ret }} +CREATE FUNCTION {{ domain_schema }}.{{ e.extractor }}(a {{ dom }}) +RETURNS {{ e.ret }} +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT {{ core_schema }}.{{ e.ctor }}(a::jsonb) $$; +{% elif e.kind == "Wrapper" -%} +--! @brief Operator wrapper for {{ dom }}. +--! @param {{ e.args[0].name }} {{ e.args[0].ty }} +--! @param {{ e.args[1].name }} {{ e.args[1].ty }} +--! @return boolean +CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT {{ e.call_a }} {{ e.op }} {{ e.call_b }} $$; +{% else -%} +--! @brief Unsupported operator blocker for {{ dom }}. +--! @param {{ e.args[0].name }} {{ e.args[0].ty }} +--! @param {{ e.args[1].name }} {{ e.args[1].ty }} +--! @return {{ e.returns }} +CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) +RETURNS {{ e.returns }} IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '{{ e.operator_lit }}', '{{ domain_lit }}'; END; $$ +LANGUAGE plpgsql; +{% endif -%} +{% endfor -%} diff --git a/crates/eql-codegen/templates/operators.sql.j2 b/crates/eql-codegen/templates/operators.sql.j2 new file mode 100644 index 00000000..1dc1360c --- /dev/null +++ b/crates/eql-codegen/templates/operators.sql.j2 @@ -0,0 +1,13 @@ +-- AUTOMATICALLY GENERATED FILE. +{% for r in requires -%} +-- REQUIRE: {{ r }} +{% endfor %} +--! @file encrypted_domain/{{ token }}/{{ name }}_operators.sql +--! @brief Operators for {{ dom }}. +{% for o in operators %} +CREATE OPERATOR {{ o.symbol }} ( + FUNCTION = {{ domain_schema }}.{{ o.function_name }}, + LEFTARG = {{ o.leftarg }}, RIGHTARG = {{ o.rightarg }}{% if o.metadata %}, + {{ o.metadata }}{% endif %} +); +{% endfor -%} diff --git a/crates/eql-codegen/templates/types.sql.j2 b/crates/eql-codegen/templates/types.sql.j2 new file mode 100644 index 00000000..99636ac0 --- /dev/null +++ b/crates/eql-codegen/templates/types.sql.j2 @@ -0,0 +1,26 @@ +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/schema-v3.sql + +--! @file encrypted_domain/{{ token }}/{{ token }}_types.sql +--! @brief Encrypted-domain types for {{ token }}. + +DO $$ +BEGIN +{%- for d in domains %} + --! @brief Encrypted domain {{ domain_schema }}.{{ d.name }}. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = '{{ d.typname }}' AND typnamespace = '{{ domain_schema }}'::regnamespace + ) THEN + CREATE DOMAIN {{ domain_schema }}.{{ d.name }} AS jsonb + CHECK ( + jsonb_typeof(VALUE) = 'object' + {%- for k in d.keys %} + AND VALUE ? '{{ k }}' + {%- endfor %} + AND VALUE->>'v' = '2' + ); + END IF; +{% endfor -%} +END +$$; diff --git a/crates/eql-codegen/tests/parity.rs b/crates/eql-codegen/tests/parity.rs new file mode 100644 index 00000000..c0504586 --- /dev/null +++ b/crates/eql-codegen/tests/parity.rs @@ -0,0 +1,100 @@ +//! THE PARITY GATE. Runs the Rust generator (into a temp dir) and asserts the +//! int4 SQL surface is line-normalized-equal to the `tests/codegen/reference/int4` +//! golden, and that committed `_values.rs` are byte-identical to the +//! generator output. The golden reference — not the retired Python generator — +//! is the sole oracle. + +use std::fs; +use std::path::PathBuf; + +fn repo_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent().unwrap() + .parent().unwrap() + .to_path_buf() +} + +fn tempdir(tag: &str) -> PathBuf { + let mut p = std::env::temp_dir(); + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(); + p.push(format!("eql-parity-{tag}-{nanos}")); + fs::create_dir_all(&p).unwrap(); + p +} + +#[test] +fn rust_generator_matches_committed_values_rs() { + let root = repo_root(); + let out = tempdir("rust-values"); + eql_codegen::generate::generate_all(&out).expect("rust generate_all"); + + for spec in eql_scalars::CATALOG { + let token = spec.token; + let generated = out.join(format!("tests/sqlx/src/fixtures/{token}_values.rs")); + let committed = root.join(format!("tests/sqlx/src/fixtures/{token}_values.rs")); + let g = fs::read(&generated).expect("generated values.rs"); + let c = fs::read(&committed).expect("committed values.rs"); + assert_eq!( + g, c, + "{token}_values.rs: Rust generator output differs from the committed file" + ); + } +} + +#[test] +fn rust_generator_matches_int4_golden_files() { + let root = repo_root(); + let out = tempdir("rust-golden"); + eql_codegen::generate::generate_all(&out).expect("rust generate_all"); + + let ref_dir = root.join("tests/codegen/reference/int4"); + let gen_dir = out.join("src/encrypted_domain/int4"); + for entry in fs::read_dir(&ref_dir).unwrap() { + let path = entry.unwrap().path(); + if path.extension().and_then(|e| e.to_str()) != Some("sql") { continue; } + let name = path.file_name().unwrap().to_str().unwrap(); + let reference = fs::read_to_string(&path).unwrap(); + // Strip the leading `-- REFERENCE:` provenance line. What remains is the + // generated body, which already starts with the template-owned + // `-- AUTOMATICALLY GENERATED FILE.` marker — the same first line the + // materialised file carries, so no header is re-added here. + let expected: String = reference.lines() + .skip_while(|l| l.starts_with("-- REFERENCE:") || l.starts_with("// REFERENCE:")) + .map(|l| format!("{l}\n")) + .collect(); + let actual = fs::read_to_string(gen_dir.join(name)).unwrap(); + assert_eq!( + eql_codegen::context::normalize_sql(&actual), + eql_codegen::context::normalize_sql(&expected), + "{name}: materialised output differs from golden (normalized)" + ); + } +} + +/// Both Rust strippers (the in-crate `strip_reference_marker` and this file's +/// golden test) skip a variable number of leading `-- REFERENCE:` lines, while +/// the shell gate skips exactly one with `tail -n +2`. They agree only while +/// every reference file carries exactly one marker line — make that explicit. +#[test] +fn every_reference_file_has_exactly_one_marker_line() { + let root = repo_root(); + let dir = root.join("tests/codegen/reference/int4"); + for entry in fs::read_dir(&dir).unwrap() { + let path = entry.unwrap().path(); + let ext = path.extension().and_then(|e| e.to_str()); + if ext != Some("sql") && ext != Some("rs") { + continue; + } + let text = fs::read_to_string(&path).unwrap(); + let markers = text + .lines() + .take_while(|l| l.starts_with("-- REFERENCE:") || l.starts_with("// REFERENCE:")) + .count(); + assert_eq!( + markers, 1, + "{}: expected exactly 1 leading REFERENCE marker line (shell `tail -n +2` assumes one); found {markers}", + path.display() + ); + } +} diff --git a/mise.toml b/mise.toml index 78ea2cb0..e627b45d 100644 --- a/mise.toml +++ b/mise.toml @@ -96,6 +96,11 @@ run = """ mise exec python -- python -m tasks.codegen.generate --all """ +[tasks."codegen:parity"] +description = "Parity gate: Rust eql-codegen output byte-identical to the Python oracle" +dir = "{{config_root}}" +run = "bash tasks/codegen-parity.sh" + [tasks."test:codegen"] description = "Run the encrypted-domain codegen generator tests (no database required)" dir = "{{config_root}}" diff --git a/tasks/codegen-parity.sh b/tasks/codegen-parity.sh new file mode 100755 index 00000000..ff3a90a6 --- /dev/null +++ b/tasks/codegen-parity.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +#MISE description="Parity gate: Rust eql-codegen output matches the int4 golden (normalized) and committed values.rs" + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +echo "==> Generating with the Rust generator (writes the real repo tree)" +cargo run -q -p eql-codegen -- > /dev/null + +echo "==> Diffing Rust int4 SQL vs golden reference (line-normalized)" +norm() { sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | grep -v '^$'; } +for f in tests/codegen/reference/int4/*.sql; do + name="$(basename "$f")" + # Reference: drop the 1-line `-- REFERENCE:` provenance line. What remains — + # and the whole generated file — both start with the template-owned + # `-- AUTOMATICALLY GENERATED FILE.` marker, so no header strip is needed. + diff <(tail -n +2 "$f" | norm) \ + <(norm < "src/encrypted_domain/int4/$name") +done + +echo "==> Verifying committed _values.rs are byte-identical (git clean)" +git diff --exit-code -- tests/sqlx/src/fixtures/*_values.rs + +echo "PARITY OK: Rust generator matches the int4 golden (normalized) and committed values.rs." diff --git a/tasks/test.sh b/tasks/test.sh index 806d6e99..74e044df 100755 --- a/tasks/test.sh +++ b/tasks/test.sh @@ -22,24 +22,22 @@ echo "" echo "Building EQL..." mise run --output prefix --force build -# Run encrypted-domain codegen generator tests -echo "" -echo "==============================================" -echo "1/3: Running encrypted-domain codegen tests" -echo "==============================================" -mise run --output prefix test:codegen +# The encrypted-domain codegen drift suite (`test:codegen`) is the deprecated +# Python generator's pytest and is intentionally not run here. The Rust +# generator is gated against the hand-written reference by the dedicated +# "Encrypted-domain codegen" CI job (`mise run codegen:parity`). # Run lints on sqlx tests echo "" echo "==============================================" -echo "2/3: Running linting checks on SQLx Rust tests" +echo "1/2: Running linting checks on SQLx Rust tests" echo "==============================================" mise run --output prefix test:lint # Run SQLx Rust tests echo "" echo "==============================================" -echo "3/3: Running SQLx Rust Tests" +echo "2/2: Running SQLx Rust Tests" echo "==============================================" mise run --output prefix test:sqlx @@ -49,7 +47,6 @@ echo "✅ ALL TESTS PASSED" echo "==============================================" echo "" echo "Summary:" -echo " ✓ Encrypted-domain codegen tests" echo " ✓ SQLx Rust lint checks" echo " ✓ SQLx Rust tests" echo "" diff --git a/tests/codegen/reference/int4/int4_eq_functions.sql b/tests/codegen/reference/int4/int4_eq_functions.sql index 14344ba2..21fddfd5 100644 --- a/tests/codegen/reference/int4/int4_eq_functions.sql +++ b/tests/codegen/reference/int4/int4_eq_functions.sql @@ -1,4 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql @@ -6,9 +7,9 @@ -- REQUIRE: src/hmac_256/functions.sql --! @file encrypted_domain/int4/int4_eq_functions.sql ---! @brief Equality-only domain of the int4 encrypted-domain family — comparison/path functions. +--! @brief Functions for eql_v3.int4_eq. ---! @brief Index extractor for the eql_v3.int4_eq variant. +--! @brief Index extractor for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @return eql_v2.hmac_256 CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq) @@ -16,7 +17,7 @@ RETURNS eql_v2.hmac_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v2.hmac_256(a::jsonb) $$; ---! @brief Equality wrapper for eql_v3.int4_eq. +--! @brief Operator wrapper for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq --! @return boolean @@ -24,7 +25,7 @@ CREATE FUNCTION eql_v3.eq(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.eq_term(a) = eql_v3.eq_term(b) $$; ---! @brief Equality wrapper for eql_v3.int4_eq (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean @@ -32,7 +33,7 @@ CREATE FUNCTION eql_v3.eq(a eql_v3.int4_eq, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.eq_term(a) = eql_v3.eq_term(b::eql_v3.int4_eq) $$; ---! @brief Equality wrapper for eql_v3.int4_eq (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq --! @return boolean @@ -40,7 +41,7 @@ CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.eq_term(a::eql_v3.int4_eq) = eql_v3.eq_term(b) $$; ---! @brief Inequality wrapper for eql_v3.int4_eq. +--! @brief Operator wrapper for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq --! @return boolean @@ -48,7 +49,7 @@ CREATE FUNCTION eql_v3.neq(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.eq_term(a) <> eql_v3.eq_term(b) $$; ---! @brief Inequality wrapper for eql_v3.int4_eq (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb --! @return boolean @@ -56,7 +57,7 @@ CREATE FUNCTION eql_v3.neq(a eql_v3.int4_eq, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.eq_term(a) <> eql_v3.eq_term(b::eql_v3.int4_eq) $$; ---! @brief Inequality wrapper for eql_v3.int4_eq (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq --! @return boolean @@ -64,343 +65,343 @@ CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4_eq) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.eq_term(a::eql_v3.int4_eq) <> eql_v3.eq_term(b) $$; ---! @brief Blocker for < on eql_v3.int4_eq. +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lt(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v3.int4_eq (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lt(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v3.int4_eq. +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lte(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v3.int4_eq (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lte(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v3.int4_eq. +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gt(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v3.int4_eq (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gt(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v3.int4_eq. +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gte(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v3.int4_eq (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gte(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '>='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4_eq. +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4_eq (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_eq. +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_eq (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_eq, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4_eq) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_eq (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param selector text ---! @return eql_v3.int4_eq (never returns; always raises) +--! @return eql_v3.int4_eq CREATE FUNCTION eql_v3."->"(a eql_v3.int4_eq, selector text) RETURNS eql_v3.int4_eq IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_eq (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param selector integer ---! @return eql_v3.int4_eq (never returns; always raises) +--! @return eql_v3.int4_eq CREATE FUNCTION eql_v3."->"(a eql_v3.int4_eq, selector integer) RETURNS eql_v3.int4_eq IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param selector eql_v3.int4_eq ---! @return eql_v3.int4_eq (never returns; always raises) +--! @return eql_v3.int4_eq CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4_eq) RETURNS eql_v3.int4_eq IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_eq (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param selector text ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_eq, selector text) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_eq (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param selector integer ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_eq, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param selector eql_v3.int4_eq ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4_eq) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v3.int4_eq (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?"(a eql_v3.int4_eq, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v3.int4_eq (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?|"(a eql_v3.int4_eq, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '?|'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v3.int4_eq (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?&"(a eql_v3.int4_eq, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '?&'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v3.int4_eq (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@?"(a eql_v3.int4_eq, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v3.int4_eq (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@@"(a eql_v3.int4_eq, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_eq', '@@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v3.int4_eq (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#>"(a eql_v3.int4_eq, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v3.int4_eq (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text[] ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4_eq, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_eq (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_eq, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_eq (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b integer ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_eq, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_eq (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_eq, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v3.int4_eq (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#-"(a eql_v3.int4_eq, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_eq. +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b eql_v3.int4_eq ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4_eq, b eql_v3.int4_eq) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_eq (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a eql_v3.int4_eq --! @param b jsonb ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4_eq, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_eq'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_eq (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_eq. --! @param a jsonb --! @param b eql_v3.int4_eq ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4_eq) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_eq'; END; $$ diff --git a/tests/codegen/reference/int4/int4_eq_operators.sql b/tests/codegen/reference/int4/int4_eq_operators.sql index a39951f6..fa0d44cd 100644 --- a/tests/codegen/reference/int4/int4_eq_operators.sql +++ b/tests/codegen/reference/int4/int4_eq_operators.sql @@ -1,10 +1,11 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_eq_functions.sql --! @file encrypted_domain/int4/int4_eq_operators.sql ---! @brief Equality-only domain of the int4 encrypted-domain family — operator declarations. +--! @brief Operators for eql_v3.int4_eq. CREATE OPERATOR = ( FUNCTION = eql_v3.eq, @@ -42,229 +43,191 @@ CREATE OPERATOR <> ( COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel ); --- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( FUNCTION = eql_v3.lt, LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( FUNCTION = eql_v3.lt, LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( FUNCTION = eql_v3.lt, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( FUNCTION = eql_v3.lte, LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( FUNCTION = eql_v3.lte, LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( FUNCTION = eql_v3.lte, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( FUNCTION = eql_v3.gt, LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( FUNCTION = eql_v3.gt, LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( FUNCTION = eql_v3.gt, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( FUNCTION = eql_v3.gte, LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( FUNCTION = eql_v3.gte, LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( FUNCTION = eql_v3.gte, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4_eq, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4_eq, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( FUNCTION = eql_v3."?", LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( FUNCTION = eql_v3."?|", LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( FUNCTION = eql_v3."?&", LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( FUNCTION = eql_v3."@?", LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( FUNCTION = eql_v3."@@", LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( FUNCTION = eql_v3."#>", LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( FUNCTION = eql_v3."#>>", LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_eq, RIGHTARG = text ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_eq, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( FUNCTION = eql_v3."#-", LEFTARG = eql_v3.int4_eq, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4_eq, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_eq diff --git a/tests/codegen/reference/int4/int4_functions.sql b/tests/codegen/reference/int4/int4_functions.sql index a60bc7b1..36c9df70 100644 --- a/tests/codegen/reference/int4/int4_functions.sql +++ b/tests/codegen/reference/int4/int4_functions.sql @@ -1,403 +1,404 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/functions.sql --! @file encrypted_domain/int4/int4_functions.sql ---! @brief Storage-only domain of the int4 encrypted-domain family — comparison/path functions. +--! @brief Functions for eql_v3.int4. ---! @brief Blocker for = on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.eq(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for = on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.eq(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for = on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <> on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.neq(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <> on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.neq(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <> on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lt(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lt(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for < on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lte(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lte(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <= on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gt(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gt(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for > on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gte(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gte(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for >= on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '>='); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4 (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param selector text ---! @return eql_v3.int4 (never returns; always raises) +--! @return eql_v3.int4 CREATE FUNCTION eql_v3."->"(a eql_v3.int4, selector text) RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4 (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param selector integer ---! @return eql_v3.int4 (never returns; always raises) +--! @return eql_v3.int4 CREATE FUNCTION eql_v3."->"(a eql_v3.int4, selector integer) RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param selector eql_v3.int4 ---! @return eql_v3.int4 (never returns; always raises) +--! @return eql_v3.int4 CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4) RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4 (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param selector text ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4, selector text) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4 (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param selector integer ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param selector eql_v3.int4 ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v3.int4 (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?"(a eql_v3.int4, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v3.int4 (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?|"(a eql_v3.int4, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '?|'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v3.int4 (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?&"(a eql_v3.int4, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '?&'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v3.int4 (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@?"(a eql_v3.int4, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v3.int4 (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@@"(a eql_v3.int4, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '@@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v3.int4 (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#>"(a eql_v3.int4, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v3.int4 (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text[] ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4 (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4 (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b integer ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4 (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v3.int4 (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#-"(a eql_v3.int4, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4. +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b eql_v3.int4 ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4, b eql_v3.int4) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4 (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a eql_v3.int4 --! @param b jsonb ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4 (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4. --! @param a jsonb --! @param b eql_v3.int4 ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4'; END; $$ diff --git a/tests/codegen/reference/int4/int4_operators.sql b/tests/codegen/reference/int4/int4_operators.sql index 24ff1607..def25237 100644 --- a/tests/codegen/reference/int4/int4_operators.sql +++ b/tests/codegen/reference/int4/int4_operators.sql @@ -1,270 +1,227 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_functions.sql --! @file encrypted_domain/int4/int4_operators.sql ---! @brief Storage-only domain of the int4 encrypted-domain family — operator declarations. +--! @brief Operators for eql_v3.int4. --- Placeholder: this domain's term set does not support =; the backing function always raises. CREATE OPERATOR = ( FUNCTION = eql_v3.eq, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support =; the backing function always raises. CREATE OPERATOR = ( FUNCTION = eql_v3.eq, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support =; the backing function always raises. CREATE OPERATOR = ( FUNCTION = eql_v3.eq, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <>; the backing function always raises. CREATE OPERATOR <> ( FUNCTION = eql_v3.neq, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <>; the backing function always raises. CREATE OPERATOR <> ( FUNCTION = eql_v3.neq, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <>; the backing function always raises. CREATE OPERATOR <> ( FUNCTION = eql_v3.neq, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( FUNCTION = eql_v3.lt, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( FUNCTION = eql_v3.lt, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <; the backing function always raises. CREATE OPERATOR < ( FUNCTION = eql_v3.lt, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( FUNCTION = eql_v3.lte, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( FUNCTION = eql_v3.lte, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <=; the backing function always raises. CREATE OPERATOR <= ( FUNCTION = eql_v3.lte, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( FUNCTION = eql_v3.gt, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( FUNCTION = eql_v3.gt, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support >; the backing function always raises. CREATE OPERATOR > ( FUNCTION = eql_v3.gt, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( FUNCTION = eql_v3.gte, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( FUNCTION = eql_v3.gte, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support >=; the backing function always raises. CREATE OPERATOR >= ( FUNCTION = eql_v3.gte, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = jsonb, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( FUNCTION = eql_v3."?", LEFTARG = eql_v3.int4, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( FUNCTION = eql_v3."?|", LEFTARG = eql_v3.int4, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( FUNCTION = eql_v3."?&", LEFTARG = eql_v3.int4, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( FUNCTION = eql_v3."@?", LEFTARG = eql_v3.int4, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( FUNCTION = eql_v3."@@", LEFTARG = eql_v3.int4, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( FUNCTION = eql_v3."#>", LEFTARG = eql_v3.int4, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( FUNCTION = eql_v3."#>>", LEFTARG = eql_v3.int4, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4, RIGHTARG = text ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( FUNCTION = eql_v3."#-", LEFTARG = eql_v3.int4, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4 ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = jsonb, RIGHTARG = eql_v3.int4 diff --git a/tests/codegen/reference/int4/int4_ord_aggregates.sql b/tests/codegen/reference/int4/int4_ord_aggregates.sql index 12f5efcc..7efdf177 100644 --- a/tests/codegen/reference/int4/int4_ord_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_aggregates.sql @@ -1,22 +1,17 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql --! @file encrypted_domain/int4/int4_ord_aggregates.sql ---! @brief Ordered domain of the int4 encrypted-domain family — MIN/MAX aggregates. +--! @brief Aggregates for eql_v3.int4_ord. ---! @brief State function for min aggregate on eql_v3.int4_ord. ---! @internal ---! ---! @param state eql_v3.int4_ord running extremum ---! @param value eql_v3.int4_ord next non-NULL value ---! @return eql_v3.int4_ord the minimum of state and value --- LANGUAGE plpgsql, not sql: aggregate state functions are not index --- expressions, so opacity to the planner is fine, and a multi-statement --- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would --- also work, but the procedural form mirrors the blocker convention.) +--! @brief State function for min on eql_v3.int4_ord. +--! @param state eql_v3.int4_ord +--! @param value eql_v3.int4_ord +--! @return eql_v3.int4_ord CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.int4_ord, value eql_v3.int4_ord) RETURNS eql_v3.int4_ord LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE @@ -30,15 +25,9 @@ BEGIN END; $$; ---! @brief Find the minimum encrypted value in a group of eql_v3.int4_ord values. ---! ---! Comparison routes through the domain's `<` operator, which uses the ORE block term — no decryption. ---! ---! @param input eql_v3.int4_ord encrypted values to aggregate ---! @return eql_v3.int4_ord minimum of the group, or NULL if all inputs are NULL --- combinefunc = sfunc: min/max are associative, so merging two partial --- extrema is the same comparison. PARALLEL SAFE enables partial and --- parallel aggregation on large GROUP BY workloads, with no decryption. +--! @brief min aggregate for eql_v3.int4_ord. +--! @param input eql_v3.int4_ord +--! @return eql_v3.int4_ord CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord) ( sfunc = eql_v3.min_sfunc, stype = eql_v3.int4_ord, @@ -46,16 +35,10 @@ CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord) ( parallel = safe ); ---! @brief State function for max aggregate on eql_v3.int4_ord. ---! @internal ---! ---! @param state eql_v3.int4_ord running extremum ---! @param value eql_v3.int4_ord next non-NULL value ---! @return eql_v3.int4_ord the maximum of state and value --- LANGUAGE plpgsql, not sql: aggregate state functions are not index --- expressions, so opacity to the planner is fine, and a multi-statement --- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would --- also work, but the procedural form mirrors the blocker convention.) +--! @brief State function for max on eql_v3.int4_ord. +--! @param state eql_v3.int4_ord +--! @param value eql_v3.int4_ord +--! @return eql_v3.int4_ord CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.int4_ord, value eql_v3.int4_ord) RETURNS eql_v3.int4_ord LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE @@ -69,15 +52,9 @@ BEGIN END; $$; ---! @brief Find the maximum encrypted value in a group of eql_v3.int4_ord values. ---! ---! Comparison routes through the domain's `>` operator, which uses the ORE block term — no decryption. ---! ---! @param input eql_v3.int4_ord encrypted values to aggregate ---! @return eql_v3.int4_ord maximum of the group, or NULL if all inputs are NULL --- combinefunc = sfunc: min/max are associative, so merging two partial --- extrema is the same comparison. PARALLEL SAFE enables partial and --- parallel aggregation on large GROUP BY workloads, with no decryption. +--! @brief max aggregate for eql_v3.int4_ord. +--! @param input eql_v3.int4_ord +--! @return eql_v3.int4_ord CREATE AGGREGATE eql_v3.max(eql_v3.int4_ord) ( sfunc = eql_v3.max_sfunc, stype = eql_v3.int4_ord, diff --git a/tests/codegen/reference/int4/int4_ord_functions.sql b/tests/codegen/reference/int4/int4_ord_functions.sql index a49bb1e9..b4dda68d 100644 --- a/tests/codegen/reference/int4/int4_ord_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_functions.sql @@ -1,4 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql @@ -7,9 +8,9 @@ -- REQUIRE: src/ore_block_u64_8_256/operators.sql --! @file encrypted_domain/int4/int4_ord_functions.sql ---! @brief Ordered domain of the int4 encrypted-domain family — comparison/path functions. +--! @brief Functions for eql_v3.int4_ord. ---! @brief Index extractor for the eql_v3.int4_ord variant. +--! @brief Index extractor for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @return eql_v2.ore_block_u64_8_256 CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord) @@ -17,7 +18,7 @@ RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; ---! @brief Equality wrapper for eql_v3.int4_ord. +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord --! @return boolean @@ -25,7 +26,7 @@ CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; ---! @brief Equality wrapper for eql_v3.int4_ord (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean @@ -33,7 +34,7 @@ CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Equality wrapper for eql_v3.int4_ord (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord --! @return boolean @@ -41,7 +42,7 @@ CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) = eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v3.int4_ord. +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord --! @return boolean @@ -49,7 +50,7 @@ CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v3.int4_ord (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean @@ -57,7 +58,7 @@ CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Inequality wrapper for eql_v3.int4_ord (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord --! @return boolean @@ -65,7 +66,7 @@ CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) <> eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v3.int4_ord. +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord --! @return boolean @@ -73,7 +74,7 @@ CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v3.int4_ord (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean @@ -81,7 +82,7 @@ CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Less-than wrapper for eql_v3.int4_ord (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord --! @return boolean @@ -89,7 +90,7 @@ CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) < eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v3.int4_ord. +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord --! @return boolean @@ -97,7 +98,7 @@ CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v3.int4_ord (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean @@ -105,7 +106,7 @@ CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Less-than-or-equal wrapper for eql_v3.int4_ord (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord --! @return boolean @@ -113,7 +114,7 @@ CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) <= eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v3.int4_ord. +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord --! @return boolean @@ -121,7 +122,7 @@ CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v3.int4_ord (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean @@ -129,7 +130,7 @@ CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Greater-than wrapper for eql_v3.int4_ord (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord --! @return boolean @@ -137,7 +138,7 @@ CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) > eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord. +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord --! @return boolean @@ -145,7 +146,7 @@ CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb --! @return boolean @@ -153,7 +154,7 @@ CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b::eql_v3.int4_ord) $$; ---! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord --! @return boolean @@ -161,235 +162,235 @@ CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4_ord) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord) >= eql_v3.ord_term(b) $$; ---! @brief Blocker for @> on eql_v3.int4_ord. +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4_ord (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4_ord (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_ord. +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_ord (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_ord (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4_ord) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_ord (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param selector text ---! @return eql_v3.int4_ord (never returns; always raises) +--! @return eql_v3.int4_ord CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord, selector text) RETURNS eql_v3.int4_ord IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_ord (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param selector integer ---! @return eql_v3.int4_ord (never returns; always raises) +--! @return eql_v3.int4_ord CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord, selector integer) RETURNS eql_v3.int4_ord IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_ord (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a jsonb --! @param selector eql_v3.int4_ord ---! @return eql_v3.int4_ord (never returns; always raises) +--! @return eql_v3.int4_ord CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4_ord) RETURNS eql_v3.int4_ord IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_ord (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param selector text ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord, selector text) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_ord (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param selector integer ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_ord (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a jsonb --! @param selector eql_v3.int4_ord ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4_ord) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v3.int4_ord (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?"(a eql_v3.int4_ord, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v3.int4_ord (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?|"(a eql_v3.int4_ord, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '?|'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v3.int4_ord (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?&"(a eql_v3.int4_ord, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '?&'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v3.int4_ord (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@?"(a eql_v3.int4_ord, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v3.int4_ord (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@@"(a eql_v3.int4_ord, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord', '@@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v3.int4_ord (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#>"(a eql_v3.int4_ord, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v3.int4_ord (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text[] ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4_ord, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_ord (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_ord (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b integer ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_ord (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v3.int4_ord (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#-"(a eql_v3.int4_ord, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_ord. +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b eql_v3.int4_ord ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord, b eql_v3.int4_ord) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_ord (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a eql_v3.int4_ord --! @param b jsonb ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_ord (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord. --! @param a jsonb --! @param b eql_v3.int4_ord ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4_ord) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord'; END; $$ diff --git a/tests/codegen/reference/int4/int4_ord_operators.sql b/tests/codegen/reference/int4/int4_ord_operators.sql index f52ecebb..697f162e 100644 --- a/tests/codegen/reference/int4/int4_ord_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_operators.sql @@ -1,10 +1,11 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql --! @file encrypted_domain/int4/int4_ord_operators.sql ---! @brief Ordered domain of the int4 encrypted-domain family — operator declarations. +--! @brief Operators for eql_v3.int4_ord. CREATE OPERATOR = ( FUNCTION = eql_v3.eq, @@ -114,157 +115,131 @@ CREATE OPERATOR >= ( COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4_ord, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4_ord, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord ); --- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( FUNCTION = eql_v3."?", LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( FUNCTION = eql_v3."?|", LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( FUNCTION = eql_v3."?&", LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( FUNCTION = eql_v3."@?", LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( FUNCTION = eql_v3."@@", LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( FUNCTION = eql_v3."#>", LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( FUNCTION = eql_v3."#>>", LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_ord, RIGHTARG = text ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_ord, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( FUNCTION = eql_v3."#-", LEFTARG = eql_v3.int4_ord, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4_ord, RIGHTARG = eql_v3.int4_ord ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4_ord, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord diff --git a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql index 26396459..5b160ed7 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql @@ -1,22 +1,17 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_operators.sql --! @file encrypted_domain/int4/int4_ord_ore_aggregates.sql ---! @brief Ordered domain of the int4 encrypted-domain family — MIN/MAX aggregates. +--! @brief Aggregates for eql_v3.int4_ord_ore. ---! @brief State function for min aggregate on eql_v3.int4_ord_ore. ---! @internal ---! ---! @param state eql_v3.int4_ord_ore running extremum ---! @param value eql_v3.int4_ord_ore next non-NULL value ---! @return eql_v3.int4_ord_ore the minimum of state and value --- LANGUAGE plpgsql, not sql: aggregate state functions are not index --- expressions, so opacity to the planner is fine, and a multi-statement --- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would --- also work, but the procedural form mirrors the blocker convention.) +--! @brief State function for min on eql_v3.int4_ord_ore. +--! @param state eql_v3.int4_ord_ore +--! @param value eql_v3.int4_ord_ore +--! @return eql_v3.int4_ord_ore CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.int4_ord_ore, value eql_v3.int4_ord_ore) RETURNS eql_v3.int4_ord_ore LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE @@ -30,15 +25,9 @@ BEGIN END; $$; ---! @brief Find the minimum encrypted value in a group of eql_v3.int4_ord_ore values. ---! ---! Comparison routes through the domain's `<` operator, which uses the ORE block term — no decryption. ---! ---! @param input eql_v3.int4_ord_ore encrypted values to aggregate ---! @return eql_v3.int4_ord_ore minimum of the group, or NULL if all inputs are NULL --- combinefunc = sfunc: min/max are associative, so merging two partial --- extrema is the same comparison. PARALLEL SAFE enables partial and --- parallel aggregation on large GROUP BY workloads, with no decryption. +--! @brief min aggregate for eql_v3.int4_ord_ore. +--! @param input eql_v3.int4_ord_ore +--! @return eql_v3.int4_ord_ore CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord_ore) ( sfunc = eql_v3.min_sfunc, stype = eql_v3.int4_ord_ore, @@ -46,16 +35,10 @@ CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord_ore) ( parallel = safe ); ---! @brief State function for max aggregate on eql_v3.int4_ord_ore. ---! @internal ---! ---! @param state eql_v3.int4_ord_ore running extremum ---! @param value eql_v3.int4_ord_ore next non-NULL value ---! @return eql_v3.int4_ord_ore the maximum of state and value --- LANGUAGE plpgsql, not sql: aggregate state functions are not index --- expressions, so opacity to the planner is fine, and a multi-statement --- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would --- also work, but the procedural form mirrors the blocker convention.) +--! @brief State function for max on eql_v3.int4_ord_ore. +--! @param state eql_v3.int4_ord_ore +--! @param value eql_v3.int4_ord_ore +--! @return eql_v3.int4_ord_ore CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.int4_ord_ore, value eql_v3.int4_ord_ore) RETURNS eql_v3.int4_ord_ore LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE @@ -69,15 +52,9 @@ BEGIN END; $$; ---! @brief Find the maximum encrypted value in a group of eql_v3.int4_ord_ore values. ---! ---! Comparison routes through the domain's `>` operator, which uses the ORE block term — no decryption. ---! ---! @param input eql_v3.int4_ord_ore encrypted values to aggregate ---! @return eql_v3.int4_ord_ore maximum of the group, or NULL if all inputs are NULL --- combinefunc = sfunc: min/max are associative, so merging two partial --- extrema is the same comparison. PARALLEL SAFE enables partial and --- parallel aggregation on large GROUP BY workloads, with no decryption. +--! @brief max aggregate for eql_v3.int4_ord_ore. +--! @param input eql_v3.int4_ord_ore +--! @return eql_v3.int4_ord_ore CREATE AGGREGATE eql_v3.max(eql_v3.int4_ord_ore) ( sfunc = eql_v3.max_sfunc, stype = eql_v3.int4_ord_ore, diff --git a/tests/codegen/reference/int4/int4_ord_ore_functions.sql b/tests/codegen/reference/int4/int4_ord_ore_functions.sql index 005bf672..327bc18c 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_functions.sql @@ -1,4 +1,5 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql @@ -7,9 +8,9 @@ -- REQUIRE: src/ore_block_u64_8_256/operators.sql --! @file encrypted_domain/int4/int4_ord_ore_functions.sql ---! @brief Ordered domain of the int4 encrypted-domain family — comparison/path functions. +--! @brief Functions for eql_v3.int4_ord_ore. ---! @brief Index extractor for the eql_v3.int4_ord_ore variant. +--! @brief Index extractor for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @return eql_v2.ore_block_u64_8_256 CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord_ore) @@ -17,7 +18,7 @@ RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; ---! @brief Equality wrapper for eql_v3.int4_ord_ore. +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -25,7 +26,7 @@ CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; ---! @brief Equality wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean @@ -33,7 +34,7 @@ CREATE FUNCTION eql_v3.eq(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Equality wrapper for eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -41,7 +42,7 @@ CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) = eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v3.int4_ord_ore. +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -49,7 +50,7 @@ CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; ---! @brief Inequality wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean @@ -57,7 +58,7 @@ CREATE FUNCTION eql_v3.neq(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Inequality wrapper for eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -65,7 +66,7 @@ CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) <> eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v3.int4_ord_ore. +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -73,7 +74,7 @@ CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; ---! @brief Less-than wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean @@ -81,7 +82,7 @@ CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Less-than wrapper for eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -89,7 +90,7 @@ CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) < eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v3.int4_ord_ore. +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -97,7 +98,7 @@ CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; ---! @brief Less-than-or-equal wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean @@ -105,7 +106,7 @@ CREATE FUNCTION eql_v3.lte(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Less-than-or-equal wrapper for eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -113,7 +114,7 @@ CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) <= eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v3.int4_ord_ore. +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -121,7 +122,7 @@ CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; ---! @brief Greater-than wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean @@ -129,7 +130,7 @@ CREATE FUNCTION eql_v3.gt(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Greater-than wrapper for eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -137,7 +138,7 @@ CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) > eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord_ore. +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -145,7 +146,7 @@ CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; ---! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb --! @return boolean @@ -153,7 +154,7 @@ CREATE FUNCTION eql_v3.gte(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b::eql_v3.int4_ord_ore) $$; ---! @brief Greater-than-or-equal wrapper for eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore --! @return boolean @@ -161,235 +162,235 @@ CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v3.ord_term(a::eql_v3.int4_ord_ore) >= eql_v3.ord_term(b) $$; ---! @brief Blocker for @> on eql_v3.int4_ord_ore. +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @> on eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@>'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_ord_ore. +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a eql_v3.int4_ord_ore, b jsonb) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for <@ on eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.int4_ord_ore) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '<@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_ord_ore (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param selector text ---! @return eql_v3.int4_ord_ore (never returns; always raises) +--! @return eql_v3.int4_ord_ore CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord_ore, selector text) RETURNS eql_v3.int4_ord_ore IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_ord_ore (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param selector integer ---! @return eql_v3.int4_ord_ore (never returns; always raises) +--! @return eql_v3.int4_ord_ore CREATE FUNCTION eql_v3."->"(a eql_v3.int4_ord_ore, selector integer) RETURNS eql_v3.int4_ord_ore IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for -> on eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a jsonb --! @param selector eql_v3.int4_ord_ore ---! @return eql_v3.int4_ord_ore (never returns; always raises) +--! @return eql_v3.int4_ord_ore CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4_ord_ore) RETURNS eql_v3.int4_ord_ore IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_ord_ore (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param selector text ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord_ore, selector text) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_ord_ore (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param selector integer ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a eql_v3.int4_ord_ore, selector integer) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ->> on eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a jsonb --! @param selector eql_v3.int4_ord_ore ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.int4_ord_ore) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ? on eql_v3.int4_ord_ore (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?"(a eql_v3.int4_ord_ore, b text) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?| on eql_v3.int4_ord_ore (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?|"(a eql_v3.int4_ord_ore, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '?|'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for ?& on eql_v3.int4_ord_ore (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text[] ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."?&"(a eql_v3.int4_ord_ore, b text[]) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '?&'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @? on eql_v3.int4_ord_ore (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@?"(a eql_v3.int4_ord_ore, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@?'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for @@ on eql_v3.int4_ord_ore (domain, jsonpath). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonpath ---! @return boolean (never returns; always raises) +--! @return boolean CREATE FUNCTION eql_v3."@@"(a eql_v3.int4_ord_ore, b jsonpath) RETURNS boolean IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RETURN eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4_ord_ore', '@@'); END; $$ +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #> on eql_v3.int4_ord_ore (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#>"(a eql_v3.int4_ord_ore, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #>> on eql_v3.int4_ord_ore (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text[] ---! @return text (never returns; always raises) +--! @return text CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4_ord_ore, b text[]) RETURNS text IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_ord_ore (domain, text). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord_ore, b text) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_ord_ore (domain, integer). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b integer ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord_ore, b integer) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for - on eql_v3.int4_ord_ore (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."-"(a eql_v3.int4_ord_ore, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for #- on eql_v3.int4_ord_ore (domain, text[]). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b text[] ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."#-"(a eql_v3.int4_ord_ore, b text[]) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_ord_ore. +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b eql_v3.int4_ord_ore ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord_ore, b eql_v3.int4_ord_ore) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_ord_ore (domain, jsonb). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore --! @param b jsonb ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a eql_v3.int4_ord_ore, b jsonb) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord_ore'; END; $$ LANGUAGE plpgsql; ---! @brief Blocker for || on eql_v3.int4_ord_ore (jsonb, domain). +--! @brief Unsupported operator blocker for eql_v3.int4_ord_ore. --! @param a jsonb --! @param b eql_v3.int4_ord_ore ---! @return jsonb (never returns; always raises) +--! @return jsonb CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4_ord_ore) RETURNS jsonb IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.int4_ord_ore'; END; $$ diff --git a/tests/codegen/reference/int4/int4_ord_ore_operators.sql b/tests/codegen/reference/int4/int4_ord_ore_operators.sql index e6dc27e9..47549cdb 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_operators.sql @@ -1,10 +1,11 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql -- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql --! @file encrypted_domain/int4/int4_ord_ore_operators.sql ---! @brief Ordered domain of the int4 encrypted-domain family — operator declarations. +--! @brief Operators for eql_v3.int4_ord_ore. CREATE OPERATOR = ( FUNCTION = eql_v3.eq, @@ -114,157 +115,131 @@ CREATE OPERATOR >= ( COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support @>; the backing function always raises. CREATE OPERATOR @> ( FUNCTION = eql_v3.contains, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support <@; the backing function always raises. CREATE OPERATOR <@ ( FUNCTION = eql_v3.contained_by, LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->; the backing function always raises. CREATE OPERATOR -> ( FUNCTION = eql_v3."->", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support ->>; the backing function always raises. CREATE OPERATOR ->> ( FUNCTION = eql_v3."->>", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore ); --- Placeholder: this domain's term set does not support ?; the backing function always raises. CREATE OPERATOR ? ( FUNCTION = eql_v3."?", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); --- Placeholder: this domain's term set does not support ?|; the backing function always raises. CREATE OPERATOR ?| ( FUNCTION = eql_v3."?|", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ?&; the backing function always raises. CREATE OPERATOR ?& ( FUNCTION = eql_v3."?&", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support @?; the backing function always raises. CREATE OPERATOR @? ( FUNCTION = eql_v3."@?", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support @@; the backing function always raises. CREATE OPERATOR @@ ( FUNCTION = eql_v3."@@", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonpath ); --- Placeholder: this domain's term set does not support #>; the backing function always raises. CREATE OPERATOR #> ( FUNCTION = eql_v3."#>", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #>>; the backing function always raises. CREATE OPERATOR #>> ( FUNCTION = eql_v3."#>>", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = integer ); --- Placeholder: this domain's term set does not support -; the backing function always raises. CREATE OPERATOR - ( FUNCTION = eql_v3."-", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support #-; the backing function always raises. CREATE OPERATOR #- ( FUNCTION = eql_v3."#-", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = text[] ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = eql_v3.int4_ord_ore ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = eql_v3.int4_ord_ore, RIGHTARG = jsonb ); --- Placeholder: this domain's term set does not support ||; the backing function always raises. CREATE OPERATOR || ( FUNCTION = eql_v3."||", LEFTARG = jsonb, RIGHTARG = eql_v3.int4_ord_ore diff --git a/tests/codegen/reference/int4/int4_types.sql b/tests/codegen/reference/int4/int4_types.sql index 0b33e740..bb708a61 100644 --- a/tests/codegen/reference/int4/int4_types.sql +++ b/tests/codegen/reference/int4/int4_types.sql @@ -1,12 +1,13 @@ -- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql --! @file encrypted_domain/int4/int4_types.sql ---! @brief Encrypted-domain type family for int4. +--! @brief Encrypted-domain types for int4. DO $$ BEGIN - --! @brief Storage-only encrypted int4 domain. + --! @brief Encrypted domain eql_v3.int4. IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'int4' AND typnamespace = 'eql_v3'::regnamespace @@ -21,7 +22,7 @@ BEGIN ); END IF; - --! @brief Equality-only encrypted int4 domain. + --! @brief Encrypted domain eql_v3.int4_eq. IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'int4_eq' AND typnamespace = 'eql_v3'::regnamespace @@ -37,7 +38,7 @@ BEGIN ); END IF; - --! @brief Ordered encrypted int4 domain. Scheme-explicit twin pinning the ore scheme; prefer the converged int4_ord name. + --! @brief Encrypted domain eql_v3.int4_ord_ore. IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'int4_ord_ore' AND typnamespace = 'eql_v3'::regnamespace @@ -53,7 +54,7 @@ BEGIN ); END IF; - --! @brief Ordered encrypted int4 domain. Recommended converged name for this role. + --! @brief Encrypted domain eql_v3.int4_ord. IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'int4_ord' AND typnamespace = 'eql_v3'::regnamespace diff --git a/tests/sqlx/src/fixtures/int2_values.rs b/tests/sqlx/src/fixtures/int2_values.rs index 74aff1c4..4ff2db38 100644 --- a/tests/sqlx/src/fixtures/int2_values.rs +++ b/tests/sqlx/src/fixtures/int2_values.rs @@ -1,7 +1,4 @@ -// AUTO-GENERATED — DO NOT EDIT. -// Regenerated by `mise run build` (or `mise run codegen:domain `). -// Source of truth: tasks/codegen/types/.toml `[fixture] values`. -// This file IS committed and verified in CI (git diff --exit-code). +// AUTOMATICALLY GENERATED FILE. //! Fixture plaintext values for the int2 encrypted-domain family. //! //! Generated from tasks/codegen/types/int2.toml `[fixture] values` — diff --git a/tests/sqlx/src/fixtures/int4_values.rs b/tests/sqlx/src/fixtures/int4_values.rs index 92f6491d..c9c48b30 100644 --- a/tests/sqlx/src/fixtures/int4_values.rs +++ b/tests/sqlx/src/fixtures/int4_values.rs @@ -1,7 +1,4 @@ -// AUTO-GENERATED — DO NOT EDIT. -// Regenerated by `mise run build` (or `mise run codegen:domain `). -// Source of truth: tasks/codegen/types/.toml `[fixture] values`. -// This file IS committed and verified in CI (git diff --exit-code). +// AUTOMATICALLY GENERATED FILE. //! Fixture plaintext values for the int4 encrypted-domain family. //! //! Generated from tasks/codegen/types/int4.toml `[fixture] values` — diff --git a/tests/sqlx/tests/encrypted_domain/family/support.rs b/tests/sqlx/tests/encrypted_domain/family/support.rs index 08b19c04..5f1bdb6d 100644 --- a/tests/sqlx/tests/encrypted_domain/family/support.rs +++ b/tests/sqlx/tests/encrypted_domain/family/support.rs @@ -114,10 +114,11 @@ async fn placeholder_payload_satisfies_every_variant_check(pool: PgPool) -> Resu // successfully to every domain in the family. If a variant CHECK // tightens, this test fails and PLACEHOLDER_PAYLOAD needs updating. // - // Iterates `Variant::ALL` against `::PG_TYPE` - // rather than hardcoding domain names — when `int8` (or any future - // scalar) lands, this test picks it up automatically by extending - // the type list below. + // Iterates `Variant::ALL` for `i32`, deriving each domain name from + // `ScalarDomainSpec::new::(variant).sql_domain` rather than + // hardcoding the names. Currently `i32`-only; when `int8` (or any + // future scalar) lands, wrap this in a per-type loop so the + // PLACEHOLDER_PAYLOAD cast is exercised against every scalar. for variant in Variant::ALL { let spec = ScalarDomainSpec::new::(*variant); let sql = format!("SELECT $1::jsonb::{}", spec.sql_domain); @@ -295,12 +296,13 @@ async fn neq_propagates_null_under_three_valued_logic(pool: PgPool) -> Result<() } #[sqlx::test] -async fn no_cross_variant_equality_operator_is_declared(pool: PgPool) -> Result<()> { - // The family deliberately does NOT define operators that mix two - // different capability variants — `eql_v3.int4_eq = eql_v3.int4_ord` +async fn no_cross_variant_operator_is_declared(pool: PgPool) -> Result<()> { + // The family deliberately does NOT define ANY operator that mixes two + // different capability variants — e.g. `eql_v3.int4_eq = eql_v3.int4_ord` // would resolve against jsonb (the ultimate base type) and silently - // bypass the per-variant blockers. If someone accidentally adds such - // an operator, this test fails. + // bypass the per-variant blockers. The query below has no `oprname` + // filter, so it catches a cross-variant operator of any kind, not just + // `=`. If someone accidentally adds such an operator, this test fails. // // The check is structural (`pg_operator`) rather than dynamic // ("invoke and see it raise") so a future PG version with stricter From e186464a48b7940f5b24a1edd5be68ab6cc1de28 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 16:37:05 +1000 Subject: [PATCH 32/93] fix(codegen): align Python generator marker with Rust + fmt/clippy crates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Rust port (2737d5f) re-headed every committed/golden artifact to the `AUTOMATICALLY GENERATED FILE.` marker and updated consts.rs + CLAUDE.md, but left the Python generator on its original `AUTO-GENERATED — DO NOT EDIT.` marker. CI still runs the Python generator (codegen:domain:all, build), so its ownership check mismatched the Rust-marked files and refused to overwrite them as hand-written (OwnershipError), failing the "Encrypted-domain codegen" job. Align tasks/codegen/templates.py (and its marker tests) to the canonical single-line Rust marker — the one docs:validate greps on and consts.rs asserts. Also fix the "Rust workspace crates" job: cargo fmt the eql-codegen sources (never formatted) and resolve two latent clippy manual_contains lints in generate.rs that fmt's fail-fast had masked. Verified locally: codegen:domain:all (+ git diff), codegen:parity, and test:crates (fmt --check + clippy -D warnings + cargo test) all pass. --- crates/eql-codegen/src/consts.rs | 5 +- crates/eql-codegen/src/context.rs | 52 +++-- crates/eql-codegen/src/generate.rs | 154 +++++++++++---- crates/eql-codegen/src/operator_surface.rs | 209 ++++++++++++++++++--- crates/eql-codegen/src/templates.rs | 5 +- crates/eql-codegen/src/writer.rs | 31 ++- crates/eql-codegen/tests/parity.rs | 17 +- tasks/codegen/templates.py | 30 ++- tasks/codegen/test_templates.py | 21 ++- 9 files changed, 412 insertions(+), 112 deletions(-) diff --git a/crates/eql-codegen/src/consts.rs b/crates/eql-codegen/src/consts.rs index ae318ac8..c1b9e75c 100644 --- a/crates/eql-codegen/src/consts.rs +++ b/crates/eql-codegen/src/consts.rs @@ -43,7 +43,10 @@ mod tests { #[test] fn rust_marker_is_a_rust_comment() { - assert_eq!(AUTO_GENERATED_HEADER_RS, "// AUTOMATICALLY GENERATED FILE.\n"); + assert_eq!( + AUTO_GENERATED_HEADER_RS, + "// AUTOMATICALLY GENERATED FILE.\n" + ); for line in AUTO_GENERATED_HEADER_RS.lines() { assert!( !line.starts_with("--"), diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs index 6a104831..83eb52e9 100644 --- a/crates/eql-codegen/src/context.rs +++ b/crates/eql-codegen/src/context.rs @@ -23,12 +23,21 @@ pub fn environment() -> minijinja::Environment<'static> { env.set_keep_trailing_newline(true); env.add_template("types.sql", include_str!("../templates/types.sql.j2")) .expect("types.sql template"); - env.add_template("functions.sql", include_str!("../templates/functions.sql.j2")) - .expect("functions.sql template"); - env.add_template("operators.sql", include_str!("../templates/operators.sql.j2")) - .expect("operators.sql template"); - env.add_template("aggregates.sql", include_str!("../templates/aggregates.sql.j2")) - .expect("aggregates.sql template"); + env.add_template( + "functions.sql", + include_str!("../templates/functions.sql.j2"), + ) + .expect("functions.sql template"); + env.add_template( + "operators.sql", + include_str!("../templates/operators.sql.j2"), + ) + .expect("operators.sql template"); + env.add_template( + "aggregates.sql", + include_str!("../templates/aggregates.sql.j2"), + ) + .expect("aggregates.sql template"); env.add_global("domain_schema", DOMAIN_SCHEMA); env.add_global("core_schema", CORE_SCHEMA); env @@ -87,8 +96,8 @@ pub enum FnEntry { ctor: String, // e.g. hmac_256 (called as {{ core_schema }}.{{ ctor }}) }, Wrapper { - op: String, // SQL operator used in the body, e.g. = - function_name: String, // e.g. eq + op: String, // SQL operator used in the body, e.g. = + function_name: String, // e.g. eq args: [SqlParam; 2], call_a: String, // e.g. eql_v3.eq_term(a) (embeds extract_arg cast logic) call_b: String, // e.g. eql_v3.eq_term(b::eql_v3.int4_eq) @@ -128,8 +137,14 @@ pub fn wrapper_entry(dom: &str, op: &str, arg_a: &str, arg_b: &str, extractor: & op: op.to_string(), function_name: backing_function(op).to_string(), args: [ - SqlParam { name: "a", ty: arg_a.to_string() }, - SqlParam { name: "b", ty: arg_b.to_string() }, + SqlParam { + name: "a", + ty: arg_a.to_string(), + }, + SqlParam { + name: "b", + ty: arg_b.to_string(), + }, ], call_a: extract_arg(arg_a, extractor, dom, "a"), call_b: extract_arg(arg_b, extractor, dom, "b"), @@ -248,8 +263,14 @@ pub struct AggregateOp { /// The two aggregate ops in (min, max) order. Port of `AGGREGATE_OPS`. pub const AGGREGATE_OPS: &[AggregateOp] = &[ - AggregateOp { name: "min", comparator: "<" }, - AggregateOp { name: "max", comparator: ">" }, + AggregateOp { + name: "min", + comparator: "<", + }, + AggregateOp { + name: "max", + comparator: ">", + }, ]; /// True if the domain carries a comparator term (supports `<`). @@ -299,7 +320,12 @@ mod tests { #[test] fn environment_has_four_templates() { let env = environment(); - for name in ["types.sql", "functions.sql", "operators.sql", "aggregates.sql"] { + for name in [ + "types.sql", + "functions.sql", + "operators.sql", + "aggregates.sql", + ] { assert!(env.get_template(name).is_ok(), "missing template {name}"); } } diff --git a/crates/eql-codegen/src/generate.rs b/crates/eql-codegen/src/generate.rs index a7e1340c..6cbd259a 100644 --- a/crates/eql-codegen/src/generate.rs +++ b/crates/eql-codegen/src/generate.rs @@ -4,10 +4,10 @@ use std::path::{Path, PathBuf}; use eql_scalars::{DomainSpec, ScalarSpec, Term}; +use crate::context::{domain_name, is_ord_capable}; use crate::operator_surface::{ backing_function, BLOCKER_ONLY_OPERATORS, PATH_OPERATORS, SYMMETRIC_OPERATORS, }; -use crate::context::{domain_name, is_ord_capable}; /// The full domain name (token + suffix). suffix "" => bare token. fn full_name(token: &str, suffix: &str) -> String { @@ -131,7 +131,7 @@ pub fn render_functions_file(token: &str, domain: &DomainSpec) -> String { let dom = domain_name(&name); let domain_lit = sql_str(&dom); let supported = Term::operators_for_terms(domain.terms); - let is_supported = |op: &str| supported.iter().any(|s| *s == op); + let is_supported = |op: &str| supported.contains(&op); let mut entries = Vec::new(); for term in extractor_terms(domain.terms) { @@ -147,18 +147,34 @@ pub fn render_functions_file(token: &str, domain: &DomainSpec) -> String { } } let args = [ - SqlParam { name: "a", ty: arg_a }, - SqlParam { name: "b", ty: arg_b }, + SqlParam { + name: "a", + ty: arg_a, + }, + SqlParam { + name: "b", + ty: arg_b, + }, ]; entries.push(blocker_entry(op, args, "boolean")); } } for &op in PATH_OPERATORS { for (arg_a, arg_b) in path_shapes(&dom) { - let returns = if op == "->>" { "text".to_string() } else { dom.clone() }; + let returns = if op == "->>" { + "text".to_string() + } else { + dom.clone() + }; let args = [ - SqlParam { name: "a", ty: arg_a }, - SqlParam { name: "selector", ty: arg_b }, + SqlParam { + name: "a", + ty: arg_a, + }, + SqlParam { + name: "selector", + ty: arg_b, + }, ]; entries.push(blocker_entry(op, args, &returns)); } @@ -166,8 +182,14 @@ pub fn render_functions_file(token: &str, domain: &DomainSpec) -> String { for &op in BLOCKER_ONLY_OPERATORS { for (arg_a, arg_b, returns) in blocker_only_shapes(&dom, op) { let args = [ - SqlParam { name: "a", ty: arg_a }, - SqlParam { name: "b", ty: arg_b }, + SqlParam { + name: "a", + ty: arg_a, + }, + SqlParam { + name: "b", + ty: arg_b, + }, ]; entries.push(blocker_entry(op, args, &returns)); } @@ -194,7 +216,7 @@ pub fn render_operators_file(token: &str, domain: &DomainSpec) -> String { let name = full_name(token, domain.suffix); let dom = domain_name(&name); let supported = Term::operators_for_terms(domain.terms); - let is_supported = |op: &str| supported.iter().any(|s| *s == op); + let is_supported = |op: &str| supported.contains(&op); let mut operators = Vec::new(); for &op in SYMMETRIC_OPERATORS { @@ -252,7 +274,7 @@ pub fn render_aggregates_file(token: &str, domain: &DomainSpec) -> Option Result, pub fn generate_all(out_root: &Path) -> Result { for spec in eql_scalars::CATALOG { let token = spec.token; - let out_dir = out_root - .join("src") - .join("encrypted_domain") - .join(token); + let out_dir = out_root.join("src").join("encrypted_domain").join(token); let mut written = generate_type(spec, &out_dir)?; let rs_path = fixture_values_rs_path(out_root, token); @@ -347,11 +366,17 @@ mod tests { use eql_scalars::CATALOG; fn spec(token: &str) -> &'static ScalarSpec { - CATALOG.iter().find(|s| s.token == token).expect("catalog token") + CATALOG + .iter() + .find(|s| s.token == token) + .expect("catalog token") } fn domain<'a>(spec: &'a ScalarSpec, suffix: &str) -> &'a DomainSpec { - spec.domains.iter().find(|d| d.suffix == suffix).expect("domain suffix") + spec.domains + .iter() + .find(|d| d.suffix == suffix) + .expect("domain suffix") } use crate::templates::render_fixture_values_rs; @@ -441,7 +466,11 @@ mod tests { let path = root.join(format!("tests/codegen/reference/int4/{full}_operators.sql")); let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); let actual = render_operators_file("int4", d); - assert_eq!(normalize_sql(&actual), normalize_sql(&expected), "{full}_operators.sql"); + assert_eq!( + normalize_sql(&actual), + normalize_sql(&expected), + "{full}_operators.sql" + ); } } @@ -453,9 +482,15 @@ mod tests { for d in s.domains { if let Some(actual) = render_aggregates_file("int4", d) { let full = full_name("int4", d.suffix); - let path = root.join(format!("tests/codegen/reference/int4/{full}_aggregates.sql")); + let path = root.join(format!( + "tests/codegen/reference/int4/{full}_aggregates.sql" + )); let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); - assert_eq!(normalize_sql(&actual), normalize_sql(&expected), "{full}_aggregates.sql"); + assert_eq!( + normalize_sql(&actual), + normalize_sql(&expected), + "{full}_aggregates.sql" + ); } } } @@ -482,7 +517,10 @@ mod tests { ); checked += 1; } - assert!(checked >= 11, "expected >=11 reference SQL files, checked {checked}"); + assert!( + checked >= 11, + "expected >=11 reference SQL files, checked {checked}" + ); } #[test] @@ -491,7 +529,10 @@ mod tests { let path = root.join("tests/codegen/reference/int4/int4_values.rs"); let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); let actual = render_fixture_values_rs(spec("int4")); - assert_eq!(actual, expected, "int4_values.rs: generator diverged from golden reference"); + assert_eq!( + actual, expected, + "int4_values.rs: generator diverged from golden reference" + ); } #[test] @@ -515,7 +556,9 @@ mod tests { assert!(names.contains(&"int4_ord_aggregates.sql".to_string())); assert_eq!(written.len(), 11); for p in &written { - assert!(fs::read_to_string(p).unwrap().starts_with(crate::consts::AUTO_GENERATED_HEADER)); + assert!(fs::read_to_string(p) + .unwrap() + .starts_with(crate::consts::AUTO_GENERATED_HEADER)); } } @@ -524,7 +567,10 @@ mod tests { let sql = render_types_file(spec("int4")); assert!(sql.contains("-- REQUIRE: src/schema-v3.sql")); for dom in ["int4", "int4_eq", "int4_ord_ore", "int4_ord"] { - assert!(sql.contains(&format!("CREATE DOMAIN eql_v3.{dom} AS jsonb")), "missing {dom}"); + assert!( + sql.contains(&format!("CREATE DOMAIN eql_v3.{dom} AS jsonb")), + "missing {dom}" + ); } } @@ -535,7 +581,11 @@ mod tests { assert_eq!(sql.matches("CREATE FUNCTION").count(), 44); assert!(!sql.contains("SET search_path")); assert_eq!(sql.matches("LANGUAGE plpgsql").count(), 44); - assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 0); + assert_eq!( + sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") + .count(), + 0 + ); } #[test] @@ -545,7 +595,11 @@ mod tests { assert_eq!(sql.matches("CREATE FUNCTION").count(), 45); assert!(sql.contains("CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq)")); assert!(sql.contains("RETURNS eql_v2.hmac_256")); - assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 7); + assert_eq!( + sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") + .count(), + 7 + ); assert_eq!(sql.matches("LANGUAGE plpgsql").count(), 38); assert!(!sql.contains("SET search_path")); } @@ -557,7 +611,11 @@ mod tests { assert_eq!(sql.matches("CREATE FUNCTION").count(), 45); assert!(sql.contains("CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord)")); assert!(sql.contains("RETURNS eql_v2.ore_block_u64_8_256")); - assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 19); + assert_eq!( + sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") + .count(), + 19 + ); assert_eq!(sql.matches("LANGUAGE plpgsql").count(), 26); } @@ -596,8 +654,14 @@ mod tests { let ord = domain(s, "_ord"); let ore = domain(s, "_ord_ore"); let norm = |sql: String| sql.replace("int4_ord_ore", "T").replace("int4_ord", "T"); - assert_eq!(norm(render_functions_file(s.token, ord)), norm(render_functions_file(s.token, ore))); - assert_eq!(norm(render_operators_file(s.token, ord)), norm(render_operators_file(s.token, ore))); + assert_eq!( + norm(render_functions_file(s.token, ord)), + norm(render_functions_file(s.token, ore)) + ); + assert_eq!( + norm(render_operators_file(s.token, ord)), + norm(render_operators_file(s.token, ore)) + ); assert_eq!( norm(render_aggregates_file(s.token, ord).unwrap()), norm(render_aggregates_file(s.token, ore).unwrap()) @@ -643,8 +707,16 @@ mod tests { let s = spec("int4"); let sql = render_aggregates_file("int4", domain(s, "_ord")).unwrap(); assert_eq!(sql.matches("CREATE FUNCTION").count(), 2); - assert_eq!(sql.matches("LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE").count(), 2); - assert_eq!(sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE").count(), 0); + assert_eq!( + sql.matches("LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE") + .count(), + 2 + ); + assert_eq!( + sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") + .count(), + 0 + ); } #[test] @@ -683,8 +755,14 @@ mod tests { let entry = blocker_entry( "<", [ - SqlParam { name: "a", ty: dom.into() }, - SqlParam { name: "b", ty: dom.into() }, + SqlParam { + name: "a", + ty: dom.into(), + }, + SqlParam { + name: "b", + ty: dom.into(), + }, ], "boolean", ); @@ -701,9 +779,15 @@ mod tests { fn domain_block_escapes_quote_bearing_name() { use crate::context::domain_block; use eql_scalars::DomainSpec; - let block = domain_block("int4", &DomainSpec { suffix: "_q", terms: &[] }); + let block = domain_block( + "int4", + &DomainSpec { + suffix: "_q", + terms: &[], + }, + ); assert_eq!(block.typname, "int4_q"); // no quote present → unchanged - // keys are sql_str-escaped key tokens; none should carry a bare unescaped quote. + // keys are sql_str-escaped key tokens; none should carry a bare unescaped quote. assert!(block.keys.iter().all(|k| !k.contains("o'"))); } } diff --git a/crates/eql-codegen/src/operator_surface.rs b/crates/eql-codegen/src/operator_surface.rs index eace7e06..772ac358 100644 --- a/crates/eql-codegen/src/operator_surface.rs +++ b/crates/eql-codegen/src/operator_surface.rs @@ -41,26 +41,186 @@ pub fn backing_function(symbol: &str) -> &'static str { /// The 20-operator table. Order matches the SYMMETRIC/PATH/BLOCKER_ONLY lists. pub const OPERATORS: &[Operator] = &[ - Operator { symbol: "=", backing: "eq", kind: Kind::Symmetric, restrict: Some("eqsel"), join: Some("eqjoinsel"), commutator: Some("="), negator: Some("<>") }, - Operator { symbol: "<>", backing: "neq", kind: Kind::Symmetric, restrict: Some("neqsel"), join: Some("neqjoinsel"), commutator: Some("<>"), negator: Some("=") }, - Operator { symbol: "<", backing: "lt", kind: Kind::Symmetric, restrict: Some("scalarltsel"), join: Some("scalarltjoinsel"), commutator: Some(">"), negator: Some(">=") }, - Operator { symbol: "<=", backing: "lte", kind: Kind::Symmetric, restrict: Some("scalarlesel"), join: Some("scalarlejoinsel"), commutator: Some(">="), negator: Some(">") }, - Operator { symbol: ">", backing: "gt", kind: Kind::Symmetric, restrict: Some("scalargtsel"), join: Some("scalargtjoinsel"), commutator: Some("<"), negator: Some("<=") }, - Operator { symbol: ">=", backing: "gte", kind: Kind::Symmetric, restrict: Some("scalargesel"), join: Some("scalargejoinsel"), commutator: Some("<="), negator: Some("<") }, - Operator { symbol: "@>", backing: "contains", kind: Kind::Symmetric, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "<@", backing: "contained_by", kind: Kind::Symmetric, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "->", backing: "\"->\"", kind: Kind::Path, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "->>", backing: "\"->>\"", kind: Kind::Path, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "?", backing: "\"?\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "?|", backing: "\"?|\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "?&", backing: "\"?&\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "@?", backing: "\"@?\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "@@", backing: "\"@@\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "#>", backing: "\"#>\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "#>>", backing: "\"#>>\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "-", backing: "\"-\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "#-", backing: "\"#-\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, - Operator { symbol: "||", backing: "\"||\"", kind: Kind::BlockerOnly, restrict: None, join: None, commutator: None, negator: None }, + Operator { + symbol: "=", + backing: "eq", + kind: Kind::Symmetric, + restrict: Some("eqsel"), + join: Some("eqjoinsel"), + commutator: Some("="), + negator: Some("<>"), + }, + Operator { + symbol: "<>", + backing: "neq", + kind: Kind::Symmetric, + restrict: Some("neqsel"), + join: Some("neqjoinsel"), + commutator: Some("<>"), + negator: Some("="), + }, + Operator { + symbol: "<", + backing: "lt", + kind: Kind::Symmetric, + restrict: Some("scalarltsel"), + join: Some("scalarltjoinsel"), + commutator: Some(">"), + negator: Some(">="), + }, + Operator { + symbol: "<=", + backing: "lte", + kind: Kind::Symmetric, + restrict: Some("scalarlesel"), + join: Some("scalarlejoinsel"), + commutator: Some(">="), + negator: Some(">"), + }, + Operator { + symbol: ">", + backing: "gt", + kind: Kind::Symmetric, + restrict: Some("scalargtsel"), + join: Some("scalargtjoinsel"), + commutator: Some("<"), + negator: Some("<="), + }, + Operator { + symbol: ">=", + backing: "gte", + kind: Kind::Symmetric, + restrict: Some("scalargesel"), + join: Some("scalargejoinsel"), + commutator: Some("<="), + negator: Some("<"), + }, + Operator { + symbol: "@>", + backing: "contains", + kind: Kind::Symmetric, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "<@", + backing: "contained_by", + kind: Kind::Symmetric, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "->", + backing: "\"->\"", + kind: Kind::Path, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "->>", + backing: "\"->>\"", + kind: Kind::Path, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "?", + backing: "\"?\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "?|", + backing: "\"?|\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "?&", + backing: "\"?&\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "@?", + backing: "\"@?\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "@@", + backing: "\"@@\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "#>", + backing: "\"#>\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "#>>", + backing: "\"#>>\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "-", + backing: "\"-\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "#-", + backing: "\"#-\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, + Operator { + symbol: "||", + backing: "\"||\"", + kind: Kind::BlockerOnly, + restrict: None, + join: None, + commutator: None, + negator: None, + }, ]; #[cfg(test)] @@ -74,7 +234,10 @@ mod tests { #[test] fn operator_lists_match() { - assert_eq!(SYMMETRIC_OPERATORS, &["=", "<>", "<", "<=", ">", ">=", "@>", "<@"]); + assert_eq!( + SYMMETRIC_OPERATORS, + &["=", "<>", "<", "<=", ">", ">=", "@>", "<@"] + ); assert_eq!(PATH_OPERATORS, &["->", "->>"]); assert_eq!( BLOCKER_ONLY_OPERATORS, @@ -84,7 +247,9 @@ mod tests { #[test] fn no_like_operators() { - assert!(OPERATORS.iter().all(|o| o.symbol != "~~" && o.symbol != "~~*")); + assert!(OPERATORS + .iter() + .all(|o| o.symbol != "~~" && o.symbol != "~~*")); } #[test] diff --git a/crates/eql-codegen/src/templates.rs b/crates/eql-codegen/src/templates.rs index df4e3065..484db6c8 100644 --- a/crates/eql-codegen/src/templates.rs +++ b/crates/eql-codegen/src/templates.rs @@ -34,7 +34,10 @@ mod tests { use eql_scalars::CATALOG; fn spec(token: &str) -> &'static ScalarSpec { - CATALOG.iter().find(|s| s.token == token).expect("catalog token") + CATALOG + .iter() + .find(|s| s.token == token) + .expect("catalog token") } #[test] diff --git a/crates/eql-codegen/src/writer.rs b/crates/eql-codegen/src/writer.rs index 172c336d..7675af9f 100644 --- a/crates/eql-codegen/src/writer.rs +++ b/crates/eql-codegen/src/writer.rs @@ -129,16 +129,25 @@ pub(crate) mod test_support { pub struct TempDir(PathBuf); impl TempDir { - pub fn path(&self) -> &Path { &self.0 } + pub fn path(&self) -> &Path { + &self.0 + } } impl Drop for TempDir { - fn drop(&mut self) { let _ = fs::remove_dir_all(&self.0); } + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } } pub fn tempdir() -> TempDir { let mut p = std::env::temp_dir(); let nanos = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(); - p.push(format!("eql-codegen-test-{nanos}-{:?}", std::thread::current().id())); + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + p.push(format!( + "eql-codegen-test-{nanos}-{:?}", + std::thread::current().id() + )); fs::create_dir_all(&p).unwrap(); TempDir(p) } @@ -146,8 +155,8 @@ pub(crate) mod test_support { #[cfg(test)] mod tests { - use super::*; use super::test_support::tempdir as tmp; + use super::*; #[test] fn is_generated_true_for_header() { @@ -202,7 +211,11 @@ mod tests { let d = tmp(); let generated = d.path().join("int4_types.sql"); let hand = d.path().join("int4_eq_functions.sql"); - fs::write(&generated, format!("{AUTO_GENERATED_HEADER}-- old generated\n")).unwrap(); + fs::write( + &generated, + format!("{AUTO_GENERATED_HEADER}-- old generated\n"), + ) + .unwrap(); fs::write(&hand, "-- REQUIRE: src/schema.sql\n-- hand-written\n").unwrap(); let err = ensure_generated_paths_writable(&[generated.clone(), hand.clone()]).unwrap_err(); assert!(err.to_string().contains("int4_eq_functions.sql")); @@ -257,7 +270,11 @@ mod tests { fn is_generated_rs_true_for_rust_header() { let d = tmp(); let p = d.path().join("int4_values.rs"); - fs::write(&p, format!("{AUTO_GENERATED_HEADER_RS}pub const VALUES: &[i32] = &[];\n")).unwrap(); + fs::write( + &p, + format!("{AUTO_GENERATED_HEADER_RS}pub const VALUES: &[i32] = &[];\n"), + ) + .unwrap(); assert!(is_generated_rs(&p)); } diff --git a/crates/eql-codegen/tests/parity.rs b/crates/eql-codegen/tests/parity.rs index c0504586..87c27526 100644 --- a/crates/eql-codegen/tests/parity.rs +++ b/crates/eql-codegen/tests/parity.rs @@ -9,15 +9,19 @@ use std::path::PathBuf; fn repo_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent().unwrap() - .parent().unwrap() + .parent() + .unwrap() + .parent() + .unwrap() .to_path_buf() } fn tempdir(tag: &str) -> PathBuf { let mut p = std::env::temp_dir(); let nanos = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(); + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); p.push(format!("eql-parity-{tag}-{nanos}")); fs::create_dir_all(&p).unwrap(); p @@ -52,14 +56,17 @@ fn rust_generator_matches_int4_golden_files() { let gen_dir = out.join("src/encrypted_domain/int4"); for entry in fs::read_dir(&ref_dir).unwrap() { let path = entry.unwrap().path(); - if path.extension().and_then(|e| e.to_str()) != Some("sql") { continue; } + if path.extension().and_then(|e| e.to_str()) != Some("sql") { + continue; + } let name = path.file_name().unwrap().to_str().unwrap(); let reference = fs::read_to_string(&path).unwrap(); // Strip the leading `-- REFERENCE:` provenance line. What remains is the // generated body, which already starts with the template-owned // `-- AUTOMATICALLY GENERATED FILE.` marker — the same first line the // materialised file carries, so no header is re-added here. - let expected: String = reference.lines() + let expected: String = reference + .lines() .skip_while(|l| l.starts_with("-- REFERENCE:") || l.starts_with("// REFERENCE:")) .map(|l| format!("{l}\n")) .collect(); diff --git a/tasks/codegen/templates.py b/tasks/codegen/templates.py index 0c446fec..d79eb3ee 100644 --- a/tasks/codegen/templates.py +++ b/tasks/codegen/templates.py @@ -13,24 +13,18 @@ term_json_keys, ) -AUTO_GENERATED_HEADER = ( - "-- AUTO-GENERATED — DO NOT EDIT.\n" - "-- Regenerated automatically by `mise run build`; " - "also `mise run codegen:domain ` to refresh one type.\n" - "-- Source of truth: tasks/codegen/types/.toml\n" - "-- This file is gitignored; never commit it.\n" -) - -# Rust counterpart of AUTO_GENERATED_HEADER. Unlike the gitignored SQL surface, -# the fixture-value const IS committed and verified by the CI staleness guard, -# so the wording differs deliberately. -AUTO_GENERATED_HEADER_RS = ( - "// AUTO-GENERATED — DO NOT EDIT.\n" - "// Regenerated by `mise run build` " - "(or `mise run codegen:domain `).\n" - "// Source of truth: tasks/codegen/types/.toml `[fixture] values`.\n" - "// This file IS committed and verified in CI (git diff --exit-code).\n" -) +# SQL generated-file marker, emitted as the first line of every generated SQL +# file. Must stay byte-identical to the Rust generator's AUTO_GENERATED_HEADER +# (crates/eql-codegen/src/consts.rs) so the two generators are at byte parity +# (mise run codegen:parity). The `^-- AUTOMATICALLY GENERATED FILE` first line +# is also what tasks/docs/validate/{coverage,required-tags}.sh grep on to skip +# generated SQL — keep this and that grep in lockstep. +AUTO_GENERATED_HEADER = "-- AUTOMATICALLY GENERATED FILE.\n" + +# Rust counterpart, prepended to the committed `_values.rs` (which has no +# template). Rust comment syntax (`//`) so the `.rs` file stays valid; must stay +# byte-identical to the Rust generator's AUTO_GENERATED_HEADER_RS. +AUTO_GENERATED_HEADER_RS = "// AUTOMATICALLY GENERATED FILE.\n" ENVELOPE_KEYS = ["v", "i"] CIPHERTEXT_KEY = "c" diff --git a/tasks/codegen/test_templates.py b/tasks/codegen/test_templates.py index 7f30e4cb..221dcd9b 100644 --- a/tasks/codegen/test_templates.py +++ b/tasks/codegen/test_templates.py @@ -24,16 +24,17 @@ def test_auto_generated_header_present(): - assert "AUTO-GENERATED" in AUTO_GENERATED_HEADER - assert "DO NOT EDIT" in AUTO_GENERATED_HEADER + # Byte-identical to the Rust generator's marker + # (crates/eql-codegen/src/consts.rs) and to the `^-- AUTOMATICALLY GENERATED + # FILE` prefix that tasks/docs/validate/*.sh grep on to skip generated SQL. + assert AUTO_GENERATED_HEADER == "-- AUTOMATICALLY GENERATED FILE.\n" + assert "AUTOMATICALLY GENERATED FILE" in AUTO_GENERATED_HEADER -def test_rust_header_is_comment_and_marks_committed(): - # Rust uses // comments, not SQL's --, and unlike the gitignored SQL - # surface this file is committed and CI-verified. - assert AUTO_GENERATED_HEADER_RS.startswith("// AUTO-GENERATED") - assert "DO NOT EDIT" in AUTO_GENERATED_HEADER_RS - assert "committed" in AUTO_GENERATED_HEADER_RS +def test_rust_header_is_a_rust_comment(): + # Rust uses // comments, not SQL's --. Byte-identical to the Rust + # generator's AUTO_GENERATED_HEADER_RS (crates/eql-codegen/src/consts.rs). + assert AUTO_GENERATED_HEADER_RS == "// AUTOMATICALLY GENERATED FILE.\n" # No line is an SQL-style (`--`) comment — this is Rust, not SQL. assert not any( line.startswith("--") for line in AUTO_GENERATED_HEADER_RS.splitlines() @@ -55,8 +56,8 @@ def test_render_fixture_values_rs_emits_typed_const(): assert " -1,\n" in body assert " 0,\n" in body # ZERO and "1" both literal assert " 1,\n" in body - # No AUTO-GENERATED header in the body — the writer prepends it. - assert "AUTO-GENERATED" not in body + # No generated-file marker in the body — the writer prepends it. + assert "AUTOMATICALLY GENERATED FILE" not in body def test_render_fixture_values_rs_preserves_manifest_order(): From 0f0597802a50a4dbde875ddffba398da9696d46a Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 17:05:28 +1000 Subject: [PATCH 33/93] fix(codegen): address CodeRabbit review findings on generator parity - Point generated values.rs header at eql-scalars::CATALOG, not deleted TOML - Cover int2 in scalar_family_inlinable_operators_are_clean lint test - codegen-parity.sh: catch untracked generated values.rs via git status - main.rs: make non-zero Ok exit-code arm an explicit FAILURE - Fix stale fixture.rs breadcrumb in cipherstash.rs to int4.rs - FIXTURE_SCHEMA.md: int4 fixture is now 17 rows incl signed extremes + zero --- crates/eql-codegen/src/main.rs | 4 ++-- crates/eql-codegen/src/templates.rs | 4 ++-- tasks/codegen-parity.sh | 9 ++++++++- tests/codegen/reference/int4/int4_values.rs | 2 +- tests/sqlx/fixtures/FIXTURE_SCHEMA.md | 15 +++++++++------ tests/sqlx/src/fixtures/cipherstash.rs | 2 +- tests/sqlx/src/fixtures/int2_values.rs | 2 +- tests/sqlx/src/fixtures/int4_values.rs | 2 +- tests/sqlx/tests/lint_tests.rs | 2 +- 9 files changed, 26 insertions(+), 16 deletions(-) diff --git a/crates/eql-codegen/src/main.rs b/crates/eql-codegen/src/main.rs index 6e88340e..b1b96de8 100644 --- a/crates/eql-codegen/src/main.rs +++ b/crates/eql-codegen/src/main.rs @@ -30,10 +30,10 @@ fn main() -> ExitCode { // No args: generate every type's SQL + _values.rs. match generate_all(&repo_root()) { Ok(0) => return ExitCode::SUCCESS, - Ok(code) => return ExitCode::from(code.clamp(0, 255) as u8), + Ok(_) => return ExitCode::FAILURE, // any non-zero codegen result is a failure Err(e) => { eprintln!("error: {e}"); - return ExitCode::from(1); + return ExitCode::FAILURE; } } } diff --git a/crates/eql-codegen/src/templates.rs b/crates/eql-codegen/src/templates.rs index 484db6c8..1f2a33e3 100644 --- a/crates/eql-codegen/src/templates.rs +++ b/crates/eql-codegen/src/templates.rs @@ -17,7 +17,7 @@ pub fn render_fixture_values_rs(spec: &ScalarSpec) -> String { format!( "//! Fixture plaintext values for the {token} encrypted-domain family.\n\ //!\n\ - //! Generated from tasks/codegen/types/{token}.toml `[fixture] values` —\n\ + //! Generated from the `{token}` row in `eql-scalars::CATALOG` (`fixtures`) —\n\ //! the single source of truth shared by the fixture generator\n\ //! (`fixtures::eql_v2_{token}`) and the matrix oracle\n\ //! (`ScalarType::FIXTURE_VALUES`).\n\n\ @@ -44,7 +44,7 @@ mod tests { fn fixture_values_rs_emits_typed_const_for_int4() { let body = render_fixture_values_rs(spec("int4")); assert!(body.contains("pub const VALUES: &[i32] = &[")); - assert!(body.contains("tasks/codegen/types/int4.toml")); + assert!(body.contains("eql-scalars::CATALOG")); assert!(body.contains(" i32::MIN,\n")); assert!(body.contains(" i32::MAX,\n")); assert!(body.contains(" -1,\n")); diff --git a/tasks/codegen-parity.sh b/tasks/codegen-parity.sh index ff3a90a6..1f8d1e10 100755 --- a/tasks/codegen-parity.sh +++ b/tasks/codegen-parity.sh @@ -21,6 +21,13 @@ for f in tests/codegen/reference/int4/*.sql; do done echo "==> Verifying committed _values.rs are byte-identical (git clean)" -git diff --exit-code -- tests/sqlx/src/fixtures/*_values.rs +# `git diff` only catches modifications to tracked files; a newly-generated but +# uncommitted _values.rs would slip through. `git status --porcelain` also +# reports untracked files, mirroring the CI codegen job. +if [ -n "$(git status --porcelain -- tests/sqlx/src/fixtures/)" ]; then + echo "values.rs stale or uncommitted after regeneration" >&2 + git status --porcelain -- tests/sqlx/src/fixtures/ >&2 + exit 1 +fi echo "PARITY OK: Rust generator matches the int4 golden (normalized) and committed values.rs." diff --git a/tests/codegen/reference/int4/int4_values.rs b/tests/codegen/reference/int4/int4_values.rs index 3e6b1ec6..77a69ad0 100644 --- a/tests/codegen/reference/int4/int4_values.rs +++ b/tests/codegen/reference/int4/int4_values.rs @@ -1,7 +1,7 @@ // REFERENCE: hand-reviewed parity baseline for tasks/codegen/ — see ../README.md //! Fixture plaintext values for the int4 encrypted-domain family. //! -//! Generated from tasks/codegen/types/int4.toml `[fixture] values` — +//! Generated from the `int4` row in `eql-scalars::CATALOG` (`fixtures`) — //! the single source of truth shared by the fixture generator //! (`fixtures::eql_v2_int4`) and the matrix oracle //! (`ScalarType::FIXTURE_VALUES`). diff --git a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md index f01edce7..c8122b06 100644 --- a/tests/sqlx/fixtures/FIXTURE_SCHEMA.md +++ b/tests/sqlx/fixtures/FIXTURE_SCHEMA.md @@ -191,9 +191,11 @@ CREATE TABLE bench ( ## eql_v2_int4.sql -**Purpose:** 14 encrypted integers for verifying encrypted-integer fixture -structure. Unlike its neighbours, this is a **generated** fixture — produced by -`mise run fixture:generate eql_v2_int4` (the Rust fixture framework in +**Purpose:** 17 encrypted integers for verifying encrypted-integer fixture +structure. The set MUST include the signed extremes (`i32::MIN`/`i32::MAX`) and +zero — they are the matrix comparison pivots, which is why the count grew from +14 to 17. Unlike its neighbours, this is a **generated** fixture — produced by +`mise run fixture:generate:all` (the Rust fixture framework in `tests/sqlx/src/fixtures/`) and **not committed** (see `.gitignore`). It is plain SQL with **no EQL dependency**: `payload` is `jsonb`, so the script applies standalone. @@ -220,9 +222,10 @@ CREATE TABLE fixtures.eql_v2_int4 ( ``` **Data:** -- 14 rows, ids 1-14; `id = N` is the Nth generated value. -- `plaintext` values: `-100, -1, 1, 2, 5, 10, 17, 25, 42, 50, 100, 250, 1000, 9999` - — a negative boundary plus small/medium/large/extreme magnitudes. +- 17 rows, ids 1-17; `id = N` is the Nth generated value. +- `plaintext` values: `i32::MIN, -100, -1, 0, 1, 2, 5, 10, 17, 25, 42, 50, 100, 250, 1000, 9999, i32::MAX` + — the signed extremes and zero (matrix comparison pivots) plus + small/medium/large magnitudes. - `plaintext` is the **in-table oracle**: consuming tests filter `WHERE plaintext = N` directly, so no Rust value constant is shared. - Each `payload` is a cipherstash-client-encrypted JSONB object carrying diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs index d7a8e8ca..303e3997 100644 --- a/tests/sqlx/src/fixtures/cipherstash.rs +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -294,7 +294,7 @@ mod live_tests { /// Assert the well-formed Store shape: the payload is a JSON object /// with non-null `v`, `c`, `hm`, `ob`, and `i` fields. Mirrors the - /// per-key assertions in `tests/encrypted_domain/scalars/int4/fixture.rs`. + /// per-key assertions in `tests/sqlx/tests/encrypted_domain/scalars/int4.rs`. fn assert_store_shape(payload: &Value) { let obj = payload.as_object().expect("payload must be a JSON object"); for key in ["v", "c", "hm", "ob", "i"] { diff --git a/tests/sqlx/src/fixtures/int2_values.rs b/tests/sqlx/src/fixtures/int2_values.rs index 4ff2db38..a1c0a847 100644 --- a/tests/sqlx/src/fixtures/int2_values.rs +++ b/tests/sqlx/src/fixtures/int2_values.rs @@ -1,7 +1,7 @@ // AUTOMATICALLY GENERATED FILE. //! Fixture plaintext values for the int2 encrypted-domain family. //! -//! Generated from tasks/codegen/types/int2.toml `[fixture] values` — +//! Generated from the `int2` row in `eql-scalars::CATALOG` (`fixtures`) — //! the single source of truth shared by the fixture generator //! (`fixtures::eql_v2_int2`) and the matrix oracle //! (`ScalarType::FIXTURE_VALUES`). diff --git a/tests/sqlx/src/fixtures/int4_values.rs b/tests/sqlx/src/fixtures/int4_values.rs index c9c48b30..d0c31a63 100644 --- a/tests/sqlx/src/fixtures/int4_values.rs +++ b/tests/sqlx/src/fixtures/int4_values.rs @@ -1,7 +1,7 @@ // AUTOMATICALLY GENERATED FILE. //! Fixture plaintext values for the int4 encrypted-domain family. //! -//! Generated from tasks/codegen/types/int4.toml `[fixture] values` — +//! Generated from the `int4` row in `eql-scalars::CATALOG` (`fixtures`) — //! the single source of truth shared by the fixture generator //! (`fixtures::eql_v2_int4`) and the matrix oracle //! (`ScalarType::FIXTURE_VALUES`). diff --git a/tests/sqlx/tests/lint_tests.rs b/tests/sqlx/tests/lint_tests.rs index 0387e396..a7f9152d 100644 --- a/tests/sqlx/tests/lint_tests.rs +++ b/tests/sqlx/tests/lint_tests.rs @@ -19,7 +19,7 @@ use sqlx::PgPool; /// materialised. Extending the family (e.g. when `int8`/`bool`/`date` /// land) is a one-line array extension here — every downstream /// parameterised test picks it up automatically. -const SCALAR_PG_TYPES: &[&str] = &["int4"]; +const SCALAR_PG_TYPES: &[&str] = &["int4", "int2"]; #[derive(Debug, sqlx::FromRow)] struct LintRow { From e85bc5712e6ac37203b06ec99ebce40a11eda0e6 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 20:48:06 +1000 Subject: [PATCH 34/93] refactor(codegen): model operator surface as a central PostgreSQL catalog Replace the generic `Kind` enum (Symmetric/Path/BlockerOnly) and the parallel `SYMMETRIC_OPERATORS`/`PATH_OPERATORS`/`BLOCKER_ONLY_OPERATORS` slices + per-shape helpers with a single `OPERATORS` catalog where each operator carries strongly-typed PostgreSQL signatures and pure-data planner metadata. - operator_surface.rs: add `TypeSlot`, `OperatorSignature`, `RenderedSignature`, and `OperatorMetadata`; each `Operator` now owns its `signatures` and `metadata`. Rename `backing` -> `function_name` and `backing_function` -> `operator_function_name`. Delete `Kind` and the three category slices. - generate.rs: `render_functions_file`/`render_operators_file` iterate the catalog x signatures and choose wrapper vs unsupported-operator entries per domain from `Term::operators_for_terms`. Remove the category loops and `*_shapes` helpers; preserve the `selector` param name for `->`/`->>` via a small `arg_b_name` helper. - context.rs: `operator_entry` takes `&Operator` and renders metadata only when the domain supports the operator; rename `FnEntry::Blocker` -> `Unsupported` and `blocker_entry` -> `unsupported_entry`. Generated SQL is byte-identical (codegen:parity passes); blocking is now a per-domain emission decision rather than a static operator category. Adds direct unit tests for metadata gating, the selector naming, the jsonpath/text[] slots, and a non-empty-signatures catalog invariant. Spec: docs/development/operator-surface-generation-spec.md --- crates/eql-codegen/src/context.rs | 82 +-- crates/eql-codegen/src/generate.rs | 173 +++--- crates/eql-codegen/src/operator_surface.rs | 592 ++++++++++++++------- 3 files changed, 512 insertions(+), 335 deletions(-) diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs index 83eb52e9..cdb68e67 100644 --- a/crates/eql-codegen/src/context.rs +++ b/crates/eql-codegen/src/context.rs @@ -1,6 +1,7 @@ //! minijinja environment + serde context structs + relocated logic helpers. use crate::consts::*; +use crate::operator_surface::Operator; use eql_scalars::{DomainSpec, Term}; /// Line-normalize SQL for best-effort byte-exact comparison: trim each line's @@ -77,8 +78,8 @@ pub fn domain_block(token: &str, domain: &DomainSpec) -> DomainBlock { } } -/// One SQL parameter (name + SQL type), shared by wrapper/blocker signatures -/// and their `@param` docs tags. +/// One SQL parameter (name + SQL type), shared by wrapper and +/// unsupported-operator signatures and their `@param` docs tags. #[derive(serde::Serialize)] pub struct SqlParam { pub name: &'static str, // "a", "b", or "selector" @@ -86,7 +87,8 @@ pub struct SqlParam { } /// One generated function entry. The serde tag drives the template's three-way -/// switch; the blocker arm is never merged with the others (footgun separation). +/// switch; the unsupported-operator arm is never merged with the others (footgun +/// separation — its body must always raise). #[derive(serde::Serialize)] #[serde(tag = "kind")] pub enum FnEntry { @@ -102,7 +104,7 @@ pub enum FnEntry { call_a: String, // e.g. eql_v3.eq_term(a) (embeds extract_arg cast logic) call_b: String, // e.g. eql_v3.eq_term(b::eql_v3.int4_eq) }, - Blocker { + Unsupported { operator_lit: String, // sql_str(op), escaped content for the RAISE literal function_name: String, // e.g. lt / "->" / "#>" args: [SqlParam; 2], @@ -132,10 +134,10 @@ pub fn extractor_entry(term: Term) -> FnEntry { /// Build an inlinable comparison-wrapper entry for a supported operator. /// `dom` is the schema-qualified domain name. pub fn wrapper_entry(dom: &str, op: &str, arg_a: &str, arg_b: &str, extractor: &str) -> FnEntry { - use crate::operator_surface::backing_function; + use crate::operator_surface::operator_function_name; FnEntry::Wrapper { op: op.to_string(), - function_name: backing_function(op).to_string(), + function_name: operator_function_name(op).to_string(), args: [ SqlParam { name: "a", @@ -151,14 +153,14 @@ pub fn wrapper_entry(dom: &str, op: &str, arg_a: &str, arg_b: &str, extractor: & } } -/// Build an unsupported-operator blocker entry. Every blocker shares one -/// uniform `RAISE EXCEPTION` body; only signature facts vary. -pub fn blocker_entry(op: &str, args: [SqlParam; 2], returns: &str) -> FnEntry { - use crate::operator_surface::backing_function; - FnEntry::Blocker { +/// Build an unsupported-operator entry. Every such entry shares one uniform +/// `RAISE EXCEPTION` body; only signature facts vary. +pub fn unsupported_entry(op: &str, args: [SqlParam; 2], returns: &str) -> FnEntry { + use crate::operator_surface::operator_function_name; + FnEntry::Unsupported { // operator_lit is sql_str-escaped defensively for the single-quoted RAISE literal. operator_lit: sql_str(op), - function_name: backing_function(op).to_string(), + function_name: operator_function_name(op).to_string(), args, returns: returns.to_string(), } @@ -183,39 +185,18 @@ pub struct OperatorsContext { pub operators: Vec, } -/// Build one CREATE OPERATOR entry. The metadata line exists only for supported -/// symmetric operators that carry at least one extra (the `@>`/`<@` symmetric- -/// but-empty trap collapses to `None`). -pub fn operator_entry( - op: &str, - function_name: &str, - leftarg: &str, - rightarg: &str, - supported: bool, -) -> OpEntry { - use crate::operator_surface::{operator, Kind}; - let meta = operator(op); - let metadata = if supported && meta.kind == Kind::Symmetric { - let mut extras = Vec::new(); - if let Some(c) = meta.commutator { - extras.push(format!("COMMUTATOR = {c}")); - } - if let Some(n) = meta.negator { - extras.push(format!("NEGATOR = {n}")); - } - if let Some(r) = meta.restrict { - extras.push(format!("RESTRICT = {r}")); - } - if let Some(j) = meta.join { - extras.push(format!("JOIN = {j}")); - } - (!extras.is_empty()).then(|| extras.join(", ")) // empty → None (the @>/<@ trap) +/// Build one CREATE OPERATOR entry. Planner metadata is emitted only when the +/// current domain supports the operator and the operator carries metadata (the +/// `@>`/`<@` empty-metadata case collapses to `None`). +pub fn operator_entry(op: &Operator, leftarg: &str, rightarg: &str, supported: bool) -> OpEntry { + let metadata = if supported { + op.metadata.render() } else { None }; OpEntry { - symbol: op.to_string(), - function_name: function_name.to_string(), + symbol: op.symbol.to_string(), + function_name: op.function_name.to_string(), leftarg: leftarg.to_string(), rightarg: rightarg.to_string(), metadata, @@ -329,4 +310,23 @@ mod tests { assert!(env.get_template(name).is_ok(), "missing template {name}"); } } + + #[test] + fn operator_entry_emits_metadata_only_when_supported() { + use crate::operator_surface::operator; + // Supported comparison operator carries its planner metadata. + let eq = operator_entry(&operator("="), "eql_v3.int4_eq", "eql_v3.int4_eq", true); + assert_eq!(eq.symbol, "="); + assert_eq!(eq.function_name, "eq"); + assert_eq!( + eq.metadata.as_deref(), + Some("COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel") + ); + // The same operator, unsupported on this domain → no metadata line. + let eq_unsupported = operator_entry(&operator("="), "eql_v3.int4", "eql_v3.int4", false); + assert_eq!(eq_unsupported.metadata, None); + // Supported but metadata-less operator (`@>`) → still no metadata line. + let contains = operator_entry(&operator("@>"), "eql_v3.int4_eq", "eql_v3.int4_eq", true); + assert_eq!(contains.metadata, None); + } } diff --git a/crates/eql-codegen/src/generate.rs b/crates/eql-codegen/src/generate.rs index 6cbd259a..c7625545 100644 --- a/crates/eql-codegen/src/generate.rs +++ b/crates/eql-codegen/src/generate.rs @@ -5,55 +5,21 @@ use std::path::{Path, PathBuf}; use eql_scalars::{DomainSpec, ScalarSpec, Term}; use crate::context::{domain_name, is_ord_capable}; -use crate::operator_surface::{ - backing_function, BLOCKER_ONLY_OPERATORS, PATH_OPERATORS, SYMMETRIC_OPERATORS, -}; +use crate::operator_surface::OPERATORS; /// The full domain name (token + suffix). suffix "" => bare token. fn full_name(token: &str, suffix: &str) -> String { format!("{token}{suffix}") } -/// Symmetric-operator argument shapes. Port of `_symmetric_shapes`. -fn symmetric_shapes(dom: &str) -> Vec<(String, String)> { - vec![ - (dom.to_string(), dom.to_string()), - (dom.to_string(), "jsonb".to_string()), - ("jsonb".to_string(), dom.to_string()), - ] -} - -/// Path-operator argument shapes. Port of `_path_shapes`. -fn path_shapes(dom: &str) -> Vec<(String, String)> { - vec![ - (dom.to_string(), "text".to_string()), - (dom.to_string(), "integer".to_string()), - ("jsonb".to_string(), dom.to_string()), - ] -} - -/// Blocker-only argument shapes for a native jsonb operator. -/// Port of `_blocker_only_shapes`. Returns (arg_a, arg_b, returns). -fn blocker_only_shapes(dom: &str, op: &str) -> Vec<(String, String, String)> { - let d = dom.to_string(); - match op { - "?" => vec![(d, "text".into(), "boolean".into())], - "?|" | "?&" => vec![(d, "text[]".into(), "boolean".into())], - "@?" | "@@" => vec![(d, "jsonpath".into(), "boolean".into())], - "#>" => vec![(d, "text[]".into(), "jsonb".into())], - "#>>" => vec![(d, "text[]".into(), "text".into())], - "-" => vec![ - (d.clone(), "text".into(), "jsonb".into()), - (d.clone(), "integer".into(), "jsonb".into()), - (d, "text[]".into(), "jsonb".into()), - ], - "#-" => vec![(d, "text[]".into(), "jsonb".into())], - "||" => vec![ - (d.clone(), d.clone(), "jsonb".into()), - (d.clone(), "jsonb".into(), "jsonb".into()), - ("jsonb".into(), d, "jsonb".into()), - ], - other => panic!("unhandled blocker-only operator: {other}"), +/// The second-parameter name for an operator's generated signature. The `->` and +/// `->>` path operators take a path *selector* as their right operand; every +/// other operator uses the generic `b`. This is a naming convention only — it +/// has no bearing on whether the operator is supported. +fn arg_b_name(symbol: &str) -> &'static str { + match symbol { + "->" | "->>" => "selector", + _ => "b", } } @@ -125,7 +91,7 @@ fn extractor_terms(terms: &[Term]) -> Vec { pub fn render_functions_file(token: &str, domain: &DomainSpec) -> String { use crate::consts::sql_str; use crate::context::{ - blocker_entry, environment, extractor_entry, wrapper_entry, FunctionsContext, SqlParam, + environment, extractor_entry, unsupported_entry, wrapper_entry, FunctionsContext, SqlParam, }; let name = full_name(token, domain.suffix); let dom = domain_name(&name); @@ -137,61 +103,33 @@ pub fn render_functions_file(token: &str, domain: &DomainSpec) -> String { for term in extractor_terms(domain.terms) { entries.push(extractor_entry(term)); } - for &op in SYMMETRIC_OPERATORS { - let extractor = Term::extractor_for_operator(domain.terms, op); - for (arg_a, arg_b) in symmetric_shapes(&dom) { - if is_supported(op) { + for op in OPERATORS { + let extractor = Term::extractor_for_operator(domain.terms, op.symbol); + for sig in op.signatures { + let rendered = sig.render(&dom); + if is_supported(op.symbol) { if let Some(ex) = extractor { - entries.push(wrapper_entry(&dom, op, &arg_a, &arg_b, ex)); + entries.push(wrapper_entry( + &dom, + op.symbol, + &rendered.left, + &rendered.right, + ex, + )); continue; } } let args = [ SqlParam { name: "a", - ty: arg_a, - }, - SqlParam { - name: "b", - ty: arg_b, - }, - ]; - entries.push(blocker_entry(op, args, "boolean")); - } - } - for &op in PATH_OPERATORS { - for (arg_a, arg_b) in path_shapes(&dom) { - let returns = if op == "->>" { - "text".to_string() - } else { - dom.clone() - }; - let args = [ - SqlParam { - name: "a", - ty: arg_a, - }, - SqlParam { - name: "selector", - ty: arg_b, - }, - ]; - entries.push(blocker_entry(op, args, &returns)); - } - } - for &op in BLOCKER_ONLY_OPERATORS { - for (arg_a, arg_b, returns) in blocker_only_shapes(&dom, op) { - let args = [ - SqlParam { - name: "a", - ty: arg_a, + ty: rendered.left, }, SqlParam { - name: "b", - ty: arg_b, + name: arg_b_name(op.symbol), + ty: rendered.right, }, ]; - entries.push(blocker_entry(op, args, &returns)); + entries.push(unsupported_entry(op.symbol, args, &rendered.returns)); } } @@ -219,22 +157,17 @@ pub fn render_operators_file(token: &str, domain: &DomainSpec) -> String { let is_supported = |op: &str| supported.contains(&op); let mut operators = Vec::new(); - for &op in SYMMETRIC_OPERATORS { - let function_name = backing_function(op); - for (l, r) in symmetric_shapes(&dom) { - operators.push(operator_entry(op, function_name, &l, &r, is_supported(op))); - } - } - for &op in PATH_OPERATORS { - let function_name = backing_function(op); - for (l, r) in path_shapes(&dom) { - operators.push(operator_entry(op, function_name, &l, &r, false)); - } - } - for &op in BLOCKER_ONLY_OPERATORS { - let function_name = backing_function(op); - for (l, r, _ret) in blocker_only_shapes(&dom, op) { - operators.push(operator_entry(op, function_name, &l, &r, false)); + for op in OPERATORS { + for sig in op.signatures { + // CREATE OPERATOR only needs the operand types; `rendered.returns` is + // intentionally discarded here (it matters only for the function body). + let rendered = sig.render(&dom); + operators.push(operator_entry( + op, + &rendered.left, + &rendered.right, + is_supported(op.symbol), + )); } } @@ -428,6 +361,28 @@ mod tests { panic!("unrecognised reference filename: {name}"); } + #[test] + fn arg_b_name_is_selector_only_for_path_operators() { + assert_eq!(arg_b_name("->"), "selector"); + assert_eq!(arg_b_name("->>"), "selector"); + assert_eq!(arg_b_name("="), "b"); + assert_eq!(arg_b_name("||"), "b"); + assert_eq!(arg_b_name("@>"), "b"); + } + + #[test] + fn functions_render_supported_wrappers_and_unsupported_entries_from_catalog() { + let s = spec("int4"); + let d = domain(s, "_eq"); + let sql = render_functions_file("int4", d); + assert!(sql.contains("CREATE FUNCTION eql_v3.eq(")); + assert!(sql.contains("AS $$ SELECT")); + assert!(sql.contains("CREATE FUNCTION eql_v3.lt(")); + assert!(sql.contains("RAISE EXCEPTION 'operator % is not supported for %', '<'")); + assert!(sql.contains("CREATE FUNCTION eql_v3.\"->\"(")); + assert!(sql.contains("RAISE EXCEPTION 'operator % is not supported for %', '->'")); + } + #[test] fn types_file_normalized_matches_golden() { use crate::context::normalize_sql; @@ -747,12 +702,12 @@ mod tests { // --- Escaping guards over the context builders (synthetic inputs) --- #[test] - fn blocker_entry_preserves_operator_literal_and_domain_lit_is_escaped() { + fn unsupported_entry_preserves_operator_literal_and_domain_lit_is_escaped() { use crate::consts::sql_str; - use crate::context::{blocker_entry, FnEntry, SqlParam}; + use crate::context::{unsupported_entry, FnEntry, SqlParam}; let dom = "eql_v3.o'dom"; let domain_lit = sql_str(dom); - let entry = blocker_entry( + let entry = unsupported_entry( "<", [ SqlParam { @@ -767,11 +722,11 @@ mod tests { "boolean", ); match entry { - FnEntry::Blocker { operator_lit, .. } => { + FnEntry::Unsupported { operator_lit, .. } => { assert_eq!(domain_lit, "eql_v3.o''dom"); // quote doubled by sql_str assert_eq!(operator_lit, "<"); } - _ => panic!("expected blocker"), + _ => panic!("expected unsupported-operator entry"), } } diff --git a/crates/eql-codegen/src/operator_surface.rs b/crates/eql-codegen/src/operator_surface.rs index 772ac358..38f1ec19 100644 --- a/crates/eql-codegen/src/operator_surface.rs +++ b/crates/eql-codegen/src/operator_surface.rs @@ -4,25 +4,179 @@ #[derive(Clone, Copy)] pub struct Operator { pub symbol: &'static str, - pub backing: &'static str, - pub kind: Kind, + pub function_name: &'static str, + pub signatures: &'static [OperatorSignature], + pub metadata: OperatorMetadata, +} + +/// Optional `CREATE OPERATOR` planner metadata. Pure data — whether it is +/// emitted is a per-domain decision (supported operators only), not a property +/// of the operator's category. +#[derive(Clone, Copy)] +pub struct OperatorMetadata { pub restrict: Option<&'static str>, pub join: Option<&'static str>, pub commutator: Option<&'static str>, pub negator: Option<&'static str>, } +impl OperatorMetadata { + /// Metadata with no planner hints — the common case for operators that carry + /// no commutator/negator/selectivity estimators. + pub const fn none() -> Self { + Self { + restrict: None, + join: None, + commutator: None, + negator: None, + } + } + + /// Render the `CREATE OPERATOR` metadata clause, or `None` when no hint is + /// present (the `@>`/`<@` symmetric-but-empty case collapses to `None`). + pub fn render(self) -> Option { + let mut extras = Vec::new(); + if let Some(c) = self.commutator { + extras.push(format!("COMMUTATOR = {c}")); + } + if let Some(n) = self.negator { + extras.push(format!("NEGATOR = {n}")); + } + if let Some(r) = self.restrict { + extras.push(format!("RESTRICT = {r}")); + } + if let Some(j) = self.join { + extras.push(format!("JOIN = {j}")); + } + (!extras.is_empty()).then(|| extras.join(", ")) + } +} + +/// A type position in a PostgreSQL operator overload. `Domain` renders to the +/// concrete encrypted domain being generated; every other slot renders to a +/// fixed PostgreSQL type name. #[derive(Clone, Copy, PartialEq, Eq)] -pub enum Kind { - Symmetric, - Path, - BlockerOnly, +pub enum TypeSlot { + Domain, + Jsonb, + Text, + Integer, + TextArray, + Jsonpath, + Boolean, +} + +impl TypeSlot { + fn render(self, dom: &str) -> String { + match self { + TypeSlot::Domain => dom.to_string(), + TypeSlot::Jsonb => "jsonb".to_string(), + TypeSlot::Text => "text".to_string(), + TypeSlot::Integer => "integer".to_string(), + TypeSlot::TextArray => "text[]".to_string(), + TypeSlot::Jsonpath => "jsonpath".to_string(), + TypeSlot::Boolean => "boolean".to_string(), + } + } } -pub const SYMMETRIC_OPERATORS: &[&str] = &["=", "<>", "<", "<=", ">", ">=", "@>", "<@"]; -pub const PATH_OPERATORS: &[&str] = &["->", "->>"]; -pub const BLOCKER_ONLY_OPERATORS: &[&str] = - &["?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||"]; +/// One PostgreSQL-shaped operator overload: left/right argument slots and the +/// return slot. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct OperatorSignature { + pub left: TypeSlot, + pub right: TypeSlot, + pub returns: TypeSlot, +} + +/// An `OperatorSignature` with every slot resolved to a concrete SQL type name. +pub struct RenderedSignature { + pub left: String, + pub right: String, + pub returns: String, +} + +impl OperatorSignature { + pub fn render(self, dom: &str) -> RenderedSignature { + RenderedSignature { + left: self.left.render(dom), + right: self.right.render(dom), + returns: self.returns.render(dom), + } + } +} + +/// Terse constructor for the static signature tables below. +const fn sig(left: TypeSlot, right: TypeSlot, returns: TypeSlot) -> OperatorSignature { + OperatorSignature { + left, + right, + returns, + } +} + +/// Symmetric boolean overloads (`domain`/`jsonb` convenience pairs), shared by +/// `=`, `<>`, `<`, `<=`, `>`, `>=`, `@>`, `<@`. +const BOOL_SYMMETRIC_SIGNATURES: &[OperatorSignature] = &[ + sig(TypeSlot::Domain, TypeSlot::Domain, TypeSlot::Boolean), + sig(TypeSlot::Domain, TypeSlot::Jsonb, TypeSlot::Boolean), + sig(TypeSlot::Jsonb, TypeSlot::Domain, TypeSlot::Boolean), +]; + +/// `->` path-selector overloads (returns the domain). +const ARROW_SIGNATURES: &[OperatorSignature] = &[ + sig(TypeSlot::Domain, TypeSlot::Text, TypeSlot::Domain), + sig(TypeSlot::Domain, TypeSlot::Integer, TypeSlot::Domain), + sig(TypeSlot::Jsonb, TypeSlot::Domain, TypeSlot::Domain), +]; + +/// `->>` path-selector overloads (returns text). +const ARROW_TEXT_SIGNATURES: &[OperatorSignature] = &[ + sig(TypeSlot::Domain, TypeSlot::Text, TypeSlot::Text), + sig(TypeSlot::Domain, TypeSlot::Integer, TypeSlot::Text), + sig(TypeSlot::Jsonb, TypeSlot::Domain, TypeSlot::Text), +]; + +/// `?` key-existence overload. +const HAS_KEY_SIGNATURES: &[OperatorSignature] = + &[sig(TypeSlot::Domain, TypeSlot::Text, TypeSlot::Boolean)]; + +/// `?|` / `?&` any/all-keys overloads. +const HAS_ANY_KEYS_SIGNATURES: &[OperatorSignature] = &[sig( + TypeSlot::Domain, + TypeSlot::TextArray, + TypeSlot::Boolean, +)]; + +/// `@?` / `@@` jsonpath-predicate overloads. +const JSONPATH_SIGNATURES: &[OperatorSignature] = + &[sig(TypeSlot::Domain, TypeSlot::Jsonpath, TypeSlot::Boolean)]; + +/// `#>` path-extract overload (returns jsonb). +const PATH_EXTRACT_JSONB_SIGNATURES: &[OperatorSignature] = + &[sig(TypeSlot::Domain, TypeSlot::TextArray, TypeSlot::Jsonb)]; + +/// `#>>` path-extract overload (returns text). +const PATH_EXTRACT_TEXT_SIGNATURES: &[OperatorSignature] = + &[sig(TypeSlot::Domain, TypeSlot::TextArray, TypeSlot::Text)]; + +/// `-` delete-key overloads. +const DELETE_SIGNATURES: &[OperatorSignature] = &[ + sig(TypeSlot::Domain, TypeSlot::Text, TypeSlot::Jsonb), + sig(TypeSlot::Domain, TypeSlot::Integer, TypeSlot::Jsonb), + sig(TypeSlot::Domain, TypeSlot::TextArray, TypeSlot::Jsonb), +]; + +/// `#-` delete-path overload. +const DELETE_PATH_SIGNATURES: &[OperatorSignature] = + &[sig(TypeSlot::Domain, TypeSlot::TextArray, TypeSlot::Jsonb)]; + +/// `||` concatenation overloads (`domain`/`jsonb` convenience pairs). +const CONCAT_SIGNATURES: &[OperatorSignature] = &[ + sig(TypeSlot::Domain, TypeSlot::Domain, TypeSlot::Jsonb), + sig(TypeSlot::Domain, TypeSlot::Jsonb, TypeSlot::Jsonb), + sig(TypeSlot::Jsonb, TypeSlot::Domain, TypeSlot::Jsonb), +]; /// Look up the operator metadata for a symbol. Panics on an unknown symbol — /// the generator only ever passes catalog symbols, matching Python's KeyError. @@ -34,192 +188,148 @@ pub fn operator(symbol: &str) -> Operator { .unwrap_or_else(|| panic!("unknown operator symbol: {symbol}")) } -/// The eql_v2 backing function name for an operator symbol. -pub fn backing_function(symbol: &str) -> &'static str { - operator(symbol).backing +/// The generated SQL function name for an operator symbol (e.g. `eq`, `"->"`). +pub fn operator_function_name(symbol: &str) -> &'static str { + operator(symbol).function_name } -/// The 20-operator table. Order matches the SYMMETRIC/PATH/BLOCKER_ONLY lists. +/// Comparison-operator metadata (commutator/negator/selectivity estimators). +const fn cmp_metadata( + restrict: &'static str, + join: &'static str, + commutator: &'static str, + negator: &'static str, +) -> OperatorMetadata { + OperatorMetadata { + restrict: Some(restrict), + join: Some(join), + commutator: Some(commutator), + negator: Some(negator), + } +} + +/// The 20-operator catalog. Order is: comparison operators, then path-selector +/// operators, then the remaining native jsonb operators. pub const OPERATORS: &[Operator] = &[ Operator { symbol: "=", - backing: "eq", - kind: Kind::Symmetric, - restrict: Some("eqsel"), - join: Some("eqjoinsel"), - commutator: Some("="), - negator: Some("<>"), + function_name: "eq", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: cmp_metadata("eqsel", "eqjoinsel", "=", "<>"), }, Operator { symbol: "<>", - backing: "neq", - kind: Kind::Symmetric, - restrict: Some("neqsel"), - join: Some("neqjoinsel"), - commutator: Some("<>"), - negator: Some("="), + function_name: "neq", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: cmp_metadata("neqsel", "neqjoinsel", "<>", "="), }, Operator { symbol: "<", - backing: "lt", - kind: Kind::Symmetric, - restrict: Some("scalarltsel"), - join: Some("scalarltjoinsel"), - commutator: Some(">"), - negator: Some(">="), + function_name: "lt", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: cmp_metadata("scalarltsel", "scalarltjoinsel", ">", ">="), }, Operator { symbol: "<=", - backing: "lte", - kind: Kind::Symmetric, - restrict: Some("scalarlesel"), - join: Some("scalarlejoinsel"), - commutator: Some(">="), - negator: Some(">"), + function_name: "lte", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: cmp_metadata("scalarlesel", "scalarlejoinsel", ">=", ">"), }, Operator { symbol: ">", - backing: "gt", - kind: Kind::Symmetric, - restrict: Some("scalargtsel"), - join: Some("scalargtjoinsel"), - commutator: Some("<"), - negator: Some("<="), + function_name: "gt", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: cmp_metadata("scalargtsel", "scalargtjoinsel", "<", "<="), }, Operator { symbol: ">=", - backing: "gte", - kind: Kind::Symmetric, - restrict: Some("scalargesel"), - join: Some("scalargejoinsel"), - commutator: Some("<="), - negator: Some("<"), + function_name: "gte", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: cmp_metadata("scalargesel", "scalargejoinsel", "<=", "<"), }, Operator { symbol: "@>", - backing: "contains", - kind: Kind::Symmetric, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "contains", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "<@", - backing: "contained_by", - kind: Kind::Symmetric, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "contained_by", + signatures: BOOL_SYMMETRIC_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "->", - backing: "\"->\"", - kind: Kind::Path, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"->\"", + signatures: ARROW_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "->>", - backing: "\"->>\"", - kind: Kind::Path, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"->>\"", + signatures: ARROW_TEXT_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "?", - backing: "\"?\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"?\"", + signatures: HAS_KEY_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "?|", - backing: "\"?|\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"?|\"", + signatures: HAS_ANY_KEYS_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "?&", - backing: "\"?&\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"?&\"", + signatures: HAS_ANY_KEYS_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "@?", - backing: "\"@?\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"@?\"", + signatures: JSONPATH_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "@@", - backing: "\"@@\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"@@\"", + signatures: JSONPATH_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "#>", - backing: "\"#>\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"#>\"", + signatures: PATH_EXTRACT_JSONB_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "#>>", - backing: "\"#>>\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"#>>\"", + signatures: PATH_EXTRACT_TEXT_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "-", - backing: "\"-\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"-\"", + signatures: DELETE_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "#-", - backing: "\"#-\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"#-\"", + signatures: DELETE_PATH_SIGNATURES, + metadata: OperatorMetadata::none(), }, Operator { symbol: "||", - backing: "\"||\"", - kind: Kind::BlockerOnly, - restrict: None, - join: None, - commutator: None, - negator: None, + function_name: "\"||\"", + signatures: CONCAT_SIGNATURES, + metadata: OperatorMetadata::none(), }, ]; @@ -227,21 +337,127 @@ pub const OPERATORS: &[Operator] = &[ mod tests { use super::*; + fn rendered_signatures(op: &str) -> Vec<(String, String, String)> { + operator(op) + .signatures + .iter() + .map(|sig| sig.render("eql_v3.int4_ord")) + .map(|sig| (sig.left, sig.right, sig.returns)) + .collect() + } + #[test] - fn twenty_operators_total() { - assert_eq!(OPERATORS.len(), 20); + fn signature_slots_render_for_domain() { + let sig = OperatorSignature { + left: TypeSlot::Domain, + right: TypeSlot::Text, + returns: TypeSlot::Boolean, + }; + let rendered = sig.render("eql_v3.int4_eq"); + assert_eq!(rendered.left, "eql_v3.int4_eq"); + assert_eq!(rendered.right, "text"); + assert_eq!(rendered.returns, "boolean"); } #[test] - fn operator_lists_match() { + fn operator_catalog_carries_postgres_signatures() { + let arrow = operator("->"); + let rendered: Vec<_> = arrow + .signatures + .iter() + .map(|sig| sig.render("eql_v3.int4")) + .map(|sig| (sig.left, sig.right, sig.returns)) + .collect(); assert_eq!( - SYMMETRIC_OPERATORS, - &["=", "<>", "<", "<=", ">", ">=", "@>", "<@"] + rendered, + vec![ + ( + "eql_v3.int4".to_string(), + "text".to_string(), + "eql_v3.int4".to_string() + ), + ( + "eql_v3.int4".to_string(), + "integer".to_string(), + "eql_v3.int4".to_string() + ), + ( + "jsonb".to_string(), + "eql_v3.int4".to_string(), + "eql_v3.int4".to_string() + ), + ] ); - assert_eq!(PATH_OPERATORS, &["->", "->>"]); + } + + #[test] + fn equality_signatures_match_existing_symmetric_shapes() { assert_eq!( - BLOCKER_ONLY_OPERATORS, - &["?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||"] + rendered_signatures("="), + vec![ + ( + "eql_v3.int4_ord".into(), + "eql_v3.int4_ord".into(), + "boolean".into() + ), + ("eql_v3.int4_ord".into(), "jsonb".into(), "boolean".into()), + ("jsonb".into(), "eql_v3.int4_ord".into(), "boolean".into()), + ] + ); + } + + #[test] + fn native_jsonb_signatures_match_existing_operator_shapes() { + assert_eq!( + rendered_signatures("||"), + vec![ + ( + "eql_v3.int4_ord".into(), + "eql_v3.int4_ord".into(), + "jsonb".into() + ), + ("eql_v3.int4_ord".into(), "jsonb".into(), "jsonb".into()), + ("jsonb".into(), "eql_v3.int4_ord".into(), "jsonb".into()), + ] + ); + assert_eq!( + rendered_signatures("?|"), + vec![("eql_v3.int4_ord".into(), "text[]".into(), "boolean".into())] + ); + } + + #[test] + fn jsonpath_and_text_array_signatures_render() { + // `@?` carries the jsonpath slot and `#>`/`#>>` carry the text[] slot — + // the slot kinds not asserted by the symmetric/arrow signature tests. + assert_eq!( + rendered_signatures("@?"), + vec![( + "eql_v3.int4_ord".into(), + "jsonpath".into(), + "boolean".into() + )] + ); + assert_eq!( + rendered_signatures("#>"), + vec![("eql_v3.int4_ord".into(), "text[]".into(), "jsonb".into())] + ); + assert_eq!( + rendered_signatures("#>>"), + vec![("eql_v3.int4_ord".into(), "text[]".into(), "text".into())] + ); + } + + #[test] + fn twenty_operators_total() { + assert_eq!(OPERATORS.len(), 20); + } + + #[test] + fn every_operator_has_signatures() { + assert!( + OPERATORS.iter().all(|o| !o.signatures.is_empty()), + "every catalog operator must declare at least one signature" ); } @@ -253,62 +469,68 @@ mod tests { } #[test] - fn backing_function_names() { - assert_eq!(backing_function("="), "eq"); - assert_eq!(backing_function("<>"), "neq"); - assert_eq!(backing_function("<"), "lt"); - assert_eq!(backing_function("<="), "lte"); - assert_eq!(backing_function(">"), "gt"); - assert_eq!(backing_function(">="), "gte"); - assert_eq!(backing_function("@>"), "contains"); - assert_eq!(backing_function("<@"), "contained_by"); - assert_eq!(backing_function("->"), "\"->\""); - assert_eq!(backing_function("->>"), "\"->>\""); - assert_eq!(backing_function("?"), "\"?\""); - assert_eq!(backing_function("?|"), "\"?|\""); - assert_eq!(backing_function("?&"), "\"?&\""); - assert_eq!(backing_function("@?"), "\"@?\""); - assert_eq!(backing_function("@@"), "\"@@\""); - assert_eq!(backing_function("#>"), "\"#>\""); - assert_eq!(backing_function("#>>"), "\"#>>\""); - assert_eq!(backing_function("-"), "\"-\""); - assert_eq!(backing_function("#-"), "\"#-\""); - assert_eq!(backing_function("||"), "\"||\""); + fn function_names() { + assert_eq!(operator_function_name("="), "eq"); + assert_eq!(operator_function_name("<>"), "neq"); + assert_eq!(operator_function_name("<"), "lt"); + assert_eq!(operator_function_name("<="), "lte"); + assert_eq!(operator_function_name(">"), "gt"); + assert_eq!(operator_function_name(">="), "gte"); + assert_eq!(operator_function_name("@>"), "contains"); + assert_eq!(operator_function_name("<@"), "contained_by"); + assert_eq!(operator_function_name("->"), "\"->\""); + assert_eq!(operator_function_name("->>"), "\"->>\""); + assert_eq!(operator_function_name("?"), "\"?\""); + assert_eq!(operator_function_name("?|"), "\"?|\""); + assert_eq!(operator_function_name("?&"), "\"?&\""); + assert_eq!(operator_function_name("@?"), "\"@?\""); + assert_eq!(operator_function_name("@@"), "\"@@\""); + assert_eq!(operator_function_name("#>"), "\"#>\""); + assert_eq!(operator_function_name("#>>"), "\"#>>\""); + assert_eq!(operator_function_name("-"), "\"-\""); + assert_eq!(operator_function_name("#-"), "\"#-\""); + assert_eq!(operator_function_name("||"), "\"||\""); } #[test] fn selectivity_estimators() { - assert_eq!(operator("=").restrict, Some("eqsel")); - assert_eq!(operator("=").join, Some("eqjoinsel")); - assert_eq!(operator("<>").restrict, Some("neqsel")); - assert_eq!(operator("<").restrict, Some("scalarltsel")); - assert_eq!(operator("<=").restrict, Some("scalarlesel")); - assert_eq!(operator(">").restrict, Some("scalargtsel")); - assert_eq!(operator(">=").restrict, Some("scalargesel")); + assert_eq!(operator("=").metadata.restrict, Some("eqsel")); + assert_eq!(operator("=").metadata.join, Some("eqjoinsel")); + assert_eq!(operator("<>").metadata.restrict, Some("neqsel")); + assert_eq!(operator("<").metadata.restrict, Some("scalarltsel")); + assert_eq!(operator("<=").metadata.restrict, Some("scalarlesel")); + assert_eq!(operator(">").metadata.restrict, Some("scalargtsel")); + assert_eq!(operator(">=").metadata.restrict, Some("scalargesel")); } #[test] fn negators_and_commutators() { - assert_eq!(operator("=").negator, Some("<>")); - assert_eq!(operator("<>").negator, Some("=")); - assert_eq!(operator("<").commutator, Some(">")); - assert_eq!(operator("<").negator, Some(">=")); - assert_eq!(operator(">=").commutator, Some("<=")); + assert_eq!(operator("=").metadata.negator, Some("<>")); + assert_eq!(operator("<>").metadata.negator, Some("=")); + assert_eq!(operator("<").metadata.commutator, Some(">")); + assert_eq!(operator("<").metadata.negator, Some(">=")); + assert_eq!(operator(">=").metadata.commutator, Some("<=")); } #[test] - fn known_jsonb_operators_match_table_keys() { - let union: Vec<&str> = SYMMETRIC_OPERATORS - .iter() - .chain(PATH_OPERATORS) - .chain(BLOCKER_ONLY_OPERATORS) - .copied() - .collect(); + fn metadata_renders_only_when_present() { + assert_eq!( + operator("=").metadata.render().unwrap(), + "COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel" + ); + assert_eq!(operator("->").metadata.render(), None); + assert_eq!(operator("@>").metadata.render(), None); + } + + #[test] + fn catalog_symbols_match_expected_order() { let keys: Vec<&str> = OPERATORS.iter().map(|o| o.symbol).collect(); - assert_eq!(union, keys); assert_eq!( - union.len(), - SYMMETRIC_OPERATORS.len() + PATH_OPERATORS.len() + BLOCKER_ONLY_OPERATORS.len() + keys, + vec![ + "=", "<>", "<", "<=", ">", ">=", "@>", "<@", "->", "->>", "?", "?|", "?&", "@?", + "@@", "#>", "#>>", "-", "#-", "||" + ] ); } } From 2c08b8e7ec7995961dfe2bc360670046e1a075eb Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 20:56:18 +1000 Subject: [PATCH 35/93] fix(codegen): align Python fixture-values header with Rust generator The committed `_values.rs` files carry the Rust generator's header ("Generated from the `` row in `eql-scalars::CATALOG`"), but the Python generator still emitted the older "tasks/codegen/types/.toml" wording. CI's "regenerate fixture-value consts" step runs the Python generator (`codegen:domain:all`) and diffs the result, so the header mismatch failed the build. Re-align `templates.py` to the Rust header (the recurrence of the drift fixed in e186464) and update the now-stale assertion in `test_templates.py`. Verified: `codegen:domain:all` leaves tests/sqlx/src/fixtures clean and `codegen:parity` still passes. --- tasks/codegen/templates.py | 4 ++-- tasks/codegen/test_templates.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/codegen/templates.py b/tasks/codegen/templates.py index d79eb3ee..14fefbc6 100644 --- a/tasks/codegen/templates.py +++ b/tasks/codegen/templates.py @@ -76,8 +76,8 @@ def render_fixture_values_rs(spec: TypeSpec) -> str: f"//! Fixture plaintext values for the {spec.token} " "encrypted-domain family.\n" "//!\n" - f"//! Generated from tasks/codegen/types/{spec.token}.toml " - "`[fixture] values` —\n" + f"//! Generated from the `{spec.token}` row in `eql-scalars::CATALOG` " + "(`fixtures`) —\n" "//! the single source of truth shared by the fixture generator\n" f"//! (`fixtures::eql_v2_{spec.token}`) and the matrix oracle\n" "//! (`ScalarType::FIXTURE_VALUES`).\n\n" diff --git a/tasks/codegen/test_templates.py b/tasks/codegen/test_templates.py index 221dcd9b..4a24f923 100644 --- a/tasks/codegen/test_templates.py +++ b/tasks/codegen/test_templates.py @@ -49,7 +49,7 @@ def test_render_fixture_values_rs_emits_typed_const(): ) body = render_fixture_values_rs(spec) assert "pub const VALUES: &[i32] = &[" in body - assert "tasks/codegen/types/int4.toml" in body + assert "`int4` row in `eql-scalars::CATALOG`" in body # Sentinels map to named consts; numeric tokens pass through. assert "i32::MIN," in body assert "i32::MAX," in body From 92a833b47e5a5fb94d19fbf881fe69ae3db7d21f Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 08:13:52 +1000 Subject: [PATCH 36/93] refactor(codegen): split functions template into per-kind partials Replace the if/elif/else body in functions.sql.j2 with a dynamic {% include %} driven off each entry's serde "kind" tag (Extractor/Wrapper/Unsupported -> functions/.sql.j2). The parent template keeps the once-per-file header, -- REQUIRE: loop, and entry loop; each per-kind body lives in its own partial under templates/functions/. Dispatch stays in the template layer off the existing #[serde(tag="kind")] value, so FnEntry, generate.rs, and the Rust render path are unchanged. Generated SQL is line-normalized-identical (golden test + codegen:parity). --- crates/eql-codegen/src/context.rs | 32 +++++++++++++++++-- crates/eql-codegen/templates/functions.sql.j2 | 27 +--------------- .../templates/functions/extractor.sql.j2 | 7 ++++ .../templates/functions/unsupported.sql.j2 | 8 +++++ .../templates/functions/wrapper.sql.j2 | 7 ++++ 5 files changed, 52 insertions(+), 29 deletions(-) create mode 100644 crates/eql-codegen/templates/functions/extractor.sql.j2 create mode 100644 crates/eql-codegen/templates/functions/unsupported.sql.j2 create mode 100644 crates/eql-codegen/templates/functions/wrapper.sql.j2 diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs index cdb68e67..7ea33db1 100644 --- a/crates/eql-codegen/src/context.rs +++ b/crates/eql-codegen/src/context.rs @@ -15,8 +15,11 @@ pub fn normalize_sql(s: &str) -> String { .join("\n") } -/// Build the minijinja environment with the four embedded whole-file templates. -/// Templates are compiled in via `include_str!` — no runtime file IO. +/// Build the minijinja environment with the embedded templates: one whole-file +/// template per output file (`types`/`functions`/`operators`/`aggregates`) plus +/// the per-kind function-body partials that `functions.sql` dynamically +/// `{% include %}`s. Templates are compiled in via `include_str!` — no runtime +/// file IO. pub fn environment() -> minijinja::Environment<'static> { let mut env = minijinja::Environment::new(); // Preserve each template file's trailing newline so generated SQL files end @@ -29,6 +32,24 @@ pub fn environment() -> minijinja::Environment<'static> { include_str!("../templates/functions.sql.j2"), ) .expect("functions.sql template"); + // Per-kind function bodies, dynamically `{% include %}`d by the parent + // `functions.sql` template based on each entry's `kind` tag + // (Extractor/Wrapper/Unsupported -> extractor/wrapper/unsupported). + env.add_template( + "functions/extractor.sql.j2", + include_str!("../templates/functions/extractor.sql.j2"), + ) + .expect("functions/extractor.sql.j2 template"); + env.add_template( + "functions/wrapper.sql.j2", + include_str!("../templates/functions/wrapper.sql.j2"), + ) + .expect("functions/wrapper.sql.j2 template"); + env.add_template( + "functions/unsupported.sql.j2", + include_str!("../templates/functions/unsupported.sql.j2"), + ) + .expect("functions/unsupported.sql.j2 template"); env.add_template( "operators.sql", include_str!("../templates/operators.sql.j2"), @@ -299,13 +320,18 @@ mod tests { } #[test] - fn environment_has_four_templates() { + fn environment_has_whole_file_and_partial_templates() { let env = environment(); for name in [ + // One whole-file template per generated SQL file. "types.sql", "functions.sql", "operators.sql", "aggregates.sql", + // Per-kind partials included by functions.sql. + "functions/extractor.sql.j2", + "functions/wrapper.sql.j2", + "functions/unsupported.sql.j2", ] { assert!(env.get_template(name).is_ok(), "missing template {name}"); } diff --git a/crates/eql-codegen/templates/functions.sql.j2 b/crates/eql-codegen/templates/functions.sql.j2 index 0b75cd3a..ba4f3a02 100644 --- a/crates/eql-codegen/templates/functions.sql.j2 +++ b/crates/eql-codegen/templates/functions.sql.j2 @@ -5,30 +5,5 @@ --! @file encrypted_domain/{{ token }}/{{ name }}_functions.sql --! @brief Functions for {{ dom }}. {% for e in entries %} -{% if e.kind == "Extractor" -%} ---! @brief Index extractor for {{ dom }}. ---! @param a {{ dom }} ---! @return {{ e.ret }} -CREATE FUNCTION {{ domain_schema }}.{{ e.extractor }}(a {{ dom }}) -RETURNS {{ e.ret }} -LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT {{ core_schema }}.{{ e.ctor }}(a::jsonb) $$; -{% elif e.kind == "Wrapper" -%} ---! @brief Operator wrapper for {{ dom }}. ---! @param {{ e.args[0].name }} {{ e.args[0].ty }} ---! @param {{ e.args[1].name }} {{ e.args[1].ty }} ---! @return boolean -CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) -RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT {{ e.call_a }} {{ e.op }} {{ e.call_b }} $$; -{% else -%} ---! @brief Unsupported operator blocker for {{ dom }}. ---! @param {{ e.args[0].name }} {{ e.args[0].ty }} ---! @param {{ e.args[1].name }} {{ e.args[1].ty }} ---! @return {{ e.returns }} -CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) -RETURNS {{ e.returns }} IMMUTABLE PARALLEL SAFE -AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '{{ e.operator_lit }}', '{{ domain_lit }}'; END; $$ -LANGUAGE plpgsql; -{% endif -%} +{% include "functions/" ~ e.kind|lower ~ ".sql.j2" -%} {% endfor -%} diff --git a/crates/eql-codegen/templates/functions/extractor.sql.j2 b/crates/eql-codegen/templates/functions/extractor.sql.j2 new file mode 100644 index 00000000..da649b21 --- /dev/null +++ b/crates/eql-codegen/templates/functions/extractor.sql.j2 @@ -0,0 +1,7 @@ +--! @brief Index extractor for {{ dom }}. +--! @param a {{ dom }} +--! @return {{ e.ret }} +CREATE FUNCTION {{ domain_schema }}.{{ e.extractor }}(a {{ dom }}) +RETURNS {{ e.ret }} +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT {{ core_schema }}.{{ e.ctor }}(a::jsonb) $$; diff --git a/crates/eql-codegen/templates/functions/unsupported.sql.j2 b/crates/eql-codegen/templates/functions/unsupported.sql.j2 new file mode 100644 index 00000000..f33dab6c --- /dev/null +++ b/crates/eql-codegen/templates/functions/unsupported.sql.j2 @@ -0,0 +1,8 @@ +--! @brief Unsupported operator blocker for {{ dom }}. +--! @param {{ e.args[0].name }} {{ e.args[0].ty }} +--! @param {{ e.args[1].name }} {{ e.args[1].ty }} +--! @return {{ e.returns }} +CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) +RETURNS {{ e.returns }} IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '{{ e.operator_lit }}', '{{ domain_lit }}'; END; $$ +LANGUAGE plpgsql; diff --git a/crates/eql-codegen/templates/functions/wrapper.sql.j2 b/crates/eql-codegen/templates/functions/wrapper.sql.j2 new file mode 100644 index 00000000..243f2465 --- /dev/null +++ b/crates/eql-codegen/templates/functions/wrapper.sql.j2 @@ -0,0 +1,7 @@ +--! @brief Operator wrapper for {{ dom }}. +--! @param {{ e.args[0].name }} {{ e.args[0].ty }} +--! @param {{ e.args[1].name }} {{ e.args[1].ty }} +--! @return boolean +CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT {{ e.call_a }} {{ e.op }} {{ e.call_b }} $$; From 73ef27dc54d429dc1a67ed6dab116cf3016727ee Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 08:26:40 +1000 Subject: [PATCH 37/93] refactor(codegen): prune dead consts and enable dead-code detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove VERSION_KEY and ENVELOPE_VERSION — dead leftovers from the Python port never wired into the Rust generator (the version-pin clause is a literal 'v'='2' in types.sql.j2). Fold the always-present CIPHERTEXT_KEY into ENVELOPE_KEYS as ["v","i","c"]; context::domain_block keeps the same v,i,c ordering ahead of term keys, so generated SQL is byte-identical. Demote all consts.rs items from pub to pub(crate). Nothing outside the crate imports eql_codegen::consts, so this is safe and lets the built-in dead_code lint see them — the existing `clippy -- -D warnings` CI step (mise run test:crates) now fails on unused internal items, no workflow change needed. Verified: clippy -D warnings clean, 63 lib + 3 parity tests pass, codegen:parity OK (generated SQL unchanged). --- crates/eql-codegen/src/consts.rs | 22 +++++++++------------- crates/eql-codegen/src/context.rs | 1 - 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/crates/eql-codegen/src/consts.rs b/crates/eql-codegen/src/consts.rs index c1b9e75c..3f2fdbf0 100644 --- a/crates/eql-codegen/src/consts.rs +++ b/crates/eql-codegen/src/consts.rs @@ -2,29 +2,25 @@ /// SQL generated-file marker. The SQL templates emit this as their first line; /// the writer uses it only to recognise files it owns (overwrite/clean safety). -pub const AUTO_GENERATED_HEADER: &str = "-- AUTOMATICALLY GENERATED FILE.\n"; +pub(crate) const AUTO_GENERATED_HEADER: &str = "-- AUTOMATICALLY GENERATED FILE.\n"; /// Rust generated-file marker, prepended to `_values.rs` (which has no /// template). Rust comment syntax so the `.rs` file stays valid. -pub const AUTO_GENERATED_HEADER_RS: &str = "// AUTOMATICALLY GENERATED FILE.\n"; +pub(crate) const AUTO_GENERATED_HEADER_RS: &str = "// AUTOMATICALLY GENERATED FILE.\n"; /// Schema housing the encrypted-domain families. -pub const DOMAIN_SCHEMA: &str = "eql_v3"; +pub(crate) const DOMAIN_SCHEMA: &str = "eql_v3"; /// Schema owning the core index-term types/constructors. -pub const CORE_SCHEMA: &str = "eql_v2"; +pub(crate) const CORE_SCHEMA: &str = "eql_v2"; -/// Envelope keys checked for presence in every domain CHECK, in order. -pub const ENVELOPE_KEYS: &[&str] = &["v", "i"]; -/// Ciphertext payload key. -pub const CIPHERTEXT_KEY: &str = "c"; -/// Envelope-version key whose value is pinned. -pub const VERSION_KEY: &str = "v"; -/// EQL payload-format version pinned by the domain CHECK. -pub const ENVELOPE_VERSION: u32 = 2; +/// Always-present payload keys checked for presence in every domain CHECK, in +/// order: envelope version (`v`), ident (`i`), ciphertext (`c`). Term-specific +/// keys are appended after these by `context::domain_block`. +pub(crate) const ENVELOPE_KEYS: &[&str] = &["v", "i", "c"]; /// Escape a string for use inside a single-quoted SQL literal by doubling /// embedded single quotes. Port of templates.py `_sql_str`. -pub fn sql_str(s: &str) -> String { +pub(crate) fn sql_str(s: &str) -> String { s.replace('\'', "''") } diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs index 7ea33db1..7066377f 100644 --- a/crates/eql-codegen/src/context.rs +++ b/crates/eql-codegen/src/context.rs @@ -85,7 +85,6 @@ pub fn domain_block(token: &str, domain: &DomainSpec) -> DomainBlock { let name = full_domain_name(token, domain.suffix); let mut keys: Vec = ENVELOPE_KEYS.iter().map(|k| sql_str(k)).collect(); - keys.push(sql_str(CIPHERTEXT_KEY)); for k in Term::term_json_keys(domain.terms) { keys.push(sql_str(k)); } From 22c7be485588110cddf5c0cbdd91427d6f08cec2 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 08:47:51 +1000 Subject: [PATCH 38/93] refactor(codegen): use a raw string for the values.rs renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the \n\-continuation format string in render_fixture_values_rs with a flush-left raw string literal — same emitted bytes, far more legible. Kept as format! (not a minijinja template) deliberately: _values.rs is validated byte-exact, unlike the line-normalized SQL surface, and the type-aware literal rendering stays in Rust regardless. Verified byte-identical via codegen:parity (rust_generator_matches_ committed_values_rs); fmt/clippy -D warnings clean, all tests pass. --- crates/eql-codegen/src/templates.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/eql-codegen/src/templates.rs b/crates/eql-codegen/src/templates.rs index 1f2a33e3..e0da5290 100644 --- a/crates/eql-codegen/src/templates.rs +++ b/crates/eql-codegen/src/templates.rs @@ -14,17 +14,20 @@ pub fn render_fixture_values_rs(spec: &ScalarSpec) -> String { for &f in spec.fixtures { literals.push_str(&format!(" {},\n", f.render_literal(spec.kind))); } + // Raw string keeps the emitted shape legible while staying byte-exact; + // lines are flush-left because raw-string whitespace is literal output. format!( - "//! Fixture plaintext values for the {token} encrypted-domain family.\n\ - //!\n\ - //! Generated from the `{token}` row in `eql-scalars::CATALOG` (`fixtures`) —\n\ - //! the single source of truth shared by the fixture generator\n\ - //! (`fixtures::eql_v2_{token}`) and the matrix oracle\n\ - //! (`ScalarType::FIXTURE_VALUES`).\n\n\ - /// Distinct plaintext values present in the `eql_v2_{token}` fixture.\n\ - pub const VALUES: &[{rust_type}] = &[\n\ - {literals}\ - ];\n" + r#"//! Fixture plaintext values for the {token} encrypted-domain family. +//! +//! Generated from the `{token}` row in `eql-scalars::CATALOG` (`fixtures`) — +//! the single source of truth shared by the fixture generator +//! (`fixtures::eql_v2_{token}`) and the matrix oracle +//! (`ScalarType::FIXTURE_VALUES`). + +/// Distinct plaintext values present in the `eql_v2_{token}` fixture. +pub const VALUES: &[{rust_type}] = &[ +{literals}]; +"# ) } From 88ad559414b7f98fc4ba7b8dc342423ca609c4cb Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 08:58:05 +1000 Subject: [PATCH 39/93] docs(tests): clarify eql_v2_int* are fixture names, not domain types The `eql_v2_int4`/`eql_v2_int2` fixtures are plain jsonb-payload test tables; the encrypted-domain types they exercise are correctly under eql_v3 (eql_v3.int4_eq/_ord), derived from the scalar type and applied via per-query cast. Reword matrix.rs's misleading "EQL domain type name" comment on `eql_type` to say fixture/table name, and point the fixtures mod.rs value-const docs at eql-scalars::CATALOG instead of the retired tasks/codegen Python path. Comment-only; no behaviour change. --- tests/sqlx/src/fixtures/mod.rs | 8 ++++---- tests/sqlx/src/matrix.rs | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index ac363a49..f616f556 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -26,14 +26,14 @@ pub mod cipherstash; pub mod driver; -/// Generated from tasks/codegen/types/int4.toml `[fixture] values`. -/// Committed and verified by CI; never hand-edit (`mise run codegen:domain int4`). +/// Generated from the `int4` row in `eql-scalars::CATALOG` (`fixtures`). +/// Committed and verified by CI; never hand-edit (regenerated by `eql-codegen`). pub mod int4_values; pub mod eql_v2_int4; -/// Generated from tasks/codegen/types/int2.toml `[fixture] values`. -/// Committed and verified by CI; never hand-edit (`mise run codegen:domain int2`). +/// Generated from the `int2` row in `eql-scalars::CATALOG` (`fixtures`). +/// Committed and verified by CI; never hand-edit (regenerated by `eql-codegen`). pub mod int2_values; pub mod eql_v2_int2; diff --git a/tests/sqlx/src/matrix.rs b/tests/sqlx/src/matrix.rs index ac690a16..644ea351 100644 --- a/tests/sqlx/src/matrix.rs +++ b/tests/sqlx/src/matrix.rs @@ -142,9 +142,11 @@ fn collect_index_scan_nodes(value: &serde_json::Value, found: &mut Vec<(String, /// supported comparison operators, 2 path operators, and the standard /// blocker / index partitions. /// -/// `eql_type` is the EQL domain type name (e.g. `"eql_v2_int4"`). It is -/// used as the SQLx fixture `scripts(...)` ref, which sqlx parses as a -/// token-level string literal — so it must be a literal, not derived. +/// `eql_type` is the fixture/table name (e.g. `"eql_v2_int4"`), used as the +/// SQLx fixture `scripts(...)` ref — sqlx parses it as a token-level string +/// literal, so it must be a literal, not derived. It is NOT a domain type +/// name: the `eql_v3.*` domains exercised here are derived from the scalar +/// type (see `scalar_domains.rs`, `format!("eql_v3.{}…", T::PG_TYPE)`). /// /// Pivots — the comparison anchors swept by the correctness / cross-shape /// arms — are derived from the scalar type: `MIN`, `MAX`, and zero From fd6f3a098cde258de9440d6f7cab14c5e3c57ad3 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 09:54:08 +1000 Subject: [PATCH 40/93] docs(comments): correct stale domain references in lint catalog and fixtures The domain_over_domain/domain_opclass lint-catalog header described the target as an eql_v2_* domain, but the checks match both eql_v3.* and legacy public.eql_v2_*. The int2/int4 fixture doc-comments called the fixture struct names a 'domain'; the actual domains are eql_v3.int2 / eql_v3.int4 (see 88ad559). Comment-only; no behaviour change. --- src/lint/lints.sql | 8 +++++--- tests/sqlx/src/fixtures/eql_v2_int2.rs | 2 +- tests/sqlx/src/fixtures/eql_v2_int4.rs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lint/lints.sql b/src/lint/lints.sql index cf66f7d4..f7870abd 100644 --- a/src/lint/lints.sql +++ b/src/lint/lints.sql @@ -48,13 +48,15 @@ --! PostgreSQL skips the body and returns NULL --! on NULL arguments, silently bypassing the --! RAISE. ---! `domain_over_domain` — an `eql_v2_*` domain is derived from another ---! `eql_v2_*` domain rather than jsonb. +--! `domain_over_domain` — an encrypted domain (`eql_v3.*` or +--! `public.eql_v2_*`) is derived from another +--! encrypted domain rather than jsonb. --! Operators resolve against the ultimate base --! type, so the derived domain does not --! inherit the base domain's blocker surface. --! `domain_opclass` — an operator class is declared FOR TYPE on an ---! `eql_v2_*` domain. Opclasses on domains +--! encrypted domain (`eql_v3.*` or +--! `public.eql_v2_*`). Opclasses on domains --! bypass operator resolution; use a --! functional index on the extractor instead. --! diff --git a/tests/sqlx/src/fixtures/eql_v2_int2.rs b/tests/sqlx/src/fixtures/eql_v2_int2.rs index ec4a1333..0848f85e 100644 --- a/tests/sqlx/src/fixtures/eql_v2_int2.rs +++ b/tests/sqlx/src/fixtures/eql_v2_int2.rs @@ -4,7 +4,7 @@ //! (`MIN`/`MAX`), zero, a pair near the ±32767 boundary, and //! small/medium/large magnitudes. The generated //! `tests/sqlx/fixtures/eql_v2_int2.sql` is a plain `jsonb`-payload table with -//! no EQL dependency; the `eql_v2_int2` domain is layered on top by casting +//! no EQL dependency; the `eql_v3.int2` domain is layered on top by casting //! `payload` per query. use super::int2_values::VALUES; diff --git a/tests/sqlx/src/fixtures/eql_v2_int4.rs b/tests/sqlx/src/fixtures/eql_v2_int4.rs index 429e47d9..fd28b15b 100644 --- a/tests/sqlx/src/fixtures/eql_v2_int4.rs +++ b/tests/sqlx/src/fixtures/eql_v2_int4.rs @@ -3,7 +3,7 @@ //! 17 integers spanning a negative boundary, the i32 signed extremes //! (`MIN`/`MAX`), zero, and small/medium/large magnitudes. The generated //! `tests/sqlx/fixtures/eql_v2_int4.sql` is a plain `jsonb`-payload table with -//! no EQL dependency; #225 layers the `eql_v2_int4` domain on top by casting +//! no EQL dependency; #225 layers the `eql_v3.int4` domain on top by casting //! `payload` per query. use super::int4_values::VALUES; From 0dc8363f9ed4619e8c7b55cfa9ef1236a3370974 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:09:09 +1000 Subject: [PATCH 41/93] build(test): path-dep eql-scalars from tests/sqlx for catalog-driven fixtures --- Cargo.lock | 1 + tests/sqlx/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ebc39165..2d902e93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1174,6 +1174,7 @@ version = "0.1.0" dependencies = [ "anyhow", "cipherstash-client", + "eql-scalars", "hex", "jsonschema", "paste", diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index 50f7d035..c7a8525a 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -13,6 +13,7 @@ hex = "0.4" jsonschema = { version = "0.46.4", default-features = false } cipherstash-client = { version = "0.35", features = ["tokio"] } paste = "1" +eql-scalars = { path = "../../crates/eql-scalars" } [dev-dependencies] # None needed - tests live in this crate From 21e0c9eb2aff93d48e5939b47430f1e0992969d8 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:10:21 +1000 Subject: [PATCH 42/93] build: generate encrypted-domain SQL via eql-codegen (Rust), not Python --- tasks/build.sh | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tasks/build.sh b/tasks/build.sh index cef25521..621b0b72 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash #MISE description="Build SQL into single release file" #MISE alias="b" -#MISE sources=["src/**/*.sql", "tasks/pin_search_path.sql", "tasks/uninstall.sql", "tasks/uninstall-protect.sql", "tasks/codegen/types/*.toml", "tasks/codegen/*.py"] +#MISE sources=["src/**/*.sql", "tasks/pin_search_path.sql", "tasks/uninstall.sql", "tasks/uninstall-protect.sql", "crates/eql-scalars/src/**/*.rs", "crates/eql-codegen/src/**/*.rs"] #MISE outputs=["release/cipherstash-encrypt.sql","release/cipherstash-encrypt-uninstall.sql","release/cipherstash-encrypt-protect.sql","release/cipherstash-encrypt-protect-uninstall.sql"] #USAGE flag "--version " help="Specify release version of EQL" default="DEV" @@ -9,25 +9,27 @@ set -euo pipefail -# Regenerate encrypted-domain SQL from TOML specs before building. +# Regenerate encrypted-domain SQL from the Rust catalog before building. # Generated files (src/encrypted_domain//_*.sql) are gitignored; the -# manifest at tasks/codegen/types/.toml is the source of truth. +# catalog at crates/eql-scalars/src (eql-scalars::CATALOG) is the source of +# truth, rendered by the eql-codegen binary. # -# Nuke every generated file first so a deleted or renamed manifest can't +# Nuke every generated file first so a type removed from the catalog can't # leave orphans in src/ that the `src/**/*.sql` build glob would silently -# pick up. writer.py cleans within a directory it's regenerating, but it -# never runs for a type whose manifest no longer exists. Hand-written -# *_extensions.sql is preserved by the name patterns; -mindepth 2 keeps -# the type-agnostic src/encrypted_domain/functions.sql safe. +# pick up. eql-codegen cleans within a directory it regenerates, but never +# runs for a type no longer in the catalog. Hand-written *_extensions.sql is +# preserved by the name patterns; -mindepth 2 keeps the type-agnostic +# src/encrypted_domain/functions.sql safe. find src/encrypted_domain -mindepth 2 -type f \ \( -name '*_types.sql' -o -name '*_functions.sql' -o -name '*_operators.sql' \ -o -name '*_aggregates.sql' \) \ -delete 2>/dev/null || true -# Regenerate every type — single source of truth for the enumeration lives in -# tasks/codegen/generate.py (sorted, deterministic, aggregate exit code). The -# orphan sweep above still handles the manifest-deleted case --all cannot. -mise exec python -- python -m tasks.codegen.generate --all +# Regenerate every type — the catalog (eql-scalars::CATALOG) is the single +# source of truth for the enumeration; eql-codegen renders all SQL and all +# tests/sqlx/src/fixtures/_values.rs in one deterministic run. The orphan +# sweep above still handles the catalog-removed case the generator cannot. +cargo run -p eql-codegen # Fail loudly if any file referenced in a tsorted dep list doesn't exist. # Without this, `xargs cat` would print `cat: foo.sql: No such file or directory` From a3cc631d0eca3963505ae0ea8b1593b700dc0511 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:11:44 +1000 Subject: [PATCH 43/93] test(fixtures): add catalog-driven generate-all-fixtures entry point --- tests/sqlx/tests/generate_all_fixtures.rs | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/sqlx/tests/generate_all_fixtures.rs diff --git a/tests/sqlx/tests/generate_all_fixtures.rs b/tests/sqlx/tests/generate_all_fixtures.rs new file mode 100644 index 00000000..bbc1f595 --- /dev/null +++ b/tests/sqlx/tests/generate_all_fixtures.rs @@ -0,0 +1,46 @@ +//! Catalog-driven "generate every encrypted fixture" entry point. +//! +//! Replaces the Python-era `fixture:generate ` per-type scripts and the +//! `fixture:generate:all` TOML-glob loop (which spawned a separate `cargo test` +//! per type). This runs ALL scalar fixture generators in ONE process, iterating +//! `eql_scalars::CATALOG` for the authoritative token set. +//! +//! The encrypted-fixture logic itself is unchanged — each type's +//! `fixtures::eql_v2_::spec().run()` still produces +//! `tests/sqlx/fixtures/eql_v2_.sql` exactly as before. +//! +//! Gated behind `fixture-gen` (needs a live Postgres + CS_* creds). Run via: +//! mise run fixture:generate:all +#![cfg(feature = "fixture-gen")] + +use eql_scalars::CATALOG; +use eql_tests::fixtures; + +/// Map a catalog token to its fixture generator and run it. A token present in +/// the catalog but missing here is a wiring gap — fail loudly so a new scalar +/// type cannot silently skip fixture generation. +async fn generate_for_token(token: &str) -> anyhow::Result<()> { + match token { + "int2" => fixtures::eql_v2_int2::spec().run().await, + "int4" => fixtures::eql_v2_int4::spec().run().await, + other => anyhow::bail!( + "no fixture generator wired for catalog token '{other}'. \ + Add an arm to generate_for_token in tests/sqlx/tests/generate_all_fixtures.rs \ + (and the eql_v2_{other} fixture module). See the encrypted-domain spec §9." + ), + } +} + +#[tokio::test] +#[ignore = "generator — run via `mise run fixture:generate:all`"] +async fn generate_all() -> anyhow::Result<()> { + let mut generated = 0usize; + for spec in CATALOG { + eprintln!("Generating fixture eql_v2_{}...", spec.token); + generate_for_token(spec.token).await?; + generated += 1; + } + assert!(generated > 0, "CATALOG is empty — nothing to generate"); + eprintln!("Regenerated {generated} scalar fixture(s)."); + Ok(()) +} From 76e1e0681ee6563acd8dec6a2357e6843d19a432 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:13:58 +1000 Subject: [PATCH 44/93] build(fixtures): collapse fixture:generate{,:all} into one catalog-driven task --- tasks/fixtures.toml | 61 +++++++++++---------------------------------- 1 file changed, 14 insertions(+), 47 deletions(-) diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml index ecfe3ad9..f24bc684 100644 --- a/tasks/fixtures.toml +++ b/tasks/fixtures.toml @@ -1,8 +1,13 @@ -["fixture:generate"] -description = "Generate a SQLx fixture script via cipherstash-client" -# Runs the gated generator for the named fixture. Writes -# tests/sqlx/fixtures/.sql. Must run inside the crate — there is no -# root Cargo.toml — matching test:schema / test:sqlx:watch. +["fixture:generate:all"] +description = "Regenerate every scalar SQLx fixture in one process, driven by eql-scalars::CATALOG" +# Replaces the Python-era per-type `fixture:generate ` script and the +# TOML-glob `fixture:generate:all` loop (one `cargo test` per type). The +# generate_all_fixtures test iterates eql-scalars::CATALOG and runs every +# eql_v2_ fixture generator in a SINGLE process. The encrypted-fixture logic +# is unchanged; only enumeration + entry point changed. +# +# Writes tests/sqlx/fixtures/eql_v2_.sql (gitignored — regenerated on every +# `mise run test:sqlx`). # # Prerequisites: # - mise run postgres:up (Postgres with EQL installed) @@ -11,48 +16,10 @@ description = "Generate a SQLx fixture script via cipherstash-client" # CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN ZeroKMS auth (AutoStrategy) # CS_CLIENT_ID + CS_CLIENT_KEY client key (EnvKeyProvider) # -# Usage: mise run fixture:generate eql_v2_int4 +# Must run inside the crate — a workspace member still builds from its own dir. dir = "{{config_root}}/tests/sqlx" run = """ -fixture="{{arg(name="fixture")}}" -# Match the Rust `FixtureIdentifier` rule: `^[a-z][a-z0-9_]*$`. Reject -# empty, leading-digit, and any non-lowercase-alphanumeric-underscore -# input here so the failure mode is a clear shell error rather than a -# Rust panic during the cargo test invocation. -case "$fixture" in - (''|[0-9]*|*[!a-z0-9_]*) echo "Invalid fixture name: $fixture (expected ^[a-z][a-z0-9_]*$)" >&2; exit 1 ;; -esac - -cargo test --features fixture-gen --lib \ - "fixtures::${fixture}::generate" \ - -- --ignored --exact --nocapture -""" - -["fixture:generate:all"] -description = "Regenerate every scalar SQLx fixture declared by a type manifest" -# Enumerates tasks/codegen/types/*.toml — the SAME manifests that -# `codegen:domain:all` drives — and regenerates the SQLx fixture for each type -# whose manifest declares a [fixture] table. This keeps the test fixtures in -# lockstep with the declared scalar types: adding a new scalar type (a new -# .toml with a [fixture] table) is picked up automatically, so the test -# task never has to hand-list each fixture. Same prerequisites as -# `fixture:generate` (Postgres up + CS_* credentials). -dir = "{{config_root}}" -run = """ -generated=0 -for manifest in tasks/codegen/types/*.toml; do - # Guard the no-match case (glob stays literal under POSIX sh). - [ -e "$manifest" ] || continue - # Only types that declare a [fixture] table have a SQLx fixture generator. - grep -qE '^\\[fixture\\]' "$manifest" || continue - token=$(basename "$manifest" .toml) - echo "Generating fixture eql_v2_${token}..." - mise run fixture:generate "eql_v2_${token}" - generated=$((generated + 1)) -done -if [ "$generated" -eq 0 ]; then - echo "No scalar manifests with a [fixture] table found in tasks/codegen/types/" >&2 - exit 1 -fi -echo "Regenerated ${generated} scalar fixture(s)." +set -euo pipefail +cargo test --features fixture-gen --test generate_all_fixtures \ + generate_all -- --ignored --exact --nocapture """ From b1abf23f488f7c792e23a8a0e01ac2bd9acc5e6f Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:14:23 +1000 Subject: [PATCH 45/93] docs(mise): describe catalog-driven fixture regeneration in test:sqlx --- mise.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mise.toml b/mise.toml index e627b45d..b17117b8 100644 --- a/mise.toml +++ b/mise.toml @@ -58,9 +58,10 @@ cd tests/sqlx sqlx migrate run # Regenerate fixtures every run — they are not committed (see .gitignore). -# fixture:generate:all enumerates every scalar manifest in -# tasks/codegen/types/ that declares a [fixture] table, so new scalar types -# are picked up automatically without editing this task. +# fixture:generate:all iterates eql-scalars::CATALOG and generates every +# scalar fixture in one process, so new scalar types are picked up +# automatically (add the catalog row + the fixture wiring) without editing +# this task. # Generator encrypts via cipherstash-client directly, which needs BOTH a # ZeroKMS auth credential (CS_CLIENT_ACCESS_KEY + CS_WORKSPACE_CRN, via # AutoStrategy) AND a client key (CS_CLIENT_ID + CS_CLIENT_KEY, via From de08b28d2b5935aba44d134eaaf63f83e00a8f2a Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:32:03 +1000 Subject: [PATCH 46/93] test(matrix): collapse per-type inventory to one catalog-reconciled snapshot --- mise.toml | 76 ++++++-- tests/sqlx/snapshots/int2_matrix_tests.txt | 211 --------------------- tests/sqlx/snapshots/int4_matrix_tests.txt | 211 --------------------- tests/sqlx/snapshots/matrix_tests.txt | 211 +++++++++++++++++++++ 4 files changed, 267 insertions(+), 442 deletions(-) delete mode 100644 tests/sqlx/snapshots/int2_matrix_tests.txt delete mode 100644 tests/sqlx/snapshots/int4_matrix_tests.txt create mode 100644 tests/sqlx/snapshots/matrix_tests.txt diff --git a/mise.toml b/mise.toml index b17117b8..c92d222b 100644 --- a/mise.toml +++ b/mise.toml @@ -129,29 +129,65 @@ cargo test -p eql-scalars -p eql-codegen """ [tasks."test:matrix:inventory"] -description = "Regenerate the int4/int2 matrix test-name inventory snapshots (no database required)" +description = "Verify the matrix test-name set against the single canonical snapshot, catalog-cross-checked (no database required)" dir = "{{config_root}}/tests/sqlx" run = """ -# Pin an explicit feature set so the inventory is deterministic regardless of -# the caller's local flags. `--no-default-features` keeps the `scale` arm -# (`#[cfg(feature = "scale")]`) excluded — its add/delete is a known blind spot -# of this default-feature inventory, covered instead by the scale gate + the -# family::mutations negative controls. `--list` enumerates the whole -# encrypted_domain binary (family::support, family::inlinability, -# family::mutations, scalars::int4, scalars::int2); the per-scalar `grep` -# scopes each snapshot to that matrix only, so landing other family tests -# never dirties it. `LC_ALL=C sort` makes ordering byte-stable across locales -# (a bare `sort` is locale-dependent and yields spurious CI diffs). +# ONE canonical, token-normalized snapshot (snapshots/matrix_tests.txt) pins the +# set of macro-emitted matrix test names. The two per-type snapshots are gone: +# they were byte-identical modulo the type token, so one canonical set plus a +# per-type normalize+compare carries the same signal at 1/N the committed surface. +# +# Steps: +# 1. List the encrypted_domain binary ONCE (deterministic; reused below). +# 2. Discover the set of scalar types present FROM THE BINARY'S OWN OUTPUT +# (scalars:::: prefixes) — never a directory glob. +# 3. For each discovered type, normalize its token to and assert its set +# equals the canonical snapshot. Assert at least one type is present. +# 4. Completeness cross-check: assert the discovered type set equals +# `eql-codegen list-types`. A catalog type added without its matrix wiring +# (no scalars:::: tests in the binary) fails here. +# +# `--no-default-features` excludes the `scale` arm (a known, documented blind +# spot, covered by the scale gate + family::mutations negative controls). +# `LC_ALL=C sort` makes ordering byte-stable across locales. No database needed. set -euo pipefail -mkdir -p snapshots -cargo test --no-default-features --test encrypted_domain -- --list | - sed -n 's/: test$//p' | - grep '^scalars::int4' | - LC_ALL=C sort > snapshots/int4_matrix_tests.txt -cargo test --no-default-features --test encrypted_domain -- --list | - sed -n 's/: test$//p' | - grep '^scalars::int2' | - LC_ALL=C sort > snapshots/int2_matrix_tests.txt + +test -f snapshots/matrix_tests.txt || { echo "snapshots/matrix_tests.txt missing — regenerate (see snapshots/README.md)." >&2; exit 1; } + +listing=$(cargo test --no-default-features --test encrypted_domain -- --list | sed -n 's/: test$//p') + +# Types present in the binary, from scalars:::: prefixes. +discovered=$(printf '%s\\n' "$listing" \ + | sed -n 's/^scalars::\\([a-z0-9_]*\\)::.*/\\1/p' \ + | LC_ALL=C sort -u) +[ -n "$discovered" ] || { echo "No scalars:::: tests found in the encrypted_domain binary." >&2; exit 1; } + +# Per-type normalize + compare against the canonical snapshot. +checked=0 +while IFS= read -r t; do + [ -n "$t" ] || continue + printf '%s\\n' "$listing" | grep "^scalars::${t}::" \ + | sed -e "s/^scalars::${t}::/scalars::::/" -e "s/_${t}_/__/g" | LC_ALL=C sort > "/tmp/matrix-norm-${t}.txt" + if ! cmp -s "/tmp/matrix-norm-${t}.txt" snapshots/matrix_tests.txt; then + echo "Matrix test-name set for '${t}' differs from snapshots/matrix_tests.txt:" >&2 + diff snapshots/matrix_tests.txt "/tmp/matrix-norm-${t}.txt" >&2 || true + exit 1 + fi + checked=$((checked + 1)) +done <<< "$discovered" +[ "$checked" -gt 0 ] || { echo "No scalar type matched the canonical snapshot." >&2; exit 1; } + +# Completeness cross-check against the catalog (the single source of truth). +catalog=$(cd "{{config_root}}" && cargo run -p eql-codegen -- list-types | LC_ALL=C sort -u) +if [ "$discovered" != "$catalog" ]; then + echo "Catalog types and matrix-wired types disagree." >&2 + echo " catalog (eql-codegen list-types): $(echo "$catalog" | tr '\\n' ' ')" >&2 + echo " matrix-wired (binary --list): $(echo "$discovered" | tr '\\n' ' ')" >&2 + echo "A catalog type missing its matrix wiring (or a wired type not in the catalog) trips this." >&2 + exit 1 +fi + +echo "Matrix inventory OK: ${checked} type(s) match the canonical snapshot; catalog reconciled." """ [tasks."test:matrix:expand"] diff --git a/tests/sqlx/snapshots/int2_matrix_tests.txt b/tests/sqlx/snapshots/int2_matrix_tests.txt deleted file mode 100644 index 3b6ed674..00000000 --- a/tests/sqlx/snapshots/int2_matrix_tests.txt +++ /dev/null @@ -1,211 +0,0 @@ -scalars::int2::matrix_int2_eq_aggregate_typecheck_max -scalars::int2::matrix_int2_eq_aggregate_typecheck_min -scalars::int2::matrix_int2_eq_contained_by_blocker -scalars::int2::matrix_int2_eq_contains_blocker -scalars::int2::matrix_int2_eq_count_distinct_extractor -scalars::int2::matrix_int2_eq_count_path_cast -scalars::int2::matrix_int2_eq_count_typed_column -scalars::int2::matrix_int2_eq_eq_pivot_max_correctness -scalars::int2::matrix_int2_eq_eq_pivot_max_cross_shape -scalars::int2::matrix_int2_eq_eq_pivot_min_correctness -scalars::int2::matrix_int2_eq_eq_pivot_min_cross_shape -scalars::int2::matrix_int2_eq_eq_pivot_zero_correctness -scalars::int2::matrix_int2_eq_eq_pivot_zero_cross_shape -scalars::int2::matrix_int2_eq_eq_supported_null -scalars::int2::matrix_int2_eq_gt_blocker -scalars::int2::matrix_int2_eq_gte_blocker -scalars::int2::matrix_int2_eq_index_engages_btree -scalars::int2::matrix_int2_eq_index_engages_hash -scalars::int2::matrix_int2_eq_lt_blocker -scalars::int2::matrix_int2_eq_lte_blocker -scalars::int2::matrix_int2_eq_native_absent_ops -scalars::int2::matrix_int2_eq_neq_pivot_max_correctness -scalars::int2::matrix_int2_eq_neq_pivot_max_cross_shape -scalars::int2::matrix_int2_eq_neq_pivot_min_correctness -scalars::int2::matrix_int2_eq_neq_pivot_min_cross_shape -scalars::int2::matrix_int2_eq_neq_pivot_zero_correctness -scalars::int2::matrix_int2_eq_neq_pivot_zero_cross_shape -scalars::int2::matrix_int2_eq_neq_supported_null -scalars::int2::matrix_int2_eq_path_op_blockers -scalars::int2::matrix_int2_eq_payload_check -scalars::int2::matrix_int2_eq_planner_metadata_eq -scalars::int2::matrix_int2_eq_sanity -scalars::int2::matrix_int2_eq_typed_column_blocker -scalars::int2::matrix_int2_fixture_shape -scalars::int2::matrix_int2_ord_aggregate_group_by_max -scalars::int2::matrix_int2_ord_aggregate_group_by_min -scalars::int2::matrix_int2_ord_aggregate_max -scalars::int2::matrix_int2_ord_aggregate_max_all_null -scalars::int2::matrix_int2_ord_aggregate_max_empty -scalars::int2::matrix_int2_ord_aggregate_max_mixed_null -scalars::int2::matrix_int2_ord_aggregate_min -scalars::int2::matrix_int2_ord_aggregate_min_all_null -scalars::int2::matrix_int2_ord_aggregate_min_empty -scalars::int2::matrix_int2_ord_aggregate_min_mixed_null -scalars::int2::matrix_int2_ord_aggregate_parallel_safe -scalars::int2::matrix_int2_ord_contained_by_blocker -scalars::int2::matrix_int2_ord_contains_blocker -scalars::int2::matrix_int2_ord_count_distinct_extractor -scalars::int2::matrix_int2_ord_count_path_cast -scalars::int2::matrix_int2_ord_count_typed_column -scalars::int2::matrix_int2_ord_eq_pivot_max_correctness -scalars::int2::matrix_int2_ord_eq_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_eq_pivot_min_correctness -scalars::int2::matrix_int2_ord_eq_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_eq_pivot_zero_correctness -scalars::int2::matrix_int2_ord_eq_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_eq_supported_null -scalars::int2::matrix_int2_ord_gt_pivot_max_correctness -scalars::int2::matrix_int2_ord_gt_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_gt_pivot_min_correctness -scalars::int2::matrix_int2_ord_gt_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_gt_pivot_zero_correctness -scalars::int2::matrix_int2_ord_gt_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_gt_supported_null -scalars::int2::matrix_int2_ord_gte_pivot_max_correctness -scalars::int2::matrix_int2_ord_gte_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_gte_pivot_min_correctness -scalars::int2::matrix_int2_ord_gte_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_gte_pivot_zero_correctness -scalars::int2::matrix_int2_ord_gte_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_gte_supported_null -scalars::int2::matrix_int2_ord_index_engages_btree -scalars::int2::matrix_int2_ord_lt_pivot_max_correctness -scalars::int2::matrix_int2_ord_lt_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_lt_pivot_min_correctness -scalars::int2::matrix_int2_ord_lt_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_lt_pivot_zero_correctness -scalars::int2::matrix_int2_ord_lt_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_lt_supported_null -scalars::int2::matrix_int2_ord_lte_pivot_max_correctness -scalars::int2::matrix_int2_ord_lte_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_lte_pivot_min_correctness -scalars::int2::matrix_int2_ord_lte_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_lte_pivot_zero_correctness -scalars::int2::matrix_int2_ord_lte_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_lte_supported_null -scalars::int2::matrix_int2_ord_native_absent_ops -scalars::int2::matrix_int2_ord_neq_pivot_max_correctness -scalars::int2::matrix_int2_ord_neq_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_neq_pivot_min_correctness -scalars::int2::matrix_int2_ord_neq_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_neq_pivot_zero_correctness -scalars::int2::matrix_int2_ord_neq_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_neq_supported_null -scalars::int2::matrix_int2_ord_ord_routes_through_ob -scalars::int2::matrix_int2_ord_order_by_asc_no_where -scalars::int2::matrix_int2_ord_order_by_asc_nulls_first -scalars::int2::matrix_int2_ord_order_by_asc_nulls_last -scalars::int2::matrix_int2_ord_order_by_asc_with_where -scalars::int2::matrix_int2_ord_order_by_desc_no_where -scalars::int2::matrix_int2_ord_order_by_desc_nulls_first -scalars::int2::matrix_int2_ord_order_by_desc_nulls_last -scalars::int2::matrix_int2_ord_order_by_desc_with_where -scalars::int2::matrix_int2_ord_order_by_using_gt_rejects -scalars::int2::matrix_int2_ord_order_by_using_gte_rejects -scalars::int2::matrix_int2_ord_order_by_using_lt_rejects -scalars::int2::matrix_int2_ord_order_by_using_lte_rejects -scalars::int2::matrix_int2_ord_ore_aggregate_group_by_max -scalars::int2::matrix_int2_ord_ore_aggregate_group_by_min -scalars::int2::matrix_int2_ord_ore_aggregate_max -scalars::int2::matrix_int2_ord_ore_aggregate_max_all_null -scalars::int2::matrix_int2_ord_ore_aggregate_max_empty -scalars::int2::matrix_int2_ord_ore_aggregate_max_mixed_null -scalars::int2::matrix_int2_ord_ore_aggregate_min -scalars::int2::matrix_int2_ord_ore_aggregate_min_all_null -scalars::int2::matrix_int2_ord_ore_aggregate_min_empty -scalars::int2::matrix_int2_ord_ore_aggregate_min_mixed_null -scalars::int2::matrix_int2_ord_ore_aggregate_parallel_safe -scalars::int2::matrix_int2_ord_ore_contained_by_blocker -scalars::int2::matrix_int2_ord_ore_contains_blocker -scalars::int2::matrix_int2_ord_ore_count_distinct_extractor -scalars::int2::matrix_int2_ord_ore_count_path_cast -scalars::int2::matrix_int2_ord_ore_count_typed_column -scalars::int2::matrix_int2_ord_ore_eq_pivot_max_correctness -scalars::int2::matrix_int2_ord_ore_eq_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_ore_eq_pivot_min_correctness -scalars::int2::matrix_int2_ord_ore_eq_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_ore_eq_pivot_zero_correctness -scalars::int2::matrix_int2_ord_ore_eq_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_ore_eq_supported_null -scalars::int2::matrix_int2_ord_ore_gt_pivot_max_correctness -scalars::int2::matrix_int2_ord_ore_gt_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_ore_gt_pivot_min_correctness -scalars::int2::matrix_int2_ord_ore_gt_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_ore_gt_pivot_zero_correctness -scalars::int2::matrix_int2_ord_ore_gt_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_ore_gt_supported_null -scalars::int2::matrix_int2_ord_ore_gte_pivot_max_correctness -scalars::int2::matrix_int2_ord_ore_gte_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_ore_gte_pivot_min_correctness -scalars::int2::matrix_int2_ord_ore_gte_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_ore_gte_pivot_zero_correctness -scalars::int2::matrix_int2_ord_ore_gte_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_ore_gte_supported_null -scalars::int2::matrix_int2_ord_ore_index_engages_btree -scalars::int2::matrix_int2_ord_ore_lt_pivot_max_correctness -scalars::int2::matrix_int2_ord_ore_lt_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_ore_lt_pivot_min_correctness -scalars::int2::matrix_int2_ord_ore_lt_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_ore_lt_pivot_zero_correctness -scalars::int2::matrix_int2_ord_ore_lt_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_ore_lt_supported_null -scalars::int2::matrix_int2_ord_ore_lte_pivot_max_correctness -scalars::int2::matrix_int2_ord_ore_lte_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_ore_lte_pivot_min_correctness -scalars::int2::matrix_int2_ord_ore_lte_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_ore_lte_pivot_zero_correctness -scalars::int2::matrix_int2_ord_ore_lte_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_ore_lte_supported_null -scalars::int2::matrix_int2_ord_ore_native_absent_ops -scalars::int2::matrix_int2_ord_ore_neq_pivot_max_correctness -scalars::int2::matrix_int2_ord_ore_neq_pivot_max_cross_shape -scalars::int2::matrix_int2_ord_ore_neq_pivot_min_correctness -scalars::int2::matrix_int2_ord_ore_neq_pivot_min_cross_shape -scalars::int2::matrix_int2_ord_ore_neq_pivot_zero_correctness -scalars::int2::matrix_int2_ord_ore_neq_pivot_zero_cross_shape -scalars::int2::matrix_int2_ord_ore_neq_supported_null -scalars::int2::matrix_int2_ord_ore_ord_routes_through_ob -scalars::int2::matrix_int2_ord_ore_order_by_asc_no_where -scalars::int2::matrix_int2_ord_ore_order_by_asc_nulls_first -scalars::int2::matrix_int2_ord_ore_order_by_asc_nulls_last -scalars::int2::matrix_int2_ord_ore_order_by_asc_with_where -scalars::int2::matrix_int2_ord_ore_order_by_desc_no_where -scalars::int2::matrix_int2_ord_ore_order_by_desc_nulls_first -scalars::int2::matrix_int2_ord_ore_order_by_desc_nulls_last -scalars::int2::matrix_int2_ord_ore_order_by_desc_with_where -scalars::int2::matrix_int2_ord_ore_order_by_using_gt_rejects -scalars::int2::matrix_int2_ord_ore_order_by_using_gte_rejects -scalars::int2::matrix_int2_ord_ore_order_by_using_lt_rejects -scalars::int2::matrix_int2_ord_ore_order_by_using_lte_rejects -scalars::int2::matrix_int2_ord_ore_ore_injectivity -scalars::int2::matrix_int2_ord_ore_path_op_blockers -scalars::int2::matrix_int2_ord_ore_payload_check -scalars::int2::matrix_int2_ord_ore_planner_metadata_eq -scalars::int2::matrix_int2_ord_ore_planner_metadata_ord -scalars::int2::matrix_int2_ord_ore_sanity -scalars::int2::matrix_int2_ord_ore_typed_column_blocker -scalars::int2::matrix_int2_ord_path_op_blockers -scalars::int2::matrix_int2_ord_payload_check -scalars::int2::matrix_int2_ord_planner_metadata_eq -scalars::int2::matrix_int2_ord_planner_metadata_ord -scalars::int2::matrix_int2_ord_sanity -scalars::int2::matrix_int2_ord_scale_preference_default_btree -scalars::int2::matrix_int2_ord_typed_column_blocker -scalars::int2::matrix_int2_storage_aggregate_typecheck_max -scalars::int2::matrix_int2_storage_aggregate_typecheck_min -scalars::int2::matrix_int2_storage_contained_by_blocker -scalars::int2::matrix_int2_storage_contains_blocker -scalars::int2::matrix_int2_storage_count_path_cast -scalars::int2::matrix_int2_storage_count_typed_column -scalars::int2::matrix_int2_storage_eq_blocker -scalars::int2::matrix_int2_storage_gt_blocker -scalars::int2::matrix_int2_storage_gte_blocker -scalars::int2::matrix_int2_storage_lt_blocker -scalars::int2::matrix_int2_storage_lte_blocker -scalars::int2::matrix_int2_storage_native_absent_ops -scalars::int2::matrix_int2_storage_neq_blocker -scalars::int2::matrix_int2_storage_path_op_blockers -scalars::int2::matrix_int2_storage_payload_check -scalars::int2::matrix_int2_storage_sanity -scalars::int2::matrix_int2_storage_typed_column_blocker diff --git a/tests/sqlx/snapshots/int4_matrix_tests.txt b/tests/sqlx/snapshots/int4_matrix_tests.txt deleted file mode 100644 index 1fab59bd..00000000 --- a/tests/sqlx/snapshots/int4_matrix_tests.txt +++ /dev/null @@ -1,211 +0,0 @@ -scalars::int4::matrix_int4_eq_aggregate_typecheck_max -scalars::int4::matrix_int4_eq_aggregate_typecheck_min -scalars::int4::matrix_int4_eq_contained_by_blocker -scalars::int4::matrix_int4_eq_contains_blocker -scalars::int4::matrix_int4_eq_count_distinct_extractor -scalars::int4::matrix_int4_eq_count_path_cast -scalars::int4::matrix_int4_eq_count_typed_column -scalars::int4::matrix_int4_eq_eq_pivot_max_correctness -scalars::int4::matrix_int4_eq_eq_pivot_max_cross_shape -scalars::int4::matrix_int4_eq_eq_pivot_min_correctness -scalars::int4::matrix_int4_eq_eq_pivot_min_cross_shape -scalars::int4::matrix_int4_eq_eq_pivot_zero_correctness -scalars::int4::matrix_int4_eq_eq_pivot_zero_cross_shape -scalars::int4::matrix_int4_eq_eq_supported_null -scalars::int4::matrix_int4_eq_gt_blocker -scalars::int4::matrix_int4_eq_gte_blocker -scalars::int4::matrix_int4_eq_index_engages_btree -scalars::int4::matrix_int4_eq_index_engages_hash -scalars::int4::matrix_int4_eq_lt_blocker -scalars::int4::matrix_int4_eq_lte_blocker -scalars::int4::matrix_int4_eq_native_absent_ops -scalars::int4::matrix_int4_eq_neq_pivot_max_correctness -scalars::int4::matrix_int4_eq_neq_pivot_max_cross_shape -scalars::int4::matrix_int4_eq_neq_pivot_min_correctness -scalars::int4::matrix_int4_eq_neq_pivot_min_cross_shape -scalars::int4::matrix_int4_eq_neq_pivot_zero_correctness -scalars::int4::matrix_int4_eq_neq_pivot_zero_cross_shape -scalars::int4::matrix_int4_eq_neq_supported_null -scalars::int4::matrix_int4_eq_path_op_blockers -scalars::int4::matrix_int4_eq_payload_check -scalars::int4::matrix_int4_eq_planner_metadata_eq -scalars::int4::matrix_int4_eq_sanity -scalars::int4::matrix_int4_eq_typed_column_blocker -scalars::int4::matrix_int4_fixture_shape -scalars::int4::matrix_int4_ord_aggregate_group_by_max -scalars::int4::matrix_int4_ord_aggregate_group_by_min -scalars::int4::matrix_int4_ord_aggregate_max -scalars::int4::matrix_int4_ord_aggregate_max_all_null -scalars::int4::matrix_int4_ord_aggregate_max_empty -scalars::int4::matrix_int4_ord_aggregate_max_mixed_null -scalars::int4::matrix_int4_ord_aggregate_min -scalars::int4::matrix_int4_ord_aggregate_min_all_null -scalars::int4::matrix_int4_ord_aggregate_min_empty -scalars::int4::matrix_int4_ord_aggregate_min_mixed_null -scalars::int4::matrix_int4_ord_aggregate_parallel_safe -scalars::int4::matrix_int4_ord_contained_by_blocker -scalars::int4::matrix_int4_ord_contains_blocker -scalars::int4::matrix_int4_ord_count_distinct_extractor -scalars::int4::matrix_int4_ord_count_path_cast -scalars::int4::matrix_int4_ord_count_typed_column -scalars::int4::matrix_int4_ord_eq_pivot_max_correctness -scalars::int4::matrix_int4_ord_eq_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_eq_pivot_min_correctness -scalars::int4::matrix_int4_ord_eq_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_eq_pivot_zero_correctness -scalars::int4::matrix_int4_ord_eq_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_eq_supported_null -scalars::int4::matrix_int4_ord_gt_pivot_max_correctness -scalars::int4::matrix_int4_ord_gt_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_gt_pivot_min_correctness -scalars::int4::matrix_int4_ord_gt_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_gt_pivot_zero_correctness -scalars::int4::matrix_int4_ord_gt_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_gt_supported_null -scalars::int4::matrix_int4_ord_gte_pivot_max_correctness -scalars::int4::matrix_int4_ord_gte_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_gte_pivot_min_correctness -scalars::int4::matrix_int4_ord_gte_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_gte_pivot_zero_correctness -scalars::int4::matrix_int4_ord_gte_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_gte_supported_null -scalars::int4::matrix_int4_ord_index_engages_btree -scalars::int4::matrix_int4_ord_lt_pivot_max_correctness -scalars::int4::matrix_int4_ord_lt_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_lt_pivot_min_correctness -scalars::int4::matrix_int4_ord_lt_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_lt_pivot_zero_correctness -scalars::int4::matrix_int4_ord_lt_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_lt_supported_null -scalars::int4::matrix_int4_ord_lte_pivot_max_correctness -scalars::int4::matrix_int4_ord_lte_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_lte_pivot_min_correctness -scalars::int4::matrix_int4_ord_lte_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_lte_pivot_zero_correctness -scalars::int4::matrix_int4_ord_lte_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_lte_supported_null -scalars::int4::matrix_int4_ord_native_absent_ops -scalars::int4::matrix_int4_ord_neq_pivot_max_correctness -scalars::int4::matrix_int4_ord_neq_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_neq_pivot_min_correctness -scalars::int4::matrix_int4_ord_neq_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_neq_pivot_zero_correctness -scalars::int4::matrix_int4_ord_neq_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_neq_supported_null -scalars::int4::matrix_int4_ord_ord_routes_through_ob -scalars::int4::matrix_int4_ord_order_by_asc_no_where -scalars::int4::matrix_int4_ord_order_by_asc_nulls_first -scalars::int4::matrix_int4_ord_order_by_asc_nulls_last -scalars::int4::matrix_int4_ord_order_by_asc_with_where -scalars::int4::matrix_int4_ord_order_by_desc_no_where -scalars::int4::matrix_int4_ord_order_by_desc_nulls_first -scalars::int4::matrix_int4_ord_order_by_desc_nulls_last -scalars::int4::matrix_int4_ord_order_by_desc_with_where -scalars::int4::matrix_int4_ord_order_by_using_gt_rejects -scalars::int4::matrix_int4_ord_order_by_using_gte_rejects -scalars::int4::matrix_int4_ord_order_by_using_lt_rejects -scalars::int4::matrix_int4_ord_order_by_using_lte_rejects -scalars::int4::matrix_int4_ord_ore_aggregate_group_by_max -scalars::int4::matrix_int4_ord_ore_aggregate_group_by_min -scalars::int4::matrix_int4_ord_ore_aggregate_max -scalars::int4::matrix_int4_ord_ore_aggregate_max_all_null -scalars::int4::matrix_int4_ord_ore_aggregate_max_empty -scalars::int4::matrix_int4_ord_ore_aggregate_max_mixed_null -scalars::int4::matrix_int4_ord_ore_aggregate_min -scalars::int4::matrix_int4_ord_ore_aggregate_min_all_null -scalars::int4::matrix_int4_ord_ore_aggregate_min_empty -scalars::int4::matrix_int4_ord_ore_aggregate_min_mixed_null -scalars::int4::matrix_int4_ord_ore_aggregate_parallel_safe -scalars::int4::matrix_int4_ord_ore_contained_by_blocker -scalars::int4::matrix_int4_ord_ore_contains_blocker -scalars::int4::matrix_int4_ord_ore_count_distinct_extractor -scalars::int4::matrix_int4_ord_ore_count_path_cast -scalars::int4::matrix_int4_ord_ore_count_typed_column -scalars::int4::matrix_int4_ord_ore_eq_pivot_max_correctness -scalars::int4::matrix_int4_ord_ore_eq_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_ore_eq_pivot_min_correctness -scalars::int4::matrix_int4_ord_ore_eq_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_correctness -scalars::int4::matrix_int4_ord_ore_eq_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_ore_eq_supported_null -scalars::int4::matrix_int4_ord_ore_gt_pivot_max_correctness -scalars::int4::matrix_int4_ord_ore_gt_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_ore_gt_pivot_min_correctness -scalars::int4::matrix_int4_ord_ore_gt_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_correctness -scalars::int4::matrix_int4_ord_ore_gt_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_ore_gt_supported_null -scalars::int4::matrix_int4_ord_ore_gte_pivot_max_correctness -scalars::int4::matrix_int4_ord_ore_gte_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_ore_gte_pivot_min_correctness -scalars::int4::matrix_int4_ord_ore_gte_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_correctness -scalars::int4::matrix_int4_ord_ore_gte_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_ore_gte_supported_null -scalars::int4::matrix_int4_ord_ore_index_engages_btree -scalars::int4::matrix_int4_ord_ore_lt_pivot_max_correctness -scalars::int4::matrix_int4_ord_ore_lt_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_ore_lt_pivot_min_correctness -scalars::int4::matrix_int4_ord_ore_lt_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_correctness -scalars::int4::matrix_int4_ord_ore_lt_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_ore_lt_supported_null -scalars::int4::matrix_int4_ord_ore_lte_pivot_max_correctness -scalars::int4::matrix_int4_ord_ore_lte_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_ore_lte_pivot_min_correctness -scalars::int4::matrix_int4_ord_ore_lte_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_correctness -scalars::int4::matrix_int4_ord_ore_lte_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_ore_lte_supported_null -scalars::int4::matrix_int4_ord_ore_native_absent_ops -scalars::int4::matrix_int4_ord_ore_neq_pivot_max_correctness -scalars::int4::matrix_int4_ord_ore_neq_pivot_max_cross_shape -scalars::int4::matrix_int4_ord_ore_neq_pivot_min_correctness -scalars::int4::matrix_int4_ord_ore_neq_pivot_min_cross_shape -scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_correctness -scalars::int4::matrix_int4_ord_ore_neq_pivot_zero_cross_shape -scalars::int4::matrix_int4_ord_ore_neq_supported_null -scalars::int4::matrix_int4_ord_ore_ord_routes_through_ob -scalars::int4::matrix_int4_ord_ore_order_by_asc_no_where -scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_first -scalars::int4::matrix_int4_ord_ore_order_by_asc_nulls_last -scalars::int4::matrix_int4_ord_ore_order_by_asc_with_where -scalars::int4::matrix_int4_ord_ore_order_by_desc_no_where -scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_first -scalars::int4::matrix_int4_ord_ore_order_by_desc_nulls_last -scalars::int4::matrix_int4_ord_ore_order_by_desc_with_where -scalars::int4::matrix_int4_ord_ore_order_by_using_gt_rejects -scalars::int4::matrix_int4_ord_ore_order_by_using_gte_rejects -scalars::int4::matrix_int4_ord_ore_order_by_using_lt_rejects -scalars::int4::matrix_int4_ord_ore_order_by_using_lte_rejects -scalars::int4::matrix_int4_ord_ore_ore_injectivity -scalars::int4::matrix_int4_ord_ore_path_op_blockers -scalars::int4::matrix_int4_ord_ore_payload_check -scalars::int4::matrix_int4_ord_ore_planner_metadata_eq -scalars::int4::matrix_int4_ord_ore_planner_metadata_ord -scalars::int4::matrix_int4_ord_ore_sanity -scalars::int4::matrix_int4_ord_ore_typed_column_blocker -scalars::int4::matrix_int4_ord_path_op_blockers -scalars::int4::matrix_int4_ord_payload_check -scalars::int4::matrix_int4_ord_planner_metadata_eq -scalars::int4::matrix_int4_ord_planner_metadata_ord -scalars::int4::matrix_int4_ord_sanity -scalars::int4::matrix_int4_ord_scale_preference_default_btree -scalars::int4::matrix_int4_ord_typed_column_blocker -scalars::int4::matrix_int4_storage_aggregate_typecheck_max -scalars::int4::matrix_int4_storage_aggregate_typecheck_min -scalars::int4::matrix_int4_storage_contained_by_blocker -scalars::int4::matrix_int4_storage_contains_blocker -scalars::int4::matrix_int4_storage_count_path_cast -scalars::int4::matrix_int4_storage_count_typed_column -scalars::int4::matrix_int4_storage_eq_blocker -scalars::int4::matrix_int4_storage_gt_blocker -scalars::int4::matrix_int4_storage_gte_blocker -scalars::int4::matrix_int4_storage_lt_blocker -scalars::int4::matrix_int4_storage_lte_blocker -scalars::int4::matrix_int4_storage_native_absent_ops -scalars::int4::matrix_int4_storage_neq_blocker -scalars::int4::matrix_int4_storage_path_op_blockers -scalars::int4::matrix_int4_storage_payload_check -scalars::int4::matrix_int4_storage_sanity -scalars::int4::matrix_int4_storage_typed_column_blocker diff --git a/tests/sqlx/snapshots/matrix_tests.txt b/tests/sqlx/snapshots/matrix_tests.txt new file mode 100644 index 00000000..2cdfc22b --- /dev/null +++ b/tests/sqlx/snapshots/matrix_tests.txt @@ -0,0 +1,211 @@ +scalars::::matrix__eq_aggregate_typecheck_max +scalars::::matrix__eq_aggregate_typecheck_min +scalars::::matrix__eq_contained_by_blocker +scalars::::matrix__eq_contains_blocker +scalars::::matrix__eq_count_distinct_extractor +scalars::::matrix__eq_count_path_cast +scalars::::matrix__eq_count_typed_column +scalars::::matrix__eq_eq_pivot_max_correctness +scalars::::matrix__eq_eq_pivot_max_cross_shape +scalars::::matrix__eq_eq_pivot_min_correctness +scalars::::matrix__eq_eq_pivot_min_cross_shape +scalars::::matrix__eq_eq_pivot_zero_correctness +scalars::::matrix__eq_eq_pivot_zero_cross_shape +scalars::::matrix__eq_eq_supported_null +scalars::::matrix__eq_gt_blocker +scalars::::matrix__eq_gte_blocker +scalars::::matrix__eq_index_engages_btree +scalars::::matrix__eq_index_engages_hash +scalars::::matrix__eq_lt_blocker +scalars::::matrix__eq_lte_blocker +scalars::::matrix__eq_native_absent_ops +scalars::::matrix__eq_neq_pivot_max_correctness +scalars::::matrix__eq_neq_pivot_max_cross_shape +scalars::::matrix__eq_neq_pivot_min_correctness +scalars::::matrix__eq_neq_pivot_min_cross_shape +scalars::::matrix__eq_neq_pivot_zero_correctness +scalars::::matrix__eq_neq_pivot_zero_cross_shape +scalars::::matrix__eq_neq_supported_null +scalars::::matrix__eq_path_op_blockers +scalars::::matrix__eq_payload_check +scalars::::matrix__eq_planner_metadata_eq +scalars::::matrix__eq_sanity +scalars::::matrix__eq_typed_column_blocker +scalars::::matrix__fixture_shape +scalars::::matrix__ord_aggregate_group_by_max +scalars::::matrix__ord_aggregate_group_by_min +scalars::::matrix__ord_aggregate_max +scalars::::matrix__ord_aggregate_max_all_null +scalars::::matrix__ord_aggregate_max_empty +scalars::::matrix__ord_aggregate_max_mixed_null +scalars::::matrix__ord_aggregate_min +scalars::::matrix__ord_aggregate_min_all_null +scalars::::matrix__ord_aggregate_min_empty +scalars::::matrix__ord_aggregate_min_mixed_null +scalars::::matrix__ord_aggregate_parallel_safe +scalars::::matrix__ord_contained_by_blocker +scalars::::matrix__ord_contains_blocker +scalars::::matrix__ord_count_distinct_extractor +scalars::::matrix__ord_count_path_cast +scalars::::matrix__ord_count_typed_column +scalars::::matrix__ord_eq_pivot_max_correctness +scalars::::matrix__ord_eq_pivot_max_cross_shape +scalars::::matrix__ord_eq_pivot_min_correctness +scalars::::matrix__ord_eq_pivot_min_cross_shape +scalars::::matrix__ord_eq_pivot_zero_correctness +scalars::::matrix__ord_eq_pivot_zero_cross_shape +scalars::::matrix__ord_eq_supported_null +scalars::::matrix__ord_gt_pivot_max_correctness +scalars::::matrix__ord_gt_pivot_max_cross_shape +scalars::::matrix__ord_gt_pivot_min_correctness +scalars::::matrix__ord_gt_pivot_min_cross_shape +scalars::::matrix__ord_gt_pivot_zero_correctness +scalars::::matrix__ord_gt_pivot_zero_cross_shape +scalars::::matrix__ord_gt_supported_null +scalars::::matrix__ord_gte_pivot_max_correctness +scalars::::matrix__ord_gte_pivot_max_cross_shape +scalars::::matrix__ord_gte_pivot_min_correctness +scalars::::matrix__ord_gte_pivot_min_cross_shape +scalars::::matrix__ord_gte_pivot_zero_correctness +scalars::::matrix__ord_gte_pivot_zero_cross_shape +scalars::::matrix__ord_gte_supported_null +scalars::::matrix__ord_index_engages_btree +scalars::::matrix__ord_lt_pivot_max_correctness +scalars::::matrix__ord_lt_pivot_max_cross_shape +scalars::::matrix__ord_lt_pivot_min_correctness +scalars::::matrix__ord_lt_pivot_min_cross_shape +scalars::::matrix__ord_lt_pivot_zero_correctness +scalars::::matrix__ord_lt_pivot_zero_cross_shape +scalars::::matrix__ord_lt_supported_null +scalars::::matrix__ord_lte_pivot_max_correctness +scalars::::matrix__ord_lte_pivot_max_cross_shape +scalars::::matrix__ord_lte_pivot_min_correctness +scalars::::matrix__ord_lte_pivot_min_cross_shape +scalars::::matrix__ord_lte_pivot_zero_correctness +scalars::::matrix__ord_lte_pivot_zero_cross_shape +scalars::::matrix__ord_lte_supported_null +scalars::::matrix__ord_native_absent_ops +scalars::::matrix__ord_neq_pivot_max_correctness +scalars::::matrix__ord_neq_pivot_max_cross_shape +scalars::::matrix__ord_neq_pivot_min_correctness +scalars::::matrix__ord_neq_pivot_min_cross_shape +scalars::::matrix__ord_neq_pivot_zero_correctness +scalars::::matrix__ord_neq_pivot_zero_cross_shape +scalars::::matrix__ord_neq_supported_null +scalars::::matrix__ord_ord_routes_through_ob +scalars::::matrix__ord_order_by_asc_no_where +scalars::::matrix__ord_order_by_asc_nulls_first +scalars::::matrix__ord_order_by_asc_nulls_last +scalars::::matrix__ord_order_by_asc_with_where +scalars::::matrix__ord_order_by_desc_no_where +scalars::::matrix__ord_order_by_desc_nulls_first +scalars::::matrix__ord_order_by_desc_nulls_last +scalars::::matrix__ord_order_by_desc_with_where +scalars::::matrix__ord_order_by_using_gt_rejects +scalars::::matrix__ord_order_by_using_gte_rejects +scalars::::matrix__ord_order_by_using_lt_rejects +scalars::::matrix__ord_order_by_using_lte_rejects +scalars::::matrix__ord_ore_aggregate_group_by_max +scalars::::matrix__ord_ore_aggregate_group_by_min +scalars::::matrix__ord_ore_aggregate_max +scalars::::matrix__ord_ore_aggregate_max_all_null +scalars::::matrix__ord_ore_aggregate_max_empty +scalars::::matrix__ord_ore_aggregate_max_mixed_null +scalars::::matrix__ord_ore_aggregate_min +scalars::::matrix__ord_ore_aggregate_min_all_null +scalars::::matrix__ord_ore_aggregate_min_empty +scalars::::matrix__ord_ore_aggregate_min_mixed_null +scalars::::matrix__ord_ore_aggregate_parallel_safe +scalars::::matrix__ord_ore_contained_by_blocker +scalars::::matrix__ord_ore_contains_blocker +scalars::::matrix__ord_ore_count_distinct_extractor +scalars::::matrix__ord_ore_count_path_cast +scalars::::matrix__ord_ore_count_typed_column +scalars::::matrix__ord_ore_eq_pivot_max_correctness +scalars::::matrix__ord_ore_eq_pivot_max_cross_shape +scalars::::matrix__ord_ore_eq_pivot_min_correctness +scalars::::matrix__ord_ore_eq_pivot_min_cross_shape +scalars::::matrix__ord_ore_eq_pivot_zero_correctness +scalars::::matrix__ord_ore_eq_pivot_zero_cross_shape +scalars::::matrix__ord_ore_eq_supported_null +scalars::::matrix__ord_ore_gt_pivot_max_correctness +scalars::::matrix__ord_ore_gt_pivot_max_cross_shape +scalars::::matrix__ord_ore_gt_pivot_min_correctness +scalars::::matrix__ord_ore_gt_pivot_min_cross_shape +scalars::::matrix__ord_ore_gt_pivot_zero_correctness +scalars::::matrix__ord_ore_gt_pivot_zero_cross_shape +scalars::::matrix__ord_ore_gt_supported_null +scalars::::matrix__ord_ore_gte_pivot_max_correctness +scalars::::matrix__ord_ore_gte_pivot_max_cross_shape +scalars::::matrix__ord_ore_gte_pivot_min_correctness +scalars::::matrix__ord_ore_gte_pivot_min_cross_shape +scalars::::matrix__ord_ore_gte_pivot_zero_correctness +scalars::::matrix__ord_ore_gte_pivot_zero_cross_shape +scalars::::matrix__ord_ore_gte_supported_null +scalars::::matrix__ord_ore_index_engages_btree +scalars::::matrix__ord_ore_lt_pivot_max_correctness +scalars::::matrix__ord_ore_lt_pivot_max_cross_shape +scalars::::matrix__ord_ore_lt_pivot_min_correctness +scalars::::matrix__ord_ore_lt_pivot_min_cross_shape +scalars::::matrix__ord_ore_lt_pivot_zero_correctness +scalars::::matrix__ord_ore_lt_pivot_zero_cross_shape +scalars::::matrix__ord_ore_lt_supported_null +scalars::::matrix__ord_ore_lte_pivot_max_correctness +scalars::::matrix__ord_ore_lte_pivot_max_cross_shape +scalars::::matrix__ord_ore_lte_pivot_min_correctness +scalars::::matrix__ord_ore_lte_pivot_min_cross_shape +scalars::::matrix__ord_ore_lte_pivot_zero_correctness +scalars::::matrix__ord_ore_lte_pivot_zero_cross_shape +scalars::::matrix__ord_ore_lte_supported_null +scalars::::matrix__ord_ore_native_absent_ops +scalars::::matrix__ord_ore_neq_pivot_max_correctness +scalars::::matrix__ord_ore_neq_pivot_max_cross_shape +scalars::::matrix__ord_ore_neq_pivot_min_correctness +scalars::::matrix__ord_ore_neq_pivot_min_cross_shape +scalars::::matrix__ord_ore_neq_pivot_zero_correctness +scalars::::matrix__ord_ore_neq_pivot_zero_cross_shape +scalars::::matrix__ord_ore_neq_supported_null +scalars::::matrix__ord_ore_ord_routes_through_ob +scalars::::matrix__ord_ore_order_by_asc_no_where +scalars::::matrix__ord_ore_order_by_asc_nulls_first +scalars::::matrix__ord_ore_order_by_asc_nulls_last +scalars::::matrix__ord_ore_order_by_asc_with_where +scalars::::matrix__ord_ore_order_by_desc_no_where +scalars::::matrix__ord_ore_order_by_desc_nulls_first +scalars::::matrix__ord_ore_order_by_desc_nulls_last +scalars::::matrix__ord_ore_order_by_desc_with_where +scalars::::matrix__ord_ore_order_by_using_gt_rejects +scalars::::matrix__ord_ore_order_by_using_gte_rejects +scalars::::matrix__ord_ore_order_by_using_lt_rejects +scalars::::matrix__ord_ore_order_by_using_lte_rejects +scalars::::matrix__ord_ore_ore_injectivity +scalars::::matrix__ord_ore_path_op_blockers +scalars::::matrix__ord_ore_payload_check +scalars::::matrix__ord_ore_planner_metadata_eq +scalars::::matrix__ord_ore_planner_metadata_ord +scalars::::matrix__ord_ore_sanity +scalars::::matrix__ord_ore_typed_column_blocker +scalars::::matrix__ord_path_op_blockers +scalars::::matrix__ord_payload_check +scalars::::matrix__ord_planner_metadata_eq +scalars::::matrix__ord_planner_metadata_ord +scalars::::matrix__ord_sanity +scalars::::matrix__ord_scale_preference_default_btree +scalars::::matrix__ord_typed_column_blocker +scalars::::matrix__storage_aggregate_typecheck_max +scalars::::matrix__storage_aggregate_typecheck_min +scalars::::matrix__storage_contained_by_blocker +scalars::::matrix__storage_contains_blocker +scalars::::matrix__storage_count_path_cast +scalars::::matrix__storage_count_typed_column +scalars::::matrix__storage_eq_blocker +scalars::::matrix__storage_gt_blocker +scalars::::matrix__storage_gte_blocker +scalars::::matrix__storage_lt_blocker +scalars::::matrix__storage_lte_blocker +scalars::::matrix__storage_native_absent_ops +scalars::::matrix__storage_neq_blocker +scalars::::matrix__storage_path_op_blockers +scalars::::matrix__storage_payload_check +scalars::::matrix__storage_sanity +scalars::::matrix__storage_typed_column_blocker From 8d77e6e05dc41d3ac489a8c4a7c25eb150b805ba Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:33:56 +1000 Subject: [PATCH 47/93] ci(codegen): Rust catalog/generator tests + golden parity, drop pytest --- .github/workflows/test-eql.yml | 35 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 22a61d1c..46206296 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -113,31 +113,30 @@ jobs: workspaces: . shared-key: sqlx-tests - # The Python `test:codegen` drift suite is intentionally not run here: the - # Python generator is deprecated and removed in the following PR. The - # hand-written reference (tests/codegen/reference/) is now gated against - # the Rust generator by the `mise run codegen:parity` step below. + # Crate compile/lint/test (cargo test -p eql-scalars -p eql-codegen) runs + # in the dedicated `test:crates` job; this job covers the codegen-specific + # gates only — fixture-value regeneration and golden/values parity. # Regenerate the committed Rust fixture-value consts for EVERY type from - # their manifests and fail if any differ from / are missing in the tree. - # The value lists are rendered deterministically (unlike the encrypted - # .sql fixtures, whose ciphertext is non-deterministic and gitignored), so - # a plain diff is the right guard — it catches a manifest edit that wasn't - # regenerated. `git add -N` registers any brand-new untracked const so a - # forgotten-to-commit file also trips the diff. No Postgres needed: this - # only runs the Python generator. + # the catalog and fail if any differ from / are missing in the tree. + # eql-codegen renders all _values.rs deterministically (unlike the + # encrypted .sql fixtures, whose ciphertext is non-deterministic and + # gitignored), so a plain diff is the right guard — it catches a catalog + # edit that wasn't regenerated. `git add -N` registers any brand-new + # untracked const so a forgotten-to-commit file also trips the diff. No + # Postgres needed: the generator is std-only. - name: Regenerate and verify fixture-value consts (all types) run: | - mise run codegen:domain:all + cargo run -p eql-codegen git add -N tests/sqlx/src/fixtures git diff --exit-code -- tests/sqlx/src/fixtures \ - || { echo "Fixture value const(s) stale or uncommitted — run 'mise run codegen:domain:all' and commit tests/sqlx/src/fixtures."; exit 1; } + || { echo "Fixture value const(s) stale or uncommitted — run 'cargo run -p eql-codegen' and commit tests/sqlx/src/fixtures."; exit 1; } - # Cross-generator parity: assert the Rust eql-codegen output is byte- - # identical to the Python oracle across all types, the committed - # _values.rs, and the int4 golden reference. No Postgres needed — both - # generators are deterministic and run offline. - - name: Verify Rust↔Python generator parity + # Parity gate: assert the Rust eql-codegen output is line-normalized-equal + # to the int4 golden reference and the committed _values.rs are byte- + # identical (git-clean) after regeneration. Python is no longer an oracle + # (retired in P2). No Postgres needed — the generator runs offline. + - name: Verify generator parity (golden + values) run: | mise run codegen:parity From a8e9ab77a16de062958be86903aed59e7e4bd0e2 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:34:54 +1000 Subject: [PATCH 48/93] ci(matrix): verify single canonical snapshot, catalog-reconciled, cache root workspace --- .github/workflows/test-eql.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 46206296..feacd2c6 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -160,17 +160,21 @@ jobs: workspaces: . shared-key: sqlx-tests - # Regenerate the matrix test-name inventory with the SAME pinned feature - # set the local task uses (`--no-default-features`, scale excluded), then - # fail if it differs from the committed snapshot. A coverage change shows - # up as added/removed names in the PR diff — e.g. emptying `ord_domains` - # drops ~140 names, impossible to miss in review. No Postgres needed: - # `--list` only enumerates, the suite uses runtime queries. - - name: Regenerate and verify the matrix test-name inventory + # Verify the matrix test-name set against the SINGLE canonical snapshot + # (snapshots/matrix_tests.txt) with the SAME pinned feature set the local + # task uses (`--no-default-features`, scale excluded), and cross-check the + # binary's discovered type set against `eql-codegen list-types`. A coverage + # change shows up as a diff in the snapshot; a catalog type missing its + # matrix wiring fails the cross-check. No Postgres needed: `--list` only + # enumerates, the suite uses runtime queries. + - name: Verify the matrix test-name inventory run: | mise run test:matrix:inventory - git diff --exit-code -- tests/sqlx/snapshots/int4_matrix_tests.txt \ - tests/sqlx/snapshots/int2_matrix_tests.txt \ + # Diff the whole snapshots/ directory so the single canonical file + # isn't hardcoded here; the mise task discovers the type set from the + # binary and reconciles it against `eql-codegen list-types`. + git add -N tests/sqlx/snapshots + git diff --exit-code -- tests/sqlx/snapshots \ || { echo "Coverage inventory stale — run 'mise run test:matrix:inventory' and commit."; exit 1; } test: From 86e54ef07e45a418c6807c1a855c1c6b0b2f918c Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:56:38 +1000 Subject: [PATCH 49/93] build: remove Python codegen toolchain (catalog/generator is Rust) --- mise.toml | 18 +- tasks/codegen/__init__.py | 17 - tasks/codegen/conftest.py | 6 - tasks/codegen/domain.sh | 15 - tasks/codegen/generate.py | 333 ---------------- tasks/codegen/operator_surface.py | 72 ---- tasks/codegen/scalars.py | 102 ----- tasks/codegen/spec.py | 141 ------- tasks/codegen/templates.py | 495 ----------------------- tasks/codegen/terms.py | 107 ----- tasks/codegen/test_against_reference.py | 117 ------ tasks/codegen/test_generate.py | 351 ----------------- tasks/codegen/test_operator_surface.py | 126 ------ tasks/codegen/test_scalars.py | 82 ---- tasks/codegen/test_spec.py | 208 ---------- tasks/codegen/test_templates.py | 500 ------------------------ tasks/codegen/test_terms.py | 96 ----- tasks/codegen/test_writer.py | 157 -------- tasks/codegen/types/.gitkeep | 0 tasks/codegen/types/int2.toml | 19 - tasks/codegen/types/int4.toml | 19 - tasks/codegen/writer.py | 89 ----- 22 files changed, 6 insertions(+), 3064 deletions(-) delete mode 100644 tasks/codegen/__init__.py delete mode 100644 tasks/codegen/conftest.py delete mode 100755 tasks/codegen/domain.sh delete mode 100644 tasks/codegen/generate.py delete mode 100644 tasks/codegen/operator_surface.py delete mode 100644 tasks/codegen/scalars.py delete mode 100644 tasks/codegen/spec.py delete mode 100644 tasks/codegen/templates.py delete mode 100644 tasks/codegen/terms.py delete mode 100644 tasks/codegen/test_against_reference.py delete mode 100644 tasks/codegen/test_generate.py delete mode 100644 tasks/codegen/test_operator_surface.py delete mode 100644 tasks/codegen/test_scalars.py delete mode 100644 tasks/codegen/test_spec.py delete mode 100644 tasks/codegen/test_templates.py delete mode 100644 tasks/codegen/test_terms.py delete mode 100644 tasks/codegen/test_writer.py delete mode 100644 tasks/codegen/types/.gitkeep delete mode 100644 tasks/codegen/types/int2.toml delete mode 100644 tasks/codegen/types/int4.toml delete mode 100644 tasks/codegen/writer.py diff --git a/mise.toml b/mise.toml index c92d222b..d4fabf59 100644 --- a/mise.toml +++ b/mise.toml @@ -18,6 +18,9 @@ # macro-expand-eql.yml workflow's mise-action step), so there is no separate # hardcoded version to keep in lockstep. "cargo:cargo-expand" = "1.0.122" +# Still required by the documentation tooling (`tasks/docs/generate/*.py`, run +# by `docs:generate:markdown` in the release workflow). The encrypted-domain +# codegen toolchain is now Rust (eql-scalars/eql-codegen) and needs no Python. "python" = "3.13" [task_config] @@ -90,25 +93,16 @@ run = """ cargo test --test payload_schema_tests """ -[tasks."codegen:domain:all"] -description = "Regenerate every encrypted-domain type from its TOML manifest" -dir = "{{config_root}}" -run = """ -mise exec python -- python -m tasks.codegen.generate --all -""" - [tasks."codegen:parity"] -description = "Parity gate: Rust eql-codegen output byte-identical to the Python oracle" +description = "Parity gate: Rust eql-codegen output matches the int4 golden (normalized) + committed values.rs" dir = "{{config_root}}" run = "bash tasks/codegen-parity.sh" [tasks."test:codegen"] -description = "Run the encrypted-domain codegen generator tests (no database required)" +description = "Run the encrypted-domain catalog + generator tests (no database required)" dir = "{{config_root}}" run = """ -# pytest is the only non-stdlib dependency; the install is a fast no-op once satisfied. -mise exec python -- python -m pip install --quiet --disable-pip-version-check pytest -mise exec python -- python -m pytest tasks/codegen -q +cargo test -p eql-scalars -p eql-codegen """ [tasks."test:crates"] diff --git a/tasks/codegen/__init__.py b/tasks/codegen/__init__.py deleted file mode 100644 index 4443af87..00000000 --- a/tasks/codegen/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Encrypted-domain SQL code generator for EQL scalar domain families.""" - -from .generate import generate_type, main -from .spec import DomainSpec, SpecError, TypeSpec, load_spec -from .terms import TERM_CATALOG, Term, TermError - -__all__ = [ - "DomainSpec", - "SpecError", - "TERM_CATALOG", - "Term", - "TermError", - "TypeSpec", - "generate_type", - "load_spec", - "main", -] diff --git a/tasks/codegen/conftest.py b/tasks/codegen/conftest.py deleted file mode 100644 index f03b0e8b..00000000 --- a/tasks/codegen/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -"""pytest discovery anchor for the codegen package. - -Tests import via `from tasks.codegen. import ...`; pytest runs -from the repo root (where `tasks/__init__.py` exists), so no `sys.path` -manipulation is needed. -""" diff --git a/tasks/codegen/domain.sh b/tasks/codegen/domain.sh deleted file mode 100755 index ae279a12..00000000 --- a/tasks/codegen/domain.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -#MISE description="Regenerate an encrypted-domain type from its TOML spec" -#USAGE arg "type" help="Type token, e.g. int4 (matches tasks/codegen/types/.toml)" - -set -euo pipefail - -TYPE=${usage_type:?type argument required} - -echo "Regenerating encrypted-domain type: ${TYPE}" -mise exec python -- python -m tasks.codegen.generate "${TYPE}" -echo "" -echo "✓ Regenerated src/encrypted_domain/${TYPE}/ (gitignored)" -echo " Note: 'mise run build' regenerates every type automatically;" -echo " this task is for refreshing one type while iterating on its manifest." -echo " When ready, run 'mise run clean && mise run build' then 'mise run test'." diff --git a/tasks/codegen/generate.py b/tasks/codegen/generate.py deleted file mode 100644 index 03ac6be7..00000000 --- a/tasks/codegen/generate.py +++ /dev/null @@ -1,333 +0,0 @@ -"""Top-level scalar encrypted-domain materializer.""" - -import sys -from collections.abc import Iterator -from pathlib import Path - -from .operator_surface import ( - BLOCKER_ONLY_OPERATORS, - PATH_OPERATORS, - SYMMETRIC_OPERATORS, - backing_function, -) -from .spec import DomainSpec, SpecError, TypeSpec, load_spec -from .templates import ( - AGGREGATE_OPS, - domain_name, - extractor_for_operator, - is_ord_capable, - render_aggregate, - render_blocker_bool, - render_blocker_native, - render_blocker_path, - render_domain_block, - render_extractor, - render_fixture_values_rs, - render_operator, - render_wrapper, - role_phrase, - supported_operators, -) -from .terms import TERM_CATALOG, Term, term_requires -from .writer import ( - clean_generated_files, - ensure_generated_paths_writable, - write_generated_file, - write_generated_rs, -) - -REPO_ROOT = Path(__file__).resolve().parents[2] - - -def _symmetric_shapes(dom: str) -> list[tuple[str, str]]: - return [(dom, dom), (dom, "jsonb"), ("jsonb", dom)] - - -def _path_shapes(dom: str) -> list[tuple[str, str]]: - return [(dom, "text"), (dom, "integer"), ("jsonb", dom)] - - -def _blocker_only_shapes(dom: str, op: str) -> list[tuple[str, str, str]]: - if op in {"?", "?|", "?&"}: - rhs = "text[]" if op in {"?|", "?&"} else "text" - return [(dom, rhs, "boolean")] - if op in {"@?", "@@"}: - return [(dom, "jsonpath", "boolean")] - if op == "#>": - return [(dom, "text[]", "jsonb")] - if op == "#>>": - return [(dom, "text[]", "text")] - if op == "-": - return [(dom, "text", "jsonb"), (dom, "integer", "jsonb"), (dom, "text[]", "jsonb")] - if op == "#-": - return [(dom, "text[]", "jsonb")] - if op == "||": - return [(dom, dom, "jsonb"), (dom, "jsonb", "jsonb"), ("jsonb", dom, "jsonb")] - raise ValueError(f"unhandled blocker-only operator: {op}") - - -def _types_path(token: str) -> str: - return f"src/encrypted_domain/{token}/{token}_types.sql" - - -def fixture_values_rs_path(out_root: Path, token: str) -> Path: - """Committed Rust fixture-value const for a type. Outside the gitignored - src/encrypted_domain/ SQL tree because it is consumed (and committed) by - the Rust test crate.""" - return ( - out_root / "tests" / "sqlx" / "src" / "fixtures" / f"{token}_values.rs" - ) - - -def render_types_file(spec: TypeSpec) -> str: - """Body for _types.sql: every domain in one idempotent DO block. - - Iteration order follows the manifest's declared order — the TOML file is - the source of truth for emit order. - """ - blocks = [render_domain_block(domain, spec.token) for domain in spec.domains] - return ( - "-- REQUIRE: src/schema-v3.sql\n\n" - f"--! @file encrypted_domain/{spec.token}/{spec.token}_types.sql\n" - f"--! @brief Encrypted-domain type family for {spec.token}.\n\n" - "DO $$\nBEGIN\n" - + "\n".join(blocks) - + "END\n$$;\n" - ) - - -def _functions_requires(spec: TypeSpec, domain: DomainSpec) -> list[str]: - reqs = [ - "src/schema.sql", - "src/schema-v3.sql", - _types_path(spec.token), - "src/encrypted_domain/functions.sql", - ] - for extra in term_requires(domain.terms): - if extra not in reqs: - reqs.append(extra) - return reqs - - -def _extractor_terms(domain: DomainSpec) -> Iterator[Term]: - seen: set[str] = set() - for term_name in domain.terms: - term = TERM_CATALOG[term_name] - if term.extractor not in seen: - seen.add(term.extractor) - yield term - - -def render_functions_file(spec: TypeSpec, domain: DomainSpec) -> str: - """Body for a domain's _functions.sql.""" - dom = domain_name(domain.name) - supported = set(supported_operators(domain)) - parts: list[str] = [] - - for term in _extractor_terms(domain): - parts.append(render_extractor(domain, term)) - - for op in SYMMETRIC_OPERATORS: - extractor = extractor_for_operator(domain, op) - for arg_a, arg_b in _symmetric_shapes(dom): - if op in supported and extractor is not None: - parts.append(render_wrapper(domain, op, arg_a, arg_b, extractor)) - else: - parts.append(render_blocker_bool(domain, op, arg_a, arg_b)) - - for op in PATH_OPERATORS: - for arg_a, arg_b in _path_shapes(dom): - parts.append(render_blocker_path(domain, op, arg_a, arg_b)) - - for op in BLOCKER_ONLY_OPERATORS: - for arg_a, arg_b, returns in _blocker_only_shapes(dom, op): - parts.append(render_blocker_native(domain, op, arg_a, arg_b, returns)) - - requires = "\n".join(f"-- REQUIRE: {r}" for r in _functions_requires(spec, domain)) - header = ( - requires + "\n\n" - f"--! @file encrypted_domain/{spec.token}/{domain.name}_functions.sql\n" - f"--! @brief {role_phrase(domain.terms)} domain of the {spec.token} " - f"encrypted-domain family — comparison/path functions.\n\n" - ) - return header + "\n".join(parts) - - -def render_operators_file(spec: TypeSpec, domain: DomainSpec) -> str: - """Body for a domain's _operators.sql: 44 CREATE OPERATOR statements.""" - dom = domain_name(domain.name) - supported = set(supported_operators(domain)) - parts: list[str] = [] - - for op in SYMMETRIC_OPERATORS: - backing = backing_function(op) - for leftarg, rightarg in _symmetric_shapes(dom): - parts.append( - render_operator( - op, backing, leftarg, rightarg, - supported=op in supported, - ) - ) - for op in PATH_OPERATORS: - backing = backing_function(op) - for leftarg, rightarg in _path_shapes(dom): - parts.append( - render_operator(op, backing, leftarg, rightarg, supported=False) - ) - for op in BLOCKER_ONLY_OPERATORS: - backing = backing_function(op) - for leftarg, rightarg, _returns in _blocker_only_shapes(dom, op): - parts.append( - render_operator(op, backing, leftarg, rightarg, supported=False) - ) - - requires = ( - "-- REQUIRE: src/schema-v3.sql\n" - f"-- REQUIRE: {_types_path(spec.token)}\n" - f"-- REQUIRE: src/encrypted_domain/{spec.token}/" - f"{domain.name}_functions.sql\n" - ) - header = ( - requires + "\n" - f"--! @file encrypted_domain/{spec.token}/{domain.name}_operators.sql\n" - f"--! @brief {role_phrase(domain.terms)} domain of the {spec.token} " - f"encrypted-domain family — operator declarations.\n\n" - ) - return header + "\n".join(parts) - - -def render_aggregates_file(spec: TypeSpec, domain: DomainSpec) -> str | None: - """Body for a domain's _aggregates.sql, or None if the domain has no - ordering comparator (storage/eq variants have no MIN/MAX semantics).""" - if not is_ord_capable(domain): - return None - parts = [render_aggregate(domain, AGGREGATE_OPS[name]) for name in ("min", "max")] - requires = ( - "-- REQUIRE: src/schema-v3.sql\n" - f"-- REQUIRE: {_types_path(spec.token)}\n" - f"-- REQUIRE: src/encrypted_domain/{spec.token}/" - f"{domain.name}_functions.sql\n" - f"-- REQUIRE: src/encrypted_domain/{spec.token}/" - f"{domain.name}_operators.sql\n" - ) - header = ( - requires + "\n" - f"--! @file encrypted_domain/{spec.token}/{domain.name}_aggregates.sql\n" - f"--! @brief {role_phrase(domain.terms)} domain of the {spec.token} " - f"encrypted-domain family — MIN/MAX aggregates.\n\n" - ) - return header + "\n".join(parts) - - -def generate_type(spec: TypeSpec, out_dir: Path) -> list[Path]: - """Regenerate every generated file for a type.""" - out_dir = Path(out_dir) - target_paths = [out_dir / f"{spec.token}_types.sql"] - for domain in spec.domains: - target_paths.append(out_dir / f"{domain.name}_functions.sql") - target_paths.append(out_dir / f"{domain.name}_operators.sql") - if is_ord_capable(domain): - target_paths.append(out_dir / f"{domain.name}_aggregates.sql") - ensure_generated_paths_writable(target_paths) - clean_generated_files(out_dir) - - written: list[Path] = [] - - types_path = out_dir / f"{spec.token}_types.sql" - write_generated_file(types_path, render_types_file(spec)) - written.append(types_path) - - for domain in spec.domains: - fn_path = out_dir / f"{domain.name}_functions.sql" - write_generated_file(fn_path, render_functions_file(spec, domain)) - written.append(fn_path) - - op_path = out_dir / f"{domain.name}_operators.sql" - write_generated_file(op_path, render_operators_file(spec, domain)) - written.append(op_path) - - agg_body = render_aggregates_file(spec, domain) - if agg_body is not None: - agg_path = out_dir / f"{domain.name}_aggregates.sql" - write_generated_file(agg_path, agg_body) - written.append(agg_path) - - return written - - -DEFAULT_TYPES_DIR = Path(__file__).parent / "types" - - -def generate_one(token: str, *, types_dir: Path, out_root: Path) -> int: - """Regenerate one type from types_dir/.toml. - - Returns 0 on success, 1 when the manifest is missing or its inferred token - does not match. A malformed manifest raises SpecError — the caller decides - whether to surface it (single-type CLI) or aggregate it (--all).""" - toml_path = types_dir / f"{token}.toml" - if not toml_path.is_file(): - print(f"error: no manifest at {toml_path}", file=sys.stderr) - return 1 - spec = load_spec(toml_path) - if spec.token != token: - print( - f"error: manifest token '{spec.token}' does not match '{token}'", - file=sys.stderr, - ) - return 1 - out_dir = out_root / "src" / "encrypted_domain" / token - written = generate_type(spec, out_dir) - - if spec.fixture_values is not None: - rs_path = fixture_values_rs_path(out_root, token) - write_generated_rs(rs_path, render_fixture_values_rs(spec)) - written.append(rs_path) - - for path in written: - print(f"generated {path.relative_to(out_root)}") - print(f"generated {len(written)} files for {token}") - return 0 - - -def generate_all(*, types_dir: Path, out_root: Path) -> int: - """Regenerate every type whose manifest lives in types_dir. - - Iterates sorted(types_dir.glob('*.toml')) for deterministic order and - aggregates return codes: a missing/mismatched/malformed manifest is - reported and counted as a failure without aborting the remaining types.""" - tokens = [p.stem for p in sorted(types_dir.glob("*.toml"))] - if not tokens: - print(f"error: no manifests found in {types_dir}", file=sys.stderr) - return 1 - rc = 0 - for token in tokens: - try: - if generate_one(token, types_dir=types_dir, out_root=out_root) != 0: - rc = 1 - except SpecError as exc: - print(f"error: {token}: {exc}", file=sys.stderr) - rc = 1 - status = "ok" if rc == 0 else "FAILED" - print(f"codegen --all: {status} ({len(tokens)} types: {', '.join(tokens)})") - return rc - - -def main( - argv: list[str], - *, - types_dir: Path | None = None, - out_root: Path | None = None, -) -> int: - """CLI entrypoint: generate , or --all for every manifest.""" - types_dir = types_dir or DEFAULT_TYPES_DIR - out_root = out_root or REPO_ROOT - if len(argv) == 2 and argv[1] == "--all": - return generate_all(types_dir=types_dir, out_root=out_root) - if len(argv) != 2: - print("Usage: generate.py | generate.py --all", file=sys.stderr) - return 2 - return generate_one(argv[1], types_dir=types_dir, out_root=out_root) - - -if __name__ == "__main__": - sys.exit(main(sys.argv)) diff --git a/tasks/codegen/operator_surface.py b/tasks/codegen/operator_surface.py deleted file mode 100644 index 355e5751..00000000 --- a/tasks/codegen/operator_surface.py +++ /dev/null @@ -1,72 +0,0 @@ -"""The generated operator surface for a scalar encrypted-domain type. - -Supported comparison operators route to inlinable wrappers when the domain -has the required term. Unsupported comparisons, path operators, and native -jsonb fallback operators route to blockers. -""" - -from dataclasses import dataclass -from typing import Literal - - -@dataclass(frozen=True) -class Operator: - """One operator in the generated surface.""" - - symbol: str - backing: str # eql_v2 backing function name (bare or quoted) - kind: Literal["symmetric", "path", "blocker_only"] - restrict: str | None # selectivity estimator, symmetric ops only - join: str | None # join selectivity estimator, symmetric ops only - commutator: str | None - negator: str | None - - -SYMMETRIC_OPERATORS = ["=", "<>", "<", "<=", ">", ">=", "@>", "<@"] -PATH_OPERATORS = ["->", "->>"] -BLOCKER_ONLY_OPERATORS = ["?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||"] - - -OPERATORS: dict[str, Operator] = { - "=": Operator("=", "eq", "symmetric", "eqsel", "eqjoinsel", "=", "<>"), - "<>": Operator("<>", "neq", "symmetric", "neqsel", "neqjoinsel", "<>", "="), - "<": Operator("<", "lt", "symmetric", "scalarltsel", "scalarltjoinsel", ">", ">="), - "<=": Operator("<=", "lte", "symmetric", "scalarlesel", "scalarlejoinsel", ">=", ">"), - ">": Operator(">", "gt", "symmetric", "scalargtsel", "scalargtjoinsel", "<", "<="), - ">=": Operator(">=", "gte", "symmetric", "scalargesel", "scalargejoinsel", "<=", "<"), - "@>": Operator("@>", "contains", "symmetric", None, None, None, None), - "<@": Operator("<@", "contained_by", "symmetric", None, None, None, None), - "->": Operator("->", '"->"', "path", None, None, None, None), - "->>": Operator("->>", '"->>"', "path", None, None, None, None), - "?": Operator("?", '"?"', "blocker_only", None, None, None, None), - "?|": Operator("?|", '"?|"', "blocker_only", None, None, None, None), - "?&": Operator("?&", '"?&"', "blocker_only", None, None, None, None), - "@?": Operator("@?", '"@?"', "blocker_only", None, None, None, None), - "@@": Operator("@@", '"@@"', "blocker_only", None, None, None, None), - "#>": Operator("#>", '"#>"', "blocker_only", None, None, None, None), - "#>>": Operator("#>>", '"#>>"', "blocker_only", None, None, None, None), - "-": Operator("-", '"-"', "blocker_only", None, None, None, None), - "#-": Operator("#-", '"#-"', "blocker_only", None, None, None, None), - "||": Operator("||", '"||"', "blocker_only", None, None, None, None), -} - - -def backing_function(symbol: str) -> str: - """Return the eql_v2 backing function name for an operator symbol.""" - return OPERATORS[symbol].backing - - -# The full union of operator symbols the generator knows about: supported -# wrappers, path operators, and explicit blockers. Together these are exactly -# the native jsonb operator surface for PG 14-17, so this set is the basis of -# the storage-only "every native jsonb operator is blocked" guarantee. -# -# A live-DB structural guard (tests/sqlx/.../family/jsonb_operator_surface.rs) -# queries pg_operator for every operator with a jsonb argument and asserts the -# set is a subset of this union — if a future PG version adds a jsonb operator -# not enumerated here, that test fails rather than silently letting native -# plaintext-jsonb semantics through on an encrypted column. Keep that test's -# hardcoded expectation in sync with this set. -KNOWN_JSONB_OPERATORS: frozenset[str] = frozenset( - SYMMETRIC_OPERATORS + PATH_OPERATORS + BLOCKER_ONLY_OPERATORS -) diff --git a/tasks/codegen/scalars.py b/tasks/codegen/scalars.py deleted file mode 100644 index eee01dc4..00000000 --- a/tasks/codegen/scalars.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Fixed scalar-kind catalog for fixture-value emission. - -A `ScalarKind` knows how to turn a manifest fixture-value token into a Rust -literal of the type's native Rust scalar, and how to resolve it to a numeric -value for the MIN/MAX/zero invariant check. The manifest carries only the -list of value tokens; the per-type behaviour lives here (mirroring terms.py), -not in free-form TOML fields. - -Recognised sentinels are ``MIN`` / ``MAX`` / ``ZERO``; every other token is a -numeric literal validated against the type's representable range. -""" - -from dataclasses import dataclass - - -class ScalarError(Exception): - """Raised for an unknown scalar token or an invalid fixture value.""" - - -_SENTINELS = ("MIN", "MAX", "ZERO") - - -@dataclass(frozen=True) -class ScalarKind: - """One scalar type's Rust rendering rules for fixture values.""" - - token: str - rust_type: str - min_symbol: str - max_symbol: str - zero_symbol: str - min_value: int - max_value: int - - def _parse(self, value: str) -> int: - if value == "MIN": - return self.min_value - if value == "MAX": - return self.max_value - if value == "ZERO": - return 0 - try: - n = int(value) - except ValueError as exc: - raise ScalarError( - f"{self.token}: {value!r} is not a valid {self.rust_type} " - f"literal or sentinel ({'/'.join(_SENTINELS)})" - ) from exc - if not (self.min_value <= n <= self.max_value): - raise ScalarError( - f"{self.token}: {value!r} out of range for {self.rust_type} " - f"[{self.min_value}, {self.max_value}]" - ) - return n - - def numeric_value(self, value: str) -> int: - """Resolve a fixture token to its numeric value (validates range).""" - return self._parse(value) - - def render_literal(self, value: str) -> str: - """Render a fixture token as a Rust literal of this scalar type.""" - symbols = { - "MIN": self.min_symbol, - "MAX": self.max_symbol, - "ZERO": self.zero_symbol, - } - if value in symbols: - return symbols[value] - return str(self._parse(value)) - - -SCALAR_KINDS: dict[str, ScalarKind] = { - "int4": ScalarKind( - token="int4", - rust_type="i32", - min_symbol="i32::MIN", - max_symbol="i32::MAX", - zero_symbol="0", - min_value=-2147483648, - max_value=2147483647, - ), - "int2": ScalarKind( - token="int2", - rust_type="i16", - min_symbol="i16::MIN", - max_symbol="i16::MAX", - zero_symbol="0", - min_value=-32768, - max_value=32767, - ), -} - - -def require_scalar(token: str) -> ScalarKind: - """Return the catalog kind for `token`, or raise ScalarError.""" - try: - return SCALAR_KINDS[token] - except KeyError as exc: - raise ScalarError( - f"unknown scalar token '{token}' " - f"(expected one of {sorted(SCALAR_KINDS)})" - ) from exc diff --git a/tasks/codegen/spec.py b/tasks/codegen/spec.py deleted file mode 100644 index 40e28cac..00000000 --- a/tasks/codegen/spec.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Minimal TOML manifest loader for scalar encrypted-domain codegen.""" - -import re -import tomllib -from dataclasses import dataclass -from pathlib import Path - -from .scalars import ScalarError, require_scalar -from .terms import TermError, require_terms - - -_SQL_IDENTIFIER = re.compile(r"^[a-z][a-z0-9_]*$") - - -class SpecError(Exception): - """Raised when a TOML manifest is missing or invalid.""" - - -@dataclass(frozen=True) -class DomainSpec: - """One generated public domain and the fixed terms it carries.""" - - name: str - terms: list[str] - - -@dataclass(frozen=True) -class TypeSpec: - """A scalar encrypted-domain manifest loaded from one TOML file.""" - - token: str - domains: list[DomainSpec] - fixture_values: list[str] | None = None - - -def _load_fixture_values(raw: dict, token: str) -> list[str] | None: - """Parse and validate the optional [fixture] table. - - Returns the ordered list of value tokens, or None when no [fixture] table - is present. The tokens are the manifest source of truth for the generated - Rust fixture-value const; the scalar kind validates each one and the set - must include MIN, MAX, and zero (the matrix comparison pivots).""" - if "fixture" not in raw: - return None - - fixture_table = raw["fixture"] - if not isinstance(fixture_table, dict) or "values" not in fixture_table: - raise SpecError("[fixture]: missing required key 'values'") - - values = fixture_table["values"] - if not isinstance(values, list): - raise SpecError("[fixture] values: must be a list of value tokens") - if not values: - raise SpecError("[fixture] values: must not be empty") - if any(not isinstance(v, str) for v in values): - raise SpecError("[fixture] values: must be strings") - - try: - kind = require_scalar(token) - resolved = [(v, kind.numeric_value(v)) for v in values] - for v in values: - kind.render_literal(v) - except ScalarError as exc: - raise SpecError(f"[fixture] values: {exc}") from exc - - # Distinct-plaintext contract: the matrix oracle treats each fixture value - # as a distinct plaintext, and the generated Rust const must not repeat a - # literal. Detect duplicates against the *resolved numeric* value so that - # both copy-paste token dups ("1", "1") and sentinel/literal aliases - # (e.g. "MIN" alongside the same number as a literal) are rejected. - seen: dict[int, str] = {} - duplicates: list[str] = [] - for token_value, number in resolved: - if number in seen: - duplicates.append( - f"{token_value!r} duplicates {seen[number]!r} (both resolve to {number})" - if token_value != seen[number] - else f"{token_value!r}" - ) - else: - seen[number] = token_value - if duplicates: - raise SpecError( - "[fixture] values: must be distinct, but found duplicate values: " - + ", ".join(duplicates) - ) - - numbers = set(seen) - if not ({kind.min_value, kind.max_value, 0} <= numbers): - raise SpecError( - "[fixture] values: must include MIN, MAX, and zero " - "(the matrix comparison pivots)" - ) - - return list(values) - - -def load_spec(path: Path | str) -> TypeSpec: - """Load and validate a per-type scalar-domain manifest.""" - path = Path(path) - with path.open("rb") as fh: - raw = tomllib.load(fh) - - if "domain" not in raw: - raise SpecError("spec: missing required table '[domain]'") - - domain_table = raw["domain"] - if not isinstance(domain_table, dict) or not domain_table: - raise SpecError("[domain]: at least one domain is required") - - token = path.stem - if not _SQL_IDENTIFIER.match(token): - raise SpecError( - f"spec: token {token!r} must match {_SQL_IDENTIFIER.pattern}" - ) - domains: list[DomainSpec] = [] - for name, terms in domain_table.items(): - if not isinstance(name, str) or not _SQL_IDENTIFIER.match(name): - raise SpecError( - f"[domain] {name}: domain name {name!r} must match " - f"{_SQL_IDENTIFIER.pattern}" - ) - if name != token and not name.startswith(f"{token}_"): - raise SpecError( - f"[domain] {name}: domain name must start with '{token}'" - ) - if not isinstance(terms, list): - raise SpecError( - f"[domain] {name}: value must be a list of term names" - ) - if any(not isinstance(term, str) for term in terms): - raise SpecError(f"[domain] {name}: term names must be strings") - try: - require_terms(list(terms)) - except TermError as exc: - raise SpecError(f"[domain] {name}: {exc}") from exc - domains.append(DomainSpec(name=name, terms=list(terms))) - - fixture_values = _load_fixture_values(raw, token) - - return TypeSpec(token=token, domains=domains, fixture_values=fixture_values) diff --git a/tasks/codegen/templates.py b/tasks/codegen/templates.py deleted file mode 100644 index 14fefbc6..00000000 --- a/tasks/codegen/templates.py +++ /dev/null @@ -1,495 +0,0 @@ -"""Per-construct SQL template functions for scalar encrypted-domain codegen.""" - -from dataclasses import dataclass - -from .operator_surface import OPERATORS -from .scalars import require_scalar -from .spec import DomainSpec, TypeSpec -from .terms import ( - Term, - extractor_for_operator as _catalog_extractor_for_operator, - operators_for_terms, - role_for_terms, - term_json_keys, -) - -# SQL generated-file marker, emitted as the first line of every generated SQL -# file. Must stay byte-identical to the Rust generator's AUTO_GENERATED_HEADER -# (crates/eql-codegen/src/consts.rs) so the two generators are at byte parity -# (mise run codegen:parity). The `^-- AUTOMATICALLY GENERATED FILE` first line -# is also what tasks/docs/validate/{coverage,required-tags}.sh grep on to skip -# generated SQL — keep this and that grep in lockstep. -AUTO_GENERATED_HEADER = "-- AUTOMATICALLY GENERATED FILE.\n" - -# Rust counterpart, prepended to the committed `_values.rs` (which has no -# template). Rust comment syntax (`//`) so the `.rs` file stays valid; must stay -# byte-identical to the Rust generator's AUTO_GENERATED_HEADER_RS. -AUTO_GENERATED_HEADER_RS = "// AUTOMATICALLY GENERATED FILE.\n" - -ENVELOPE_KEYS = ["v", "i"] -CIPHERTEXT_KEY = "c" -# EQL payload-format version. The domain CHECK pins the 'v' envelope key to -# this value, matching EQL's repo-wide rule (eql_v2._encrypted_check_v, -# src/encrypted/constraints.sql). Presence of 'v' is enforced via -# ENVELOPE_KEYS; this pins its value so a stale/foreign-version payload is -# rejected on insert or cast rather than surfacing later at query time. -VERSION_KEY = "v" -ENVELOPE_VERSION = 2 - - -def _sql_str(s: str) -> str: - """Escape a Python string for use *inside* a single-quoted SQL string - literal by doubling embedded single quotes. - - Use this at every `'{...}'` interpolation boundary in the render_* - helpers — payload keys, operator symbols, domain names rendered into - RAISE messages, etc. NOT for schema-qualified identifiers like - ``eql_v3.foo``: those are emitted unquoted and must not be doubled. - - Today every catalog string (term keys, operator symbols) is quote-free, - so this is a no-op on real input and output stays byte-identical. It - exists so a future quote-bearing catalog string can never break out of - its SQL literal — nothing else enforces the quote-free invariant.""" - return s.replace("'", "''") - - -# Schema housing the encrypted-domain families: the domains themselves plus -# their index-term extractors, comparison wrappers, blockers, and aggregates. -# New in v3 and distinct from the core eql_v2 schema, which still owns the -# shared index-term types the extractors return and construct -# (eql_v2.hmac_256, eql_v2.ore_block_u64_8_256). -DOMAIN_SCHEMA = "eql_v3" -# Schema owning the core index-term types/constructors the extractors reuse. -CORE_SCHEMA = "eql_v2" - - -def render_fixture_values_rs(spec: TypeSpec) -> str: - """Body for tests/sqlx/src/fixtures/_values.rs. - - Emits one `pub const VALUES: &[]` from the manifest's - `[fixture] values`, preserving declaration order. The writer prepends the - AUTO-GENERATED Rust header, so the body carries none.""" - kind = require_scalar(spec.token) - values = spec.fixture_values or [] - literals = "".join(f" {kind.render_literal(v)},\n" for v in values) - return ( - f"//! Fixture plaintext values for the {spec.token} " - "encrypted-domain family.\n" - "//!\n" - f"//! Generated from the `{spec.token}` row in `eql-scalars::CATALOG` " - "(`fixtures`) —\n" - "//! the single source of truth shared by the fixture generator\n" - f"//! (`fixtures::eql_v2_{spec.token}`) and the matrix oracle\n" - "//! (`ScalarType::FIXTURE_VALUES`).\n\n" - f"/// Distinct plaintext values present in the `eql_v2_{spec.token}` " - "fixture.\n" - f"pub const VALUES: &[{kind.rust_type}] = &[\n" - f"{literals}" - "];\n" - ) - -OPERATOR_PHRASES: dict[str, str] = { - "=": "Equality", - "<>": "Inequality", - "<": "Less-than", - "<=": "Less-than-or-equal", - ">": "Greater-than", - ">=": "Greater-than-or-equal", - "@>": "Contains", - "<@": "Contained-by", -} - -DOMAIN_ROLE_PHRASES: dict[str, str] = { - "storage": "Storage-only", - "eq": "Equality-only", - "ord": "Ordered", -} - - -def role_phrase(terms: list[str]) -> str: - """Proper-cased prose label for a domain with these terms — the single - source of truth for role → human prose. Every renderer that wants to - describe a domain's role in @brief lines reaches for this, so a rename - in DOMAIN_ROLE_PHRASES propagates to every generated file.""" - return DOMAIN_ROLE_PHRASES[role_for_terms(terms)] - - -def _scheme_suffix(name: str, token: str, role: str) -> str | None: - """The scheme tag of a domain name, or None for the converged name. - - The naming convention is ``_`` for the recommended converged - domain and ``__`` for a scheme-explicit twin that - pins the same role to one concrete index scheme. ``storage`` has no role - segment, so its converged name is the bare ````. - - Generic by construction: it reads ``token`` and ``role`` rather than any - hard-coded type or scheme string, so it works for int8/date/etc. and for - schemes other than ``ore``. Returns the scheme segment (e.g. ``"ore"``) - for a twin, or None when ``name`` is the converged name (or doesn't match - the convention at all).""" - converged = token if role == "storage" else f"{token}_{role}" - if name == converged: - return None - prefix = converged + "_" - if name.startswith(prefix): - scheme = name[len(prefix):] - if scheme: - return scheme - return None - - -# Roles that come in converged + scheme-explicit-twin pairs and therefore need -# a disambiguating @brief clause. Ordered domains are the case the reviewer -# flagged: int4_ord and int4_ord_ore carry identical terms (["ore"]) and would -# otherwise render an identical brief. Driven by role (generic across int8, -# date, etc.), never by a literal type/scheme name. eq and storage have a -# single name each, so no disambiguation is needed (or wanted — it'd be noise). -_TWINNABLE_ROLES = frozenset({"ord"}) - - -def brief_role_clause(domain: DomainSpec, token: str) -> str: - """The trailing clause distinguishing the recommended converged domain - from a scheme-explicit twin, for use in a per-domain @brief. - - Two domains that carry identical terms (e.g. ``int4_ord`` and - ``int4_ord_ore``, both ``["ore"]``) would otherwise render an identical - brief. The converged name is the recommended one to reach for; the twin - names the concrete scheme explicitly. Returns "" for roles that don't come - in converged/twin pairs (eq, storage) and for names that match no pattern. - - Generic by construction: keyed on the term-derived role and the - ``_[_]`` name shape, never on a literal type or scheme - string, so int8/date/etc. and non-ore schemes work unchanged.""" - role = role_for_terms(domain.terms) - if role not in _TWINNABLE_ROLES: - return "" - scheme = _scheme_suffix(domain.name, token, role) - if scheme is not None: - return ( - f" Scheme-explicit twin pinning the {scheme} scheme; " - f"prefer the converged {token}_{role} name." - ) - if domain.name == f"{token}_{role}": - return " Recommended converged name for this role." - return "" - - -def domain_name(domain: str) -> str: - """The schema-qualified SQL domain type name, e.g. ``eql_v3.int4_eq``.""" - return f"{DOMAIN_SCHEMA}.{domain}" - - -def _arg_label(dom: str, arg_type: str) -> str: - """Doxygen brief shape qualifier for one operand: 'domain' if it's - the encrypted-domain type, otherwise the literal SQL type.""" - return "domain" if arg_type == dom else arg_type - - -def _shape_qualifier(dom: str, arg_a: str, arg_b: str) -> str: - """Doxygen brief parenthetical. Empty for the canonical (dom, dom) shape.""" - if arg_a == dom and arg_b == dom: - return "" - return f" ({_arg_label(dom, arg_a)}, {_arg_label(dom, arg_b)})" - - -def render_domain_block(domain: DomainSpec, token: str) -> str: - """One idempotent IF NOT EXISTS CREATE DOMAIN block, prefixed by a - per-domain --! @brief derived from role + token.""" - dom = domain_name(domain.name) - keys = ENVELOPE_KEYS + [CIPHERTEXT_KEY] + term_json_keys(domain.terms) - presence = "\n AND ".join(f"VALUE ? '{_sql_str(key)}'" for key in keys) - checks = ( - presence - + f"\n AND VALUE->>'{_sql_str(VERSION_KEY)}' = '{ENVELOPE_VERSION}'" - ) - phrase = role_phrase(domain.terms) - clause = brief_role_clause(domain, token) - return ( - f" --! @brief {phrase} encrypted {token} domain.{clause}\n" - f" IF NOT EXISTS (\n" - f" SELECT 1 FROM pg_type\n" - f" WHERE typname = '{_sql_str(domain.name)}' " - f"AND typnamespace = '{DOMAIN_SCHEMA}'::regnamespace\n" - f" ) THEN\n" - f" CREATE DOMAIN {dom} AS jsonb\n" - f" CHECK (\n" - f" jsonb_typeof(VALUE) = 'object'\n" - f" AND {checks}\n" - f" );\n" - f" END IF;\n" - ) - - -def render_extractor(domain: DomainSpec, term: Term) -> str: - """The inlinable index-term extractor for a domain term.""" - dom = domain_name(domain.name) - doxy = ( - f"--! @brief Index extractor for the {dom} variant.\n" - f"--! @param a {dom}\n" - f"--! @return {term.returns}\n" - ) - return doxy + ( - f"CREATE FUNCTION {DOMAIN_SCHEMA}.{term.extractor}(a {dom})\n" - f"RETURNS {term.returns}\n" - f"LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" - f"AS $$ SELECT {CORE_SCHEMA}.{term.ctor}(a::jsonb) $$;\n" - ) - - -def _extract_arg(arg_type: str, extractor: str, domain: str, arg: str) -> str: - """The extractor-call SQL for one operand, casting jsonb to the domain first.""" - if arg_type == "jsonb": - return f"{DOMAIN_SCHEMA}.{extractor}({arg}::{domain})" - return f"{DOMAIN_SCHEMA}.{extractor}({arg})" - - -def render_wrapper( - domain: DomainSpec, op: str, arg_a: str, arg_b: str, extractor: str -) -> str: - """An inlinable comparison wrapper for a supported operator.""" - dom = domain_name(domain.name) - backing = OPERATORS[op].backing - call_a = _extract_arg(arg_a, extractor, dom, "a") - call_b = _extract_arg(arg_b, extractor, dom, "b") - doxy = ( - f"--! @brief {OPERATOR_PHRASES[op]} wrapper for {dom}" - f"{_shape_qualifier(dom, arg_a, arg_b)}.\n" - f"--! @param a {arg_a}\n" - f"--! @param b {arg_b}\n" - f"--! @return boolean\n" - ) - return doxy + ( - f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, b {arg_b})\n" - f"RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" - f"AS $$ SELECT {call_a} {op} {call_b} $$;\n" - ) - - -def render_blocker_bool( - domain: DomainSpec, op: str, arg_a: str, arg_b: str -) -> str: - """A boolean-returning blocker. NEVER STRICT, ALWAYS LANGUAGE plpgsql - so the RAISE survives inlining and planner-time elision; see CLAUDE.md - footguns and the encrypted-domain spec §4.""" - dom = domain_name(domain.name) - backing = OPERATORS[op].backing - doxy = ( - f"--! @brief Blocker for {op} on {dom}" - f"{_shape_qualifier(dom, arg_a, arg_b)}.\n" - f"--! @param a {arg_a}\n" - f"--! @param b {arg_b}\n" - f"--! @return boolean (never returns; always raises)\n" - ) - return doxy + ( - f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, b {arg_b})\n" - f"RETURNS boolean IMMUTABLE PARALLEL SAFE\n" - f"AS $$ BEGIN RETURN {DOMAIN_SCHEMA}.encrypted_domain_unsupported_bool(" - f"'{_sql_str(dom)}', '{_sql_str(op)}'); END; $$\n" - f"LANGUAGE plpgsql;\n" - ) - - -def render_blocker_path( - domain: DomainSpec, op: str, arg_a: str, arg_b: str -) -> str: - """A path-operator blocker. NEVER STRICT, ALWAYS LANGUAGE plpgsql - so the RAISE survives inlining and planner-time elision; see CLAUDE.md - footguns and the encrypted-domain spec §4.""" - dom = domain_name(domain.name) - backing = OPERATORS[op].backing - returns = "text" if op == "->>" else dom - doxy = ( - f"--! @brief Blocker for {op} on {dom} " - f"({_arg_label(dom, arg_a)}, {_arg_label(dom, arg_b)}).\n" - f"--! @param a {arg_a}\n" - f"--! @param selector {arg_b}\n" - f"--! @return {returns} (never returns; always raises)\n" - ) - return doxy + ( - f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, selector {arg_b})\n" - f"RETURNS {returns} IMMUTABLE PARALLEL SAFE\n" - f"AS $$ BEGIN RAISE EXCEPTION " - f"'operator % is not supported for %', '{_sql_str(op)}', " - f"'{_sql_str(dom)}'; END; $$\n" - f"LANGUAGE plpgsql;\n" - ) - - -def render_blocker_native( - domain: DomainSpec, op: str, arg_a: str, arg_b: str, returns: str -) -> str: - """A blocker for a native jsonb fallback operator. NEVER STRICT, ALWAYS - LANGUAGE plpgsql. Boolean blockers delegate to the shared helper so lint - recognition and messages stay uniform; other return types raise directly. - """ - dom = domain_name(domain.name) - backing = OPERATORS[op].backing - doxy = ( - f"--! @brief Blocker for {op} on {dom}" - f"{_shape_qualifier(dom, arg_a, arg_b)}.\n" - f"--! @param a {arg_a}\n" - f"--! @param b {arg_b}\n" - f"--! @return {returns} (never returns; always raises)\n" - ) - if returns == "boolean": - body = ( - f"BEGIN RETURN {DOMAIN_SCHEMA}.encrypted_domain_unsupported_bool(" - f"'{_sql_str(dom)}', '{_sql_str(op)}'); END;" - ) - else: - body = ( - "BEGIN RAISE EXCEPTION " - f"'operator % is not supported for %', '{_sql_str(op)}', " - f"'{_sql_str(dom)}'; END;" - ) - return doxy + ( - f"CREATE FUNCTION {DOMAIN_SCHEMA}.{backing}(a {arg_a}, b {arg_b})\n" - f"RETURNS {returns} IMMUTABLE PARALLEL SAFE\n" - f"AS $$ {body} $$\n" - f"LANGUAGE plpgsql;\n" - ) - - -def extractor_for_operator(domain: DomainSpec, op: str) -> str | None: - """Return the catalog extractor that supports op for this domain.""" - return _catalog_extractor_for_operator(domain.terms, op) - - -def supported_operators(domain: DomainSpec) -> list[str]: - """Supported operators for this domain.""" - return operators_for_terms(domain.terms) - - -@dataclass(frozen=True) -class AggregateOp: - """One aggregate operator definition (min or max).""" - - name: str # public function name, e.g. "min" - sfunc_name: str # state function name, e.g. "min_sfunc" - comparator: str # SQL comparator used to choose the new state: "<" or ">" - phrase: str # short prose label used in --! @brief lines - - -AGGREGATE_OPS: dict[str, AggregateOp] = { - "min": AggregateOp("min", "min_sfunc", "<", "minimum"), - "max": AggregateOp("max", "max_sfunc", ">", "maximum"), -} - - -def is_ord_capable(domain: DomainSpec) -> bool: - """True if the domain carries a comparator term (i.e. supports `<`).""" - return role_for_terms(domain.terms) == "ord" - - -def render_aggregate(domain: DomainSpec, op: AggregateOp) -> str: - """Render state function + CREATE AGGREGATE for one aggregate op on one - domain. The ord-capability gate lives at the file-level renderer - (`render_aggregates_file`); callers may legitimately render a single - aggregate without re-asserting that precondition. MIN/MAX on a non-ord - domain is structurally well-formed text but semantically meaningless — - the file-level gate is what stops it ever reaching disk.""" - dom = domain_name(domain.name) - sfunc_doxy = ( - f"--! @brief State function for {op.name} aggregate on {dom}.\n" - f"--! @internal\n" - f"--!\n" - f"--! @param state {dom} running extremum\n" - f"--! @param value {dom} next non-NULL value\n" - f"--! @return {dom} the {op.phrase} of state and value\n" - ) - # plpgsql + STRICT: PG seeds the state with the first non-NULL value and - # skips NULL inputs. plpgsql (not sql) because aggregate state functions - # aren't index expressions — opacity to the planner is fine — and a - # multi-statement BEGIN/IF/END body is the natural shape. - # - # The same rationale is mirrored into the emitted SQL below so a reader of - # the generated file (who never sees this Python) understands why it isn't - # an inlinable LANGUAGE sql CASE. - sfunc_rationale = ( - "-- LANGUAGE plpgsql, not sql: aggregate state functions are not index\n" - "-- expressions, so opacity to the planner is fine, and a multi-statement\n" - "-- BEGIN/IF/END body is the natural shape. (A LANGUAGE sql CASE would\n" - "-- also work, but the procedural form mirrors the blocker convention.)\n" - ) - sfunc = sfunc_rationale + ( - f"CREATE FUNCTION {DOMAIN_SCHEMA}.{op.sfunc_name}(state {dom}, value {dom})\n" - f"RETURNS {dom}\n" - f"LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE\n" - f"SET search_path = pg_catalog, extensions, public\n" - f"AS $$\n" - f"BEGIN\n" - f" IF value {op.comparator} state THEN\n" - f" RETURN value;\n" - f" END IF;\n" - f" RETURN state;\n" - f"END;\n" - f"$$;\n" - ) - agg_doxy = ( - f"--! @brief Find the {op.phrase} encrypted value in a group of " - f"{dom} values.\n" - f"--!\n" - f"--! Comparison routes through the domain's `{op.comparator}` " - f"operator, which uses the ORE block term — no decryption.\n" - f"--!\n" - f"--! @param input {dom} encrypted values to aggregate\n" - f"--! @return {dom} {op.phrase} of the group, or NULL if all " - f"inputs are NULL\n" - ) - # min/max are associative, so the state function doubles as the combine - # function: merging two partial extrema is the same comparison. With a - # PARALLEL SAFE sfunc/combinefunc and `parallel = safe`, PG can use partial - # and parallel aggregation on the large GROUP BY workloads these ORE - # aggregates exist to serve — still with no decryption. The combinefunc is - # STRICT (it is the sfunc), so PG carries a null partial state through as - # "no value yet", matching the serial seed-and-skip semantics. - aggregate = ( - "-- combinefunc = sfunc: min/max are associative, so merging two partial\n" - "-- extrema is the same comparison. PARALLEL SAFE enables partial and\n" - "-- parallel aggregation on large GROUP BY workloads, with no decryption.\n" - f"CREATE AGGREGATE {DOMAIN_SCHEMA}.{op.name}({dom}) (\n" - f" sfunc = {DOMAIN_SCHEMA}.{op.sfunc_name},\n" - f" stype = {dom},\n" - f" combinefunc = {DOMAIN_SCHEMA}.{op.sfunc_name},\n" - f" parallel = safe\n" - f");\n" - ) - return sfunc_doxy + sfunc + "\n" + agg_doxy + aggregate - - -def render_operator( - op: str, backing: str, leftarg: str, rightarg: str, supported: bool -) -> str: - """A CREATE OPERATOR declaration. - - Unsupported operators are still declared, but their backing function is a - blocker that always raises. We emit them so the operator resolves on the - domain (rather than silently falling through to a native jsonb operator), - and a leading SQL comment explains the placeholder to future readers.""" - meta = OPERATORS[op] - lines = [] - if not supported: - lines.append( - f"-- Placeholder: this domain's term set does not support {op}; " - f"the backing function always raises." - ) - lines += [ - f"CREATE OPERATOR {op} (", - f" FUNCTION = {DOMAIN_SCHEMA}.{backing},", - f" LEFTARG = {leftarg}, RIGHTARG = {rightarg}", - ] - if supported and meta.kind == "symmetric": - extras = [] - if meta.commutator: - extras.append(f"COMMUTATOR = {meta.commutator}") - if meta.negator: - extras.append(f"NEGATOR = {meta.negator}") - if meta.restrict: - extras.append(f"RESTRICT = {meta.restrict}") - if meta.join: - extras.append(f"JOIN = {meta.join}") - if extras: - lines[-1] += "," - lines.append(" " + ", ".join(extras)) - lines.append(");") - return "\n".join(lines) + "\n" diff --git a/tasks/codegen/terms.py b/tasks/codegen/terms.py deleted file mode 100644 index 32a7c788..00000000 --- a/tasks/codegen/terms.py +++ /dev/null @@ -1,107 +0,0 @@ -"""Fixed index-term catalog for scalar encrypted-domain codegen.""" - -from collections.abc import Iterable -from dataclasses import dataclass - - -class TermError(Exception): - """Raised when a manifest references an unknown term.""" - - -@dataclass(frozen=True) -class Term: - """One fixed index term known to the scalar materializer.""" - - name: str - json_key: str - extractor: str - returns: str - ctor: str - role: str - operators: tuple[str, ...] - requires: tuple[str, ...] - - -TERM_CATALOG: dict[str, Term] = { - "hm": Term( - name="hm", - json_key="hm", - extractor="eq_term", - returns="eql_v2.hmac_256", - ctor="hmac_256", - role="eq", - operators=("=", "<>"), - requires=("src/hmac_256/functions.sql",), - ), - "ore": Term( - name="ore", - json_key="ob", - extractor="ord_term", - returns="eql_v2.ore_block_u64_8_256", - ctor="ore_block_u64_8_256", - role="ord", - operators=("=", "<>", "<", "<=", ">", ">="), - requires=( - "src/ore_block_u64_8_256/functions.sql", - "src/ore_block_u64_8_256/operators.sql", - ), - ), -} - - -def _dedupe_preserving_order(values: Iterable[str]) -> list[str]: - """Stable dedupe — first occurrence wins. `dict.fromkeys` preserves insert order.""" - return list(dict.fromkeys(values)) - - -def require_terms(names: list[str]) -> list[Term]: - """Return catalog terms for manifest names, preserving input order.""" - terms: list[Term] = [] - for name in names: - try: - terms.append(TERM_CATALOG[name]) - except KeyError as exc: - raise TermError( - f"unknown term '{name}' (expected one of {sorted(TERM_CATALOG)})" - ) from exc - return terms - - -def operators_for_terms(names: list[str]) -> list[str]: - """Supported operators for the union of a domain's terms.""" - return _dedupe_preserving_order( - op for term in require_terms(names) for op in term.operators - ) - - -def term_json_keys(names: list[str]) -> list[str]: - """JSON payload keys required by these terms.""" - return _dedupe_preserving_order( - term.json_key for term in require_terms(names) - ) - - -def term_requires(names: list[str]) -> list[str]: - """SQL REQUIRE edges needed by these terms.""" - return _dedupe_preserving_order( - req for term in require_terms(names) for req in term.requires - ) - - -def extractor_for_operator(names: list[str], op: str) -> str | None: - """The catalog extractor that supports `op` for a domain carrying `names`.""" - for term in require_terms(names): - if op in term.operators: - return term.extractor - return None - - -def role_for_terms(names: list[str]) -> str: - """Generated-file role label for a domain with these terms. - - A domain with no terms is `storage`; otherwise the role comes from - the first term's catalog role (e.g. `hm` -> `eq`, `ore` -> `ord`). - """ - if not names: - return "storage" - return require_terms(names)[0].role diff --git a/tasks/codegen/test_against_reference.py b/tasks/codegen/test_against_reference.py deleted file mode 100644 index e7ea62e9..00000000 --- a/tasks/codegen/test_against_reference.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Identity guard: the generator must reproduce the frozen manual -reference under tests/codegen/reference// byte-for-byte. - -The reference is the reviewed manual implementation. If the generator's -output diverges from the reference, either the generator regressed (fix -it) or the reference is being deliberately updated (commit the new -reference in this PR). - -Compares in-memory `render_*_file` output directly against the reference, -so it runs anywhere regardless of whether the build has materialised -src/encrypted_domain// (those files are gitignored — `tasks/build.sh` -regenerates them on each build). -""" -from pathlib import Path - -import pytest - -from tasks.codegen.generate import ( - REPO_ROOT, - render_aggregates_file, - render_functions_file, - render_operators_file, - render_types_file, -) -from tasks.codegen.spec import load_spec -from tasks.codegen.templates import render_fixture_values_rs - -_REFERENCE_ROOT = REPO_ROOT / "tests" / "codegen" / "reference" -_TYPES_DIR = REPO_ROOT / "tasks" / "codegen" / "types" - - -def _strip_reference_marker(text: str) -> str: - """Drop any leading `-- REFERENCE:` / `// REFERENCE:` lines. They label the - file as the parity baseline (see tests/codegen/reference/README.md) and are - not part of the generator's output. Both comment styles are recognised so - the same helper serves SQL and Rust reference files.""" - lines = text.splitlines(keepends=True) - while lines and lines[0].startswith(("-- REFERENCE:", "// REFERENCE:")): - lines.pop(0) - return "".join(lines) - - -def _reference_files() -> list[Path]: - """Every SQL file under tests/codegen/reference//.""" - if not _REFERENCE_ROOT.is_dir(): - return [] - return sorted(_REFERENCE_ROOT.glob("*/*.sql")) - - -def _render(reference_path: Path) -> str: - """Render the corresponding generator output for a reference file.""" - token = reference_path.parent.name - name = reference_path.name - spec = load_spec(_TYPES_DIR / f"{token}.toml") - - if name == f"{token}_types.sql": - return render_types_file(spec) - - for domain in spec.domains: - if name == f"{domain.name}_functions.sql": - return render_functions_file(spec, domain) - if name == f"{domain.name}_operators.sql": - return render_operators_file(spec, domain) - if name == f"{domain.name}_aggregates.sql": - body = render_aggregates_file(spec, domain) - if body is None: - pytest.fail( - f"reference {reference_path.relative_to(REPO_ROOT)} exists " - f"but the generator skipped this variant (not ord-capable). " - f"Remove the reference file or update the manifest." - ) - return body - - pytest.fail(f"unrecognised reference filename: {name}") - - -@pytest.mark.parametrize( - "reference_path", - _reference_files(), - ids=lambda p: f"{p.parent.name}/{p.name}", -) -def test_generator_matches_manual_reference(reference_path: Path): - """Generator render output must equal the reviewed reference.""" - token = reference_path.parent.name - fix = ( - f"either the generator regressed (fix tasks/codegen/) or the " - f"manual reference is being updated deliberately — commit the " - f"new reference at {reference_path.relative_to(REPO_ROOT)} in " - f"this PR. Regenerate via: mise run codegen:domain {token}" - ) - - expected = _strip_reference_marker(reference_path.read_text(encoding="utf-8")) - actual = _render(reference_path) - - assert actual == expected, f"{reference_path.name}: {fix}" - - -def test_generator_matches_rust_fixture_values_reference(): - """The generated Rust fixture-value const must match the reviewed reference. - - Guards the committed tests/sqlx/src/fixtures/int4_values.rs against drift - from the manifest (the same property the CI staleness guard enforces, but - runnable without a checkout diff).""" - reference_path = _REFERENCE_ROOT / "int4" / "int4_values.rs" - spec = load_spec(_TYPES_DIR / "int4.toml") - - expected = _strip_reference_marker( - reference_path.read_text(encoding="utf-8") - ) - actual = render_fixture_values_rs(spec) - - assert actual == expected, ( - "int4_values.rs: either the generator regressed (fix tasks/codegen/) " - "or the reference is being updated deliberately — commit the new " - f"reference at {reference_path.relative_to(REPO_ROOT)} in this PR. " - "Regenerate via: mise run codegen:domain int4" - ) diff --git a/tasks/codegen/test_generate.py b/tasks/codegen/test_generate.py deleted file mode 100644 index db93b989..00000000 --- a/tasks/codegen/test_generate.py +++ /dev/null @@ -1,351 +0,0 @@ -"""Tests for composing scalar encrypted-domain files from a manifest.""" - -import textwrap - -import pytest - -from tasks.codegen.generate import ( - generate_type, - main, - render_aggregates_file, - render_functions_file, - render_operators_file, - render_types_file, -) -from tasks.codegen.spec import load_spec -from tasks.codegen.templates import AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS -from tasks.codegen.writer import OwnershipError - - -INT4_TOML = textwrap.dedent(""" - [domain] - int4 = [] - int4_eq = ["hm"] - int4_ord_ore = ["ore"] - int4_ord = ["ore"] -""") - -INT4_FIXTURE_TOML = INT4_TOML + textwrap.dedent(""" - [fixture] - values = ["MIN", "-1", "ZERO", "1", "MAX"] -""") - -# A second, synthetic type for multi-type (--all) coverage. No [fixture] table, -# so it never touches scalars.py (which only registers int4) — it exercises the -# enumeration, not fixture rendering. -INT4X_TOML = textwrap.dedent(""" - [domain] - int4x = [] - int4x_eq = ["hm"] - int4x_ord = ["ore"] -""") - - -def _fixture_values_rs(out_root): - return out_root / "tests" / "sqlx" / "src" / "fixtures" / "int4_values.rs" - - -def load(tmp_path): - p = tmp_path / "int4.toml" - p.write_text(INT4_TOML) - return load_spec(p) - - -def test_types_file_has_all_four_domains(tmp_path): - spec = load(tmp_path) - sql = render_types_file(spec) - assert "-- REQUIRE: src/schema-v3.sql" in sql - for dom in ("int4", "int4_eq", - "int4_ord", "int4_ord_ore"): - assert f"CREATE DOMAIN eql_v3.{dom} AS jsonb" in sql - - -def test_storage_functions_file_is_all_blockers(tmp_path): - spec = load(tmp_path) - storage = next(d for d in spec.domains if d.name == "int4") - sql = render_functions_file(spec, storage) - assert sql.count("CREATE FUNCTION") == 44 - assert "SET search_path" not in sql - assert sql.count("LANGUAGE plpgsql") == 44 - assert sql.count("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") == 0 - - -def test_eq_functions_file_counts_and_extractor(tmp_path): - spec = load(tmp_path) - eq = next(d for d in spec.domains if d.name == "int4_eq") - sql = render_functions_file(spec, eq) - assert sql.count("CREATE FUNCTION") == 45 - assert "CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq)" in sql - assert "RETURNS eql_v2.hmac_256" in sql - # 1 extractor + 6 wrappers (=, <> across 3 arg-shapes) inlined as SQL; - # 38 blockers across the remaining native jsonb surface as plpgsql. - assert sql.count("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") == 7 - assert sql.count("LANGUAGE plpgsql") == 38 - assert "SET search_path" not in sql - - -def test_ore_functions_file_counts_and_extractor(tmp_path): - spec = load(tmp_path) - ordered = next(d for d in spec.domains if d.name == "int4_ord") - sql = render_functions_file(spec, ordered) - assert sql.count("CREATE FUNCTION") == 45 - assert "CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord)" in sql - assert "RETURNS eql_v2.ore_block_u64_8_256" in sql - # 1 extractor + 18 wrappers (=, <>, <, <=, >, >= across 3 shapes); - # 26 blockers across containment/path/native-jsonb fallback ops. - assert sql.count("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") == 19 - assert sql.count("LANGUAGE plpgsql") == 26 - assert "SET search_path" not in sql - - -def test_operators_file_has_forty_four(tmp_path): - spec = load(tmp_path) - eq = next(d for d in spec.domains if d.name == "int4_eq") - sql = render_operators_file(spec, eq) - assert sql.count("CREATE OPERATOR") == 44 - - -def test_generate_type_writes_expected_files(tmp_path): - spec = load(tmp_path) - out_dir = tmp_path / "int4" - written = generate_type(spec, out_dir) - names = {p.name for p in written} - assert "int4_types.sql" in names - for domain in ("int4", "int4_eq", "int4_ord", "int4_ord_ore"): - assert f"{domain}_functions.sql" in names - assert f"{domain}_operators.sql" in names - # Aggregates only emitted for ord-capable variants — storage and eq skip. - assert "int4_aggregates.sql" not in names - assert "int4_eq_aggregates.sql" not in names - assert "int4_ord_aggregates.sql" in names - assert "int4_ord_ore_aggregates.sql" in names - # 1 types + 4 functions + 4 operators + 2 aggregates = 11 - assert len(written) == 11 - for p in written: - assert p.read_text().startswith(AUTO_GENERATED_HEADER) - - -def test_generate_type_cleans_stale_files(tmp_path): - spec = load(tmp_path) - out_dir = tmp_path / "int4" - out_dir.mkdir() - stale = out_dir / "int4_removed_functions.sql" - stale.write_text(AUTO_GENERATED_HEADER + "-- orphan\n") - generate_type(spec, out_dir) - assert not stale.exists() - - -def test_generate_type_preserves_hand_written_extension_file(tmp_path): - spec = load(tmp_path) - out_dir = tmp_path / "int4" - out_dir.mkdir() - extension = out_dir / "int4_extensions.sql" - body = ( - "-- REQUIRE: src/encrypted_domain/int4/int4_types.sql\n" - "-- hand-written extension SQL\n" - ) - extension.write_text(body) - generate_type(spec, out_dir) - assert extension.read_text() == body - - -def test_generate_type_preflights_hand_written_target_before_cleanup(tmp_path): - spec = load(tmp_path) - out_dir = tmp_path / "int4" - out_dir.mkdir() - generated = out_dir / "int4_types.sql" - protected = out_dir / "int4_eq_functions.sql" - original_generated = AUTO_GENERATED_HEADER + "-- old generated\n" - original_protected = "-- REQUIRE: src/schema.sql\n-- hand-written\n" - generated.write_text(original_generated) - protected.write_text(original_protected) - - with pytest.raises(OwnershipError, match="hand-written"): - generate_type(spec, out_dir) - - assert generated.read_text() == original_generated - assert protected.read_text() == original_protected - assert not (out_dir / "int4_eq_operators.sql").exists() - - -def _seed_types_dir(tmp_path, name: str = "int4.toml", body: str = INT4_TOML): - types_dir = tmp_path / "types" - types_dir.mkdir() - (types_dir / name).write_text(body) - return types_dir - - -def test_main_rejects_wrong_argv_length(capsys): - rc = main(["generate.py"]) - assert rc == 2 - err = capsys.readouterr().err - assert "Usage: generate.py " in err - - -def test_main_errors_on_missing_manifest(tmp_path, capsys): - types_dir = tmp_path / "types" - types_dir.mkdir() - rc = main( - ["generate.py", "int4"], - types_dir=types_dir, - out_root=tmp_path, - ) - assert rc == 1 - err = capsys.readouterr().err - assert "no manifest at" in err - assert "int4.toml" in err - - -def test_main_errors_on_token_mismatch(tmp_path, capsys): - """Manifest stem must equal argv token — guards against a copy/rename.""" - types_dir = _seed_types_dir(tmp_path, name="int4.toml") - rc = main( - ["generate.py", "int8"], - types_dir=types_dir, - out_root=tmp_path, - ) - # int8.toml doesn't exist — first failure is missing manifest, not mismatch. - # To exercise the mismatch branch we need a manifest at int8.toml that - # declares int4 domains (impossible — the loader infers token from stem). - # The branch is therefore unreachable via the normal types/.toml - # convention; the assertion below just confirms the missing-manifest - # error path fires when the names diverge. - assert rc == 1 - err = capsys.readouterr().err - assert "no manifest at" in err - assert "int8.toml" in err - - -def test_main_happy_path_writes_files(tmp_path, capsys): - types_dir = _seed_types_dir(tmp_path) - rc = main( - ["generate.py", "int4"], - types_dir=types_dir, - out_root=tmp_path, - ) - assert rc == 0 - out_dir = tmp_path / "src" / "encrypted_domain" / "int4" - assert (out_dir / "int4_types.sql").is_file() - assert (out_dir / "int4_eq_functions.sql").is_file() - assert (out_dir / "int4_ord_operators.sql").is_file() - assert (out_dir / "int4_ord_aggregates.sql").is_file() - assert (out_dir / "int4_ord_ore_aggregates.sql").is_file() - assert not (out_dir / "int4_aggregates.sql").exists() - assert not (out_dir / "int4_eq_aggregates.sql").exists() - stdout = capsys.readouterr().out - assert "generated 11 files for int4" in stdout - - -def test_main_emits_fixture_values_rs_when_manifest_has_fixture(tmp_path, capsys): - types_dir = _seed_types_dir(tmp_path, body=INT4_FIXTURE_TOML) - rc = main(["generate.py", "int4"], types_dir=types_dir, out_root=tmp_path) - assert rc == 0 - rs = _fixture_values_rs(tmp_path) - assert rs.is_file() - text = rs.read_text() - assert text.startswith(AUTO_GENERATED_HEADER_RS) - assert "pub const VALUES: &[i32] = &[" in text - assert "i32::MIN," in text and "i32::MAX," in text - stdout = capsys.readouterr().out - assert "int4_values.rs" in stdout - - -def test_main_omits_fixture_values_rs_when_no_fixture_table(tmp_path, capsys): - types_dir = _seed_types_dir(tmp_path, body=INT4_TOML) - rc = main(["generate.py", "int4"], types_dir=types_dir, out_root=tmp_path) - assert rc == 0 - assert not _fixture_values_rs(tmp_path).exists() - - -def _seed_two_types(tmp_path): - types_dir = _seed_types_dir(tmp_path, name="int4.toml", body=INT4_TOML) - (types_dir / "int4x.toml").write_text(INT4X_TOML) - return types_dir - - -def test_main_all_generates_every_type(tmp_path, capsys): - types_dir = _seed_two_types(tmp_path) - rc = main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) - assert rc == 0 - assert (tmp_path / "src/encrypted_domain/int4/int4_types.sql").is_file() - assert (tmp_path / "src/encrypted_domain/int4x/int4x_types.sql").is_file() - out = capsys.readouterr().out - assert "generated 11 files for int4" in out - assert "codegen --all: ok (2 types: int4, int4x)" in out - - -def test_main_all_generates_in_sorted_order(tmp_path, capsys): - types_dir = _seed_two_types(tmp_path) - main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) - out = capsys.readouterr().out - assert out.index("for int4\n") < out.index("for int4x\n") - - -def test_main_all_errors_when_no_manifests(tmp_path, capsys): - types_dir = tmp_path / "types" - types_dir.mkdir() - rc = main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) - assert rc == 1 - assert "no manifests found" in capsys.readouterr().err - - -def test_main_all_aggregates_nonzero_on_bad_manifest(tmp_path, capsys): - types_dir = _seed_types_dir(tmp_path, name="int4.toml", body=INT4_TOML) - # 'broken' sorts before 'int4', so it is processed first; its domain name - # does not start with the token, so load_spec raises SpecError. - (types_dir / "broken.toml").write_text("[domain]\nwrongprefix = []\n") - rc = main(["generate.py", "--all"], types_dir=types_dir, out_root=tmp_path) - assert rc == 1 - captured = capsys.readouterr() - assert "broken" in captured.err - assert "codegen --all: FAILED" in captured.out - # The good type still generated despite the broken sibling. - assert (tmp_path / "src/encrypted_domain/int4/int4_types.sql").is_file() - - -def test_ordered_files_are_byte_identical_modulo_typename(tmp_path): - spec = load(tmp_path) - ord_domain = next(d for d in spec.domains if d.name == "int4_ord") - ore_domain = next(d for d in spec.domains if d.name == "int4_ord_ore") - - for renderer in (render_functions_file, render_operators_file, render_aggregates_file): - ord_sql = renderer(spec, ord_domain) - ore_sql = renderer(spec, ore_domain) - normalised_ord = ord_sql.replace("int4_ord_ore", "T").replace( - "int4_ord", "T" - ) - normalised_ore = ore_sql.replace("int4_ord_ore", "T").replace( - "int4_ord", "T" - ) - assert normalised_ord == normalised_ore, ( - f"{renderer.__name__}: int4_ord and int4_ord_ore must produce " - f"byte-identical SQL modulo their typenames" - ) - - -def test_render_aggregates_file_only_for_ord_variants(tmp_path): - spec = load(tmp_path) - storage = next(d for d in spec.domains if d.name == "int4") - eq = next(d for d in spec.domains if d.name == "int4_eq") - ordered = next(d for d in spec.domains if d.name == "int4_ord") - ore = next(d for d in spec.domains if d.name == "int4_ord_ore") - - assert render_aggregates_file(spec, storage) is None - assert render_aggregates_file(spec, eq) is None - assert render_aggregates_file(spec, ordered) is not None - assert render_aggregates_file(spec, ore) is not None - - -def test_render_aggregates_file_carries_both_min_and_max(tmp_path): - spec = load(tmp_path) - ordered = next(d for d in spec.domains if d.name == "int4_ord") - sql = render_aggregates_file(spec, ordered) - assert sql is not None - assert sql.count("CREATE FUNCTION") == 2 - assert sql.count("CREATE AGGREGATE") == 2 - assert "eql_v3.min_sfunc" in sql - assert "eql_v3.max_sfunc" in sql - # REQUIRE edges: types + functions + operators must all be declared. - assert "-- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql" in sql - assert "-- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql" in sql - assert "-- REQUIRE: src/encrypted_domain/int4/int4_types.sql" in sql diff --git a/tasks/codegen/test_operator_surface.py b/tasks/codegen/test_operator_surface.py deleted file mode 100644 index a513ce82..00000000 --- a/tasks/codegen/test_operator_surface.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Tests for the scalar operator surface definition.""" -from tasks.codegen.operator_surface import ( - BLOCKER_ONLY_OPERATORS, - KNOWN_JSONB_OPERATORS, - OPERATORS, - PATH_OPERATORS, - SYMMETRIC_OPERATORS, - backing_function, -) - - -def test_twenty_operators_total(): - """The surface covers supported wrappers plus native jsonb fallbacks.""" - assert len(OPERATORS) == 20 - - -def test_eight_symmetric_operators(): - """8 symmetric boolean operators.""" - assert SYMMETRIC_OPERATORS == ["=", "<>", "<", "<=", ">", ">=", "@>", "<@"] - - -def test_two_path_operators(): - """2 path operators.""" - assert PATH_OPERATORS == ["->", "->>"] - - -def test_ten_blocker_only_jsonb_fallback_operators(): - """Native jsonb operators not otherwise supported are blocker-only.""" - assert BLOCKER_ONLY_OPERATORS == [ - "?", - "?|", - "?&", - "@?", - "@@", - "#>", - "#>>", - "-", - "#-", - "||", - ] - - -def test_no_like_operators(): - """The surface excludes ~~ and ~~* (int4 has no LIKE support).""" - assert "~~" not in OPERATORS - assert "~~*" not in OPERATORS - - -def test_backing_function_names(): - """Each operator maps to its eql_v2 backing function name.""" - assert backing_function("=") == "eq" - assert backing_function("<>") == "neq" - assert backing_function("<") == "lt" - assert backing_function("<=") == "lte" - assert backing_function(">") == "gt" - assert backing_function(">=") == "gte" - assert backing_function("@>") == "contains" - assert backing_function("<@") == "contained_by" - assert backing_function("->") == '"->"' - assert backing_function("->>") == '"->>"' - assert backing_function("?") == '"?"' - assert backing_function("?|") == '"?|"' - assert backing_function("?&") == '"?&"' - assert backing_function("@?") == '"@?"' - assert backing_function("@@") == '"@@"' - assert backing_function("#>") == '"#>"' - assert backing_function("#>>") == '"#>>"' - assert backing_function("-") == '"-"' - assert backing_function("#-") == '"#-"' - assert backing_function("||") == '"||"' - - -def test_selectivity_estimators(): - """Symmetric ops carry RESTRICT/JOIN selectivity estimators.""" - assert OPERATORS["="].restrict == "eqsel" - assert OPERATORS["="].join == "eqjoinsel" - assert OPERATORS["<>"].restrict == "neqsel" - assert OPERATORS["<"].restrict == "scalarltsel" - assert OPERATORS["<="].restrict == "scalarlesel" - assert OPERATORS[">"].restrict == "scalargtsel" - assert OPERATORS[">="].restrict == "scalargesel" - - -def test_negators_and_commutators(): - """= / <> are negators; range ops commute as documented.""" - assert OPERATORS["="].negator == "<>" - assert OPERATORS["<>"].negator == "=" - assert OPERATORS["<"].commutator == ">" - assert OPERATORS["<"].negator == ">=" - assert OPERATORS[">="].commutator == "<=" - - -def test_known_jsonb_operators_is_union_of_the_three_lists(): - """The exported union is exactly the three enumerated lists, deduped.""" - assert KNOWN_JSONB_OPERATORS == frozenset( - SYMMETRIC_OPERATORS + PATH_OPERATORS + BLOCKER_ONLY_OPERATORS - ) - - -def test_known_jsonb_operators_matches_operators_keys(): - """The union must stay in lockstep with the OPERATORS table itself, so a - new operator added to one but not the other is caught here rather than - leaving a hole in the storage-only blocker guarantee.""" - assert KNOWN_JSONB_OPERATORS == frozenset(OPERATORS) - - -def test_known_jsonb_operators_full_native_surface(): - """Pin the full native jsonb operator surface for PG 14-17. This is the - source-of-truth the live-DB structural guard - (tests/sqlx/.../family/jsonb_operator_surface.rs) asserts pg_operator is a - subset of. If PG adds a jsonb operator, that DB test fails; if this list is - edited, both must move together. The three lists are disjoint, so the union - size equals their combined length.""" - assert KNOWN_JSONB_OPERATORS == frozenset( - { - # symmetric (supported wrappers) - "=", "<>", "<", "<=", ">", ">=", "@>", "<@", - # path - "->", "->>", - # blocker-only native jsonb fallbacks - "?", "?|", "?&", "@?", "@@", "#>", "#>>", "-", "#-", "||", - } - ) - assert len(KNOWN_JSONB_OPERATORS) == ( - len(SYMMETRIC_OPERATORS) + len(PATH_OPERATORS) + len(BLOCKER_ONLY_OPERATORS) - ) diff --git a/tasks/codegen/test_scalars.py b/tasks/codegen/test_scalars.py deleted file mode 100644 index 1f15f1c3..00000000 --- a/tasks/codegen/test_scalars.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Tests for the scalar-kind catalog driving fixture-value emission.""" - -import pytest - -from tasks.codegen.scalars import ( - ScalarError, - require_scalar, - SCALAR_KINDS, -) - - -def test_int4_kind_fields(): - kind = require_scalar("int4") - assert kind.token == "int4" - assert kind.rust_type == "i32" - assert kind.min_symbol == "i32::MIN" - assert kind.max_symbol == "i32::MAX" - assert kind.zero_symbol == "0" - assert kind.min_value == -2147483648 - assert kind.max_value == 2147483647 - - -def test_render_literal_maps_sentinels(): - kind = require_scalar("int4") - assert kind.render_literal("MIN") == "i32::MIN" - assert kind.render_literal("MAX") == "i32::MAX" - assert kind.render_literal("ZERO") == "0" - - -def test_render_literal_passes_through_numeric(): - kind = require_scalar("int4") - assert kind.render_literal("-100") == "-100" - assert kind.render_literal("0") == "0" - assert kind.render_literal("9999") == "9999" - - -def test_render_literal_rejects_non_numeric(): - kind = require_scalar("int4") - with pytest.raises(ScalarError, match="not a valid i32 literal or sentinel"): - kind.render_literal("oops") - - -def test_render_literal_rejects_out_of_range(): - kind = require_scalar("int4") - with pytest.raises(ScalarError, match="out of range"): - kind.render_literal("2147483648") # i32::MAX + 1 - - -def test_numeric_value_resolves_sentinels_and_literals(): - kind = require_scalar("int4") - assert kind.numeric_value("MIN") == -2147483648 - assert kind.numeric_value("MAX") == 2147483647 - assert kind.numeric_value("ZERO") == 0 - assert kind.numeric_value("42") == 42 - assert kind.numeric_value("-1") == -1 - - -def test_require_scalar_unknown_raises(): - with pytest.raises(ScalarError, match="unknown scalar token 'bogus'"): - require_scalar("bogus") - - -def test_int4_registered_in_catalog(): - assert "int4" in SCALAR_KINDS - - -def test_int2_kind_resolves_and_renders(): - kind = require_scalar("int2") - assert kind.rust_type == "i16" - assert kind.numeric_value("MIN") == -32768 - assert kind.numeric_value("MAX") == 32767 - assert kind.numeric_value("ZERO") == 0 - assert kind.render_literal("MIN") == "i16::MIN" - assert kind.render_literal("MAX") == "i16::MAX" - assert kind.render_literal("ZERO") == "0" - assert kind.render_literal("30000") == "30000" - - -def test_int2_kind_rejects_out_of_range(): - kind = require_scalar("int2") - with pytest.raises(ScalarError, match="out of range"): - kind.numeric_value("40000") diff --git a/tasks/codegen/test_spec.py b/tasks/codegen/test_spec.py deleted file mode 100644 index 151a03cb..00000000 --- a/tasks/codegen/test_spec.py +++ /dev/null @@ -1,208 +0,0 @@ -"""Tests for the scalar-domain manifest loader.""" - -import textwrap - -import pytest - -from tasks.codegen.spec import DomainSpec, SpecError, TypeSpec, load_spec - - -VALID_TOML = textwrap.dedent(""" - [domain] - int4 = [] - int4_eq = ["hm"] - int4_ord_ore = ["ore"] - int4_ord = ["ore"] -""") - - -def write(tmp_path, name, text): - p = tmp_path / name - p.write_text(text) - return p - - -def test_loads_valid_manifest_and_infers_token_from_filename(tmp_path): - spec = load_spec(write(tmp_path, "int4.toml", VALID_TOML)) - assert isinstance(spec, TypeSpec) - assert spec.token == "int4" - assert spec.domains == [ - DomainSpec(name="int4", terms=[]), - DomainSpec(name="int4_eq", terms=["hm"]), - DomainSpec(name="int4_ord_ore", terms=["ore"]), - DomainSpec(name="int4_ord", terms=["ore"]), - ] - - -def test_missing_domain_table_raises(tmp_path): - with pytest.raises(SpecError, match="missing required table '\\[domain\\]'"): - load_spec(write(tmp_path, "int4.toml", "")) - - -def test_empty_domain_table_raises(tmp_path): - with pytest.raises(SpecError, match="at least one domain"): - load_spec(write(tmp_path, "int4.toml", "[domain]\n")) - - -def test_domain_value_must_be_list(tmp_path): - bad = textwrap.dedent(""" - [domain] - int4_eq = "hm" - """) - with pytest.raises(SpecError, match="must be a list of term names"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_domain_term_must_be_string(tmp_path): - bad = textwrap.dedent(""" - [domain] - int4_eq = [1] - """) - with pytest.raises(SpecError, match="term names must be strings"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_unknown_term_raises_with_domain_context(tmp_path): - bad = textwrap.dedent(""" - [domain] - int4_eq = ["bogus"] - """) - with pytest.raises(SpecError, match="\\[domain\\] int4_eq: unknown term 'bogus'"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_domain_name_must_start_with_type_token(tmp_path): - bad = textwrap.dedent(""" - [domain] - text = [] - """) - with pytest.raises(SpecError, match="domain name must start with 'int4'"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_domain_name_must_be_token_or_token_underscore(tmp_path): - bad = textwrap.dedent(""" - [domain] - int4xfoo = [] - """) - with pytest.raises(SpecError, match="domain name must start with 'int4'"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -@pytest.mark.parametrize("filename", [ - "Int4.toml", - "int-4.toml", - "int 4.toml", - "4int.toml", - "int4;drop.toml", -]) -def test_token_must_be_sql_identifier(tmp_path, filename): - with pytest.raises(SpecError, match=r"token .* must match"): - load_spec(write(tmp_path, filename, VALID_TOML)) - - -@pytest.mark.parametrize("bad_name", [ - "int4-eq", - "int4 eq", - "INT4_eq", - "int4;drop", -]) -def test_domain_name_must_be_sql_identifier(tmp_path, bad_name): - bad = textwrap.dedent(f""" - [domain] - "{bad_name}" = [] - """) - with pytest.raises(SpecError, match=r"domain name .* must match"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -FIXTURE_TOML = VALID_TOML + textwrap.dedent(""" - [fixture] - values = ["MIN", "-100", "-1", "ZERO", "1", "9999", "MAX"] -""") - - -def test_fixture_values_default_to_none_when_absent(tmp_path): - spec = load_spec(write(tmp_path, "int4.toml", VALID_TOML)) - assert spec.fixture_values is None - - -def test_loads_fixture_values_when_present(tmp_path): - spec = load_spec(write(tmp_path, "int4.toml", FIXTURE_TOML)) - assert spec.fixture_values == [ - "MIN", "-100", "-1", "ZERO", "1", "9999", "MAX", - ] - - -def test_fixture_values_must_be_a_list(tmp_path): - bad = VALID_TOML + '\n[fixture]\nvalues = "MIN"\n' - with pytest.raises(SpecError, match=r"\[fixture\] values: must be a list"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_table_requires_values_key(tmp_path): - bad = VALID_TOML + "\n[fixture]\nother = 1\n" - with pytest.raises(SpecError, match=r"\[fixture\]: missing required key 'values'"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_values_must_be_non_empty(tmp_path): - bad = VALID_TOML + "\n[fixture]\nvalues = []\n" - with pytest.raises(SpecError, match=r"\[fixture\] values: must not be empty"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_values_must_be_strings(tmp_path): - bad = VALID_TOML + "\n[fixture]\nvalues = [1, 2]\n" - with pytest.raises(SpecError, match=r"\[fixture\] values: must be strings"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_values_reject_invalid_literal(tmp_path): - bad = VALID_TOML + '\n[fixture]\nvalues = ["MIN", "oops", "ZERO", "MAX"]\n' - with pytest.raises(SpecError, match="not a valid i32 literal"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_values_require_min_max_zero(tmp_path): - bad = VALID_TOML + '\n[fixture]\nvalues = ["1", "2", "3"]\n' - with pytest.raises(SpecError, match="must include MIN, MAX, and zero"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_values_require_max_even_if_min_and_zero_present(tmp_path): - bad = VALID_TOML + '\n[fixture]\nvalues = ["MIN", "ZERO", "1"]\n' - with pytest.raises(SpecError, match="must include MIN, MAX, and zero"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_values_reject_duplicate_literal(tmp_path): - bad = VALID_TOML + '\n[fixture]\nvalues = ["MIN", "1", "ZERO", "1", "MAX"]\n' - with pytest.raises(SpecError, match=r"must be distinct.*duplicate values.*'1'"): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_values_reject_sentinel_literal_alias(tmp_path): - # "MIN" and the i32::MIN literal resolve to the same plaintext value; - # the distinct-plaintext contract must reject the pair. - bad = ( - VALID_TOML - + '\n[fixture]\nvalues = ["MIN", "-2147483648", "ZERO", "MAX"]\n' - ) - with pytest.raises( - SpecError, - match=r"must be distinct.*'-2147483648' duplicates 'MIN' \(both resolve to -2147483648\)", - ): - load_spec(write(tmp_path, "int4.toml", bad)) - - -def test_fixture_for_unknown_scalar_token_raises(tmp_path): - bad = textwrap.dedent(""" - [domain] - int8 = [] - - [fixture] - values = ["1"] - """) - with pytest.raises(SpecError, match="unknown scalar token 'int8'"): - load_spec(write(tmp_path, "int8.toml", bad)) diff --git a/tasks/codegen/test_templates.py b/tasks/codegen/test_templates.py deleted file mode 100644 index 4a24f923..00000000 --- a/tasks/codegen/test_templates.py +++ /dev/null @@ -1,500 +0,0 @@ -"""Tests for per-construct SQL template functions.""" - -from tasks.codegen.spec import DomainSpec, TypeSpec -from tasks.codegen.templates import ( - AGGREGATE_OPS, - AUTO_GENERATED_HEADER, - AUTO_GENERATED_HEADER_RS, - _sql_str, - brief_role_clause, - domain_name, - extractor_for_operator, - is_ord_capable, - render_aggregate, - render_blocker_bool, - render_blocker_native, - render_blocker_path, - render_domain_block, - render_extractor, - render_fixture_values_rs, - render_operator, - render_wrapper, -) -from tasks.codegen.terms import TERM_CATALOG - - -def test_auto_generated_header_present(): - # Byte-identical to the Rust generator's marker - # (crates/eql-codegen/src/consts.rs) and to the `^-- AUTOMATICALLY GENERATED - # FILE` prefix that tasks/docs/validate/*.sh grep on to skip generated SQL. - assert AUTO_GENERATED_HEADER == "-- AUTOMATICALLY GENERATED FILE.\n" - assert "AUTOMATICALLY GENERATED FILE" in AUTO_GENERATED_HEADER - - -def test_rust_header_is_a_rust_comment(): - # Rust uses // comments, not SQL's --. Byte-identical to the Rust - # generator's AUTO_GENERATED_HEADER_RS (crates/eql-codegen/src/consts.rs). - assert AUTO_GENERATED_HEADER_RS == "// AUTOMATICALLY GENERATED FILE.\n" - # No line is an SQL-style (`--`) comment — this is Rust, not SQL. - assert not any( - line.startswith("--") for line in AUTO_GENERATED_HEADER_RS.splitlines() - ) - - -def test_render_fixture_values_rs_emits_typed_const(): - spec = TypeSpec( - token="int4", - domains=[], - fixture_values=["MIN", "-1", "ZERO", "1", "MAX"], - ) - body = render_fixture_values_rs(spec) - assert "pub const VALUES: &[i32] = &[" in body - assert "`int4` row in `eql-scalars::CATALOG`" in body - # Sentinels map to named consts; numeric tokens pass through. - assert "i32::MIN," in body - assert "i32::MAX," in body - assert " -1,\n" in body - assert " 0,\n" in body # ZERO and "1" both literal - assert " 1,\n" in body - # No generated-file marker in the body — the writer prepends it. - assert "AUTOMATICALLY GENERATED FILE" not in body - - -def test_render_fixture_values_rs_preserves_manifest_order(): - spec = TypeSpec( - token="int4", - domains=[], - fixture_values=["MIN", "ZERO", "MAX"], - ) - body = render_fixture_values_rs(spec) - assert body.index("i32::MIN") < body.index("0,") < body.index("i32::MAX") - - -def test_domain_block_storage_uses_fixed_envelope_only(): - domain = DomainSpec(name="int4", terms=[]) - sql = render_domain_block(domain, "int4") - assert "CREATE DOMAIN eql_v3.int4 AS jsonb" in sql - assert "VALUE ? 'v'" in sql - assert "VALUE ? 'i'" in sql - assert "VALUE ? 'c'" in sql - assert "VALUE ? 'hm'" not in sql - assert "VALUE ? 'ob'" not in sql - - -def test_domain_block_uses_catalog_json_keys(): - domain = DomainSpec(name="int4_ord", terms=["ore"]) - sql = render_domain_block(domain, "int4") - assert "CREATE DOMAIN eql_v3.int4_ord AS jsonb" in sql - assert "VALUE ? 'ob'" in sql - assert "VALUE ? 'ore'" not in sql - - -def test_domain_block_check_pins_envelope_version(): - """Thread D: the CHECK both verifies the envelope `v` key is PRESENT and - pins its value to the EQL payload-format version (2), matching the - repo-wide eql_v2._encrypted_check_v rule. The v=1 payloads in - tests/sqlx/fixtures/aggregate_minmax_data.sql belong to the separate - composite-type (eql_v2_encrypted) aggregate stream, not these domains, so - pinning the value here rejects stale/foreign-version payloads without - affecting that fixture.""" - for domain in ( - DomainSpec(name="int4", terms=[]), - DomainSpec(name="int4_eq", terms=["hm"]), - DomainSpec(name="int4_ord", terms=["ore"]), - ): - sql = render_domain_block(domain, "int4") - assert "VALUE ? 'v'" in sql # presence checked - assert "VALUE->>'v' = '2'" in sql # value pinned to version 2 - - -def test_extractor_is_catalog_derived_and_inlinable(): - domain = DomainSpec(name="int4_eq", terms=["hm"]) - sql = render_extractor(domain, TERM_CATALOG["hm"]) - assert "CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq)" in sql - assert "RETURNS eql_v2.hmac_256" in sql - assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" in sql - assert "SELECT eql_v2.hmac_256(a::jsonb)" in sql - assert "SET search_path" not in sql - - -def test_wrapper_uses_term_extractor_for_supported_operator(): - domain = DomainSpec(name="int4_ord", terms=["ore"]) - sql = render_wrapper( - domain, - op="<", - arg_a="eql_v3.int4_ord", - arg_b="jsonb", - extractor="ord_term", - ) - assert "CREATE FUNCTION eql_v3.lt(a eql_v3.int4_ord, b jsonb)" in sql - assert "SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b::eql_v3.int4_ord)" in sql - - -def test_wrapper_is_inlinable_sql(): - """Wrappers must be single-statement LANGUAGE sql with no search_path pin.""" - domain = DomainSpec(name="int4_eq", terms=["hm"]) - sql = render_wrapper( - domain, - op="=", - arg_a="eql_v3.int4_eq", - arg_b="eql_v3.int4_eq", - extractor="eq_term", - ) - assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" in sql - assert "SET search_path" not in sql - assert "LANGUAGE plpgsql" not in sql - - -def test_extractor_for_operator_selects_catalog_term(): - domain = DomainSpec(name="int4_ord", terms=["ore"]) - assert extractor_for_operator(domain, "=") == "ord_term" - assert extractor_for_operator(domain, "<") == "ord_term" - - -def test_extractor_for_operator_returns_none_for_unsupported_operator(): - domain = DomainSpec(name="int4_eq", terms=["hm"]) - assert extractor_for_operator(domain, "<") is None - - -def test_blocker_bool_is_not_strict(): - """Footgun: a STRICT blocker lets Postgres skip the body on NULL input, - silently bypassing the 'operator not supported' raise. Assert the exact - attribute line so any future refactor that re-adds STRICT fails loudly.""" - domain = DomainSpec(name="int4", terms=[]) - sql = render_blocker_bool( - domain, op="<", arg_a="eql_v3.int4", arg_b="eql_v3.int4", - ) - assert "CREATE FUNCTION eql_v3.lt(a eql_v3.int4, b eql_v3.int4)" in sql - assert "encrypted_domain_unsupported_bool('eql_v3.int4', '<')" in sql - assert "RETURNS boolean IMMUTABLE PARALLEL SAFE\n" in sql - assert "LANGUAGE plpgsql" in sql - assert "STRICT" not in sql - - -def test_blocker_path_is_not_strict(): - """Mirror of test_blocker_bool_is_not_strict for path blockers.""" - domain = DomainSpec(name="int4", terms=[]) - sql = render_blocker_path( - domain, op="->", arg_a="eql_v3.int4", arg_b="text", - ) - assert "RETURNS eql_v3.int4 IMMUTABLE PARALLEL SAFE\n" in sql - assert "LANGUAGE plpgsql" in sql - assert "STRICT" not in sql - - -def test_blocker_path_returns_domain_or_text(): - domain = DomainSpec(name="int4", terms=[]) - arrow = render_blocker_path( - domain, op="->", arg_a="eql_v3.int4", arg_b="text", - ) - assert 'CREATE FUNCTION eql_v3."->"(a eql_v3.int4, selector text)' in arrow - assert "RETURNS eql_v3.int4" in arrow - arrow2 = render_blocker_path( - domain, op="->>", arg_a="eql_v3.int4", arg_b="text", - ) - assert "RETURNS text" in arrow2 - - -def test_blocker_path_for_jsonb_left_arg_returns_domain(): - """The (jsonb, dom) shape from _path_shapes still routes to the domain - return type for `->` (only `->>` returns text).""" - domain = DomainSpec(name="int4", terms=[]) - sql = render_blocker_path( - domain, op="->", arg_a="jsonb", arg_b="eql_v3.int4", - ) - assert 'CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.int4)' in sql - assert "RETURNS eql_v3.int4" in sql - - -def test_blocker_native_bool_uses_helper_and_is_not_strict(): - domain = DomainSpec(name="int4", terms=[]) - sql = render_blocker_native( - domain, op="?", arg_a="eql_v3.int4", arg_b="text", returns="boolean", - ) - assert 'CREATE FUNCTION eql_v3."?"(a eql_v3.int4, b text)' in sql - assert "encrypted_domain_unsupported_bool('eql_v3.int4', '?')" in sql - assert "RETURNS boolean IMMUTABLE PARALLEL SAFE\n" in sql - assert "LANGUAGE plpgsql" in sql - assert "STRICT" not in sql - - -def test_blocker_native_jsonb_result_raises_and_is_not_strict(): - domain = DomainSpec(name="int4", terms=[]) - sql = render_blocker_native( - domain, op="#>", arg_a="eql_v3.int4", arg_b="text[]", returns="jsonb", - ) - assert 'CREATE FUNCTION eql_v3."#>"(a eql_v3.int4, b text[])' in sql - assert "RETURNS jsonb IMMUTABLE PARALLEL SAFE\n" in sql - assert "RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.int4'" in sql - assert "LANGUAGE plpgsql" in sql - assert "STRICT" not in sql - - -def test_blocker_native_text_result_raises_and_is_not_strict(): - domain = DomainSpec(name="int4", terms=[]) - sql = render_blocker_native( - domain, op="#>>", arg_a="eql_v3.int4", arg_b="text[]", returns="text", - ) - assert 'CREATE FUNCTION eql_v3."#>>"(a eql_v3.int4, b text[])' in sql - assert "RETURNS text IMMUTABLE PARALLEL SAFE\n" in sql - assert "LANGUAGE plpgsql" in sql - assert "STRICT" not in sql - - -def test_blocker_native_concat_cross_shape(): - domain = DomainSpec(name="int4", terms=[]) - sql = render_blocker_native( - domain, op="||", arg_a="jsonb", arg_b="eql_v3.int4", returns="jsonb", - ) - assert 'CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.int4)' in sql - assert "RETURNS jsonb" in sql - - -def test_operator_symmetric_metadata(): - sql = render_operator( - op="=", backing="eq", - leftarg="eql_v3.int4_eq", rightarg="eql_v3.int4_eq", - supported=True, - ) - assert "CREATE OPERATOR = (" in sql - assert "FUNCTION = eql_v3.eq" in sql - assert "LEFTARG = eql_v3.int4_eq, RIGHTARG = eql_v3.int4_eq" in sql - assert "NEGATOR = <>" in sql - assert "RESTRICT = eqsel" in sql - - -def test_render_operator_unsupported_emits_only_function_and_args(): - """Unsupported routing must not emit NEGATOR / RESTRICT / JOIN / COMMUTATOR - (those would lie about selectivity for a function that always raises).""" - sql = render_operator( - op="=", backing="eq", - leftarg="eql_v3.int4", rightarg="eql_v3.int4", - supported=False, - ) - assert "CREATE OPERATOR = (" in sql - assert "FUNCTION = eql_v3.eq" in sql - assert "LEFTARG = eql_v3.int4, RIGHTARG = eql_v3.int4" in sql - assert "NEGATOR" not in sql - assert "RESTRICT" not in sql - assert "JOIN" not in sql - assert "COMMUTATOR" not in sql - - -def test_render_aggregate_min_int4_ord_emits_state_function_and_aggregate(): - """Pin the rendered shape for the canonical (int4_ord, min) case.""" - domain = DomainSpec(name="int4_ord", terms=["ore"]) - sql = render_aggregate(domain, AGGREGATE_OPS["min"]) - assert "CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.int4_ord, value eql_v3.int4_ord)" in sql - assert "RETURNS eql_v3.int4_ord" in sql - assert "LANGUAGE plpgsql IMMUTABLE STRICT" in sql - assert "SET search_path = pg_catalog, extensions, public" in sql - assert "IF value < state THEN" in sql - assert "CREATE AGGREGATE eql_v3.min(eql_v3.int4_ord) (" in sql - assert "sfunc = eql_v3.min_sfunc" in sql - assert "stype = eql_v3.int4_ord" in sql - - -def test_render_aggregate_max_uses_greater_than_comparator(): - """Symmetric pin: max uses `>` not `<`.""" - domain = DomainSpec(name="int4_ord_ore", terms=["ore"]) - sql = render_aggregate(domain, AGGREGATE_OPS["max"]) - assert "CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.int4_ord_ore, value eql_v3.int4_ord_ore)" in sql - assert "IF value > state THEN" in sql - assert "CREATE AGGREGATE eql_v3.max(eql_v3.int4_ord_ore) (" in sql - - -def test_render_aggregate_state_function_is_not_inlinable(): - """Footgun mirror: blockers must be LANGUAGE plpgsql; the state function - deliberately is too, so the planner can't elide an IMMUTABLE STRICT - aggregate state call away. STRICT + plpgsql + SET search_path together.""" - domain = DomainSpec(name="int4_ord", terms=["ore"]) - sql = render_aggregate(domain, AGGREGATE_OPS["min"]) - assert "LANGUAGE plpgsql" in sql - assert "STRICT" in sql - # Inlinable-SQL shape — explicitly absent. - assert "LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE" not in sql - - -def test_is_ord_capable_matches_role(): - assert is_ord_capable(DomainSpec(name="int4_ord", terms=["ore"])) is True - assert is_ord_capable(DomainSpec(name="int4_ord_ore", terms=["ore"])) is True - assert is_ord_capable(DomainSpec(name="int4_eq", terms=["hm"])) is False - assert is_ord_capable(DomainSpec(name="int4", terms=[])) is False - - -def test_render_operator_for_containment_omits_commutator(): - """@> has no commutator / negator / selectivity in OPERATORS; supported=True - must still omit those clauses.""" - sql = render_operator( - op="@>", backing="contains", - leftarg="eql_v3.int4_ord", rightarg="eql_v3.int4_ord", - supported=True, - ) - assert "CREATE OPERATOR @> (" in sql - assert "FUNCTION = eql_v3.contains" in sql - assert "COMMUTATOR" not in sql - assert "NEGATOR" not in sql - assert "RESTRICT" not in sql - assert "JOIN" not in sql - - -# --- ITEM A: placeholder/blocker operator comment ------------------------- - - -def test_render_operator_unsupported_emits_placeholder_comment(): - """Thread A: a blocker-backed (unsupported) operator must carry a leading - SQL comment explaining it is a placeholder that raises, so a future - reviewer doesn't wonder why an ordering op is declared on an eq-only - domain.""" - sql = render_operator( - op="<", backing="lt", - leftarg="eql_v3.int4_eq", rightarg="eql_v3.int4_eq", - supported=False, - ) - assert sql.startswith("-- Placeholder:") - assert "does not support <" in sql - assert "always raises" in sql - # The comment precedes the CREATE OPERATOR. - assert sql.index("-- Placeholder:") < sql.index("CREATE OPERATOR") - - -def test_render_operator_supported_has_no_placeholder_comment(): - """Supported operators route to real wrappers — no placeholder comment.""" - sql = render_operator( - op="=", backing="eq", - leftarg="eql_v3.int4_eq", rightarg="eql_v3.int4_eq", - supported=True, - ) - assert "Placeholder" not in sql - - -# --- ITEM B & J: aggregate SQL rationale comments ------------------------- - - -def test_render_aggregate_state_function_emits_plpgsql_rationale_comment(): - """Thread B: the plpgsql rationale must appear in the emitted SQL (not just - as a Python comment) so a SQL reader sees why it isn't an inlinable - LANGUAGE sql CASE.""" - domain = DomainSpec(name="int4_ord", terms=["ore"]) - sql = render_aggregate(domain, AGGREGATE_OPS["min"]) - assert "-- LANGUAGE plpgsql, not sql:" in sql - assert "not index" in sql - # The rationale precedes the state-function definition. - assert sql.index("-- LANGUAGE plpgsql, not sql:") < sql.index( - "CREATE FUNCTION eql_v3.min_sfunc" - ) - - -def test_render_aggregate_enables_parallel_and_combinefunc(): - """Thread #22: MIN/MAX aggregates declare a combine function (the state - function itself — min/max are associative) and PARALLEL = SAFE, so PG can - use partial/parallel aggregation on the large GROUP BY workloads these ORE - aggregates exist to serve. The sfunc is likewise PARALLEL SAFE.""" - for op_name, sfunc in (("min", "min_sfunc"), ("max", "max_sfunc")): - domain = DomainSpec(name="int4_ord", terms=["ore"]) - sql = render_aggregate(domain, AGGREGATE_OPS[op_name]) - # The state function must be parallel-safe... - assert "LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE" in sql - # ...and the aggregate must declare the combinefunc + parallel safety - # inside the CREATE AGGREGATE option list (not merely in prose). - aggregate_body = sql[sql.index(f"CREATE AGGREGATE eql_v3.{op_name}"):] - assert f"combinefunc = eql_v3.{sfunc}" in aggregate_body - assert "parallel = safe" in aggregate_body - # The stale "intentionally disabled" omission note must be gone. - assert "intentionally disabled" not in sql - assert "-- No COMBINEFUNC" not in sql - - -# --- ITEM K: differentiated @brief for converged vs scheme-explicit ------- - - -def test_domain_brief_distinguishes_converged_from_scheme_twin(): - """Thread K: int4_ord (converged) and int4_ord_ore (scheme twin) carry the - same terms but must render distinct, sensible briefs.""" - ord_dom = DomainSpec(name="int4_ord", terms=["ore"]) - ore_dom = DomainSpec(name="int4_ord_ore", terms=["ore"]) - ord_sql = render_domain_block(ord_dom, "int4") - ore_sql = render_domain_block(ore_dom, "int4") - - ord_brief = next( - line for line in ord_sql.splitlines() if "@brief" in line - ) - ore_brief = next( - line for line in ore_sql.splitlines() if "@brief" in line - ) - # Both still lead with the role phrase... - assert "Ordered encrypted int4 domain." in ord_brief - assert "Ordered encrypted int4 domain." in ore_brief - # ...but the trailing clause differs and reads sensibly. - assert ord_brief != ore_brief - assert "Recommended converged name" in ord_brief - assert "Scheme-explicit twin" in ore_brief - assert "ore scheme" in ore_brief - assert "int4_ord" in ore_brief # points back at the converged name - - -def test_brief_role_clause_is_generic_over_token_and_scheme(): - """The disambiguation reads token/role/scheme from the name, not a - hard-coded literal — so it works for other types (int8) and schemes.""" - # Converged ordered name for a different token. - assert "Recommended converged name" in brief_role_clause( - DomainSpec(name="int8_ord", terms=["ore"]), "int8" - ) - # Scheme-explicit twin with a hypothetical non-ore scheme label. - clause = brief_role_clause( - DomainSpec(name="date_ord_lex", terms=["ore"]), "date" - ) - assert "Scheme-explicit twin" in clause - assert "lex scheme" in clause - assert "date_ord" in clause - - -def test_brief_role_clause_empty_for_storage_and_eq(): - """Storage and eq domains have no converged/twin ambiguity (only one name - each), so they get no disambiguating clause — brief stays unchanged.""" - assert brief_role_clause(DomainSpec(name="int4", terms=[]), "int4") == "" - assert brief_role_clause( - DomainSpec(name="int4_eq", terms=["hm"]), "int4" - ) == "" - - -# --- THREAD 1: SQL-string interpolation hardening ------------------------- - - -def test_sql_str_doubles_single_quotes(): - """_sql_str doubles embedded single quotes so a value can't break out of - its SQL string literal.""" - assert _sql_str("o'brien") == "o''brien" - assert _sql_str("a'b'c") == "a''b''c" - # Quote-free input is unchanged — current catalog strings stay byte-stable. - assert _sql_str("int4_eq") == "int4_eq" - assert _sql_str("<=") == "<=" - - -def test_blocker_escapes_quote_bearing_domain_in_rendered_sql(): - """A hypothetical quote-bearing domain name must be doubled inside the - helper-call string literal in the rendered blocker, not interpolated raw. - - (op can't carry a quote in practice — it's looked up in the operator - catalog — so the domain name is the live escaping path through the blocker - string literals.)""" - domain = DomainSpec(name="o'dom", terms=[]) - sql = render_blocker_bool( - domain, op="<", arg_a="eql_v3.o'dom", arg_b="eql_v3.o'dom", - ) - # The dom flows into encrypted_domain_unsupported_bool('', '') - # as a single-quoted literal — the quote must be doubled. - assert "encrypted_domain_unsupported_bool('eql_v3.o''dom', '<')" in sql - # The raw, unescaped single-quoted form must not appear. - assert "'eql_v3.o'dom'" not in sql - - -def test_domain_block_escapes_quote_bearing_key_in_check(): - """A hypothetical quote-bearing payload key must be doubled inside the - VALUE ? '' check rather than interpolated raw.""" - # A term-free domain whose name carries a quote exercises the typname - # literal escaping in the IF NOT EXISTS guard. - quoted = DomainSpec(name="we'ird", terms=[]) - sql = render_domain_block(quoted, "int4") - assert "typname = 'we''ird'" in sql - assert "typname = 'we'ird'" not in sql diff --git a/tasks/codegen/test_terms.py b/tasks/codegen/test_terms.py deleted file mode 100644 index 8ac7aa4b..00000000 --- a/tasks/codegen/test_terms.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Tests for the fixed scalar-domain term catalog.""" - -import pytest - -from tasks.codegen.terms import ( - TermError, - extractor_for_operator, - operators_for_terms, - require_terms, - role_for_terms, - term_json_keys, - term_requires, -) - - -def test_hm_term_provides_equality(): - terms = require_terms(["hm"]) - hm = terms[0] - assert hm.name == "hm" - assert hm.json_key == "hm" - assert hm.extractor == "eq_term" - assert hm.returns == "eql_v2.hmac_256" - assert hm.ctor == "hmac_256" - assert hm.role == "eq" - assert hm.operators == ("=", "<>") - assert hm.requires == ("src/hmac_256/functions.sql",) - - -def test_ore_term_preserves_existing_int4_sql_contract(): - terms = require_terms(["ore"]) - ore = terms[0] - assert ore.name == "ore" - assert ore.json_key == "ob" - assert ore.extractor == "ord_term" - assert ore.returns == "eql_v2.ore_block_u64_8_256" - assert ore.ctor == "ore_block_u64_8_256" - assert ore.role == "ord" - assert ore.operators == ("=", "<>", "<", "<=", ">", ">=") - assert ore.requires == ( - "src/ore_block_u64_8_256/functions.sql", - "src/ore_block_u64_8_256/operators.sql", - ) - - -def test_unknown_term_raises(): - with pytest.raises(TermError, match="unknown term 'bogus'"): - require_terms(["bogus"]) - - -def test_operators_are_union_in_catalog_order(): - assert operators_for_terms(["ore", "hm"]) == [ - "=", "<>", "<", "<=", ">", ">=", - ] - - -def test_json_keys_come_from_catalog_not_manifest_names(): - assert term_json_keys(["hm", "ore"]) == ["hm", "ob"] - - -def test_term_requires_are_deduplicated(): - assert term_requires(["ore", "ore", "hm"]) == [ - "src/ore_block_u64_8_256/functions.sql", - "src/ore_block_u64_8_256/operators.sql", - "src/hmac_256/functions.sql", - ] - - -def test_role_for_terms_handles_storage_eq_ord(): - assert role_for_terms([]) == "storage" - assert role_for_terms(["hm"]) == "eq" - assert role_for_terms(["ore"]) == "ord" - - -def test_operators_for_terms_handles_empty_list(): - assert operators_for_terms([]) == [] - - -def test_term_json_keys_handles_empty_list(): - assert term_json_keys([]) == [] - - -def test_term_requires_handles_empty_list(): - assert term_requires([]) == [] - - -def test_extractor_for_operator_picks_first_term_supporting_op(): - assert extractor_for_operator(["hm"], "=") == "eq_term" - assert extractor_for_operator(["ore"], "<") == "ord_term" - # Multi-term domains: first supporting term wins. - assert extractor_for_operator(["hm", "ore"], "=") == "eq_term" - assert extractor_for_operator(["hm", "ore"], "<") == "ord_term" - - -def test_extractor_for_operator_returns_none_when_no_term_supports_op(): - assert extractor_for_operator(["hm"], "<") is None - assert extractor_for_operator([], "=") is None diff --git a/tasks/codegen/test_writer.py b/tasks/codegen/test_writer.py deleted file mode 100644 index 81805cb1..00000000 --- a/tasks/codegen/test_writer.py +++ /dev/null @@ -1,157 +0,0 @@ -"""Tests for the ownership / overwrite-refusal / stale-cleanup rules.""" -import pytest -from tasks.codegen.generate import REPO_ROOT -from tasks.codegen.templates import AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS -from tasks.codegen.writer import ( - _MARKER, - OwnershipError, - is_generated, - is_generated_rs, - clean_generated_files, - ensure_generated_paths_writable, - write_generated_file, - write_generated_rs, -) - - -_EXPECTED_SUFFIXES = ( - "_types.sql", - "_functions.sql", - "_operators.sql", - "_aggregates.sql", - "_extensions.sql", -) - - -def test_is_generated_true_for_header(tmp_path): - p = tmp_path / "x.sql" - p.write_text(AUTO_GENERATED_HEADER + "SELECT 1;\n") - assert is_generated(p) is True - - -def test_is_generated_false_for_handwritten(tmp_path): - p = tmp_path / "x.sql" - p.write_text("-- REQUIRE: src/schema.sql\nSELECT 1;\n") - assert is_generated(p) is False - - -def test_is_generated_true_for_crlf_header(tmp_path): - p = tmp_path / "x.sql" - p.write_bytes((_MARKER + "\r\n" + "SELECT 1;\n").encode("utf-8")) - assert is_generated(p) is True - - -def test_write_generated_file_creates_with_header(tmp_path): - p = tmp_path / "int4_types.sql" - write_generated_file(p, "DO $$ BEGIN END $$;\n") - text = p.read_text() - assert text.startswith(AUTO_GENERATED_HEADER) - assert "DO $$ BEGIN END $$;" in text - - -def test_write_refuses_to_overwrite_handwritten(tmp_path): - """Refuse to clobber a hand-written file at a generated path.""" - p = tmp_path / "int4_types.sql" - p.write_text("-- REQUIRE: src/schema.sql\n-- hand-written\n") - with pytest.raises(OwnershipError, match="hand-written"): - write_generated_file(p, "DO $$ BEGIN END $$;\n") - - -def test_preflight_refuses_handwritten_target_before_cleanup(tmp_path): - generated = tmp_path / "int4_types.sql" - hand = tmp_path / "int4_eq_functions.sql" - generated.write_text(AUTO_GENERATED_HEADER + "-- old generated\n") - hand.write_text("-- REQUIRE: src/schema.sql\n-- hand-written\n") - - with pytest.raises(OwnershipError, match=r"int4_eq_functions\.sql"): - ensure_generated_paths_writable([generated, hand]) - - assert generated.exists() - assert hand.exists() - - -def test_write_overwrites_existing_generated_file(tmp_path): - """A file that already carries the header may be overwritten.""" - p = tmp_path / "int4_types.sql" - p.write_text(AUTO_GENERATED_HEADER + "-- old content\n") - write_generated_file(p, "-- new content\n") - text = p.read_text() - assert "-- new content" in text - assert "-- old content" not in text - - -def test_clean_removes_only_generated_files(tmp_path): - """Clean deletes every generated file, keeps the rest.""" - gen1 = tmp_path / "int4_eq_functions.sql" - gen2 = tmp_path / "int4_old_domain_functions.sql" # stale orphan - hand = tmp_path / "int4_jsonb_extra.sql" - gen1.write_text(AUTO_GENERATED_HEADER + "SELECT 1;\n") - gen2.write_text(AUTO_GENERATED_HEADER + "SELECT 2;\n") - hand.write_text("-- REQUIRE: src/schema.sql\n-- hand-written\n") - - removed = clean_generated_files(tmp_path) - - assert not gen1.exists() - assert not gen2.exists() # stale orphan cleaned up - assert hand.exists() # hand-written file untouched - assert set(removed) == {gen1, gen2} - - -def test_clean_on_empty_directory(tmp_path): - """Clean on a greenfield directory removes nothing and does not error.""" - removed = clean_generated_files(tmp_path) - assert removed == [] - - -def test_write_generated_rs_creates_with_rust_header(tmp_path): - p = tmp_path / "int4_values.rs" - write_generated_rs(p, "pub const VALUES: &[i32] = &[];\n") - text = p.read_text() - assert text.startswith(AUTO_GENERATED_HEADER_RS) - assert "pub const VALUES" in text - - -def test_is_generated_rs_true_for_rust_header(tmp_path): - p = tmp_path / "int4_values.rs" - p.write_text(AUTO_GENERATED_HEADER_RS + "pub const VALUES: &[i32] = &[];\n") - assert is_generated_rs(p) is True - - -def test_is_generated_rs_false_for_handwritten(tmp_path): - p = tmp_path / "int4_values.rs" - p.write_text("//! hand-written\npub const VALUES: &[i32] = &[];\n") - assert is_generated_rs(p) is False - - -def test_write_generated_rs_refuses_to_overwrite_handwritten(tmp_path): - p = tmp_path / "int4_values.rs" - p.write_text("//! hand-written\n") - with pytest.raises(OwnershipError, match="hand-written"): - write_generated_rs(p, "pub const VALUES: &[i32] = &[];\n") - - -def test_write_generated_rs_overwrites_existing_generated(tmp_path): - p = tmp_path / "int4_values.rs" - p.write_text(AUTO_GENERATED_HEADER_RS + "// old\n") - write_generated_rs(p, "// new\n") - text = p.read_text() - assert "// new" in text - assert "// old" not in text - - -def test_no_misnamed_sql_files_in_generated_dirs(): - """Files under src/encrypted_domain// must end in one of the four - documented suffixes — catches mistakes like `int4_extension.sql` - (singular), which the build would silently include despite violating - the documented convention.""" - root = REPO_ROOT / "src" / "encrypted_domain" - misnamed = [ - path.relative_to(REPO_ROOT) - for type_dir in root.iterdir() if type_dir.is_dir() - for path in sorted(type_dir.glob("*.sql")) - if not path.name.endswith(_EXPECTED_SUFFIXES) - ] if root.is_dir() else [] - assert not misnamed, ( - f"misnamed SQL files in src/encrypted_domain/ — expected suffix in " - f"{_EXPECTED_SUFFIXES}: {misnamed}" - ) diff --git a/tasks/codegen/types/.gitkeep b/tasks/codegen/types/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tasks/codegen/types/int2.toml b/tasks/codegen/types/int2.toml deleted file mode 100644 index 314bc698..00000000 --- a/tasks/codegen/types/int2.toml +++ /dev/null @@ -1,19 +0,0 @@ -# Encrypted-domain scalar manifest for int2. -# The filename supplies the type token. Each domain lists the index terms -# it carries; term capabilities are fixed in tasks/codegen/terms.py. - -[domain] -int2 = [] -int2_eq = ["hm"] -int2_ord_ore = ["ore"] -int2_ord = ["ore"] - -# Single source of truth for the int2 fixture plaintext list. Drives the -# generated tests/sqlx/src/fixtures/int2_values.rs const, shared by the fixture -# generator and the matrix oracle. Sentinels MIN/MAX/ZERO map to i16 named -# consts; the set MUST include MIN, MAX, and zero (matrix comparison pivots). -[fixture] -values = [ - "MIN", "-30000", "-100", "-1", "ZERO", "1", "2", "5", "10", "17", "25", - "42", "50", "100", "250", "1000", "9999", "30000", "MAX", -] diff --git a/tasks/codegen/types/int4.toml b/tasks/codegen/types/int4.toml deleted file mode 100644 index 606d80ee..00000000 --- a/tasks/codegen/types/int4.toml +++ /dev/null @@ -1,19 +0,0 @@ -# Encrypted-domain scalar manifest for int4. -# The filename supplies the type token. Each domain lists the index terms -# it carries; term capabilities are fixed in tasks/codegen/terms.py. - -[domain] -int4 = [] -int4_eq = ["hm"] -int4_ord_ore = ["ore"] -int4_ord = ["ore"] - -# Single source of truth for the int4 fixture plaintext list. Drives the -# generated tests/sqlx/src/fixtures/int4_values.rs const, shared by the fixture -# generator and the matrix oracle. Sentinels MIN/MAX/ZERO map to i32 named -# consts; the set MUST include MIN, MAX, and zero (matrix comparison pivots). -[fixture] -values = [ - "MIN", "-100", "-1", "ZERO", "1", "2", "5", "10", "17", "25", - "42", "50", "100", "250", "1000", "9999", "MAX", -] diff --git a/tasks/codegen/writer.py b/tasks/codegen/writer.py deleted file mode 100644 index aa0cdd99..00000000 --- a/tasks/codegen/writer.py +++ /dev/null @@ -1,89 +0,0 @@ -"""File writer enforcing the AUTO-GENERATED-header ownership rule. - -The generator owns only files carrying the AUTO-GENERATED header. It -preflights expected output paths, deletes generated files to clear stale -orphans, and refuses to overwrite a hand-written file at a generated path. -""" - -from pathlib import Path - -from .templates import AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS - -# The first line of each header is the ownership marker. -_MARKER = AUTO_GENERATED_HEADER.splitlines()[0] -_RS_MARKER = AUTO_GENERATED_HEADER_RS.splitlines()[0] - - -class OwnershipError(Exception): - """Raised when the generator would clobber a hand-written file.""" - - -def _first_line(path: Path) -> str: - with path.open("r", encoding="utf-8") as fh: - return fh.readline().rstrip("\r\n") - - -def is_generated(path: Path) -> bool: - """True if the file at `path` carries the SQL AUTO-GENERATED marker.""" - if not path.is_file(): - return False - return _first_line(path) == _MARKER - - -def is_generated_rs(path: Path) -> bool: - """True if the file at `path` carries the Rust AUTO-GENERATED marker.""" - if not path.is_file(): - return False - return _first_line(path) == _RS_MARKER - - -def clean_generated_files(directory: Path) -> list[Path]: - """Delete every generated .sql file in `directory`. Returns the list - of removed paths. Hand-written files are left untouched. A no-op on a - directory that does not exist or holds no generated files.""" - directory = Path(directory) - if not directory.is_dir(): - return [] - removed: list[Path] = [] - for path in sorted(directory.glob("*.sql")): - if is_generated(path): - path.unlink() - removed.append(path) - return removed - - -def ensure_generated_paths_writable(paths: list[Path]) -> None: - """Refuse a generation run before cleanup if any target is hand-written.""" - for path in paths: - path = Path(path) - if path.exists() and not is_generated(path): - raise OwnershipError( - f"refusing to overwrite hand-written file: {path} " - f"(no AUTO-GENERATED header). Remove it by hand if it is a " - f"one-time generator-adoption target." - ) - - -def write_generated_file(path: Path, body: str) -> None: - """Write `body` to `path`, prefixed with the SQL AUTO-GENERATED header. - - Refuses (OwnershipError) if `path` exists and is hand-written — a file - at a generated path that lacks the header is never clobbered.""" - path = Path(path) - ensure_generated_paths_writable([path]) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(AUTO_GENERATED_HEADER + body, encoding="utf-8") - - -def write_generated_rs(path: Path, body: str) -> None: - """Write `body` to a Rust file, prefixed with the Rust AUTO-GENERATED - header. Unlike the SQL surface this file is committed; the header still - guards against clobbering a hand-written file at the same path.""" - path = Path(path) - if path.exists() and not is_generated_rs(path): - raise OwnershipError( - f"refusing to overwrite hand-written file: {path} " - f"(no AUTO-GENERATED header)." - ) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(AUTO_GENERATED_HEADER_RS + body, encoding="utf-8") From af6eb8f1181f65591bafd904bf02ae5213341b5f Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 15:58:23 +1000 Subject: [PATCH 50/93] docs(CLAUDE): describe Rust catalog codegen, single matrix snapshot, no TOML/Python --- CLAUDE.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 10477062..65da42bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,7 @@ This project uses `mise` for task management. Common commands: - Run SQLx tests directly: `mise run test:sqlx` - Run SQLx tests in watch mode: `mise run test:sqlx:watch` - Tests are located in `tests/sqlx/` using Rust and SQLx framework -- Regenerate the scalar matrix coverage snapshots: `mise run test:matrix:inventory` (no database required). These committed `tests/sqlx/snapshots/_matrix_tests.txt` baselines pin the set of `scalars::::*` test names so a silently dropped/renamed/`#[cfg]`-gated test fails CI's `matrix-coverage` job. When you add or remove matrix tests (or add a scalar type), regenerate and commit the affected snapshot in the same change. See `tests/sqlx/snapshots/README.md`. +- Verify the scalar matrix coverage snapshot: `mise run test:matrix:inventory` (no database required). ONE committed `tests/sqlx/snapshots/matrix_tests.txt` baseline pins the token-normalized set of `scalars::::*` test names so a silently dropped/renamed/`#[cfg]`-gated test fails CI's `matrix-coverage` job. The task discovers the present scalar types from the test binary's `--list` and cross-checks them against `cargo run -p eql-codegen -- list-types`, so a catalog type missing its matrix wiring also fails. When you change which matrix tests the macro emits, regenerate and commit the single snapshot in the same change. See `tests/sqlx/snapshots/README.md`. ### Build System - Dependencies are resolved using `-- REQUIRE:` comments in SQL files @@ -78,11 +78,11 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search `src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, `text`, and `jsonb` follow this materializer pattern. `text`, `numeric`, and `jsonb` are planned but have no generated SQL surface yet — `jsonb` in particular needs a separate SQL design beyond the ordered-scalar materializer. The `eql-scalars` fixture catalog (`crates/eql-scalars`) already models their fixture values ahead of the SQL surface. -Adding a scalar encrypted-domain type is generated from a minimal manifest at `tasks/codegen/types/.toml`: the filename supplies ``, and the `[domain]` table maps each generated domain name to the fixed index terms it carries. Example: `int4_eq = ["hm"]`, `int4_ord = ["ore"]`. Term capabilities are fixed in `tasks/codegen/terms.py`: `hm` provides equality, and `ore` provides equality plus ordering. `mise run build` regenerates the scalar SQL surface into `src/encrypted_domain//` from every manifest at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. Use `mise run codegen:domain ` to refresh a single type manually while iterating on its manifest, or `mise run codegen:domain:all` to regenerate every type at once (the same enumeration `mise run build` uses). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` files are gitignored and never committed — the TOML manifest plus `tasks/codegen/terms.py` are the source of truth. Generated files carry an `AUTOMATICALLY GENERATED FILE — DO NOT EDIT` header (the project-wide marker that `docs:validate` greps on to skip generated SQL); change the manifest or term catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` have no generated SQL surface yet (they are planned, not out of scope); `jsonb` needs a separate SQL design beyond this ordered-scalar materializer. +Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/encrypted_domain//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed; the committed `tests/sqlx/src/fixtures/_values.rs` consts are also generated (CI diffs them). Generated SQL carries an `AUTOMATICALLY GENERATED FILE — DO NOT EDIT` header (the project-wide marker `docs:validate` greps on) and the committed `_values.rs` carries an `AUTO-GENERATED — DO NOT EDIT` header; change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. -**Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the manifest only declares domain names and terms. New term behavior belongs in `tasks/codegen/terms.py` with tests, not in free-form TOML fields. +**Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the catalog row only declares the token, kind, domain suffixes, and terms. New term behavior belongs in the `Term` enum's `impl` methods in `crates/eql-scalars/src` with tests, not in free-form catalog data. -Regeneration is deterministic: identical manifest + term catalog produce byte-identical SQL. If `mise run build` produces unexpected output, the change is in the manifest, `tasks/codegen/terms.py`, or `tasks/codegen/templates.py` — not in random run-to-run variation. +Regeneration is deterministic: an identical `CATALOG` produces byte-identical SQL. If `mise run build` produces unexpected output, the change is in `crates/eql-scalars/src` (the catalog/terms) or `crates/eql-codegen/src` (the renderers) — not in random run-to-run variation. Footguns the spec exists to prevent: @@ -90,7 +90,7 @@ Footguns the spec exists to prevent: - **No domain-over-domain** (`CREATE DOMAIN a AS b`). Operators resolve against the ultimate base type (`jsonb`), so a derived domain does not inherit the base domain's operator surface — blockers stop engaging. - **No operator class on a domain.** Index through a functional index on the extractor (`eq_term` / `ord_term`), whose return type already carries a default opclass. - **Inlinable functions** (extractors, comparison wrappers) need `LANGUAGE sql`, a single-statement `SELECT`, `IMMUTABLE`, and **no `SET` clause** — a pinned `search_path` disables inlining. No per-type allowlist edit: the `pin_search_path.sql` structural rule recognises encrypted-domain functions intrinsically and `tasks/test/splinter.sh` covers the converged extractor/wrapper names. -- **Blockers must be `LANGUAGE plpgsql`, not `LANGUAGE sql`.** The inverse of the rule above. A blocker exists to always raise, but a `LANGUAGE sql` body is inlinable and the planner can elide the call when the result is provably unused (dead `CASE` branch, folded predicate). `LANGUAGE plpgsql` is opaque to the planner, so the call — and its `RAISE` — survives. The generator in `tasks/codegen/templates.py` enforces this; don't "simplify" the rendered blockers to `LANGUAGE sql` even though the body is a single expression. +- **Blockers must be `LANGUAGE plpgsql`, not `LANGUAGE sql`.** The inverse of the rule above. A blocker exists to always raise, but a `LANGUAGE sql` body is inlinable and the planner can elide the call when the result is provably unused (dead `CASE` branch, folded predicate). `LANGUAGE plpgsql` is opaque to the planner, so the call — and its `RAISE` — survives. The blocker renderers in `crates/eql-codegen/src` enforce this; don't "simplify" the rendered blockers to `LANGUAGE sql` even though the body is a single expression. - **Build with `mise run clean && mise run build`** — a bare build can leave stale `release/*.sql`. ### Testing Infrastructure @@ -220,7 +220,7 @@ Prefer `LANGUAGE SQL` over `LANGUAGE plpgsql` unless you need procedural feature - Exception handling (`BEGIN...EXCEPTION...END`) - Complex control flow (loops, early returns) - Dynamic SQL (`EXECUTE`) -- Functions that must remain opaque to the planner — typically blockers whose only job is to `RAISE`. `LANGUAGE sql` would be inlined and may be elided when the result is provably unused; `LANGUAGE plpgsql` is never inlined, so the body always runs. See the encrypted-domain footgun list above and the blocker renderers in `tasks/codegen/templates.py`. +- Functions that must remain opaque to the planner — typically blockers whose only job is to `RAISE`. `LANGUAGE sql` would be inlined and may be elided when the result is provably unused; `LANGUAGE plpgsql` is never inlined, so the body always runs. See the encrypted-domain footgun list above and the blocker renderers in `crates/eql-codegen/src`. ## Release & changelog discipline From f5ca6333876f27c3fa519119f0fad6bd28657642 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 16:01:44 +1000 Subject: [PATCH 51/93] docs(spec): rewrite encrypted-domain spec for Rust catalog, single matrix snapshot --- .../encrypted-domain-implementation-spec.md | 264 ++++++++++-------- 1 file changed, 146 insertions(+), 118 deletions(-) diff --git a/docs/reference/encrypted-domain-implementation-spec.md b/docs/reference/encrypted-domain-implementation-spec.md index e21c8d6a..838f870c 100644 --- a/docs/reference/encrypted-domain-implementation-spec.md +++ b/docs/reference/encrypted-domain-implementation-spec.md @@ -2,7 +2,7 @@ This is the scalar encrypted-domain generator contract used by `int4`. It applies to scalar domains whose searchable payloads are represented by -the fixed term catalog in `tasks/codegen/terms.py`. +the fixed `Term` catalog in `crates/eql-scalars/src`. `text` and `jsonb` are outside this scalar materializer. @@ -10,33 +10,41 @@ the fixed term catalog in `tasks/codegen/terms.py`. Each generated domain is a concrete `jsonb` domain in the `eql_v3` schema named `eql_v3.` (dropped by `DROP SCHEMA eql_v3 CASCADE`; -survives an `eql_v2` uninstall). The manifest is intentionally small: - -```toml -[domain] -int4 = [] -int4_eq = ["hm"] -int4_ord_ore = ["ore"] -int4_ord = ["ore"] +survives an `eql_v2` uninstall). A type's catalog row is intentionally +small — a `ScalarSpec` whose `domains` field lists each generated domain +as a `DomainSpec` (a `suffix` plus the fixed terms it carries): + +```rust +ScalarSpec { + token: "int4", + kind: ScalarKind::I32, + domains: &[ + DomainSpec { suffix: "", terms: &[] }, + DomainSpec { suffix: "_eq", terms: &[Term::Hm] }, + DomainSpec { suffix: "_ord_ore", terms: &[Term::Ore] }, + DomainSpec { suffix: "_ord", terms: &[Term::Ore] }, + ], + fixtures: &[/* see §9 */], +} ``` -The TOML filename supplies the type token. The `[domain]` table maps each -generated domain name to the fixed terms it carries. The generator -emits files in the manifest's declared order, so order keys in the TOML -in the order you want them to appear in generated output. Term capabilities -come only from `tasks/codegen/terms.py`: +The `token` supplies the type token; each domain's full name is `token` ++ `suffix`. The generator emits domains in the order the `domains` slice +declares them, so order the slice the way you want the generated output to +read. Term capabilities are fixed by the `Term` enum +(`crates/eql-scalars/src`): | Term | JSON key | Extractor | Return type | Supported operators | |---|---|---|---|---| -| `hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` / `<>` | -| `ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` / `<>` / `<` / `<=` / `>` / `>=` | +| `Hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` / `<>` | +| `Ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` / `<>` / `<` / `<=` / `>` / `>=` | -For current `int4`, domains carrying `ore` use JSON key `ob`, extractor +For current `int4`, domains carrying `Ore` use JSON key `ob`, extractor `ord_term`, and the ORE block supports equality plus ordering. A type that needs a non-ORE equality term on an ordered domain needs a new -catalog term design, not a manifest flag. +`Term` design, not a catalog flag. -The manifest above declares two ordered domains, `int4_ord` and +The row above declares two ordered domains, `int4_ord` and `int4_ord_ore`, carrying the same term. They are intentional twins: the generator emits byte-identical SQL (modulo type name) so callers can pick a name that documents intent without committing to a term family in a @@ -44,42 +52,38 @@ future migration. ## 2. Checklist -- [ ] Author `tasks/codegen/types/.toml`. The filename supplies ``. - The `[domain]` table maps generated domain names to fixed terms: - - ```toml - [domain] - int4 = [] - int4_eq = ["hm"] - int4_ord_ore = ["ore"] - int4_ord = ["ore"] - ``` - - Terms determine operator support: `hm` provides `=` / `<>`; `ore` - provides `=` / `<>` / `<` / `<=` / `>` / `>=`. -- [ ] Add or update catalog terms in `tasks/codegen/terms.py` with tests. -- [ ] **If `` is a new scalar kind, register a `ScalarKind` in - `tasks/codegen/scalars.py`** (use the `int4` entry as the template): its - `token`, `rust_type`, the `MIN` / `MAX` / `ZERO` Rust symbols, and the - numeric `min_value` / `max_value` bounds. This is a code change with - tests, exactly like a new catalog term in `terms.py` — not a manifest - field. `load_spec` resolves the scalar before it validates anything, so - without this entry `mise run codegen:domain ` raises - `ScalarError: unknown scalar token ''` and emits nothing. Then search - the codegen tests for any fixture using `` as a negative "unknown - scalar" example (e.g. `test_spec.py`) and update it — registering the - kind makes that token valid. -- [ ] Declare the fixture plaintext list once in the manifest's `[fixture]` - table (see §9). The list MUST include `MIN`, `MAX`, and zero. -- [ ] Run `mise run codegen:domain ` to materialise generated SQL and the - committed `tests/sqlx/src/fixtures/_values.rs` while iterating, or - just `mise run build` — every build regenerates from the manifest first. - Commit the regenerated `_values.rs` (CI diffs it). +- [ ] Add a row to the Rust catalog `eql-scalars::CATALOG` + (`crates/eql-scalars/src/lib.rs`). A `ScalarSpec` declares: + + - `token` — the type token (e.g. `int8`); supplies `` everywhere. + - `kind` — the `ScalarKind` (`I16` / `I32` / `I64`), which carries the + Rust type name, the `MIN`/`MAX`/zero symbols, and the numeric bounds. + - `domains` — a `&[DomainSpec]`, each a `suffix` + the fixed `Term`s it + carries. The storage domain is suffix `""` with no terms; `_eq => [Hm]`; + `_ord` and `_ord_ore => [Ore]`. + - `fixtures` — the `Fixture` value list (see §9). It MUST include `Min`, + `Max`, and zero. + + Terms determine operator support: `Hm` provides `=` / `<>`; `Ore` + provides `=` / `<>` / `<` / `<=` / `>` / `>=`. There is no TOML manifest + and no Python: the catalog is the source of truth, validated by the + compiler (an undefined `Term` or unknown `ScalarKind` is a compile error) + plus catalog `#[test]`s over `CATALOG`. +- [ ] **If `` needs a new scalar width**, add a `ScalarKind` enum variant in + `crates/eql-scalars/src/lib.rs` with its rust-type name, `MIN`/`MAX`/zero + symbols, and numeric bounds, and unit-test its `impl` methods. New term + behaviour likewise belongs in the `Term` enum's `impl` methods with tests + — not in free-form catalog data. +- [ ] Run `cargo run -p eql-codegen` to materialise the generated SQL + (`src/encrypted_domain//_{types,functions,operators,aggregates}.sql`, + gitignored) and the committed `tests/sqlx/src/fixtures/_values.rs` + const, or just `mise run build` — every build runs the generator first. + Commit the regenerated `_values.rs` (CI diffs it). There is no per-type + codegen task: one run generates every type from `CATALOG`. - [ ] Generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / - `*_aggregates.sql` are gitignored and never committed. The TOML - manifest plus `tasks/codegen/terms.py` are the source of truth. - Change the manifest or catalog and rebuild; do not hand-edit - generated SQL. + `*_aggregates.sql` are gitignored and never committed. The catalog + (`eql-scalars::CATALOG`) plus the `eql-codegen` renderers are the source + of truth. Change the catalog and rebuild; do not hand-edit generated SQL. - [ ] Put optional hand-written SQL in `src/encrypted_domain//_extensions.sql` with explicit `-- REQUIRE:` edges. This file IS committed. @@ -87,25 +91,47 @@ future migration. single golden master for the type-generic generator: the SQL templates are pure token substitution and the only type-specific rendering is `_values.rs`, so a per-type baseline can only fail when `int4`'s already - would. Drift protection for the new type comes from the `int4` reference - (shared templates + `terms.py`), the committed `_values.rs` const guarded - by the CI staleness check (`mise run codegen:domain ` + `git diff - --exit-code`) and the `` cases in `tasks/codegen/test_scalars.py`, and - the `ordered_numeric_matrix!` SQLx suite (behaviour, not bytes). -- [ ] Run `mise run test:matrix:inventory` and commit the regenerated - `tests/sqlx/snapshots/_matrix_tests.txt` — the sorted inventory of every - `scalars::::*` test name in the `encrypted_domain` binary. CI diffs it - (same as `_values.rs`); a stale snapshot fails the `matrix-coverage` - job with "Coverage inventory stale". This baseline is what catches a - silently dropped, renamed, or `#[cfg]`-gated matrix test. See §8. -- [ ] Run `mise run test:codegen`, the relevant SQLx suites, and the - PostgreSQL matrix before merging. + would. Drift protection for the new type comes from the `int4` reference, + the committed `_values.rs` const guarded by the CI staleness check + (`cargo run -p eql-codegen` + `git diff --exit-code`) and the catalog/ + generator `#[test]`s (`cargo test -p eql-scalars -p eql-codegen`), and the + `ordered_numeric_matrix!` SQLx suite (behaviour, not bytes). +- [ ] Wire the SQLx matrix oracle. The generated SQL is enough to install the + domains, but the `ordered_numeric_matrix!` suite only runs once the Rust + harness knows about the scalar. Copy each piece from the `int4` + reference — these are hand-maintained registration lists (the Phase-4 + `scalar_types!` registry, a separate plan, will collapse them): + + | File | Add | + |------|-----| + | `tests/sqlx/src/fixtures/eql_plaintext.rs` | A sealed `EqlPlaintext` impl for the scalar's Rust type: `impl Sealed for {}`, a `PlaintextSqlType` const for its base column type, `impl EqlPlaintext for ` (`CAST`, `PLAINTEXT_SQL_TYPE`, `to_plaintext` → the right `Plaintext` variant), plus the two `#[test]` casts. | + | `tests/sqlx/src/fixtures/eql_v2_.rs` | `crate::scalar_fixture!("eql_v2_", , VALUES);` (pulls `super::_values::VALUES`). | + | `tests/sqlx/src/fixtures/mod.rs` | `pub mod _values;` and `pub mod eql_v2_;`. | + | `tests/sqlx/tests/generate_all_fixtures.rs` | An arm in `generate_for_token`: `"" => fixtures::eql_v2_::spec().run().await,`. The match is exhaustive over the catalog — a catalog token with no arm fails the generator loudly. | + | `tests/sqlx/src/scalar_domains.rs` | `impl ScalarType for ` — `PG_TYPE` (the base PG type, e.g. `"int8"`) and `FIXTURE_VALUES = crate::fixtures::_values::VALUES`. | + | `tests/sqlx/tests/encrypted_domain/scalars/.rs` | `ordered_numeric_matrix! { suite = , scalar = , eql_type = "eql_v2_" }`. | + | `tests/sqlx/tests/encrypted_domain/scalars/mod.rs` | `pub mod ;`. | + + `` is the scalar's Rust type (`i32` for `int4`, `i16` for `int2`). + Forget one and the matrix simply does not run for the type — the matrix + inventory cross-check (next step) surfaces it, because the catalog has the + type but the binary has no `scalars::::` tests. +- [ ] Run `mise run test:matrix:inventory`. It verifies every present type's + token-normalized `scalars::::*` name set equals the single canonical + `tests/sqlx/snapshots/matrix_tests.txt`, and cross-checks the present type + set against `cargo run -p eql-codegen -- list-types`. You do **not** edit a + per-type snapshot — there is one canonical snapshot; you only regenerate it + when the macro's emitted name set itself changes. A catalog type missing + its matrix wiring fails the cross-check. See §8 and + `tests/sqlx/snapshots/README.md`. +- [ ] Run `mise run test:codegen` (`cargo test -p eql-scalars -p eql-codegen`), + the relevant SQLx suites, and the PostgreSQL matrix before merging. ## 3. Domain Generation The generator emits `src/encrypted_domain//_types.sql` (gitignored; -materialised on every `mise run build` and on `mise run codegen:domain -`) with one idempotent `DO $$ ... $$` block. Domain `CHECK` +materialised on every `mise run build` and every `cargo run -p eql-codegen`) +with one idempotent `DO $$ ... $$` block. Domain `CHECK` constraints always require: - fixed envelope keys `v` and `i`; @@ -126,9 +152,9 @@ that bypass the fixed operator surface. ## 4. Extractors And Wrappers -Extractor names and return types come from `tasks/codegen/terms.py`, not -from TOML. Generated extractors and supported comparison wrappers are -inline-friendly SQL functions: +Extractor names and return types come from the `Term` enum +(`crates/eql-scalars/src`), not from catalog data. Generated extractors and +supported comparison wrappers are inline-friendly SQL functions: ```sql LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE @@ -225,7 +251,7 @@ Optional hand-written SQL beyond the fixed scalar surface belongs in: src/encrypted_domain//_extensions.sql ``` -The generator must not create this file, list it in TOML, add an +The generator must not create this file, list it in the catalog, add an auto-generated header, or clean it during regeneration. The file must declare its own `-- REQUIRE:` edges, usually to `_types.sql` and whichever generated function or operator file it extends. Unlike the @@ -261,7 +287,7 @@ Cover each generated domain with SQLx tests appropriate to its terms: - real typed columns are tested, not only cast literals; - generated ordered-domain twins remain byte-identical modulo type name (the shared generator is anchored by the `int4` golden master in - `tests/codegen/reference/int4/` via `tasks/codegen/test_against_reference.py`; + `tests/codegen/reference/int4/` via the eql-codegen parity test; new types add no baseline of their own — see §2). For ordered numeric scalars this coverage is generated by the @@ -283,21 +309,24 @@ the catalog does not promise. ### Matrix coverage inventory snapshot -The *set of test names* the matrix emits is itself guarded. `mise run -test:matrix:inventory` lists every test in the `encrypted_domain` binary -under a pinned feature set (`--no-default-features`, which deliberately -excludes the `scale` arm — see the task comment in `mise.toml`), greps it to -each `scalars::::*` matrix, `LC_ALL=C sort`s for byte-stable ordering, and -writes one committed snapshot per scalar at -`tests/sqlx/snapshots/_matrix_tests.txt`. The CI `matrix-coverage` job -regenerates with the same feature set and `git diff --exit-code`s every -snapshot; a divergence fails with "Coverage inventory stale". This is the -guard that catches a silently dropped, renamed, or `#[cfg]`-gated matrix -test — a behaviour the SQLx assertions above cannot see, because a deleted -test simply stops running. When you add a scalar you add a new snapshot; -when you add or remove matrix tests you regenerate and commit the affected -snapshot in the same change. The files are a committed test baseline, **not** -gitignored generated SQL. See `tests/sqlx/snapshots/README.md`. +The *set of test names* the matrix emits is guarded by ONE committed, +token-normalized snapshot at `tests/sqlx/snapshots/matrix_tests.txt` — the +sorted inventory of every `scalars::::*` test name with the type token +replaced by the literal ``. (The per-type `_matrix_tests.txt` files are +gone: they were byte-identical modulo the token, so one canonical set plus a +per-type normalize-and-compare carries the same signal at a fraction of the +committed surface.) This is the guard that catches a silently dropped, renamed, +or `#[cfg]`-gated matrix test, a behaviour the SQLx assertions above cannot see. +The snapshot is a committed test baseline, **not** gitignored generated SQL. + +`mise run test:matrix:inventory` discovers the present scalar types from the +`encrypted_domain` binary's `--list`, normalizes each type's token to ``, +asserts every type's set equals the canonical snapshot, and cross-checks the +discovered type set against `cargo run -p eql-codegen -- list-types` (the catalog +is the single source). The CI `matrix-coverage` job gates it. **`tests/sqlx/snapshots/README.md` +is the source of truth** for the mechanics (pinned feature set, the catalog +cross-check, the CI diff, and when to regenerate); see it rather than +duplicating the detail here. ## 9. Fixtures @@ -317,47 +346,46 @@ absent. ### Single-sourcing the value list -The plaintext value list is declared **once**, in the manifest's optional -`[fixture]` table, and generated into Rust — never hand-maintained in two -places: +The plaintext value list is declared **once**, in the catalog row's `fixtures` +field, and generated into Rust — never hand-maintained in two places: -```toml -[fixture] -values = [ - "MIN", "-100", "-1", "ZERO", "1", "2", "5", "10", "17", "25", - "42", "50", "100", "250", "1000", "9999", "MAX", -] +```rust +fixtures: &[Fixture::Min, Fixture::N(-100), Fixture::N(-1), Fixture::Zero, + Fixture::N(1), Fixture::N(2), Fixture::N(5), Fixture::N(10), + Fixture::N(17), Fixture::N(25), Fixture::N(42), Fixture::N(50), + Fixture::N(100), Fixture::N(250), Fixture::N(1000), + Fixture::N(9999), Fixture::Max], ``` -Values are strings so the convention is type-agnostic. The sentinels `MIN`, -`MAX`, and `ZERO` map to the scalar's Rust named consts (for `int4`: -`i32::MIN`, `i32::MAX`, `0`); every other token is a numeric literal -validated against the type's representable range. The per-type rendering -rules live in `tasks/codegen/scalars.py` (mirroring `terms.py`), not in -free-form TOML fields. `load_spec` enforces the matrix invariant: the set -**must** include `MIN`, `MAX`, and zero, or the build fails. +`Fixture::Min` / `Fixture::Max` / `Fixture::Zero` resolve to the scalar's Rust +named consts (for `int4`: `i32::MIN`, `i32::MAX`, `0`); every `Fixture::N(_)` is +a numeric literal validated against the `ScalarKind`'s representable range by a +catalog `#[test]` (`numeric_value` is infallible, so the range check is the +explicit invariant `every_fixture_value_is_within_kind_bounds`). The same test +enforces the matrix invariant: the set **must** include `Min`, `Max`, and zero, +or the test fails (the compile-time analogue of the old `load_spec` validation). -The generator emits `tests/sqlx/src/fixtures/_values.rs` exposing one +`eql-codegen` emits `tests/sqlx/src/fixtures/_values.rs` exposing one `pub const VALUES: &[]`. Both consumers reference that single symbol — the fixture generator (`fixtures::eql_v2_::spec`) and the matrix -oracle (`impl ScalarType for { const FIXTURE_VALUES }`) — so the -oracle cannot drift from the values the generator encrypts. +oracle (`impl ScalarType for { const FIXTURE_VALUES }`) — so the oracle +cannot drift from the values the generator encrypts. Unlike the gitignored `*_*.sql` surface and the gitignored encrypted `tests/sqlx/fixtures/eql_v2_.sql` (whose ciphertext is non-deterministic -per-encrypt), `_values.rs` **is committed**: its rendering is -deterministic, so the CI `codegen` job regenerates it and runs -`git diff --exit-code` to catch a manifest edit that wasn't regenerated. -Regenerate with `mise run codegen:domain ` and commit the result; never -hand-edit it. +per-encrypt), `_values.rs` **is committed**: its rendering is deterministic, +so the CI `codegen` job regenerates it (`cargo run -p eql-codegen`) and runs +`git diff --exit-code` to catch a catalog edit that wasn't regenerated. +Regenerate with `cargo run -p eql-codegen` (or `mise run build`) and commit the +result; never hand-edit it. ## 10. Build And Verification -- `mise run codegen:domain ` (optional; refreshes one type while - iterating on its manifest before a full build) -- `mise run test:codegen` -- `mise run clean && mise run build` (regenerates every type's SQL - from its manifest first, then builds the release artefacts) +- `cargo run -p eql-codegen` (optional; refreshes all generated SQL + + `_values.rs` from the catalog before a full build) +- `mise run test:codegen` (`cargo test -p eql-scalars -p eql-codegen`) +- `mise run clean && mise run build` (regenerates every type's SQL from + the catalog first, then builds the release artefacts) - relevant SQLx suites - `mise run test` across supported PostgreSQL versions - `mise run --output prefix test:splinter --postgres 17` after a From f60386aa628a81195c9e7e18f707a8201b88caac Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 16:02:34 +1000 Subject: [PATCH 52/93] docs(snapshots): rewrite README for single catalog-driven matrix snapshot --- tests/sqlx/snapshots/README.md | 80 ++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/tests/sqlx/snapshots/README.md b/tests/sqlx/snapshots/README.md index a4ce5ae9..213b034f 100644 --- a/tests/sqlx/snapshots/README.md +++ b/tests/sqlx/snapshots/README.md @@ -1,23 +1,26 @@ -# Matrix coverage inventory snapshots +# Matrix coverage inventory snapshot -This directory holds one committed snapshot per scalar encrypted-domain type: +This directory holds ONE committed snapshot, `matrix_tests.txt` — the canonical, +token-normalized list of every `scalars::::*` test name in the +`encrypted_domain` SQLx binary, with each type token replaced by the literal +``. It is a **committed test baseline**, not gitignored generated SQL — keep +it in version control. -- `int4_matrix_tests.txt` -- `int2_matrix_tests.txt` +The per-type `_matrix_tests.txt` files are gone. They were byte-identical +modulo the type token (the matrix tests are macro-generated from one +`ordered_numeric_matrix!` invocation per type with no per-type variation), so a +single canonical set plus a per-type normalize-and-compare carries the same +signal at a fraction of the committed surface. -Each file is a sorted, byte-stable list of every `scalars::::*` test name in -the `encrypted_domain` SQLx binary. They are a **committed test baseline**, not -gitignored generated SQL — keep them in version control. - -## What they guard +## What it guards The SQLx assertions verify that the tests which run produce the right results. They cannot see a test that *stops running* — a matrix test that is deleted, renamed, or hidden behind a `#[cfg]` gate simply vanishes silently, quietly -shrinking coverage. These snapshots close that gap: they pin the *set of test +shrinking coverage. This snapshot closes that gap: it pins the *set of test names* so any such change shows up as an added/removed line in the PR diff. -## How they are generated +## How it is generated / checked Run: @@ -25,11 +28,21 @@ Run: mise run test:matrix:inventory ``` -The task (`mise.toml`, `[tasks."test:matrix:inventory"]`) enumerates the binary -with `cargo test --test encrypted_domain -- --list`, greps each -`scalars::` matrix into its own file, and `LC_ALL=C sort`s for ordering -that is byte-stable across locales. No database is required — `--list` only -enumerates; the suite uses runtime queries. +The task (`mise.toml`, `[tasks."test:matrix:inventory"]`): + +1. Lists the `encrypted_domain` binary ONCE with + `cargo test --no-default-features --test encrypted_domain -- --list`. +2. Discovers the set of scalar types present **from the binary's own output** + (the `scalars::::` prefixes) — never a directory glob. +3. Normalizes each type's token to `` and asserts that type's set equals the + canonical `matrix_tests.txt`. Asserts at least one type is present. +4. **Completeness cross-check:** asserts the discovered type set equals + `cargo run -p eql-codegen -- list-types` (the catalog is the single source). + A catalog type added without its matrix wiring — no `scalars::::` tests in + the binary — fails here. + +`LC_ALL=C sort` makes ordering byte-stable across locales. No database is +required — `--list` only enumerates; the suite uses runtime queries. It pins `--no-default-features` so the inventory is deterministic regardless of the caller's local flags. That deliberately excludes the `scale` feature arm @@ -38,16 +51,29 @@ instead by the scale gate plus the `family::mutations` negative controls. ## CI enforcement -The `matrix-coverage` job in `.github/workflows/test-eql.yml` regenerates with -the same pinned feature set and runs `git diff --exit-code` against every -snapshot in this directory. A divergence fails the job with: - -> Coverage inventory stale — run 'mise run test:matrix:inventory' and commit. - -## When you must update these - -- **Adding a new scalar type** → a new `_matrix_tests.txt` appears; commit it. -- **Adding / removing / renaming matrix tests** → regenerate and commit the - affected snapshot in the same change. +The `matrix-coverage` job in `.github/workflows/test-eql.yml` runs the same +task, then `git add -N tests/sqlx/snapshots` and +`git diff --exit-code -- tests/sqlx/snapshots`. The `git add -N` makes a +brand-new, never-committed snapshot trip the diff too. A divergence (or a failed +catalog cross-check) fails the job. + +## When you must update this + +- **Adding a new scalar type** → add the catalog row in + `eql-scalars::CATALOG`, wire the SQLx matrix oracle (see the implementation + spec §2), then run `mise run test:matrix:inventory`. If the new type's + normalized name set matches the canonical snapshot (it will, for a standard + `ordered_numeric_matrix!` type), no snapshot edit is needed — the cross-check + just confirms the type is wired. +- **Removing a scalar type** → remove the catalog row and its matrix wiring; the + cross-check then sees the type gone from both sides. +- **Changing which matrix tests the macro emits** → regenerate and commit + `matrix_tests.txt` in the same change: + ```bash + cd tests/sqlx + cargo test --no-default-features --test encrypted_domain -- --list \ + | sed -n 's/: test$//p' | grep '^scalars::int4::' \ + | sed -e 's/^scalars::int4::/scalars::::/' -e 's/_int4_/__/g' | LC_ALL=C sort > snapshots/matrix_tests.txt + ``` See `docs/reference/encrypted-domain-implementation-spec.md` §2 and §8. From 7feb83d693bd66b56d3548cf9a43d2479a9b2dd1 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 16:04:16 +1000 Subject: [PATCH 53/93] docs(changelog): record Rust catalog codegen + Python toolchain removal --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c75b70db..1a280a71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,9 +23,13 @@ Each entry that ships in a published release links to the PR that introduced it. ### Added - **`eql_v3` encrypted-domain schema, with the `int4` family as its first member.** Encrypted-domain type families now live in a new, additional `eql_v3` schema (the existing `eql_v2` schema is unchanged — it keeps the core types/operators and stays the documented public API). Four jsonb-backed domains for encrypted `int4` columns: `eql_v3.int4` (storage-only), `eql_v3.int4_eq` (`=` / `<>` via HMAC), and `eql_v3.int4_ord` / `eql_v3.int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. The extractors still return the core `eql_v2.hmac_256` / `eql_v2.ore_block_u64_8_256` index-term types, which remain in `eql_v2` and are referenced cross-schema. Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`, namespaced under its own schema. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) -- **`eql_v3.int2` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int2` columns — `eql_v3.int2` (storage-only), `eql_v3.int2_eq` (`=` / `<>` via HMAC), and `eql_v3.int2_ord` / `eql_v3.int2_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from `tasks/codegen/types/int2.toml` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `smallint` column, proving the scalar generator generalizes beyond the `int4` reference. ([#243](https://github.com/cipherstash/encrypt-query-language/pull/243)) +- **`eql_v3.int2` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int2` columns — `eql_v3.int2` (storage-only), `eql_v3.int2_eq` (`=` / `<>` via HMAC), and `eql_v3.int2_ord` / `eql_v3.int2_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from the `int2` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `smallint` column, proving the scalar generator generalizes beyond the `int4` reference. ([#243](https://github.com/cipherstash/encrypt-query-language/pull/243)) - **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v3.min(eql_v3._ord)` / `eql_v3.max(eql_v3._ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) +### Changed + +- **Scalar encrypted-domain types are now defined in a Rust catalog, not TOML manifests; the Python codegen toolchain is removed.** Adding a scalar encrypted-domain type (`int4`, `int8`, …) is now one row in `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`) instead of authoring `tasks/codegen/types/.toml`. `mise run build` regenerates the same gitignored SQL surface and the same committed `tests/sqlx/src/fixtures/_values.rs` consts via `cargo run -p eql-codegen` (Rust, std-only) rather than the Python generator. The shipped SQL is unchanged — `release/*.sql` is byte-identical across the cutover — so there is no change for callers installing EQL; this only affects contributors who extend the scalar domain families. The `python` mise tool, the `pytest`-based `test:codegen` (now `cargo test -p eql-scalars -p eql-codegen`), the per-type `mise run codegen:domain` tasks, and the per-type `tests/sqlx/snapshots/_matrix_tests.txt` baselines (collapsed into one catalog-reconciled `tests/sqlx/snapshots/matrix_tests.txt`) are gone. Why: a single compiler-validated source of truth shared by the generator and the SQLx test harness, and one fewer toolchain in the build/test path — building and testing EQL no longer needs Python (Python remains only for the separate docs-markdown tooling). ([#PR](https://github.com/cipherstash/encrypt-query-language/pull/PR)) + ## [2.3.1] — 2026-05-21 ### Fixed From 35bcd9f3c8a2f07174b84c06369f75a4c732ef60 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 2 Jun 2026 16:15:48 +1000 Subject: [PATCH 54/93] docs(CLAUDE): match actual generated-file markers (-- / // AUTOMATICALLY GENERATED FILE) --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 65da42bd..ec0788ca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,7 +78,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search `src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, `text`, and `jsonb` follow this materializer pattern. `text`, `numeric`, and `jsonb` are planned but have no generated SQL surface yet — `jsonb` in particular needs a separate SQL design beyond the ordered-scalar materializer. The `eql-scalars` fixture catalog (`crates/eql-scalars`) already models their fixture values ahead of the SQL surface. -Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/encrypted_domain//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed; the committed `tests/sqlx/src/fixtures/_values.rs` consts are also generated (CI diffs them). Generated SQL carries an `AUTOMATICALLY GENERATED FILE — DO NOT EDIT` header (the project-wide marker `docs:validate` greps on) and the committed `_values.rs` carries an `AUTO-GENERATED — DO NOT EDIT` header; change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. +Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/encrypted_domain//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed; the committed `tests/sqlx/src/fixtures/_values.rs` consts are also generated (CI diffs them). Generated SQL carries a `-- AUTOMATICALLY GENERATED FILE` header (the project-wide marker `docs:validate` greps on) and the committed `_values.rs` carries a `// AUTOMATICALLY GENERATED FILE` header; change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. **Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the catalog row only declares the token, kind, domain suffixes, and terms. New term behavior belongs in the `Term` enum's `impl` methods in `crates/eql-scalars/src` with tests, not in free-form catalog data. From 959eb85f0b999be9ffe8dd7d090dfe6cd463569f Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 09:41:35 +1000 Subject: [PATCH 55/93] refactor(codegen): drop generated _values.rs; materialise fixtures from catalog The committed tests/sqlx/src/fixtures/{int4,int2}_values.rs were a Rust->Rust codegen round-trip: eql-codegen re-serialised each catalog row's Fixture list into a committed .rs that the test crate then imported -- even though that crate already depends on eql-scalars. Now the catalog row is the single definition for both the SQL surface and the test fixtures, as originally intended. eql-scalars materialises each row's Fixture list into a typed const at compile time via the new int_values! macro (numeric_value is now a const fn): pub const INT4_VALUES: &[i32] = ...; pub const INT2_VALUES: &[i16] = ...; ScalarType::FIXTURE_VALUES reads them directly. The trait contract is unchanged, so matrix.rs, the int4_expanded.rs snapshot, and mutations.rs are untouched. - delete int4_values.rs, int2_values.rs, the golden reference/int4/int4_values.rs, the templates.rs renderer, and the now-dead Rust-header writer machinery (write_generated_rs / is_generated_rs / AUTO_GENERATED_HEADER_RS) - eql-scalars: add int_values! + the two consts + values_tests pinning the lists - CI/tasks: drop the regenerate-and-diff fixture-const step and the values.rs git-clean check in codegen-parity.sh - docs: CLAUDE.md, CHANGELOG, implementation spec, reference README Shipped SQL is unchanged (release/*.sql byte-identical). Verified: cargo test -p eql-scalars -p eql-codegen, cargo check -p eql_tests --all-targets, mise run codegen:parity, mise run clean && mise run build. --- .github/workflows/test-eql.yml | 27 ++-- CHANGELOG.md | 2 +- CLAUDE.md | 2 +- crates/eql-codegen/src/consts.rs | 18 --- crates/eql-codegen/src/context.rs | 33 ----- crates/eql-codegen/src/generate.rs | 77 +++--------- crates/eql-codegen/src/lib.rs | 9 +- crates/eql-codegen/src/main.rs | 2 +- crates/eql-codegen/src/templates.rs | 78 ------------ crates/eql-codegen/src/writer.rs | 77 +----------- crates/eql-codegen/tests/parity.rs | 47 +++---- crates/eql-scalars/src/lib.rs | 116 +++++++++++++++++- .../encrypted-domain-implementation-spec.md | 63 +++++----- tasks/build.sh | 7 +- tasks/codegen-parity.sh | 44 ++++--- tests/codegen/reference/README.md | 16 ++- .../reference/int4/int4_eq_functions.sql | 2 +- .../reference/int4/int4_eq_operators.sql | 2 +- .../codegen/reference/int4/int4_functions.sql | 2 +- .../codegen/reference/int4/int4_operators.sql | 2 +- .../reference/int4/int4_ord_aggregates.sql | 2 +- .../reference/int4/int4_ord_functions.sql | 2 +- .../reference/int4/int4_ord_operators.sql | 2 +- .../int4/int4_ord_ore_aggregates.sql | 2 +- .../reference/int4/int4_ord_ore_functions.sql | 2 +- .../reference/int4/int4_ord_ore_operators.sql | 2 +- tests/codegen/reference/int4/int4_types.sql | 2 +- tests/codegen/reference/int4/int4_values.rs | 28 ----- tests/sqlx/src/fixtures/eql_v2_int2.rs | 2 +- tests/sqlx/src/fixtures/eql_v2_int4.rs | 2 +- tests/sqlx/src/fixtures/int2_values.rs | 30 ----- tests/sqlx/src/fixtures/int4_values.rs | 28 ----- tests/sqlx/src/fixtures/mod.rs | 11 +- tests/sqlx/src/fixtures/scalar_fixture.rs | 2 +- tests/sqlx/src/scalar_domains.rs | 20 +-- 35 files changed, 264 insertions(+), 499 deletions(-) delete mode 100644 crates/eql-codegen/src/templates.rs delete mode 100644 tests/codegen/reference/int4/int4_values.rs delete mode 100644 tests/sqlx/src/fixtures/int2_values.rs delete mode 100644 tests/sqlx/src/fixtures/int4_values.rs diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index feacd2c6..d74f2946 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -115,28 +115,15 @@ jobs: # Crate compile/lint/test (cargo test -p eql-scalars -p eql-codegen) runs # in the dedicated `test:crates` job; this job covers the codegen-specific - # gates only — fixture-value regeneration and golden/values parity. - - # Regenerate the committed Rust fixture-value consts for EVERY type from - # the catalog and fail if any differ from / are missing in the tree. - # eql-codegen renders all _values.rs deterministically (unlike the - # encrypted .sql fixtures, whose ciphertext is non-deterministic and - # gitignored), so a plain diff is the right guard — it catches a catalog - # edit that wasn't regenerated. `git add -N` registers any brand-new - # untracked const so a forgotten-to-commit file also trips the diff. No - # Postgres needed: the generator is std-only. - - name: Regenerate and verify fixture-value consts (all types) - run: | - cargo run -p eql-codegen - git add -N tests/sqlx/src/fixtures - git diff --exit-code -- tests/sqlx/src/fixtures \ - || { echo "Fixture value const(s) stale or uncommitted — run 'cargo run -p eql-codegen' and commit tests/sqlx/src/fixtures."; exit 1; } + # gate only — golden parity. The plaintext fixture lists are no longer a + # generated file: they live in the catalog (`eql_scalars::INT4_VALUES` / + # `INT2_VALUES`) and are pinned by `eql-scalars`'s own unit tests, so there + # is nothing to regenerate-and-diff here. # Parity gate: assert the Rust eql-codegen output is line-normalized-equal - # to the int4 golden reference and the committed _values.rs are byte- - # identical (git-clean) after regeneration. Python is no longer an oracle - # (retired in P2). No Postgres needed — the generator runs offline. - - name: Verify generator parity (golden + values) + # to the int4 golden reference. Python is no longer an oracle (retired in + # P2). No Postgres needed — the generator runs offline. + - name: Verify generator parity (golden) run: | mise run codegen:parity diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a280a71..853e5e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ Each entry that ships in a published release links to the PR that introduced it. ### Changed -- **Scalar encrypted-domain types are now defined in a Rust catalog, not TOML manifests; the Python codegen toolchain is removed.** Adding a scalar encrypted-domain type (`int4`, `int8`, …) is now one row in `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`) instead of authoring `tasks/codegen/types/.toml`. `mise run build` regenerates the same gitignored SQL surface and the same committed `tests/sqlx/src/fixtures/_values.rs` consts via `cargo run -p eql-codegen` (Rust, std-only) rather than the Python generator. The shipped SQL is unchanged — `release/*.sql` is byte-identical across the cutover — so there is no change for callers installing EQL; this only affects contributors who extend the scalar domain families. The `python` mise tool, the `pytest`-based `test:codegen` (now `cargo test -p eql-scalars -p eql-codegen`), the per-type `mise run codegen:domain` tasks, and the per-type `tests/sqlx/snapshots/_matrix_tests.txt` baselines (collapsed into one catalog-reconciled `tests/sqlx/snapshots/matrix_tests.txt`) are gone. Why: a single compiler-validated source of truth shared by the generator and the SQLx test harness, and one fewer toolchain in the build/test path — building and testing EQL no longer needs Python (Python remains only for the separate docs-markdown tooling). ([#PR](https://github.com/cipherstash/encrypt-query-language/pull/PR)) +- **Scalar encrypted-domain types are now defined in a Rust catalog, not TOML manifests; the Python codegen toolchain is removed.** Adding a scalar encrypted-domain type (`int4`, `int8`, …) is now one row in `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`) instead of authoring `tasks/codegen/types/.toml`. `mise run build` regenerates the gitignored SQL surface via `cargo run -p eql-codegen` (Rust, std-only) rather than the Python generator. The catalog row's `Fixture` list is the single source of truth for that type's plaintext fixtures: the SQLx test matrix reads it directly as a compile-time-materialised const (`eql_scalars::INT4_VALUES` / `INT2_VALUES`, `ScalarType::FIXTURE_VALUES`), so there is no longer a generated, committed `tests/sqlx/src/fixtures/_values.rs` — a Rust source of truth no longer round-trips through generated Rust. The shipped SQL is unchanged — `release/*.sql` is byte-identical across the cutover — so there is no change for callers installing EQL; this only affects contributors who extend the scalar domain families. The `python` mise tool, the `pytest`-based `test:codegen` (now `cargo test -p eql-scalars -p eql-codegen`), the per-type `mise run codegen:domain` tasks, and the per-type `tests/sqlx/snapshots/_matrix_tests.txt` baselines (collapsed into one catalog-reconciled `tests/sqlx/snapshots/matrix_tests.txt`) are gone. Why: a single compiler-validated source of truth shared by the generator and the SQLx test harness, and one fewer toolchain in the build/test path — building and testing EQL no longer needs Python (Python remains only for the separate docs-markdown tooling). ([#PR](https://github.com/cipherstash/encrypt-query-language/pull/PR)) ## [2.3.1] — 2026-05-21 diff --git a/CLAUDE.md b/CLAUDE.md index ec0788ca..9cb5b80f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,7 +78,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search `src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, `text`, and `jsonb` follow this materializer pattern. `text`, `numeric`, and `jsonb` are planned but have no generated SQL surface yet — `jsonb` in particular needs a separate SQL design beyond the ordered-scalar materializer. The `eql-scalars` fixture catalog (`crates/eql-scalars`) already models their fixture values ahead of the SQL surface. -Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/encrypted_domain//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed; the committed `tests/sqlx/src/fixtures/_values.rs` consts are also generated (CI diffs them). Generated SQL carries a `-- AUTOMATICALLY GENERATED FILE` header (the project-wide marker `docs:validate` greps on) and the committed `_values.rs` carries a `// AUTOMATICALLY GENERATED FILE` header; change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. +Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/encrypted_domain//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed. The per-type plaintext fixture lists the SQLx matrix consumes are **not** a generated file — they are materialised from each `CATALOG` row at compile time as `eql_scalars::INT4_VALUES` / `INT2_VALUES` (the `int_values!` macro) and read directly by `ScalarType::FIXTURE_VALUES`; a Rust source of truth no longer round-trips through a committed generated `.rs`. Generated SQL carries a `-- AUTOMATICALLY GENERATED FILE` header (the project-wide marker `docs:validate` greps on); change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. **Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the catalog row only declares the token, kind, domain suffixes, and terms. New term behavior belongs in the `Term` enum's `impl` methods in `crates/eql-scalars/src` with tests, not in free-form catalog data. diff --git a/crates/eql-codegen/src/consts.rs b/crates/eql-codegen/src/consts.rs index 3f2fdbf0..64d2e438 100644 --- a/crates/eql-codegen/src/consts.rs +++ b/crates/eql-codegen/src/consts.rs @@ -4,10 +4,6 @@ /// the writer uses it only to recognise files it owns (overwrite/clean safety). pub(crate) const AUTO_GENERATED_HEADER: &str = "-- AUTOMATICALLY GENERATED FILE.\n"; -/// Rust generated-file marker, prepended to `_values.rs` (which has no -/// template). Rust comment syntax so the `.rs` file stays valid. -pub(crate) const AUTO_GENERATED_HEADER_RS: &str = "// AUTOMATICALLY GENERATED FILE.\n"; - /// Schema housing the encrypted-domain families. pub(crate) const DOMAIN_SCHEMA: &str = "eql_v3"; /// Schema owning the core index-term types/constructors. @@ -37,20 +33,6 @@ mod tests { assert!(AUTO_GENERATED_HEADER.contains("AUTOMATICALLY GENERATED FILE")); } - #[test] - fn rust_marker_is_a_rust_comment() { - assert_eq!( - AUTO_GENERATED_HEADER_RS, - "// AUTOMATICALLY GENERATED FILE.\n" - ); - for line in AUTO_GENERATED_HEADER_RS.lines() { - assert!( - !line.starts_with("--"), - "rust marker must not contain SQL comments" - ); - } - } - #[test] fn sql_str_doubles_single_quotes() { assert_eq!(sql_str("o'brien"), "o''brien"); diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs index 7066377f..01e80c6c 100644 --- a/crates/eql-codegen/src/context.rs +++ b/crates/eql-codegen/src/context.rs @@ -4,17 +4,6 @@ use crate::consts::*; use crate::operator_surface::Operator; use eql_scalars::{DomainSpec, Term}; -/// Line-normalize SQL for best-effort byte-exact comparison: trim each line's -/// leading/trailing whitespace and drop blank lines; preserve intra-line -/// spacing. NOT used for `_values.rs` (which stays byte-exact). -pub fn normalize_sql(s: &str) -> String { - s.lines() - .map(|l| l.trim()) - .filter(|l| !l.is_empty()) - .collect::>() - .join("\n") -} - /// Build the minijinja environment with the embedded templates: one whole-file /// template per output file (`types`/`functions`/`operators`/`aggregates`) plus /// the per-kind function-body partials that `functions.sql` dynamically @@ -296,28 +285,6 @@ mod tests { assert!(!is_ord_capable(&[])); } - #[test] - fn normalize_trims_lines_and_drops_blanks() { - let input = " CREATE DOMAIN x\n\n CHECK (a) \n\n"; - assert_eq!(normalize_sql(input), "CREATE DOMAIN x\nCHECK (a)"); - } - - #[test] - fn normalize_preserves_intra_line_spacing() { - let input = "RAISE EXCEPTION 'operator % is not supported for %';"; - assert_eq!( - normalize_sql(input), - "RAISE EXCEPTION 'operator % is not supported for %';" - ); - } - - #[test] - fn normalize_equal_modulo_indentation_and_blank_lines() { - let a = "DO $$\nBEGIN\n IF NOT EXISTS (\n ) THEN\n END IF;\nEND\n$$;\n"; - let b = "DO $$\n\nBEGIN\n IF NOT EXISTS (\n ) THEN\nEND IF;\nEND\n$$;"; - assert_eq!(normalize_sql(a), normalize_sql(b)); - } - #[test] fn environment_has_whole_file_and_partial_templates() { let env = environment(); diff --git a/crates/eql-codegen/src/generate.rs b/crates/eql-codegen/src/generate.rs index c7625545..581f0244 100644 --- a/crates/eql-codegen/src/generate.rs +++ b/crates/eql-codegen/src/generate.rs @@ -28,16 +28,6 @@ fn types_path(token: &str) -> String { format!("src/encrypted_domain/{token}/{token}_types.sql") } -/// Committed Rust fixture-value const path. Port of `fixture_values_rs_path`. -pub fn fixture_values_rs_path(out_root: &Path, token: &str) -> PathBuf { - out_root - .join("tests") - .join("sqlx") - .join("src") - .join("fixtures") - .join(format!("{token}_values.rs")) -} - /// Body for _types.sql: every domain in one idempotent DO block. /// Port of `render_types_file`. pub fn render_types_file(spec: &ScalarSpec) -> String { @@ -219,10 +209,8 @@ pub fn render_aggregates_file(token: &str, domain: &DomainSpec) -> Option Result, Ok(written) } -/// Generate every catalog type's SQL + committed _values.rs under `out_root`. -/// The single entry point: replaces Python's per-type and --all forms. +/// Generate every catalog type's gitignored SQL surface under `out_root`. The +/// single entry point: replaces Python's per-type and --all forms. The +/// plaintext fixture lists are not generated — they live in the catalog +/// (`eql_scalars::INT4_VALUES` / `INT2_VALUES`), read directly by the SQLx tests. pub fn generate_all(out_root: &Path) -> Result { for spec in eql_scalars::CATALOG { let token = spec.token; let out_dir = out_root.join("src").join("encrypted_domain").join(token); - let mut written = generate_type(spec, &out_dir)?; - - let rs_path = fixture_values_rs_path(out_root, token); - write_generated_rs(&rs_path, &render_fixture_values_rs(spec))?; - written.push(rs_path); + let written = generate_type(spec, &out_dir)?; for p in &written { let rel = p.strip_prefix(out_root).unwrap_or(p); @@ -312,7 +298,6 @@ mod tests { .expect("domain suffix") } - use crate::templates::render_fixture_values_rs; use std::fs; fn repo_root() -> PathBuf { @@ -384,18 +369,16 @@ mod tests { } #[test] - fn types_file_normalized_matches_golden() { - use crate::context::normalize_sql; + fn types_file_matches_golden() { let root = repo_root(); let path = root.join("tests/codegen/reference/int4/int4_types.sql"); let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); let actual = render_types_file(spec("int4")); - assert_eq!(normalize_sql(&actual), normalize_sql(&expected)); + assert_eq!(actual, expected); } #[test] - fn functions_files_normalized_match_golden() { - use crate::context::normalize_sql; + fn functions_files_match_golden() { let root = repo_root(); let s = spec("int4"); for d in s.domains { @@ -403,17 +386,12 @@ mod tests { let path = root.join(format!("tests/codegen/reference/int4/{full}_functions.sql")); let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); let actual = render_functions_file("int4", d); - assert_eq!( - normalize_sql(&actual), - normalize_sql(&expected), - "{full}_functions.sql diverged" - ); + assert_eq!(actual, expected, "{full}_functions.sql diverged"); } } #[test] - fn operators_files_normalized_match_golden() { - use crate::context::normalize_sql; + fn operators_files_match_golden() { let root = repo_root(); let s = spec("int4"); for d in s.domains { @@ -421,17 +399,12 @@ mod tests { let path = root.join(format!("tests/codegen/reference/int4/{full}_operators.sql")); let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); let actual = render_operators_file("int4", d); - assert_eq!( - normalize_sql(&actual), - normalize_sql(&expected), - "{full}_operators.sql" - ); + assert_eq!(actual, expected, "{full}_operators.sql"); } } #[test] - fn aggregates_files_normalized_match_golden() { - use crate::context::normalize_sql; + fn aggregates_files_match_golden() { let root = repo_root(); let s = spec("int4"); for d in s.domains { @@ -441,11 +414,7 @@ mod tests { "tests/codegen/reference/int4/{full}_aggregates.sql" )); let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); - assert_eq!( - normalize_sql(&actual), - normalize_sql(&expected), - "{full}_aggregates.sql" - ); + assert_eq!(actual, expected, "{full}_aggregates.sql"); } } } @@ -462,13 +431,11 @@ mod tests { continue; } let name = path.file_name().unwrap().to_str().unwrap().to_string(); - use crate::context::normalize_sql; let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); let actual = rendered_for("int4", &name, s); assert_eq!( - normalize_sql(&actual), - normalize_sql(&expected), - "{name}: generator diverged from golden reference (normalized)" + actual, expected, + "{name}: generator diverged from golden reference" ); checked += 1; } @@ -478,18 +445,6 @@ mod tests { ); } - #[test] - fn generator_matches_int4_values_rs_reference() { - let root = repo_root(); - let path = root.join("tests/codegen/reference/int4/int4_values.rs"); - let expected = strip_reference_marker(&fs::read_to_string(&path).unwrap()); - let actual = render_fixture_values_rs(spec("int4")); - assert_eq!( - actual, expected, - "int4_values.rs: generator diverged from golden reference" - ); - } - #[test] fn generate_type_writes_expected_files() { let d = crate::writer::test_support::tempdir(); diff --git a/crates/eql-codegen/src/lib.rs b/crates/eql-codegen/src/lib.rs index ebde05bf..aada6bb7 100644 --- a/crates/eql-codegen/src/lib.rs +++ b/crates/eql-codegen/src/lib.rs @@ -1,11 +1,12 @@ //! Scalar encrypted-domain SQL generator. Renders the `eql-scalars` catalog to -//! the gitignored SQL surface and the committed `_values.rs` consts. The SQL -//! surface is validated against the `tests/codegen/reference/int4` golden under -//! line-normalized comparison; `_values.rs` is validated byte-exact. +//! the gitignored SQL surface, validated byte-for-byte against the +//! `tests/codegen/reference/int4` golden (modulo the one `-- REFERENCE:` +//! provenance line each reference file carries). The plaintext fixture lists +//! the SQLx matrix consumes live in the catalog itself +//! (`eql_scalars::INT4_VALUES` / `INT2_VALUES`), not in a generated file. pub mod consts; pub mod context; pub mod generate; pub mod operator_surface; -pub mod templates; pub mod writer; diff --git a/crates/eql-codegen/src/main.rs b/crates/eql-codegen/src/main.rs index b1b96de8..c51bf4c2 100644 --- a/crates/eql-codegen/src/main.rs +++ b/crates/eql-codegen/src/main.rs @@ -27,7 +27,7 @@ fn main() -> ExitCode { } if args.len() == 1 { - // No args: generate every type's SQL + _values.rs. + // No args: generate every type's gitignored SQL surface. match generate_all(&repo_root()) { Ok(0) => return ExitCode::SUCCESS, Ok(_) => return ExitCode::FAILURE, // any non-zero codegen result is a failure diff --git a/crates/eql-codegen/src/templates.rs b/crates/eql-codegen/src/templates.rs deleted file mode 100644 index e0da5290..00000000 --- a/crates/eql-codegen/src/templates.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Rust fixture-const renderer. The SQL surface is rendered from minijinja -//! templates (see `context.rs`); this file emits only the committed -//! `_values.rs` consts, which stay byte-exact. - -use eql_scalars::ScalarSpec; - -/// Body for tests/sqlx/src/fixtures/_values.rs. The writer prepends the -/// AUTO-GENERATED Rust header, so the body carries none. -/// Port of templates.py `render_fixture_values_rs`. -pub fn render_fixture_values_rs(spec: &ScalarSpec) -> String { - let token = spec.token; - let rust_type = spec.kind.rust_type(); - let mut literals = String::new(); - for &f in spec.fixtures { - literals.push_str(&format!(" {},\n", f.render_literal(spec.kind))); - } - // Raw string keeps the emitted shape legible while staying byte-exact; - // lines are flush-left because raw-string whitespace is literal output. - format!( - r#"//! Fixture plaintext values for the {token} encrypted-domain family. -//! -//! Generated from the `{token}` row in `eql-scalars::CATALOG` (`fixtures`) — -//! the single source of truth shared by the fixture generator -//! (`fixtures::eql_v2_{token}`) and the matrix oracle -//! (`ScalarType::FIXTURE_VALUES`). - -/// Distinct plaintext values present in the `eql_v2_{token}` fixture. -pub const VALUES: &[{rust_type}] = &[ -{literals}]; -"# - ) -} - -#[cfg(test)] -mod tests { - use super::*; - use eql_scalars::CATALOG; - - fn spec(token: &str) -> &'static ScalarSpec { - CATALOG - .iter() - .find(|s| s.token == token) - .expect("catalog token") - } - - #[test] - fn fixture_values_rs_emits_typed_const_for_int4() { - let body = render_fixture_values_rs(spec("int4")); - assert!(body.contains("pub const VALUES: &[i32] = &[")); - assert!(body.contains("eql-scalars::CATALOG")); - assert!(body.contains(" i32::MIN,\n")); - assert!(body.contains(" i32::MAX,\n")); - assert!(body.contains(" -1,\n")); - assert!(body.contains(" 0,\n")); - assert!(body.contains(" 1,\n")); - assert!(!body.contains("AUTO-GENERATED")); - } - - #[test] - fn fixture_values_rs_preserves_catalog_order() { - let body = render_fixture_values_rs(spec("int4")); - let min = body.find("i32::MIN").unwrap(); - let zero = body.find(" 0,").unwrap(); - let max = body.find("i32::MAX").unwrap(); - assert!(min < zero && zero < max); - } - - // Adapted from the plan's `fixture_values_rs_int8_uses_i64`: the shipped - // CATALOG has no int8 (deliberately reserved for a later branch), so this - // exercises the second committed non-i32 type — int2 (i16). - #[test] - fn fixture_values_rs_int2_uses_i16() { - let body = render_fixture_values_rs(spec("int2")); - assert!(body.contains("pub const VALUES: &[i16] = &[")); - assert!(body.contains(" i16::MIN,\n")); - assert!(body.contains(" -30000,\n")); - } -} diff --git a/crates/eql-codegen/src/writer.rs b/crates/eql-codegen/src/writer.rs index 7675af9f..afbcdce0 100644 --- a/crates/eql-codegen/src/writer.rs +++ b/crates/eql-codegen/src/writer.rs @@ -4,18 +4,13 @@ use std::fs; use std::io; use std::path::{Path, PathBuf}; -use crate::consts::{AUTO_GENERATED_HEADER, AUTO_GENERATED_HEADER_RS}; +use crate::consts::AUTO_GENERATED_HEADER; /// First line of the SQL header — the ownership marker. fn sql_marker() -> &'static str { AUTO_GENERATED_HEADER.lines().next().unwrap() } -/// First line of the Rust header — the ownership marker. -fn rs_marker() -> &'static str { - AUTO_GENERATED_HEADER_RS.lines().next().unwrap() -} - /// Raised when the generator would clobber a hand-written file. #[derive(Debug)] pub enum WriteError { @@ -53,11 +48,6 @@ pub fn is_generated(path: &Path) -> bool { path.is_file() && first_line(path).map(|l| l == sql_marker()).unwrap_or(false) } -/// True if the file carries the Rust AUTO-GENERATED marker. Port of `is_generated_rs`. -pub fn is_generated_rs(path: &Path) -> bool { - path.is_file() && first_line(path).map(|l| l == rs_marker()).unwrap_or(false) -} - /// Delete every generated .sql file in `directory`, returning removed paths. /// Port of `clean_generated_files`. pub fn clean_generated_files(directory: &Path) -> io::Result> { @@ -107,21 +97,6 @@ pub fn write_generated_file(path: &Path, body: &str) -> Result<(), WriteError> { Ok(()) } -/// Write `body` to a Rust file prefixed with the Rust header. Port of `write_generated_rs`. -pub fn write_generated_rs(path: &Path, body: &str) -> Result<(), WriteError> { - if path.exists() && !is_generated_rs(path) { - return Err(WriteError::Ownership(format!( - "refusing to overwrite hand-written file: {} (no AUTO-GENERATED header).", - path.display() - ))); - } - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, format!("{AUTO_GENERATED_HEADER_RS}{body}"))?; - Ok(()) -} - #[cfg(test)] pub(crate) mod test_support { use std::fs; @@ -255,54 +230,4 @@ mod tests { let d = tmp(); assert!(clean_generated_files(d.path()).unwrap().is_empty()); } - - #[test] - fn write_generated_rs_creates_with_rust_header() { - let d = tmp(); - let p = d.path().join("int4_values.rs"); - write_generated_rs(&p, "pub const VALUES: &[i32] = &[];\n").unwrap(); - let text = fs::read_to_string(&p).unwrap(); - assert!(text.starts_with(AUTO_GENERATED_HEADER_RS)); - assert!(text.contains("pub const VALUES")); - } - - #[test] - fn is_generated_rs_true_for_rust_header() { - let d = tmp(); - let p = d.path().join("int4_values.rs"); - fs::write( - &p, - format!("{AUTO_GENERATED_HEADER_RS}pub const VALUES: &[i32] = &[];\n"), - ) - .unwrap(); - assert!(is_generated_rs(&p)); - } - - #[test] - fn is_generated_rs_false_for_handwritten() { - let d = tmp(); - let p = d.path().join("int4_values.rs"); - fs::write(&p, "//! hand-written\npub const VALUES: &[i32] = &[];\n").unwrap(); - assert!(!is_generated_rs(&p)); - } - - #[test] - fn write_generated_rs_refuses_handwritten() { - let d = tmp(); - let p = d.path().join("int4_values.rs"); - fs::write(&p, "//! hand-written\n").unwrap(); - let err = write_generated_rs(&p, "pub const VALUES: &[i32] = &[];\n").unwrap_err(); - assert!(err.to_string().contains("hand-written")); - } - - #[test] - fn write_generated_rs_overwrites_existing_generated() { - let d = tmp(); - let p = d.path().join("int4_values.rs"); - fs::write(&p, format!("{AUTO_GENERATED_HEADER_RS}// old\n")).unwrap(); - write_generated_rs(&p, "// new\n").unwrap(); - let text = fs::read_to_string(&p).unwrap(); - assert!(text.contains("// new")); - assert!(!text.contains("// old")); - } } diff --git a/crates/eql-codegen/tests/parity.rs b/crates/eql-codegen/tests/parity.rs index 87c27526..a59ed8dc 100644 --- a/crates/eql-codegen/tests/parity.rs +++ b/crates/eql-codegen/tests/parity.rs @@ -1,8 +1,10 @@ //! THE PARITY GATE. Runs the Rust generator (into a temp dir) and asserts the -//! int4 SQL surface is line-normalized-equal to the `tests/codegen/reference/int4` -//! golden, and that committed `_values.rs` are byte-identical to the -//! generator output. The golden reference — not the retired Python generator — -//! is the sole oracle. +//! int4 SQL surface is byte-for-byte equal to the `tests/codegen/reference/int4` +//! golden (modulo the one leading `-- REFERENCE:` provenance line). The golden +//! reference — not the retired Python generator — is the sole oracle. The +//! plaintext fixture lists are not generated; they live in the catalog +//! (`eql_scalars::INT4_VALUES` / `INT2_VALUES`) and are pinned by +//! `eql-scalars`'s own `values_tests`. use std::fs; use std::path::PathBuf; @@ -27,25 +29,6 @@ fn tempdir(tag: &str) -> PathBuf { p } -#[test] -fn rust_generator_matches_committed_values_rs() { - let root = repo_root(); - let out = tempdir("rust-values"); - eql_codegen::generate::generate_all(&out).expect("rust generate_all"); - - for spec in eql_scalars::CATALOG { - let token = spec.token; - let generated = out.join(format!("tests/sqlx/src/fixtures/{token}_values.rs")); - let committed = root.join(format!("tests/sqlx/src/fixtures/{token}_values.rs")); - let g = fs::read(&generated).expect("generated values.rs"); - let c = fs::read(&committed).expect("committed values.rs"); - assert_eq!( - g, c, - "{token}_values.rs: Rust generator output differs from the committed file" - ); - } -} - #[test] fn rust_generator_matches_int4_golden_files() { let root = repo_root(); @@ -61,20 +44,20 @@ fn rust_generator_matches_int4_golden_files() { } let name = path.file_name().unwrap().to_str().unwrap(); let reference = fs::read_to_string(&path).unwrap(); - // Strip the leading `-- REFERENCE:` provenance line. What remains is the - // generated body, which already starts with the template-owned - // `-- AUTOMATICALLY GENERATED FILE.` marker — the same first line the - // materialised file carries, so no header is re-added here. + // Strip the leading `-- REFERENCE:` provenance line(s), preserving the + // remaining bytes verbatim (`split_inclusive` keeps the `\n` + // terminators). What remains is the generated body, which already starts + // with the template-owned `-- AUTOMATICALLY GENERATED FILE.` marker — the + // same first line the materialised file carries — so the comparison is + // byte-for-byte with no header re-added. let expected: String = reference - .lines() + .split_inclusive('\n') .skip_while(|l| l.starts_with("-- REFERENCE:") || l.starts_with("// REFERENCE:")) - .map(|l| format!("{l}\n")) .collect(); let actual = fs::read_to_string(gen_dir.join(name)).unwrap(); assert_eq!( - eql_codegen::context::normalize_sql(&actual), - eql_codegen::context::normalize_sql(&expected), - "{name}: materialised output differs from golden (normalized)" + actual, expected, + "{name}: materialised output differs from golden" ); } } diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index 91cfae3d..f606a07b 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -263,7 +263,10 @@ impl Fixture { /// The integer value for this fixture (`Min`/`Max` -> kind bounds, `Zero` -> /// 0, `Int(n)` -> n), or `None` for the string-backed kinds. Does not /// range-check; `every_fixture_value_is_within_kind_bounds` guards the bounds. - pub fn numeric_value(self, kind: ScalarKind) -> Option { + /// + /// `const fn` so the `int_values!` materialiser can resolve a whole fixture + /// list into a typed `&'static` array at compile time. + pub const fn numeric_value(self, kind: ScalarKind) -> Option { match self { Fixture::Min => Some(kind.min_value()), Fixture::Max => Some(kind.max_value()), @@ -386,6 +389,43 @@ const INT2: ScalarSpec = ScalarSpec { /// drives generation order). New types are appended as their SQL surface lands. pub const CATALOG: &[ScalarSpec] = &[INT4, INT2]; +/// Materialise an integer scalar's fixtures into a typed `&'static` slice at +/// compile time. This is the **single-sourced** plaintext list the SQLx test +/// matrix reads as `ScalarType::FIXTURE_VALUES` and the fixture generator +/// encrypts — derived from the same `CATALOG` row that drives SQL generation, +/// so the oracle cannot drift from the fixture. (It replaces the old generated, +/// committed `tests/sqlx/src/fixtures/_values.rs` — a Rust source of truth no +/// longer needs to round-trip through generated Rust.) +/// +/// Integer kinds only: a non-numeric fixture (`Text`/`Numeric`/`Jsonb`) is a +/// const-eval error, mirroring `numeric_value`'s `None`. +macro_rules! int_values { + ($name:ident, $ty:ty, $spec:expr) => { + #[doc = concat!("Distinct plaintext fixture values for `", stringify!($spec), "`, ")] + #[doc = "materialised from its `CATALOG` row (see `int_values!`)."] + pub const $name: &[$ty] = { + const SPEC: ScalarSpec = $spec; + const N: usize = SPEC.fixtures.len(); + const ARR: [$ty; N] = { + let mut out = [0 as $ty; N]; + let mut i = 0; + while i < N { + out[i] = match SPEC.fixtures[i].numeric_value(SPEC.kind) { + Some(v) => v as $ty, + None => panic!("integer scalar fixture must resolve to a number"), + }; + i += 1; + } + out + }; + &ARR + }; + }; +} + +int_values!(INT4_VALUES, i32, INT4); +int_values!(INT2_VALUES, i16, INT2); + #[cfg(test)] mod rust_tests { use super::*; @@ -804,6 +844,80 @@ mod catalog_tests { } } +#[cfg(test)] +mod values_tests { + use super::*; + + // The exact typed lists the SQLx matrix consumes. These pin the values the + // deleted golden `int4_values.rs` / committed `_values.rs` used to pin: + // a catalog edit that changes a fixture must update these assertions. + #[test] + fn int4_values_materialise_to_typed_array() { + assert_eq!( + INT4_VALUES, + &[ + i32::MIN, + -100, + -1, + 0, + 1, + 2, + 5, + 10, + 17, + 25, + 42, + 50, + 100, + 250, + 1000, + 9999, + i32::MAX + ] + ); + } + + #[test] + fn int2_values_materialise_to_typed_array() { + assert_eq!( + INT2_VALUES, + &[ + i16::MIN, + -30000, + -100, + -1, + 0, + 1, + 2, + 5, + 10, + 17, + 25, + 42, + 50, + 100, + 250, + 1000, + 9999, + 30000, + i16::MAX + ] + ); + } + + #[test] + fn materialised_values_track_their_fixture_lists() { + // One value per fixture, in catalog order; sentinels resolve to extremes. + assert_eq!(INT4_VALUES.len(), INT4_FIXTURES.len()); + assert_eq!(INT2_VALUES.len(), INT2_FIXTURES.len()); + assert_eq!(INT4_VALUES.first(), Some(&i32::MIN)); + assert_eq!(INT4_VALUES.last(), Some(&i32::MAX)); + assert_eq!(INT2_VALUES.first(), Some(&i16::MIN)); + assert_eq!(INT2_VALUES.last(), Some(&i16::MAX)); + assert!(INT4_VALUES.contains(&0) && INT2_VALUES.contains(&0)); + } +} + #[cfg(test)] mod invariant_tests { use super::*; diff --git a/docs/reference/encrypted-domain-implementation-spec.md b/docs/reference/encrypted-domain-implementation-spec.md index 838f870c..cf1a4b19 100644 --- a/docs/reference/encrypted-domain-implementation-spec.md +++ b/docs/reference/encrypted-domain-implementation-spec.md @@ -69,6 +69,13 @@ future migration. and no Python: the catalog is the source of truth, validated by the compiler (an undefined `Term` or unknown `ScalarKind` is a compile error) plus catalog `#[test]`s over `CATALOG`. +- [ ] Materialise the type's plaintext fixture list as a typed const next to + `CATALOG`: add `int_values!(_VALUES, , );` (e.g. + `int_values!(INT8_VALUES, i64, INT8);`). The macro resolves the row's + `Fixture` list into a compile-time `&'static []` — the single source the + SQLx matrix reads as `FIXTURE_VALUES`. Pin the exact list with a + `values_tests` assertion. This replaces the old generated, committed + `_values.rs`. - [ ] **If `` needs a new scalar width**, add a `ScalarKind` enum variant in `crates/eql-scalars/src/lib.rs` with its rust-type name, `MIN`/`MAX`/zero symbols, and numeric bounds, and unit-test its `impl` methods. New term @@ -76,10 +83,11 @@ future migration. — not in free-form catalog data. - [ ] Run `cargo run -p eql-codegen` to materialise the generated SQL (`src/encrypted_domain//_{types,functions,operators,aggregates}.sql`, - gitignored) and the committed `tests/sqlx/src/fixtures/_values.rs` - const, or just `mise run build` — every build runs the generator first. - Commit the regenerated `_values.rs` (CI diffs it). There is no per-type - codegen task: one run generates every type from `CATALOG`. + gitignored), or just `mise run build` — every build runs the generator + first. There is no per-type codegen task: one run generates every type from + `CATALOG`. The plaintext fixture list is **not** generated — it is + materialised from the catalog row at compile time (see the next step), so + there is nothing to regenerate-and-commit on the test side. - [ ] Generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` are gitignored and never committed. The catalog (`eql-scalars::CATALOG`) plus the `eql-codegen` renderers are the source @@ -89,12 +97,11 @@ future migration. `-- REQUIRE:` edges. This file IS committed. - [ ] Do **not** add a `tests/codegen/reference//` baseline. `int4` is the single golden master for the type-generic generator: the SQL templates are - pure token substitution and the only type-specific rendering is - `_values.rs`, so a per-type baseline can only fail when `int4`'s already - would. Drift protection for the new type comes from the `int4` reference, - the committed `_values.rs` const guarded by the CI staleness check - (`cargo run -p eql-codegen` + `git diff --exit-code`) and the catalog/ - generator `#[test]`s (`cargo test -p eql-scalars -p eql-codegen`), and the + pure token substitution, so a per-type baseline can only fail when `int4`'s + already would. Drift protection for the new type comes from the `int4` + reference, the catalog `values_tests` pinning the materialised + `eql_scalars::_VALUES` const, the catalog/generator `#[test]`s + (`cargo test -p eql-scalars -p eql-codegen`), and the `ordered_numeric_matrix!` SQLx suite (behaviour, not bytes). - [ ] Wire the SQLx matrix oracle. The generated SQL is enough to install the domains, but the `ordered_numeric_matrix!` suite only runs once the Rust @@ -105,10 +112,10 @@ future migration. | File | Add | |------|-----| | `tests/sqlx/src/fixtures/eql_plaintext.rs` | A sealed `EqlPlaintext` impl for the scalar's Rust type: `impl Sealed for {}`, a `PlaintextSqlType` const for its base column type, `impl EqlPlaintext for ` (`CAST`, `PLAINTEXT_SQL_TYPE`, `to_plaintext` → the right `Plaintext` variant), plus the two `#[test]` casts. | - | `tests/sqlx/src/fixtures/eql_v2_.rs` | `crate::scalar_fixture!("eql_v2_", , VALUES);` (pulls `super::_values::VALUES`). | - | `tests/sqlx/src/fixtures/mod.rs` | `pub mod _values;` and `pub mod eql_v2_;`. | + | `tests/sqlx/src/fixtures/eql_v2_.rs` | `use eql_scalars::_VALUES as VALUES;` then `crate::scalar_fixture!("eql_v2_", , VALUES);`. | + | `tests/sqlx/src/fixtures/mod.rs` | `pub mod eql_v2_;`. | | `tests/sqlx/tests/generate_all_fixtures.rs` | An arm in `generate_for_token`: `"" => fixtures::eql_v2_::spec().run().await,`. The match is exhaustive over the catalog — a catalog token with no arm fails the generator loudly. | - | `tests/sqlx/src/scalar_domains.rs` | `impl ScalarType for ` — `PG_TYPE` (the base PG type, e.g. `"int8"`) and `FIXTURE_VALUES = crate::fixtures::_values::VALUES`. | + | `tests/sqlx/src/scalar_domains.rs` | `impl ScalarType for ` — `PG_TYPE` (the base PG type, e.g. `"int8"`) and `FIXTURE_VALUES = eql_scalars::_VALUES`. | | `tests/sqlx/tests/encrypted_domain/scalars/.rs` | `ordered_numeric_matrix! { suite = , scalar = , eql_type = "eql_v2_" }`. | | `tests/sqlx/tests/encrypted_domain/scalars/mod.rs` | `pub mod ;`. | @@ -347,7 +354,8 @@ absent. ### Single-sourcing the value list The plaintext value list is declared **once**, in the catalog row's `fixtures` -field, and generated into Rust — never hand-maintained in two places: +field, and materialised into a typed Rust const — never hand-maintained in two +places: ```rust fixtures: &[Fixture::Min, Fixture::N(-100), Fixture::N(-1), Fixture::Zero, @@ -365,24 +373,21 @@ explicit invariant `every_fixture_value_is_within_kind_bounds`). The same test enforces the matrix invariant: the set **must** include `Min`, `Max`, and zero, or the test fails (the compile-time analogue of the old `load_spec` validation). -`eql-codegen` emits `tests/sqlx/src/fixtures/_values.rs` exposing one -`pub const VALUES: &[]`. Both consumers reference that single -symbol — the fixture generator (`fixtures::eql_v2_::spec`) and the matrix -oracle (`impl ScalarType for { const FIXTURE_VALUES }`) — so the oracle -cannot drift from the values the generator encrypts. - -Unlike the gitignored `*_*.sql` surface and the gitignored encrypted -`tests/sqlx/fixtures/eql_v2_.sql` (whose ciphertext is non-deterministic -per-encrypt), `_values.rs` **is committed**: its rendering is deterministic, -so the CI `codegen` job regenerates it (`cargo run -p eql-codegen`) and runs -`git diff --exit-code` to catch a catalog edit that wasn't regenerated. -Regenerate with `cargo run -p eql-codegen` (or `mise run build`) and commit the -result; never hand-edit it. +The `int_values!` macro (in `crates/eql-scalars/src/lib.rs`) materialises that +`Fixture` list into a `pub const _VALUES: &[]` at compile +time, sitting next to `CATALOG`. Both consumers reference that single symbol — +the fixture generator (`fixtures::eql_v2_::spec`) and the matrix oracle +(`impl ScalarType for { const FIXTURE_VALUES = eql_scalars::_VALUES }`) +— so the oracle cannot drift from the values the generator encrypts. There is no +generated `_values.rs`: a Rust source of truth does not round-trip through +generated Rust. The exact list is pinned by a `values_tests` assertion, and the +`Fixture`-list invariants (`Min`/`Max`/zero present, in-bounds) by the catalog +`#[test]`s. ## 10. Build And Verification -- `cargo run -p eql-codegen` (optional; refreshes all generated SQL + - `_values.rs` from the catalog before a full build) +- `cargo run -p eql-codegen` (optional; refreshes all generated SQL from the + catalog before a full build) - `mise run test:codegen` (`cargo test -p eql-scalars -p eql-codegen`) - `mise run clean && mise run build` (regenerates every type's SQL from the catalog first, then builds the release artefacts) diff --git a/tasks/build.sh b/tasks/build.sh index 621b0b72..311dfbc7 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -26,9 +26,10 @@ find src/encrypted_domain -mindepth 2 -type f \ -delete 2>/dev/null || true # Regenerate every type — the catalog (eql-scalars::CATALOG) is the single -# source of truth for the enumeration; eql-codegen renders all SQL and all -# tests/sqlx/src/fixtures/_values.rs in one deterministic run. The orphan -# sweep above still handles the catalog-removed case the generator cannot. +# source of truth for the enumeration; eql-codegen renders all SQL in one +# deterministic run. The plaintext fixture lists are not generated — the SQLx +# tests read them straight from the catalog (eql_scalars::INT4_VALUES / …). The +# orphan sweep above still handles the catalog-removed case the generator cannot. cargo run -p eql-codegen # Fail loudly if any file referenced in a tsorted dep list doesn't exist. diff --git a/tasks/codegen-parity.sh b/tasks/codegen-parity.sh index 1f8d1e10..2de923be 100755 --- a/tasks/codegen-parity.sh +++ b/tasks/codegen-parity.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#MISE description="Parity gate: Rust eql-codegen output matches the int4 golden (normalized) and committed values.rs" +#MISE description="Parity gate: Rust eql-codegen output matches the int4 golden (byte-for-byte)" set -euo pipefail @@ -9,25 +9,31 @@ cd "$REPO_ROOT" echo "==> Generating with the Rust generator (writes the real repo tree)" cargo run -q -p eql-codegen -- > /dev/null -echo "==> Diffing Rust int4 SQL vs golden reference (line-normalized)" -norm() { sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | grep -v '^$'; } +echo "==> Comparing int4 generated SQL file SET vs golden (catches extra/dropped files)" +# The content loop below is golden-driven: it verifies every golden file has a +# matching generated body, so a DROPPED file fails there. It cannot see an EXTRA +# generated file (a new template output, or the new half of a rename) — that name +# is never iterated. Assert the sets are equal first to close that blind spot. +# "Generated" excludes any committed, hand-written SQL (e.g. int4_extensions.sql), +# which lives in this dir but has no golden counterpart; git-tracked == hand-written. +golden_set=$(cd tests/codegen/reference/int4 && ls *.sql | LC_ALL=C sort) +gen_set=$(cd src/encrypted_domain/int4 \ + && comm -23 <(ls *.sql | LC_ALL=C sort) \ + <(git ls-files . | sed 's#.*/##' | LC_ALL=C sort)) +if [ "$golden_set" != "$gen_set" ]; then + echo "int4 generated SQL file set differs from golden (< golden, > generated):" >&2 + diff <(echo "$golden_set") <(echo "$gen_set") >&2 || true + exit 1 +fi + +echo "==> Diffing Rust int4 SQL vs golden reference (byte-for-byte)" for f in tests/codegen/reference/int4/*.sql; do name="$(basename "$f")" - # Reference: drop the 1-line `-- REFERENCE:` provenance line. What remains — - # and the whole generated file — both start with the template-owned - # `-- AUTOMATICALLY GENERATED FILE.` marker, so no header strip is needed. - diff <(tail -n +2 "$f" | norm) \ - <(norm < "src/encrypted_domain/int4/$name") + # Drop the 1-line `-- REFERENCE:` provenance line, then compare the remaining + # bytes EXACTLY. Both the reference body (from line 2) and the whole generated + # file start with the template-owned `-- AUTOMATICALLY GENERATED FILE.` marker, + # so no header strip is needed — any whitespace or blank-line drift fails here. + diff <(tail -n +2 "$f") "src/encrypted_domain/int4/$name" done -echo "==> Verifying committed _values.rs are byte-identical (git clean)" -# `git diff` only catches modifications to tracked files; a newly-generated but -# uncommitted _values.rs would slip through. `git status --porcelain` also -# reports untracked files, mirroring the CI codegen job. -if [ -n "$(git status --porcelain -- tests/sqlx/src/fixtures/)" ]; then - echo "values.rs stale or uncommitted after regeneration" >&2 - git status --porcelain -- tests/sqlx/src/fixtures/ >&2 - exit 1 -fi - -echo "PARITY OK: Rust generator matches the int4 golden (normalized) and committed values.rs." +echo "PARITY OK: Rust generator matches the int4 golden (byte-for-byte)." diff --git a/tests/codegen/reference/README.md b/tests/codegen/reference/README.md index 58f01cc1..8a91bf68 100644 --- a/tests/codegen/reference/README.md +++ b/tests/codegen/reference/README.md @@ -1,13 +1,21 @@ # Codegen reference -The SQL files under `int4/` are the original, hand-written reference implementation for the encrypted-domain scalar generator. `int4` is the **single golden master**: the generator in `tasks/codegen/` is type-generic — its SQL templates are pure token substitution, and the only type-specific rendering is the `_values.rs` const — so one anchored type detects all template/term drift for every current and future scalar. +The SQL files under `int4/` are the hand-written golden reference for the encrypted-domain scalar generator. `int4` is the **single golden master**: the generator in `crates/eql-codegen` is type-generic — its SQL templates are pure token substitution driven by the `eql-scalars::CATALOG` rows — so one anchored type detects all template/term drift for every current and future scalar. -`tasks/codegen/test_against_reference.py` renders the generator's output for `int4` and asserts it matches these files byte-for-byte. If the generator diverges, either it regressed (fix `tasks/codegen/`) or the reference is being updated deliberately (commit the new `int4` reference in the same PR). +Each reference file's first line is a `-- REFERENCE:` provenance marker; everything after it is the generated body verbatim, starting with the template-owned `-- AUTOMATICALLY GENERATED FILE.` header. + +The parity gate renders the generator's output for `int4` and asserts it matches these files **byte-for-byte** after dropping that single provenance line. It runs three ways, all on the same reference: + +- `crates/eql-codegen/tests/parity.rs` — runs `generate_all` into a temp dir and byte-compares the materialised `int4` SQL surface; +- the in-crate golden tests in `crates/eql-codegen/src/generate.rs` — byte-compare each `render_*_file` output against the corresponding reference; +- `mise run codegen:parity` (`tasks/codegen-parity.sh`) — the CI shell gate, a plain `diff` of `tail -n +2 ` against the regenerated tree. + +If the generator diverges, either it regressed (fix `crates/eql-codegen`) or the reference is being updated deliberately (commit the new `int4` reference in the same PR). Whitespace and blank-line drift now fail the gate — there is no normalization. ## New scalar types do not add a reference Adding a scalar type (`int2`, `int8`, …) does **not** add a `tests/codegen/reference//` directory. A per-type baseline would be redundant: the SQL is byte-identical to `int4` modulo the type token, so it can only fail when `int4`'s baseline already would. New types are guaranteed three other ways: -- the `int4` reference here anchors the shared generator (templates + `terms.py`); -- the committed `tests/sqlx/src/fixtures/_values.rs` const is pinned by the CI staleness guard (`git diff --exit-code` after `mise run codegen:domain `) and by the `` cases in `tasks/codegen/test_scalars.py` (the only type-specific rendering, `i16::MIN` vs `i32::MIN`); +- the `int4` reference here anchors the shared generator (templates + the `Term` enum's capability `impl`s in `crates/eql-scalars`); +- the per-type plaintext fixture list (`eql_scalars::INT4_VALUES` / `INT2_VALUES`, materialised from each `CATALOG` row) is pinned by `eql-scalars`'s own `values_tests` — there is no generated `_values.rs` to diff; - the SQLx `ordered_numeric_matrix!` suite exercises the generated SQL's *behaviour* against a real database — a far stronger guarantee than a byte comparison. diff --git a/tests/codegen/reference/int4/int4_eq_functions.sql b/tests/codegen/reference/int4/int4_eq_functions.sql index 21fddfd5..1b244577 100644 --- a/tests/codegen/reference/int4/int4_eq_functions.sql +++ b/tests/codegen/reference/int4/int4_eq_functions.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql diff --git a/tests/codegen/reference/int4/int4_eq_operators.sql b/tests/codegen/reference/int4/int4_eq_operators.sql index fa0d44cd..a2190e16 100644 --- a/tests/codegen/reference/int4/int4_eq_operators.sql +++ b/tests/codegen/reference/int4/int4_eq_operators.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql diff --git a/tests/codegen/reference/int4/int4_functions.sql b/tests/codegen/reference/int4/int4_functions.sql index 36c9df70..6dae8388 100644 --- a/tests/codegen/reference/int4/int4_functions.sql +++ b/tests/codegen/reference/int4/int4_functions.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql diff --git a/tests/codegen/reference/int4/int4_operators.sql b/tests/codegen/reference/int4/int4_operators.sql index def25237..e461c3b7 100644 --- a/tests/codegen/reference/int4/int4_operators.sql +++ b/tests/codegen/reference/int4/int4_operators.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql diff --git a/tests/codegen/reference/int4/int4_ord_aggregates.sql b/tests/codegen/reference/int4/int4_ord_aggregates.sql index 7efdf177..08cdc10d 100644 --- a/tests/codegen/reference/int4/int4_ord_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_aggregates.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql diff --git a/tests/codegen/reference/int4/int4_ord_functions.sql b/tests/codegen/reference/int4/int4_ord_functions.sql index b4dda68d..2c0ee56b 100644 --- a/tests/codegen/reference/int4/int4_ord_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_functions.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql diff --git a/tests/codegen/reference/int4/int4_ord_operators.sql b/tests/codegen/reference/int4/int4_ord_operators.sql index 697f162e..a5321c62 100644 --- a/tests/codegen/reference/int4/int4_ord_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_operators.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql diff --git a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql index 5b160ed7..de5b0848 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql diff --git a/tests/codegen/reference/int4/int4_ord_ore_functions.sql b/tests/codegen/reference/int4/int4_ord_ore_functions.sql index 327bc18c..75f09fb9 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_functions.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema.sql -- REQUIRE: src/schema-v3.sql diff --git a/tests/codegen/reference/int4/int4_ord_ore_operators.sql b/tests/codegen/reference/int4/int4_ord_ore_operators.sql index 47549cdb..52f363cf 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_operators.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql -- REQUIRE: src/encrypted_domain/int4/int4_types.sql diff --git a/tests/codegen/reference/int4/int4_types.sql b/tests/codegen/reference/int4/int4_types.sql index bb708a61..ba4d9d89 100644 --- a/tests/codegen/reference/int4/int4_types.sql +++ b/tests/codegen/reference/int4/int4_types.sql @@ -1,4 +1,4 @@ --- REFERENCE: hand-written parity baseline for tasks/codegen/ — see ../README.md +-- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md -- AUTOMATICALLY GENERATED FILE. -- REQUIRE: src/schema-v3.sql diff --git a/tests/codegen/reference/int4/int4_values.rs b/tests/codegen/reference/int4/int4_values.rs deleted file mode 100644 index 77a69ad0..00000000 --- a/tests/codegen/reference/int4/int4_values.rs +++ /dev/null @@ -1,28 +0,0 @@ -// REFERENCE: hand-reviewed parity baseline for tasks/codegen/ — see ../README.md -//! Fixture plaintext values for the int4 encrypted-domain family. -//! -//! Generated from the `int4` row in `eql-scalars::CATALOG` (`fixtures`) — -//! the single source of truth shared by the fixture generator -//! (`fixtures::eql_v2_int4`) and the matrix oracle -//! (`ScalarType::FIXTURE_VALUES`). - -/// Distinct plaintext values present in the `eql_v2_int4` fixture. -pub const VALUES: &[i32] = &[ - i32::MIN, - -100, - -1, - 0, - 1, - 2, - 5, - 10, - 17, - 25, - 42, - 50, - 100, - 250, - 1000, - 9999, - i32::MAX, -]; diff --git a/tests/sqlx/src/fixtures/eql_v2_int2.rs b/tests/sqlx/src/fixtures/eql_v2_int2.rs index 0848f85e..661e4475 100644 --- a/tests/sqlx/src/fixtures/eql_v2_int2.rs +++ b/tests/sqlx/src/fixtures/eql_v2_int2.rs @@ -7,6 +7,6 @@ //! no EQL dependency; the `eql_v3.int2` domain is layered on top by casting //! `payload` per query. -use super::int2_values::VALUES; +use eql_scalars::INT2_VALUES as VALUES; crate::scalar_fixture!("eql_v2_int2", i16, VALUES); diff --git a/tests/sqlx/src/fixtures/eql_v2_int4.rs b/tests/sqlx/src/fixtures/eql_v2_int4.rs index fd28b15b..facb69c4 100644 --- a/tests/sqlx/src/fixtures/eql_v2_int4.rs +++ b/tests/sqlx/src/fixtures/eql_v2_int4.rs @@ -6,6 +6,6 @@ //! no EQL dependency; #225 layers the `eql_v3.int4` domain on top by casting //! `payload` per query. -use super::int4_values::VALUES; +use eql_scalars::INT4_VALUES as VALUES; crate::scalar_fixture!("eql_v2_int4", i32, VALUES); diff --git a/tests/sqlx/src/fixtures/int2_values.rs b/tests/sqlx/src/fixtures/int2_values.rs deleted file mode 100644 index a1c0a847..00000000 --- a/tests/sqlx/src/fixtures/int2_values.rs +++ /dev/null @@ -1,30 +0,0 @@ -// AUTOMATICALLY GENERATED FILE. -//! Fixture plaintext values for the int2 encrypted-domain family. -//! -//! Generated from the `int2` row in `eql-scalars::CATALOG` (`fixtures`) — -//! the single source of truth shared by the fixture generator -//! (`fixtures::eql_v2_int2`) and the matrix oracle -//! (`ScalarType::FIXTURE_VALUES`). - -/// Distinct plaintext values present in the `eql_v2_int2` fixture. -pub const VALUES: &[i16] = &[ - i16::MIN, - -30000, - -100, - -1, - 0, - 1, - 2, - 5, - 10, - 17, - 25, - 42, - 50, - 100, - 250, - 1000, - 9999, - 30000, - i16::MAX, -]; diff --git a/tests/sqlx/src/fixtures/int4_values.rs b/tests/sqlx/src/fixtures/int4_values.rs deleted file mode 100644 index d0c31a63..00000000 --- a/tests/sqlx/src/fixtures/int4_values.rs +++ /dev/null @@ -1,28 +0,0 @@ -// AUTOMATICALLY GENERATED FILE. -//! Fixture plaintext values for the int4 encrypted-domain family. -//! -//! Generated from the `int4` row in `eql-scalars::CATALOG` (`fixtures`) — -//! the single source of truth shared by the fixture generator -//! (`fixtures::eql_v2_int4`) and the matrix oracle -//! (`ScalarType::FIXTURE_VALUES`). - -/// Distinct plaintext values present in the `eql_v2_int4` fixture. -pub const VALUES: &[i32] = &[ - i32::MIN, - -100, - -1, - 0, - 1, - 2, - 5, - 10, - 17, - 25, - 42, - 50, - 100, - 250, - 1000, - 9999, - i32::MAX, -]; diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index f616f556..9a78189e 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -26,14 +26,9 @@ pub mod cipherstash; pub mod driver; -/// Generated from the `int4` row in `eql-scalars::CATALOG` (`fixtures`). -/// Committed and verified by CI; never hand-edit (regenerated by `eql-codegen`). -pub mod int4_values; - +/// Scalar fixtures read their plaintext value lists directly from the catalog +/// (`eql_scalars::INT4_VALUES` / `INT2_VALUES`) — see `scalar_fixture!`. There +/// is no generated `_values.rs` module any more. pub mod eql_v2_int4; -/// Generated from the `int2` row in `eql-scalars::CATALOG` (`fixtures`). -/// Committed and verified by CI; never hand-edit (regenerated by `eql-codegen`). -pub mod int2_values; - pub mod eql_v2_int2; diff --git a/tests/sqlx/src/fixtures/scalar_fixture.rs b/tests/sqlx/src/fixtures/scalar_fixture.rs index 2394b049..1232b8b2 100644 --- a/tests/sqlx/src/fixtures/scalar_fixture.rs +++ b/tests/sqlx/src/fixtures/scalar_fixture.rs @@ -16,7 +16,7 @@ /// - `$name` — the fixture name (`"eql_v2_int2"`), drives every derived path. /// - `$ty` — the Rust plaintext type (`i16`); `<$ty>::MIN`/`MAX` supply the /// signed-extreme assertions. -/// - `$values` — the generated value const (`int2_values::VALUES`). +/// - `$values` — the catalog-materialised value const (`eql_scalars::INT2_VALUES`). /// /// Indexes are fixed to `Unique` (HMAC, drives `=` / `<>`) and `Ore` (ORE /// block terms, drives `<` `<=` `>` `>=`) with a committed `jsonb` payload — diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index c3acc428..39f5c079 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -74,20 +74,20 @@ pub trait ScalarType: impl ScalarType for i32 { const PG_TYPE: &'static str = "int4"; - /// Single-sourced from `tasks/codegen/types/int4.toml` `[fixture] values` - /// via the generated `fixtures::int4_values::VALUES` const — the same list - /// the fixture generator encrypts, so the oracle cannot drift from the - /// fixture. Spans the negative boundary, the i32 signed extremes, and zero. - const FIXTURE_VALUES: &'static [i32] = crate::fixtures::int4_values::VALUES; + /// Single-sourced from the `int4` row in `eql-scalars::CATALOG` + /// (`eql_scalars::INT4_VALUES`, materialised from its `Fixture` list) — the + /// same list the fixture generator encrypts, so the oracle cannot drift from + /// the fixture. Spans the negative boundary, the i32 signed extremes, and zero. + const FIXTURE_VALUES: &'static [i32] = eql_scalars::INT4_VALUES; } impl ScalarType for i16 { const PG_TYPE: &'static str = "int2"; - /// Single-sourced from `tasks/codegen/types/int2.toml` `[fixture] values` - /// via the generated `fixtures::int2_values::VALUES` const — the same list - /// the fixture generator encrypts, so the oracle cannot drift from the - /// fixture. Spans the negative boundary, the i16 signed extremes, and zero. - const FIXTURE_VALUES: &'static [i16] = crate::fixtures::int2_values::VALUES; + /// Single-sourced from the `int2` row in `eql-scalars::CATALOG` + /// (`eql_scalars::INT2_VALUES`, materialised from its `Fixture` list) — the + /// same list the fixture generator encrypts, so the oracle cannot drift from + /// the fixture. Spans the negative boundary, the i16 signed extremes, and zero. + const FIXTURE_VALUES: &'static [i16] = eql_scalars::INT2_VALUES; } /// Per-domain capability + payload shape. Storage carries no terms, `Eq` From 5006d58d26902587b702b2cb731780babd6f8747 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 10:06:50 +1000 Subject: [PATCH 56/93] docs(codegen): align generator docs with Rust eql-codegen cutover Rewrite encrypted-domain-generator.md and the int4 golden-reference README to describe the Rust eql-codegen toolchain, replacing stale references to the retired Python codegen (TOML manifests, terms.py/templates.py, the test_against_reference.py byte-oracle, the codegen:domain tasks, and committed _values.rs fixtures). Source of truth is now the eql-scalars CATALOG; SQL is rendered via minijinja templates; parity is the byte-for-byte int4 golden gate (file-set guard + diff) in codegen-parity.sh, with no values.rs check. Also fix the codegen:parity mise task description, which still said "(normalized) + committed values.rs". --- docs/reference/encrypted-domain-generator.md | 702 ++++++++++--------- mise.toml | 2 +- tests/codegen/reference/README.md | 22 +- 3 files changed, 403 insertions(+), 323 deletions(-) diff --git a/docs/reference/encrypted-domain-generator.md b/docs/reference/encrypted-domain-generator.md index 9b2d6a07..770cbeaf 100644 --- a/docs/reference/encrypted-domain-generator.md +++ b/docs/reference/encrypted-domain-generator.md @@ -1,14 +1,22 @@ # Encrypted-Domain Code Generator -How `tasks/codegen/` turns a TOML manifest into the SQL surface for a -scalar encrypted-domain type. This document describes the generator -itself — its inputs, stages, outputs, and the invariants it enforces. -The contract those outputs must satisfy is in +How the Rust `eql-codegen` crate turns the `eql-scalars` catalog into the +SQL surface for a scalar encrypted-domain type. This document describes +the generator itself — its inputs, stages, outputs, and the invariants it +enforces. The contract those outputs must satisfy is in [`encrypted-domain-implementation-spec.md`](./encrypted-domain-implementation-spec.md); this file describes the machine that produces them. -The reference type is `eql_v3.int4` (PR #239). `text` and `jsonb` are -outside scope. +The reference type is `eql_v3.int4`. `text` and `jsonb` are outside scope. + +The generator is **Rust, not Python**. There is no TOML manifest, no +`tasks/codegen/` package, no `terms.py`/`templates.py`/`spec.py`. The +source of truth is the `CATALOG` const in +[`crates/eql-scalars/src/lib.rs`](../../crates/eql-scalars/src/lib.rs); +the renderers live in [`crates/eql-codegen/`](../../crates/eql-codegen/). +Adding a scalar type is adding a `ScalarSpec` row to `CATALOG`, validated +by the compiler plus catalog `#[test]`s — never an edit to free-form +manifest data. ## 1. Why a generator @@ -16,198 +24,230 @@ A single scalar encrypted-domain type emits several hundred SQL declarations across eleven files: four domains, three extractors, dozens of comparison wrappers and blockers, 176 `CREATE OPERATOR` statements (44 per domain), and MIN/MAX aggregates for every ordered domain. The shape -is mechanical and -the invariants are unforgiving — a `STRICT` blocker silently bypasses -its exception, a pinned `search_path` disables inlining and reverts -queries to seq scans. The generator exists so each new scalar type adds -one TOML file rather than ninety hand-written declarations that must -agree with each other and with `pin_search_path.sql`, +is mechanical and the invariants are unforgiving — a `STRICT` blocker +silently bypasses its exception, a pinned `search_path` disables inlining +and reverts queries to seq scans. The generator exists so each new scalar +type adds one `CATALOG` row rather than ninety hand-written declarations +that must agree with each other and with `pin_search_path.sql`, `tasks/test/splinter.sh`, and `src/encrypted_domain/functions.sql`. ## 2. Pipeline -`tasks/codegen/` is a small Python package. Entry point: -`python -m tasks.codegen.generate `, wrapped by -`mise run codegen:domain ` (`tasks/codegen/domain.sh:10`). -`tasks/build.sh` invokes the same entry point for every manifest at -the start of every `mise run build`, so the generated SQL is never -checked in — the TOML manifest is the source of truth. - -Stages, in order: - -1. **Load manifest** — `spec.load_spec(toml_path)` reads - `tasks/codegen/types/.toml`, validates the `[domain]` table, - validates the token and every domain name as SQL identifiers - (`_SQL_IDENTIFIER`, `spec.py:12`), checks each domain name starts with the - filename token, resolves every listed term against `terms.TERM_CATALOG`, - and parses the optional `[fixture]` table (`_load_fixture_values`, - `spec.py:36`). Returns a `TypeSpec` (`tasks/codegen/spec.py:98`). -2. **Resolve terms** — for each `DomainSpec`, `terms.require_terms` - maps catalog names (`hm`, `ore`) to `Term` records carrying the - extractor name, return type, JSON envelope key, supported - operators, and the SQL `-- REQUIRE:` edges those terms imply - (`tasks/codegen/terms.py:57-88`). -3. **Render** — `generate.render_types_file`, - `generate.render_functions_file`, `generate.render_operators_file`, - and `generate.render_aggregates_file` (the last only for ordered - domains) build SQL strings via the per-construct functions in - `templates.py`; when the manifest declares a `[fixture]` table, - `templates.render_fixture_values_rs` also renders the committed Rust - value const. No template engine — plain f-strings, with the structural - shape of each declaration encoded in code (`tasks/codegen/generate.py`). -4. **Write** — `writer.write_generated_file` prefixes every SQL output with - the `AUTO-GENERATED — DO NOT EDIT` header (`templates.py:13-17`) and - refuses to overwrite any pre-existing file that lacks that marker - (`tasks/codegen/writer.py:67`). The committed Rust value const is written - by `writer.write_generated_rs` (`writer.py:78`) with its own Rust - `AUTO-GENERATED` header. `generate_type` cleans stale generated files in - the target directory before rewriting so an abandoned domain disappears on - the next regeneration (`generate.py:221`). - -There is no caching layer, no incremental mode, and no rewriting of -hand-written files. Each invocation regenerates every output for one -type from a single manifest. - -## 3. Manifest format - -```toml -[domain] -int4 = [] -int4_eq = ["hm"] -int4_ord_ore = ["ore"] -int4_ord = ["ore"] +`eql-codegen` is a small Rust crate with a binary entry point. The +generator runs as `cargo run -p eql-codegen` (no subcommand), which calls +`generate::generate_all` (`crates/eql-codegen/src/generate.rs`) over every +row of `eql_scalars::CATALOG`, writing each type's SQL into +`src/encrypted_domain//`. A second subcommand, +`cargo run -p eql-codegen -- list-types`, prints the catalog tokens one per +line (consumed by the fixture and matrix-inventory enumeration). The +binary's `main` (`crates/eql-codegen/src/main.rs`) recognises exactly these +two forms; any other argument is a usage error. + +`tasks/build.sh` runs `cargo run -p eql-codegen` at the start of every +`mise run build`, so the generated SQL is never checked in — the catalog +is the source of truth. (The build first sweeps every generated +`*_{types,functions,operators,aggregates}.sql` under `src/encrypted_domain` +so a type removed from `CATALOG` cannot leave orphans the `src/**/*.sql` +build glob would pick up; hand-written `*_extensions.sql` is preserved by +the name patterns.) + +Stages, in order (`generate_all` → `generate_type`): + +1. **Read the catalog.** `eql_scalars::CATALOG` is the in-binary source of + truth — a `&[ScalarSpec]`, each row a `token`, a `ScalarKind`, an + ordered `&[DomainSpec]`, and a `&[Fixture]` list + (`crates/eql-scalars/src/lib.rs`). There is no parse/validate stage at + generation time: the catalog is validated at compile time (an undefined + `Term` or unknown `ScalarKind` does not compile) and by the catalog + `#[test]`s, so by the time `generate_all` runs the data is already + well-formed. +2. **Resolve terms.** For each `DomainSpec`, the `Term` enum's `impl` + methods supply the extractor name, return type, JSON envelope key, + supported operators, and the SQL `-- REQUIRE:` edges those terms imply + (`Term::operators_for_terms`, `term_json_keys`, `term_requires`, + `extractor_for_operator`, `role_for_terms` — `crates/eql-scalars/src/lib.rs`). +3. **Render.** `render_types_file`, `render_functions_file`, + `render_operators_file`, and `render_aggregates_file` (the last only for + ordered domains) build the context structs in + `crates/eql-codegen/src/context.rs` and render them through embedded + **minijinja** templates (`crates/eql-codegen/templates/*.j2`, + compiled in via `include_str!` — no runtime file IO). The structural + shape of each declaration is split between the context builders (Rust) + and the templates (Jinja). +4. **Write.** `clean_generated_files` first deletes every generated `.sql` + in the target directory (recognised by the header marker) so an + abandoned domain disappears on the next regeneration; + `ensure_generated_paths_writable` then refuses to proceed if any target + path is a hand-written file lacking the marker; `write_generated_file` + writes each rendered body verbatim (`crates/eql-codegen/src/writer.rs`). + The template emits the `-- AUTOMATICALLY GENERATED FILE.` marker as its + own first line, so the writer does not prepend a header — it only uses + the marker to recognise files it owns. + +There is no caching layer and no incremental mode. Each `cargo run -p +eql-codegen` regenerates every output for every catalog type from scratch. +Regeneration is deterministic: identical catalog + renderers produce +byte-identical SQL. + +## 3. Catalog format + +A scalar type is one `ScalarSpec` row +(`crates/eql-scalars/src/lib.rs`): + +```rust +ScalarSpec { + token: "int4", + kind: ScalarKind::I32, + domains: &[ + DomainSpec { suffix: "", terms: &[] }, + DomainSpec { suffix: "_eq", terms: &[Term::Hm] }, + DomainSpec { suffix: "_ord_ore", terms: &[Term::Ore] }, + DomainSpec { suffix: "_ord", terms: &[Term::Ore] }, + ], + fixtures: INT4_FIXTURES, +} ``` -Rules enforced by `spec.load_spec`: - -- The filename stem is the **type token** (`int4` here). It must match - the CLI argument and prefix every domain name. -- The TOML must have a non-empty `[domain]` table at the top level. The - only other recognised top-level key is the optional `[fixture]` table - (see §3a). -- The filename token and every domain key must be valid lowercase SQL - identifiers (`^[a-z][a-z0-9_]*$`); anything else raises `SpecError`. -- Each domain key must equal the token or start with `_`. -- Each value must be a list of strings, and each string must be a key - in `terms.TERM_CATALOG`. Unknown terms raise `SpecError`. - -The `[domain]` table declares nothing else — no extractor names, no -operator lists, no REQUIRE edges. Every behavioural fact comes from the -term catalog. +Structural rules, enforced by the type system and the catalog `#[test]`s +rather than a runtime validator: + +- `token` supplies the **type token** (`int4` here). Each domain's full + name is `token` + `suffix`; `ScalarSpec::domain_name` makes the old + "domain name must start with the token" rule structural, and + `every_domain_name_starts_with_its_token` pins it. +- `kind` is a `ScalarKind` (`I16` / `I32` / `I64` / `Numeric` / `Text` / + `Jsonb`), which carries the Rust type name, the `MIN`/`MAX`/zero symbols, + and the numeric bounds. Only the integer kinds have an i128 range with + `Min`/`Max`/`Zero` sentinels; the bounded accessors `panic!` on the + others (a misuse guard, gated by `is_int()`). +- `domains` is a non-empty `&[DomainSpec]` (pinned by + `every_type_has_at_least_one_domain`). Each `DomainSpec` is a `suffix` + plus a `&[Term]`; the storage domain is `suffix: ""` with no terms. +- `fixtures` is a `&[Fixture]` (see §3a). + +The `DomainSpec` declares nothing else — no extractor names, no operator +lists, no REQUIRE edges. Every behavioural fact comes from the `Term` +enum. Domains may be **twinned** (`int4_ord` and `int4_ord_ore` both carry -`["ore"]`). The generator emits them as independent domains with -byte-identical SQL modulo type name. Twins exist so callers can choose -a name that documents intent ("ordered, regardless of mechanism" vs -"ordered via ORE block") without committing to one term family in a -future migration. - -Manifest order is significant. The generator iterates domains in their -declared TOML order (`generate.py:48`), and that order shows up in the -generated `_types.sql` `DO` block. - -### 3a. Optional `[fixture]` table - -```toml -[fixture] -values = ["MIN", "-1", "ZERO", "1", "MAX"] +`&[Term::Ore]`). The generator emits them as independent domains with +byte-identical SQL modulo type name (`ordered_files_byte_identical_modulo_typename`). +Twins exist so callers can choose a name that documents intent ("ordered, +regardless of mechanism" vs "ordered via ORE block") without committing to +one term family in a future migration. + +Catalog order is significant. The generator iterates `CATALOG` in order +(driving generation order), and iterates each spec's `domains` slice in +order — that order shows up in the generated `_types.sql` `DO` block. + +### 3a. The `fixtures` field + +The `fixtures` field is an ordered `&[Fixture]` — the single source of +truth for the type's plaintext fixture list, consumed by the SQLx fixture +generator and the matrix oracle. A `Fixture` is value-kind tagged: +`Min` / `Max` / `Zero` (the integer matrix pivots, resolved per-kind), +`Int(i128)` (an integer literal), and `Numeric`/`Text`/`Jsonb` string +variants. The `fixtures!` macro range-checks each `Int` literal against the +kind at compile time (`N(-40000)` for an `i16` kind does not compile): + +```rust +const INT4_FIXTURES: &[Fixture] = fixtures!(int i32; + Min, N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), N(25), + N(42), N(50), N(100), N(250), N(1000), N(9999), Max); ``` -A type may declare an ordered `[fixture] values` list — the single source -of truth for the committed Rust const -`tests/sqlx/src/fixtures/_values.rs`, consumed by the SQLx fixture -generator and the matrix oracle. `_load_fixture_values` (`spec.py:36`) -requires a non-empty list of string tokens; each resolves through the -scalar-kind catalog (`scalars.py`) — the sentinels `MIN` / `MAX` / `ZERO` -plus any numeric literal in the type's representable range. Validation -enforces a **distinct-plaintext contract**: duplicates are rejected against -the *resolved numeric* value, so both copy-paste token dups (`"1", "1"`) and -sentinel/literal aliases (`"MIN"` alongside the same number) raise -`SpecError` — and the set **must include MIN, MAX, and zero** (the matrix -comparison pivots). Unlike the gitignored SQL surface, `_values.rs` -**is committed** (its rendering is deterministic), and CI regenerates it and -runs `git diff --exit-code` to catch an un-regenerated manifest edit. See -implementation spec §9 for the authoring guidance. +Catalog `#[test]`s enforce a **distinct-plaintext contract** plus the +matrix-pivot requirement: `fixture_values_are_distinct_by_resolved_number` +rejects duplicates against the resolved value (so both copy-paste dups and +sentinel/literal aliases fail), `fixtures_include_min_max_and_zero` requires +`Min`, `Max`, and zero for integer kinds, and +`every_fixture_value_is_within_kind_bounds` keeps every resolved value in +range. These are the compile/test-time analogue of the old `load_spec` +validation. + +The plaintext value list is **not** rendered to a generated file. The +`int_values!` macro (next to `CATALOG`) materialises a `Fixture` list into +a typed `pub const _VALUES: &[]` at compile time +(`INT4_VALUES`, `INT2_VALUES`). Both consumers reference that single symbol +— the fixture generator and the matrix oracle's `FIXTURE_VALUES` — so the +oracle cannot drift from the values the generator encrypts. There is no +committed `_values.rs`: a Rust source of truth does not round-trip +through generated Rust. (The old generated, committed file is gone.) The +exact materialised list is pinned by the catalog's `values_tests`. ## 4. Term catalog -`tasks/codegen/terms.py:25-49` defines every term the materializer -recognises. A term is a frozen dataclass: - -```python -Term( - name="hm", # manifest key - json_key="hm", # envelope payload key - extractor="eq_term", # SQL extractor function name - returns="eql_v2.hmac_256", # extractor return type - ctor="hmac_256", # eql_v2 constructor in jsonb - role="eq", # file-header phrasing - operators=("=", "<>"), # operators this term enables - requires=("src/hmac_256/functions.sql",) # SQL REQUIRE edges -) -``` +The `Term` enum (`crates/eql-scalars/src/lib.rs`) defines every term the +materializer recognises. The `json_key`/`extractor`/`returns`/`ctor` +values are the cross-schema SQL contract — changing one is a generated-SQL +behaviour change, not a refactor. -Current catalog: +| Term | JSON key | Extractor | Returns | Operators | +| ----- | -------- | ----------- | -------------------------------- | -------------------------- | +| `Hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` `<>` | +| `Ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` `<>` `<` `<=` `>` `>=` | -| Term | JSON key | Extractor | Returns | Operators | -| ----- | -------- | ----------- | -------------------------------- | ---------------------------------- | -| `hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` `<>` | -| `ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` `<>` `<` `<=` `>` `>=` | +The index-term return types (`eql_v2.hmac_256`, +`eql_v2.ore_block_u64_8_256`) live in `eql_v2` and are referenced +cross-schema; the domains, extractors, and wrappers live in `eql_v3`. -Adding a term is a code change to `terms.py` with matching tests in -`test_terms.py` — never a free-form manifest field. The catalog is the -only source of operator support, extractor identity, and REQUIRE edges; -the manifest is a thin selector over it. +Adding a term is a code change to the `Term` enum's `impl` methods +(`json_key`, `extractor`, `returns`, `ctor`, `role`, `operators`, +`requires`) with matching `#[test]`s (`term_tests` / `term_helper_tests`) +— never a free-form catalog field. The `Term` enum is the only source of +operator support, extractor identity, and REQUIRE edges; a `DomainSpec` is +a thin selector over it. ## 5. The operator surface -`tasks/codegen/operator_surface.py` enumerates the surface every generated -domain declares: - -- **Supported-capable comparisons**: `=` `<>` `<` `<=` `>` `>=` `@>` `<@` -- **Path blockers**: `->` `->>` -- **Native `jsonb` fallback blockers**: `?` `?|` `?&` `@?` `@@` `#>` `#>>` `-` `#-` `||` - -Comparison and path operators keep the historical three-argument shapes: - -- Symmetric: `(domain, domain)`, `(domain, jsonb)`, `(jsonb, domain)` -- Path: `(domain, text)`, `(domain, integer)`, `(jsonb, domain)` - -Native `jsonb` fallback blockers use only the shapes PostgreSQL exposes -for `jsonb` itself, for a total of **44 `CREATE OPERATOR` statements per -domain**. Supported operators are emitted with full planner metadata -(`COMMUTATOR`, `NEGATOR`, `RESTRICT`, `JOIN` selectivity estimators) and -back onto inlinable wrappers; unsupported operators carry minimal metadata -and back onto blockers. - -Path operators always back onto blockers — neither current term -enables them. The additional native `jsonb` operators are blocker-only. -Untyped string literals are a PostgreSQL resolver edge: `? 'c'` can still -select the built-in `jsonb` operator, while `? 'c'::text` and bound text -parameters select the generated blocker. - -The union of these three lists is `KNOWN_JSONB_OPERATORS`. A live-DB -structural guard +`crates/eql-codegen/src/operator_surface.rs` enumerates the 20-operator +surface every generated domain declares (`OPERATORS`): + +- **Comparison operators**: `=` `<>` `<` `<=` `>` `>=` `@>` `<@` +- **Path-selector operators**: `->` `->>` +- **Native `jsonb` operators**: `?` `?|` `?&` `@?` `@@` `#>` `#>>` `-` `#-` `||` + +Each operator carries its PostgreSQL-shaped signatures. The comparison +operators use the three symmetric shapes — `(domain, domain)`, +`(domain, jsonb)`, `(jsonb, domain)`; the path and native operators use +only the shapes PostgreSQL exposes for `jsonb` itself. Summed across all +20 operators, that is **44 `CREATE OPERATOR` statements per domain** +(`operators_file_has_forty_four`). + +Whether an operator routes to a wrapper or a blocker is a per-domain +decision driven by the domain's terms (`Term::operators_for_terms`), not a +property of the operator. Supported operators are emitted with full planner +metadata (`COMMUTATOR`, `NEGATOR`, `RESTRICT`, `JOIN` selectivity +estimators) and back onto inlinable wrappers; unsupported operators carry +minimal metadata and back onto blockers (`operator_entry` only renders +metadata when the operator is supported on that domain). + +Path operators always back onto blockers — neither current term enables +them. The native `jsonb` operators are blocker-only. Untyped string +literals are a PostgreSQL resolver edge: `? 'c'` can still select the +built-in `jsonb` operator, while `? 'c'::text` and bound text parameters +select the generated blocker. + +A live-DB structural guard (`tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs`) -queries `pg_operator` for every operator with a `jsonb` argument and asserts -the set is a subset of this union, so a future PostgreSQL version that adds a -`jsonb` operator nobody enumerated here fails the test rather than silently -routing an encrypted column to native plaintext-`jsonb` semantics. -`test_operator_surface.py` pins the Python union; the Rust test mirrors it. +queries `pg_operator` for every operator with a `jsonb` argument and +asserts the set is a subset of the surface this module enumerates, so a +future PostgreSQL version that adds a `jsonb` operator nobody enumerated +here fails the test rather than silently routing an encrypted column to +native plaintext-`jsonb` semantics. The `operator_surface` unit tests pin +the Rust surface (20 operators, signatures, metadata); the live-DB test +mirrors it. ## 6. Generated outputs -For a manifest with `D` domains of which `A` are ordered (ord-capable), -the generator writes `1 + 2D + A` SQL files into -`src/encrypted_domain//`, plus — when the manifest carries a -`[fixture]` table — one committed Rust const at -`tests/sqlx/src/fixtures/_values.rs`. For `int4` (`D = 4`, `A = 2`): -eleven SQL files and one Rust file. The SQL outputs are gitignored — `tasks/build.sh` regenerates them at the -start of every build from each `tasks/codegen/types/.toml`, -`mise run codegen:domain ` refreshes a single type manually, and -`mise run codegen:domain:all` regenerates every type in one invocation (the -same `generate.py --all` enumeration the build uses). The manifest plus -`tasks/codegen/terms.py` are the source of truth. +For a type with `D` domains of which `A` are ordered (ord-capable), the +generator writes `1 + 2D + A` SQL files into +`src/encrypted_domain//`. For `int4` (`D = 4`, `A = 2`): eleven SQL +files. The SQL outputs are **gitignored** — +`.gitignore` excludes `src/encrypted_domain/*/*_{types,functions,operators,aggregates}.sql`, +and `tasks/build.sh` regenerates them at the start of every build. There is +**no per-type codegen task**: one `cargo run -p eql-codegen` regenerates +every catalog type in a single deterministic run. | File | Content | | --------------------------------- | ---------------------------------------------------------------------------------------- | @@ -218,29 +258,30 @@ same `generate.py --all` enumeration the build uses). The manifest plus Every file: -- Opens with the `AUTO-GENERATED — DO NOT EDIT` header - (`templates.py:13-17`). +- Opens with the `-- AUTOMATICALLY GENERATED FILE.` marker (the project-wide + marker `docs:validate` greps on to skip generated SQL — + `crates/eql-codegen/src/consts.rs`). - Declares its `-- REQUIRE:` edges in dependency order — types files - require `src/schema.sql`; function files require schema, types, and + require `src/schema-v3.sql`; function files require schema, types, and `src/encrypted_domain/functions.sql` plus each term's `requires` set; - operator files require schema, types, and their domain's function - file; aggregate files require schema, types, and their domain's - function and operator files. -- Carries Doxygen `--! @file` / `--! @brief` headers describing its - role. + operator files require `src/schema-v3.sql`, types, and their domain's + function file; aggregate files require `src/schema-v3.sql`, types, and + their domain's function and operator files. +- Carries Doxygen `--! @file` / `--! @brief` headers describing its role. ### Function-count totals per domain -| Domain terms | Extractors | Wrappers | Blockers | Functions | Operators | -| ------------ | ---------: | -------: | -------: | --------: | --------: | -| none | 0 | 0 | 44 | 44 | 44 | -| `["hm"]` | 1 | 6 | 38 | 45 | 44 | -| `["ore"]` | 1 | 18 | 26 | 45 | 44 | +| Domain terms | Extractors | Wrappers | Blockers | Functions | Operators | +| ---------------- | ---------: | -------: | -------: | --------: | --------: | +| none | 0 | 0 | 44 | 44 | 44 | +| `&[Term::Hm]` | 1 | 6 | 38 | 45 | 44 | +| `&[Term::Ore]` | 1 | 18 | 26 | 45 | 44 | -Six wrappers for `hm` = `=` and `<>` × three shapes. Eighteen for `ore` +Six wrappers for `Hm` = `=` and `<>` × three shapes. Eighteen for `Ore` = six operators × three shapes. The 44-operator total never moves; the wrapper/blocker split is what shifts, and native `jsonb` fallback -operators are always blockers. +operators are always blockers. (Pinned by `storage_functions_file_is_all_blockers`, +`eq_functions_file_counts`, `ore_functions_file_counts`.) The table above covers `_functions.sql` only. Ordered domains additionally emit `_aggregates.sql` — two state functions @@ -252,67 +293,74 @@ parallel aggregation on large `GROUP BY` ORE workloads with no decryption. ## 7. Invariants the generator enforces -The generator's job is partly to write SQL and partly to make -incorrect SQL unreachable. Invariants encoded in code: - -- **Blockers are never `STRICT`.** `render_blocker_bool`, - `render_blocker_path`, and `render_blocker_native` emit - `IMMUTABLE PARALLEL SAFE` without the - `STRICT` qualifier (`templates.py:263-345`), so a `NULL` - argument still reaches the `RAISE` and the unsupported-operator - exception fires. There is no code path that produces a strict - blocker. -- **Wrappers are inlinable SQL.** `render_wrapper` and - `render_extractor` emit `LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE` - with a single-statement `SELECT` and no `SET search_path` - (`templates.py:218-260`). `pin_search_path.sql:265-290` - catches them structurally and leaves them unpinned. -- **Aggregate state functions are the deliberate exception.** - `render_aggregate` emits `min_sfunc` / `max_sfunc` as - `LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE` *with* a pinned - `SET search_path` (`templates.py:379-452`). They are aggregate transition - functions, not index expressions, so pinning is correct; the generated - `min` / `max` aggregates are allowlisted by name in `splinter.sh`. The - aggregates are `parallel = safe` with the sfunc reused as `combinefunc`. +The generator's job is partly to write SQL and partly to make incorrect +SQL unreachable. Invariants encoded in the renderers / templates and +guarded by `#[test]`s in `crates/eql-codegen/src/generate.rs`: + +- **Blockers are never `STRICT` and always `plpgsql`.** The + unsupported-operator template emits each blocker as `IMMUTABLE PARALLEL + SAFE` / `LANGUAGE plpgsql` without `STRICT`, so a `NULL` argument still + reaches the `RAISE`. `blockers_are_never_strict_and_always_plpgsql` + asserts the storage domain (all blockers) contains no `STRICT` and as + many `LANGUAGE plpgsql` as `CREATE FUNCTION`. A `LANGUAGE sql` blocker + would be inlinable and could be elided when the result is provably + unused; `plpgsql` is opaque to the planner so the `RAISE` survives. +- **Wrappers and extractors are inlinable SQL.** They emit `LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE` with a single-statement `SELECT` and **no + `SET search_path`** (`inlinable_functions_have_no_set_search_path`). A + pinned `search_path` disables inlining. `tasks/pin_search_path.sql` + recognises these functions structurally — by language (`sql`), volatility + (`IMMUTABLE`), and a jsonb-backed `DOMAIN` argument in the `eql_v3` + schema — and leaves them unpinned, with no per-type edit. +- **Aggregate state functions are the deliberate exception.** `min_sfunc` / + `max_sfunc` are `LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE` *with* + a pinned `SET search_path` (`aggregate_state_functions_are_plpgsql_not_inlinable`). + They are aggregate transition functions, not index expressions, so + pinning is correct; the generated `min` / `max` aggregates are + allowlisted by name in `splinter.sh`. - **SQL-literal injection is structurally prevented.** Every string interpolated into a single-quoted SQL literal — payload keys, operator - symbols, domain names in `RAISE` messages — passes through `_sql_str` - (`templates.py:46`), which doubles embedded single quotes. Today's catalog - strings are all quote-free so it is a no-op, but it guarantees a future - quote-bearing catalog string cannot break out of its literal. + symbols, domain names in `RAISE` messages — passes through `sql_str` + (`crates/eql-codegen/src/consts.rs`), which doubles embedded single + quotes. Today's catalog strings are all quote-free so it is a no-op, but + it guarantees a future quote-bearing string cannot break out of its + literal (`unsupported_entry_preserves_operator_literal_and_domain_lit_is_escaped`, + `domain_block_escapes_quote_bearing_name`). - **No domain-over-domain.** Every domain is `CREATE DOMAIN eql_v3. - AS jsonb`, never `AS ` (`templates.py:72`). PostgreSQL - resolves operators against the underlying base type; a derived domain - would silently bypass the fixed operator surface. -- **No operator class on a domain.** The generator emits operators, - not operator classes. Callers index through the extractor function - (e.g. `USING btree (eql_v3.ord_term(col))`), whose return type - already carries a default opclass. -- **Ownership boundary.** `writer.is_generated` recognises owned files - by their header line and refuses to overwrite anything else - (`writer.py:20-26`, `44-53`). A hand-written file at a generated - path is a hard error, not a silent clobber. Stale generated files - for removed domains are cleaned before the new files land - (`writer.py:29-41`). + AS jsonb`, never `AS ` (`types_file_has_all_four_domains`). + PostgreSQL resolves operators against the underlying base type; a derived + domain would silently bypass the fixed operator surface. +- **No operator class on a domain.** The generator emits operators, not + operator classes. Callers index through the extractor function (e.g. + `USING btree (eql_v3.ord_term(col))`), whose return type already carries + a default opclass. +- **Ownership boundary.** `is_generated` recognises owned files by their + header marker; `ensure_generated_paths_writable` refuses to overwrite + anything else, and `clean_generated_files` deletes only files carrying + the marker (`crates/eql-codegen/src/writer.rs`). A hand-written file at a + generated path is a hard error, not a silent clobber. Stale generated + files for removed domains are cleaned before the new files land. ## 8. Extension files -`_extensions.sql` is the hand-written sibling. The generator -never creates, lists, or cleans it; it has no auto-generated header -and must declare its own `-- REQUIRE:` edges. Use it for behaviour -that's specific to the type and not part of the fixed surface — e.g. -cross-domain casts, helper functions, type-specific constraints. +`_extensions.sql` is the hand-written sibling. The generator never +creates, lists, or cleans it; it has no auto-generated header and must +declare its own `-- REQUIRE:` edges. Use it for behaviour that's specific +to the type and not part of the fixed surface — e.g. cross-domain casts, +helper functions, type-specific constraints. Unlike the generated +siblings, `_extensions.sql` IS committed. (Neither `int4` nor `int2` +ships one today — there is no committed `*_extensions.sql` in the tree.) -`pin_search_path.sql:291-302` describes the fallback marker for -inline-critical extension functions that take no domain argument and -so escape the structural skip: +`tasks/pin_search_path.sql` describes the fallback marker for +inline-critical extension functions that take no domain argument and so +escape the structural skip: ```sql COMMENT ON FUNCTION eql_v2.my_helper(...) IS 'eql-inline-critical: ...'; ``` -The generator does **not** emit this marker; every function it -produces takes a domain argument and is covered by the structural skip +The generator does **not** emit this marker; every function it produces +takes a domain argument and is covered by the structural skip intrinsically. ## 9. Lint and test integration @@ -320,90 +368,116 @@ intrinsically. The generator depends on two pieces of build tooling recognising its output without per-type edits: -- **`tasks/pin_search_path.sql:265-290`** — structural skip identifies - encrypted-domain functions by language (`sql`), volatility - (`IMMUTABLE`), and the presence of at least one argument typed as a - jsonb-backed `DOMAIN` in the `eql_v3` schema. New scalar types - need no edit here. +- **`tasks/pin_search_path.sql`** — structural skip identifies + encrypted-domain functions by language (`sql`), volatility (`IMMUTABLE`), + and the presence of at least one argument typed as a jsonb-backed + `DOMAIN` in the `eql_v3` schema. New scalar types need no edit here. - **`tasks/test/splinter.sh`** — name-based allowlist. The converged - wrapper names (`eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `eq_term`, - `ord_term`) are already covered by entries originally added for - `ste_vec_entry` and friends (`splinter.sh:87-104`). Splinter matches - by name only, so a new scalar type that uses the catalog extractors - inherits coverage. Adding a new term whose extractor has a new name - requires a splinter entry. + wrapper / extractor names (`eq`, `neq`, `lt`, `lte`, `gt`, `gte`, + `eq_term`, `ord_term`) plus the generated `min` / `max` aggregates are + covered by `eql_v3`-schema entries. Splinter matches by name only, so a + new scalar type that uses the catalog extractors inherits coverage. + Adding a new term whose extractor has a new name requires a splinter + entry. ## 10. Tests -`mise run test:codegen` runs the generator test suite — `pytest -tasks/codegen` — with no database required: - -- `test_spec.py`, `test_terms.py`, `test_scalars.py`, - `test_operator_surface.py`, `test_templates.py`, `test_writer.py` — unit - tests per module. -- `test_generate.py` — end-to-end rendering tests asserting file - counts and structural shape. -- `test_against_reference.py` — byte-for-byte match of in-memory - `render_*_file` output against a hand-reviewed (header-stripped) - reference under `tests/codegen/reference/int4/`. Runs anywhere - without depending on materialised `src/encrypted_domain//`. The - reference fixture is the human-readable contract that survives - generator refactors. - -The codegen suite is a prerequisite of the PostgreSQL test matrix -(`tasks/test.sh`), so generated-SQL drift fails CI before any database +The generator's tests are Rust, run by `mise run test:codegen` +(`cargo test -p eql-scalars -p eql-codegen`) — no database required. The +broader `mise run test:crates` adds `cargo clippy ... -D warnings`. + +- **`eql-scalars` unit tests** — `rust_tests`, `term_tests`, + `term_helper_tests`, `fixture_tests`, `catalog_tests`, `invariant_tests`, + `values_tests` over `CATALOG`, the `Term`/`ScalarKind`/`Fixture` impls, + and the materialised `_VALUES` consts + (`crates/eql-scalars/src/lib.rs`). +- **`eql-codegen` unit tests** — file counts, language/volatility + invariants, escaping guards, and twin byte-identity + (`crates/eql-codegen/src/generate.rs` `#[cfg(test)]` module). +- **The parity gate** — `mise run codegen:parity` + (`tasks/codegen-parity.sh`). It runs `cargo run -p eql-codegen` into the + real tree, then: + 1. compares the int4 generated SQL **file set** against the golden under + `tests/codegen/reference/int4/*.sql`, excluding committed hand-written + files (`comm -23` of `ls` against `git ls-files`), so an extra or + dropped generated file fails; and + 2. diffs each golden file **byte-for-byte** against its generated + counterpart, after dropping the golden's single leading + `-- REFERENCE:` provenance line (`tail -n +2`). Both bodies start with + the `-- AUTOMATICALLY GENERATED FILE.` marker, so no header strip is + needed. + The same byte-for-byte assertion runs in-crate as + `crates/eql-codegen/tests/parity.rs` (`rust_generator_matches_int4_golden_files`) + and in the `generate.rs` golden tests. The golden reference — not any + Python oracle — is the sole contract that survives generator refactors. + +CI runs these in three jobs in `.github/workflows/test-eql.yml`: the +`test:crates` job (`Rust workspace crates`) compiles/lints/tests the +crates, the `codegen` job (`Encrypted-domain codegen`) runs `mise run +codegen:parity`, and the `matrix-coverage` job runs `mise run +test:matrix:inventory`. The codegen job is a prerequisite of the +PostgreSQL test matrix, so generated-SQL drift fails CI before any database test runs. ## 11. Adding a new scalar type -The end-to-end shape from a generator perspective: - -1. **Author** `tasks/codegen/types/.toml`. Domain names must - start with the token; term names must already exist in - `terms.TERM_CATALOG`. If `` is a new scalar kind, first register - a `ScalarKind` in `scalars.py` — `load_spec` resolves the scalar before - anything else, so an unregistered token raises - `ScalarError: unknown scalar token ''`. -2. **Regenerate**. Either run `mise run codegen:domain ` while - iterating, or just `mise run build` — the build regenerates every - manifest first. The generator cleans stale generated files, writes - new ones, and refuses any hand-written file at a generated path. - Generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` are - gitignored and never committed. -3. **Hand-write** `_extensions.sql` if the type needs SQL - beyond the fixed surface. Add `eql-inline-critical` markers only on - inline-critical helpers that take no domain argument. This file IS +From a generator perspective: + +1. **Add a `ScalarSpec` row to `eql_scalars::CATALOG`** + (`crates/eql-scalars/src/lib.rs`) — `token`, `kind`, the `domains` + slice, and the `fixtures` list. Term names must be `Term` variants and + the kind must be a `ScalarKind` variant, or it does not compile. If the + type needs a new scalar width, add a `ScalarKind` variant (with its + rust-type name, `MIN`/`MAX`/zero symbols, and bounds) and unit-test its + `impl`. New term behaviour belongs in the `Term` enum's `impl`, not in + catalog data. +2. **Materialise the value list** with `int_values!(_VALUES, , + );` next to `CATALOG`, and pin it with a `values_tests` + assertion. This is the single source the SQLx matrix reads as + `FIXTURE_VALUES`. There is nothing to regenerate-and-commit on the test + side — it is a compile-time const, not a generated file. +3. **Regenerate.** `cargo run -p eql-codegen` (or just `mise run build` — + the build runs the generator first). One run regenerates every catalog + type; there is no per-type codegen task. The generated + `*_{types,functions,operators,aggregates}.sql` are gitignored and never committed. -4. **Build picks it up automatically** — `tasks/build.sh` regenerates - before computing the `tsort` graph, so the new files appear in the - dependency walk via the `-- REQUIRE:` edges the generator emits. -5. **Test.** Do **not** add a `tests/codegen/reference//` baseline. - `int4` is the sole golden master for the type-generic generator: the SQL - templates are pure token substitution and the only type-specific rendering - is `_values.rs`, so a per-type baseline can only fail where `int4`'s - already would. Drift protection for the new type comes from the `int4` - reference (shared templates + `terms.py`), the committed `_values.rs` - const guarded by the codegen staleness check, the `` cases in - `test_scalars.py`, and the `ordered_numeric_matrix!` SQLx suite (behaviour, - not bytes). Run `mise run test:codegen`, the relevant SQLx suites, and the - PostgreSQL matrix. -6. **Snapshot the matrix inventory.** Run `mise run test:matrix:inventory` - and commit the new `tests/sqlx/snapshots/_matrix_tests.txt` — the - sorted list of the type's `scalars::::*` test names. CI's - `matrix-coverage` job `git diff --exit-code`s it (like `_values.rs`) - to catch a silently dropped or renamed matrix test. The snapshot is a - committed test baseline, not gitignored generated SQL. See - `tests/sqlx/snapshots/README.md`. - -Adding a new **term** is a bigger move — edit `terms.py`, add tests, -audit `splinter.sh` for a name collision, and update the reference -fixture under `tests/codegen/reference/`. +4. **Hand-write** `_extensions.sql` if the type needs SQL beyond the + fixed surface, with explicit `-- REQUIRE:` edges. This file IS committed. +5. **Do not add a `tests/codegen/reference//` baseline.** `int4` is + the sole golden master for the type-generic generator: the templates are + pure token substitution, so a per-type baseline can only fail where + `int4`'s already would. Drift protection for the new type comes from the + `int4` reference (shared templates + `Term` enum), the catalog + `values_tests` pinning the materialised `_VALUES`, the + catalog/generator `#[test]`s, and the `ordered_numeric_matrix!` SQLx + suite (behaviour, not bytes). +6. **Wire the SQLx matrix oracle and snapshot the inventory.** The + implementation spec §2 lists the hand-maintained registration files. + Then run `mise run test:matrix:inventory`: it normalizes each present + type's `scalars::::*` test-name set to ``, asserts it equals + the single canonical `tests/sqlx/snapshots/matrix_tests.txt`, and + cross-checks the present type set against `cargo run -p eql-codegen -- + list-types`. There is **no per-type snapshot** — the per-type + `_matrix_tests.txt` files were collapsed into one token-normalized + snapshot. You only regenerate `matrix_tests.txt` when the macro's + emitted name set itself changes. A catalog type added without its matrix + wiring fails the cross-check (catalog has the type, binary has no + `scalars::::` tests). See `tests/sqlx/snapshots/README.md` and + the implementation spec §2 / §8. + +Adding a new **term** is a bigger move — edit the `Term` enum's `impl` +methods, add `#[test]`s, audit `splinter.sh` for a name collision if the +extractor name is new, and (because it changes the int4 surface) update the +golden reference under `tests/codegen/reference/int4/`. ## 12. Out of scope -`text` and `jsonb` are not materialised through this generator. There -is no guard preventing a `text.toml` from being authored; the catalog -simply lacks the term shape those types would need. Text and JSONB -encrypted behaviour lives on the composite `eql_v2_encrypted` type and -its hand-written operator surface in `src/encrypted/` and -`src/operators/`, not the scalar materializer. +`text` and `jsonb` are not materialised through this generator. The +`ScalarKind` enum carries `Text`/`Numeric`/`Jsonb` variants and the +`Fixture` enum carries their string-backed shapes at the capability layer, +but `CATALOG` declares only the integer scalars today, so no `text`/`jsonb` +SQL surface is generated. Text and JSONB encrypted behaviour lives on the +composite `eql_v2_encrypted` type and its hand-written operator surface in +`src/encrypted/` and `src/operators/`, not the scalar materializer. +`jsonb` in particular needs a separate SQL design beyond this +ordered-scalar materializer. diff --git a/mise.toml b/mise.toml index d4fabf59..06c13bbc 100644 --- a/mise.toml +++ b/mise.toml @@ -94,7 +94,7 @@ cargo test --test payload_schema_tests """ [tasks."codegen:parity"] -description = "Parity gate: Rust eql-codegen output matches the int4 golden (normalized) + committed values.rs" +description = "Parity gate: Rust eql-codegen output matches the int4 golden (byte-for-byte)" dir = "{{config_root}}" run = "bash tasks/codegen-parity.sh" diff --git a/tests/codegen/reference/README.md b/tests/codegen/reference/README.md index 8a91bf68..ae7204ab 100644 --- a/tests/codegen/reference/README.md +++ b/tests/codegen/reference/README.md @@ -1,21 +1,27 @@ # Codegen reference -The SQL files under `int4/` are the hand-written golden reference for the encrypted-domain scalar generator. `int4` is the **single golden master**: the generator in `crates/eql-codegen` is type-generic — its SQL templates are pure token substitution driven by the `eql-scalars::CATALOG` rows — so one anchored type detects all template/term drift for every current and future scalar. +The SQL files under `int4/` are the hand-maintained golden reference for the encrypted-domain scalar generator, the Rust crate `crates/eql-codegen` (embedded minijinja templates in `crates/eql-codegen/templates/*.j2`). `int4` is the **single golden master**: the generator is type-generic — its templates are pure token substitution driven by the `eql_scalars::CATALOG` rows (`crates/eql-scalars/src/lib.rs`) — so one anchored type detects all template/term drift for every current and future scalar. Each reference file's first line is a `-- REFERENCE:` provenance marker; everything after it is the generated body verbatim, starting with the template-owned `-- AUTOMATICALLY GENERATED FILE.` header. -The parity gate renders the generator's output for `int4` and asserts it matches these files **byte-for-byte** after dropping that single provenance line. It runs three ways, all on the same reference: +The parity gate runs the generator (`cargo run -p eql-codegen`, which writes the real `src/encrypted_domain/int4/` tree) and asserts its output matches these files **byte-for-byte** after dropping that single provenance line. It runs three ways, all on the same reference: -- `crates/eql-codegen/tests/parity.rs` — runs `generate_all` into a temp dir and byte-compares the materialised `int4` SQL surface; -- the in-crate golden tests in `crates/eql-codegen/src/generate.rs` — byte-compare each `render_*_file` output against the corresponding reference; -- `mise run codegen:parity` (`tasks/codegen-parity.sh`) — the CI shell gate, a plain `diff` of `tail -n +2 ` against the regenerated tree. +- `mise run codegen:parity` (`tasks/codegen-parity.sh`) — the CI shell gate. It first compares the generated `int4` SQL *file set* against the golden `*.sql` set (`comm -23` against `git ls-files` excludes the committed, hand-written `int4_extensions.sql`, which has no golden counterpart) to catch extra/dropped files, then `diff`s each golden file against its generated counterpart after `tail -n +2` drops the provenance line. Any whitespace or blank-line drift fails — there is no normalization. +- `crates/eql-codegen/tests/parity.rs` (`rust_generator_matches_int4_golden_files`) — runs `generate_all` into a temp dir and byte-compares the materialised `int4` SQL surface against the same golden. +- the in-crate golden tests in `crates/eql-codegen/src/generate.rs` — byte-compare each `render_*_file` output against the corresponding reference. -If the generator diverges, either it regressed (fix `crates/eql-codegen`) or the reference is being updated deliberately (commit the new `int4` reference in the same PR). Whitespace and blank-line drift now fail the gate — there is no normalization. +The golden reference, not any retired generator, is the sole oracle. If the generator diverges, either it regressed (fix `crates/eql-codegen`) or the reference is being updated deliberately (commit the new `int4` reference in the same PR). + +See `docs/reference/encrypted-domain-generator.md` for the full generator story (manifest-free catalog, templates, term capabilities). + +## No committed fixture values + +Plaintext fixture lists are **not** generated and **not** committed as `_values.rs` files — there are none in the tree. They live in the catalog as `eql_scalars::INT4_VALUES` / `INT2_VALUES`, materialised at compile time by the `int_values!` macro in `crates/eql-scalars/src/lib.rs` from each `CATALOG` row, and pinned by `eql-scalars`'s own `values_tests`. The parity gate only globs `*.sql`; it does not check any `values.rs`. ## New scalar types do not add a reference Adding a scalar type (`int2`, `int8`, …) does **not** add a `tests/codegen/reference//` directory. A per-type baseline would be redundant: the SQL is byte-identical to `int4` modulo the type token, so it can only fail when `int4`'s baseline already would. New types are guaranteed three other ways: - the `int4` reference here anchors the shared generator (templates + the `Term` enum's capability `impl`s in `crates/eql-scalars`); -- the per-type plaintext fixture list (`eql_scalars::INT4_VALUES` / `INT2_VALUES`, materialised from each `CATALOG` row) is pinned by `eql-scalars`'s own `values_tests` — there is no generated `_values.rs` to diff; -- the SQLx `ordered_numeric_matrix!` suite exercises the generated SQL's *behaviour* against a real database — a far stronger guarantee than a byte comparison. +- a catalog row plus the compiler and `eql-scalars`'s `#[test]`/`values_tests` over `CATALOG` validate the new type's spec and materialised value list; +- the SQLx `ordered_numeric_matrix!` suite exercises the generated SQL's *behaviour* against a real database — a far stronger guarantee than a byte comparison — and `mise run test:matrix:inventory` reconciles the matrix test-name set against the single canonical, token-normalized `tests/sqlx/snapshots/matrix_tests.txt` (cross-checked against `eql-codegen list-types`) with no database required. From 7ec279e46ac0670b00c708da0cb3cd80e19217aa Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 10:55:59 +1000 Subject: [PATCH 57/93] fix(ci): pin bash via shebang for strict-mode mise tasks mise runs inline TOML tasks under `sh`, which is dash on the CI runners. `fixture:generate:all` used `set -euo pipefail` and failed there with 'Illegal option -o pipefail' (the task is new on this branch and had never run under CI's dash). `test:matrix:inventory` and `test:matrix:expand` had the same latent bug -- pipefail plus real pipes. Pin bash with a `#!/usr/bin/env bash` shebang as the first line of each strict-mode run block (mise honors it) and standardise on `set -euo pipefail`, so pipefail is portable regardless of the runner's /bin/sh. --- mise.toml | 10 +++++++--- tasks/fixtures.toml | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/mise.toml b/mise.toml index 06c13bbc..7f3e4d9c 100644 --- a/mise.toml +++ b/mise.toml @@ -109,14 +109,16 @@ cargo test -p eql-scalars -p eql-codegen description = "Compile, lint and test the std-only Rust workspace crates (no database)" dir = "{{config_root}}" run = """ +#!/usr/bin/env bash # eql-scalars / eql-codegen are the lean workspace members. Scope explicitly to # them (NOT --workspace): a workspace-wide test would drag in tests/sqlx, whose # suite needs Postgres + CS_* secrets and is already covered by the `test` job. # clippy is likewise scoped — a workspace clippy recompiles the heavy # sqlx/tokio/cipherstash-client tree for no added coverage of these crates. -# `set -eu` only (no pipefail): mise runs tasks under `sh`, which is dash on the -# CI runners, and dash rejects `set -o pipefail`. There are no pipes here. -set -eu +# bash is pinned via the `#!/usr/bin/env bash` shebang above (mise honors a +# `#!` first line), so `set -o pipefail` is available regardless of the runner's +# /bin/sh (dash on the CI images). +set -euo pipefail cargo fmt --check cargo clippy -p eql-scalars -p eql-codegen --all-targets -- -D warnings cargo test -p eql-scalars -p eql-codegen @@ -126,6 +128,7 @@ cargo test -p eql-scalars -p eql-codegen description = "Verify the matrix test-name set against the single canonical snapshot, catalog-cross-checked (no database required)" dir = "{{config_root}}/tests/sqlx" run = """ +#!/usr/bin/env bash # ONE canonical, token-normalized snapshot (snapshots/matrix_tests.txt) pins the # set of macro-emitted matrix test names. The two per-type snapshots are gone: # they were byte-identical modulo the type token, so one canonical set plus a @@ -188,6 +191,7 @@ echo "Matrix inventory OK: ${checked} type(s) match the canonical snapshot; cata description = "Regenerate the int4 matrix cargo-expand snapshot (requires the pinned nightly + cargo-expand)" dir = "{{config_root}}/tests/sqlx" run = """ +#!/usr/bin/env bash # Body-level fidelity backstop for the macro: the expanded source of the int4 # matrix arms. The `cargo +nightly-...` invocation below is the SINGLE source of # the pinned nightly date — .github/workflows/macro-expand-eql.yml greps it from diff --git a/tasks/fixtures.toml b/tasks/fixtures.toml index f24bc684..04b7d5b5 100644 --- a/tasks/fixtures.toml +++ b/tasks/fixtures.toml @@ -19,6 +19,10 @@ description = "Regenerate every scalar SQLx fixture in one process, driven by eq # Must run inside the crate — a workspace member still builds from its own dir. dir = "{{config_root}}/tests/sqlx" run = """ +#!/usr/bin/env bash +# bash is pinned via the `#!/usr/bin/env bash` shebang above (mise honors a `#!` +# first line), so `set -o pipefail` is available regardless of the runner's +# /bin/sh (dash on the CI images). set -euo pipefail cargo test --features fixture-gen --test generate_all_fixtures \ generate_all -- --ignored --exact --nocapture From 5721f13839fe9851079a5384570c15fced76374e Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 10:56:06 +1000 Subject: [PATCH 58/93] docs(reference): rename encrypted-domain spec to task-oriented guide Rename encrypted-domain-implementation-spec.md (and fold in the generator reference) to adding-a-scalar-encrypted-domain-type.md, update all cross-references (CLAUDE.md, eql-functions.md, sql-support.md, tests READMEs), and fill in the PR #252 link in the unreleased CHANGELOG entry. --- CHANGELOG.md | 2 +- CLAUDE.md | 2 +- .../adding-a-scalar-encrypted-domain-type.md | 619 ++++++++++++++++++ docs/reference/encrypted-domain-generator.md | 483 -------------- .../encrypted-domain-implementation-spec.md | 400 ----------- docs/reference/eql-functions.md | 4 +- docs/reference/sql-support.md | 2 +- tests/codegen/reference/README.md | 2 +- tests/sqlx/snapshots/README.md | 7 +- 9 files changed, 629 insertions(+), 892 deletions(-) create mode 100644 docs/reference/adding-a-scalar-encrypted-domain-type.md delete mode 100644 docs/reference/encrypted-domain-generator.md delete mode 100644 docs/reference/encrypted-domain-implementation-spec.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 853e5e81..76700194 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ Each entry that ships in a published release links to the PR that introduced it. ### Changed -- **Scalar encrypted-domain types are now defined in a Rust catalog, not TOML manifests; the Python codegen toolchain is removed.** Adding a scalar encrypted-domain type (`int4`, `int8`, …) is now one row in `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`) instead of authoring `tasks/codegen/types/.toml`. `mise run build` regenerates the gitignored SQL surface via `cargo run -p eql-codegen` (Rust, std-only) rather than the Python generator. The catalog row's `Fixture` list is the single source of truth for that type's plaintext fixtures: the SQLx test matrix reads it directly as a compile-time-materialised const (`eql_scalars::INT4_VALUES` / `INT2_VALUES`, `ScalarType::FIXTURE_VALUES`), so there is no longer a generated, committed `tests/sqlx/src/fixtures/_values.rs` — a Rust source of truth no longer round-trips through generated Rust. The shipped SQL is unchanged — `release/*.sql` is byte-identical across the cutover — so there is no change for callers installing EQL; this only affects contributors who extend the scalar domain families. The `python` mise tool, the `pytest`-based `test:codegen` (now `cargo test -p eql-scalars -p eql-codegen`), the per-type `mise run codegen:domain` tasks, and the per-type `tests/sqlx/snapshots/_matrix_tests.txt` baselines (collapsed into one catalog-reconciled `tests/sqlx/snapshots/matrix_tests.txt`) are gone. Why: a single compiler-validated source of truth shared by the generator and the SQLx test harness, and one fewer toolchain in the build/test path — building and testing EQL no longer needs Python (Python remains only for the separate docs-markdown tooling). ([#PR](https://github.com/cipherstash/encrypt-query-language/pull/PR)) +- **Scalar encrypted-domain types are now defined in a Rust catalog, not TOML manifests; the Python codegen toolchain is removed.** Adding a scalar encrypted-domain type (`int4`, `int8`, …) is now one row in `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`) instead of authoring `tasks/codegen/types/.toml`. `mise run build` regenerates the gitignored SQL surface via `cargo run -p eql-codegen` (Rust, std-only) rather than the Python generator. The catalog row's `Fixture` list is the single source of truth for that type's plaintext fixtures: the SQLx test matrix reads it directly as a compile-time-materialised const (`eql_scalars::INT4_VALUES` / `INT2_VALUES`, `ScalarType::FIXTURE_VALUES`), so there is no longer a generated, committed `tests/sqlx/src/fixtures/_values.rs` — a Rust source of truth no longer round-trips through generated Rust. The shipped SQL is unchanged — `release/*.sql` is byte-identical across the cutover — so there is no change for callers installing EQL; this only affects contributors who extend the scalar domain families. The `python` mise tool, the `pytest`-based `test:codegen` (now `cargo test -p eql-scalars -p eql-codegen`), the per-type `mise run codegen:domain` tasks, and the per-type `tests/sqlx/snapshots/_matrix_tests.txt` baselines (collapsed into one catalog-reconciled `tests/sqlx/snapshots/matrix_tests.txt`) are gone. Why: a single compiler-validated source of truth shared by the generator and the SQLx test harness, and one fewer toolchain in the build/test path — building and testing EQL no longer needs Python (Python remains only for the separate docs-markdown tooling). ([#252](https://github.com/cipherstash/encrypt-query-language/pull/252)) ## [2.3.1] — 2026-05-21 diff --git a/CLAUDE.md b/CLAUDE.md index 9cb5b80f..50766e33 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,7 +80,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/encrypted_domain//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed. The per-type plaintext fixture lists the SQLx matrix consumes are **not** a generated file — they are materialised from each `CATALOG` row at compile time as `eql_scalars::INT4_VALUES` / `INT2_VALUES` (the `int_values!` macro) and read directly by `ScalarType::FIXTURE_VALUES`; a Rust source of truth no longer round-trips through a committed generated `.rs`. Generated SQL carries a `-- AUTOMATICALLY GENERATED FILE` header (the project-wide marker `docs:validate` greps on); change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. -**Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** The mechanics are fixed for ordered scalar domains; the catalog row only declares the token, kind, domain suffixes, and terms. New term behavior belongs in the `Term` enum's `impl` methods in `crates/eql-scalars/src` with tests, not in free-form catalog data. +**Adding a new encrypted-domain type: follow `docs/reference/adding-a-scalar-encrypted-domain-type.md`.** The mechanics are fixed for ordered scalar domains; the catalog row only declares the token, kind, domain suffixes, and terms. New term behavior belongs in the `Term` enum's `impl` methods in `crates/eql-scalars/src` with tests, not in free-form catalog data. Regeneration is deterministic: an identical `CATALOG` produces byte-identical SQL. If `mise run build` produces unexpected output, the change is in `crates/eql-scalars/src` (the catalog/terms) or `crates/eql-codegen/src` (the renderers) — not in random run-to-run variation. diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md new file mode 100644 index 00000000..ee304a3d --- /dev/null +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -0,0 +1,619 @@ +# Adding a Scalar Encrypted-Domain Type + +The one reference for adding a scalar encrypted-domain type (`int4`, `int2`, +and future ordered numeric scalars). The **top half** (§§1–4) is the path you +follow to add a type; the **reference half** (§§5–7) is the detail behind it — +the generated surface, its invariants, and how the generator itself works. +Read top-down to ship a type; drop into the reference half when something +breaks or you need the *why*. + +A scalar encrypted-domain type is a family of concrete `jsonb` domains in the +**`eql_v3`** schema (`eql_v3.`, `eql_v3._eq`, +`eql_v3._ord`, …), dropped by `DROP SCHEMA eql_v3 CASCADE` and surviving +an `eql_v2` uninstall. Their extractors, comparison wrappers, and MIN/MAX +aggregates also live in `eql_v3`; the index-term types they return +(`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are +referenced cross-schema. + +The whole SQL surface is **generated** from a single Rust source of truth: the +`CATALOG` const in [`crates/eql-scalars/src/lib.rs`](../../crates/eql-scalars/src/lib.rs), +rendered by the [`eql-codegen`](../../crates/eql-codegen/) crate. There is no +TOML manifest and no Python — adding a type is adding one `ScalarSpec` row, +validated by the compiler plus catalog `#[test]`s. The reference type is +`eql_v3.int4`. **`text` and `jsonb` are out of scope** for this materializer +(see §7). + +--- + +## 1. TL;DR — the one path + +To add a scalar type `` (e.g. `int8`), with Rust type `` (e.g. `i64`): + +1. **Add a `ScalarSpec` row to `eql_scalars::CATALOG`** — `token`, `kind`, + `domains`, `fixtures` (§2). If the type needs a new scalar width, add a + `ScalarKind` variant first; if it needs new term behaviour, that goes in the + `Term` enum's `impl`, never in catalog data. +2. **Materialise the value list** — `int_values!(_VALUES, , );` + next to `CATALOG`, pinned by a `values_tests` assertion (§2). This is the + single source the SQLx matrix reads; there is no generated `_values.rs`. +3. **Wire the SQLx matrix oracle** — copy the seven small registrations from the + `int4` reference (§3). +4. **Regenerate** — `cargo run -p eql-codegen` (or just `mise run build`, which + runs the generator first). One run regenerates *every* catalog type; there is + no per-type codegen task. The generated `*_{types,functions,operators,aggregates}.sql` + are gitignored and never committed. +5. **Snapshot the matrix inventory** — `mise run test:matrix:inventory` (§3). +6. **Verify** — `mise run test:codegen`, the relevant SQLx suites, and the + PostgreSQL matrix (§4). + +Things you do **not** do: + +- **Don't commit generated SQL.** `*_types.sql` / `*_functions.sql` / + `*_operators.sql` / `*_aggregates.sql` are gitignored; the catalog plus the + renderers are the source of truth. Change the catalog and rebuild — never + hand-edit generated SQL. +- **Don't add a `tests/codegen/reference//` baseline.** `int4` is the sole + golden master (§4). +- **Don't edit `mise.toml`, the CI workflow, `pin_search_path.sql`, or + `splinter.sh`** for an ordinary type — they recognise the generated surface + intrinsically (§5, §6). The exception is a brand-new *term* whose extractor + has a new name (§5). + +Hand-written SQL beyond the fixed surface goes in +`src/encrypted_domain//_extensions.sql` with explicit `-- REQUIRE:` edges +— and **that file IS committed** (§5). + +--- + +## 2. The catalog row (`ScalarSpec`) + +A scalar type is one `ScalarSpec` row in +[`crates/eql-scalars/src/lib.rs`](../../crates/eql-scalars/src/lib.rs): + +```rust +ScalarSpec { + token: "int4", + kind: ScalarKind::I32, + domains: &[ + DomainSpec { suffix: "", terms: &[] }, + DomainSpec { suffix: "_eq", terms: &[Term::Hm] }, + DomainSpec { suffix: "_ord_ore", terms: &[Term::Ore] }, + DomainSpec { suffix: "_ord", terms: &[Term::Ore] }, + ], + fixtures: INT4_FIXTURES, +} +``` + +The fields, all enforced by the type system and the catalog `#[test]`s rather +than a runtime validator: + +- **`token`** — the type token (`int4`); supplies `` everywhere. Each + domain's full name is `token` + `suffix` (`ScalarSpec::domain_name`), pinned by + `every_domain_name_starts_with_its_token`. +- **`kind`** — a `ScalarKind` (`I16` / `I32` / `I64` / `Numeric` / `Text` / + `Jsonb`), carrying the Rust type name, the `MIN`/`MAX`/zero symbols, and the + numeric bounds. Only the integer kinds have an i128 range with `Min`/`Max`/`Zero` + sentinels; the bounded accessors `panic!` on the others (a misuse guard gated + by `is_int()`). **If `` needs a new scalar width, add a `ScalarKind` + variant** (rust-type name, `MIN`/`MAX`/zero symbols, bounds) with unit tests + over its `impl` methods. +- **`domains`** — a non-empty `&[DomainSpec]` (pinned by + `every_type_has_at_least_one_domain`), each a `suffix` + the fixed `&[Term]` it + carries. The storage domain is `suffix: ""` with no terms; `_eq => [Term::Hm]`; + `_ord` and `_ord_ore => [Term::Ore]`. A `DomainSpec` declares nothing else — no + extractor names, no operator lists, no REQUIRE edges. Every behavioural fact + comes from the `Term` enum. +- **`fixtures`** — the type's plaintext fixture list (see below). + +**Terms** are fixed by the `Term` enum (`crates/eql-scalars/src/lib.rs`). The +`json_key` / `extractor` / `returns` / `ctor` values are the cross-schema SQL +contract — changing one is a generated-SQL behaviour change, not a refactor: + +| Term | JSON key | Extractor | Returns | Operators | +| ----- | -------- | ----------- | -------------------------------- | -------------------------- | +| `Hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` `<>` | +| `Ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` `<>` `<` `<=` `>` `>=` | + +A type that needs a non-ORE equality term on an ordered domain needs a **new +`Term`**, not a catalog flag. Adding a term is a code change to the `Term` +enum's `impl` methods (`json_key`, `extractor`, `returns`, `ctor`, `role`, +`operators`, `requires`) with matching `#[test]`s (`term_tests` / +`term_helper_tests`) — never a free-form catalog field. + +**Twins.** `int4_ord` and `int4_ord_ore` both carry `&[Term::Ore]`. The +generator emits them as independent domains with byte-identical SQL modulo type +name (`ordered_files_byte_identical_modulo_typename`). Twins let callers choose +a name that documents intent ("ordered, regardless of mechanism" vs "ordered via +ORE block") without committing to one term family in a future migration. + +**Order is significant.** The generator iterates `CATALOG` in order (driving +generation order), and iterates each spec's `domains` slice in order — that +order shows up in the generated `_types.sql` `DO` block. Order the slice +the way you want the output to read. + +### Fixtures — single-sourcing the value list + +The `fixtures` field is an ordered `&[Fixture]` — the single source of truth +for the type's plaintext list, consumed by both the SQLx fixture generator and +the matrix oracle. A `Fixture` is value-kind tagged: `Min` / `Max` / `Zero` (the +integer matrix pivots, resolved per-kind), `Int(i128)` (an integer literal), and +`Numeric` / `Text` / `Jsonb` string variants. The `fixtures!` macro +range-checks each `Int` literal against the kind at compile time (`N(-40000)` +for an `i16` kind does not compile): + +```rust +const INT4_FIXTURES: &[Fixture] = fixtures!(int i32; + Min, N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), N(25), + N(42), N(50), N(100), N(250), N(1000), N(9999), Max); +``` + +Catalog `#[test]`s enforce a **distinct-plaintext contract** plus the +matrix-pivot requirement: + +- `fixture_values_are_distinct_by_resolved_number` rejects duplicates against + the *resolved* value, so both copy-paste dups and sentinel/literal aliases + (`Min` alongside the same number) fail; +- `fixtures_include_min_max_and_zero` requires `Min`, `Max`, and zero for + integer kinds — the matrix uses those three as comparison pivots and fetches + each one's ciphertext from the fixture via `fetch_fixture_payload`, which fails + loudly if the row is absent; +- `every_fixture_value_is_within_kind_bounds` keeps every resolved value in + range. + +These are the compile/test-time analogue of the old `load_spec` validation. +Beyond the pivots, choose values so range operators produce distinguishable +result counts, include useful boundaries, and cover omitted-term negative cases. + +The plaintext list is **not** rendered to a generated file. The `int_values!` +macro (next to `CATALOG`) materialises a `Fixture` list into a typed `pub const +_VALUES: &[]` at compile time (`INT4_VALUES`, `INT2_VALUES`): + +```rust +int_values!(INT4_VALUES, i32, INT4); +``` + +Both consumers reference that single symbol — the fixture generator +(`fixtures::eql_v2_::spec`) and the matrix oracle's `FIXTURE_VALUES` — so the +oracle cannot drift from the values the generator encrypts. There is no +committed `_values.rs`: a Rust source of truth does not round-trip through +generated Rust. Pin the exact materialised list with a `values_tests` assertion. + +--- + +## 3. Wire the SQLx matrix oracle + +The generated SQL is enough to *install* the domains, but the +`ordered_numeric_matrix!` suite only runs once the Rust harness knows about the +scalar. These are hand-maintained registration lists — copy each piece from the +`int4` reference. `` is the scalar's Rust type (`i32` for `int4`, `i16` for +`int2`): + +| File | Add | +|------|-----| +| `tests/sqlx/src/fixtures/eql_plaintext.rs` | A sealed `EqlPlaintext` impl for ``: `impl Sealed for {}`, a `PlaintextSqlType` const for its base column type, `impl EqlPlaintext for ` (`CAST`, `PLAINTEXT_SQL_TYPE`, `to_plaintext` → the right `Plaintext` variant), plus the two `#[test]` casts. | +| `tests/sqlx/src/fixtures/eql_v2_.rs` | `use eql_scalars::_VALUES as VALUES;` then `crate::scalar_fixture!("eql_v2_", , VALUES);`. | +| `tests/sqlx/src/fixtures/mod.rs` | `pub mod eql_v2_;`. | +| `tests/sqlx/tests/generate_all_fixtures.rs` | An arm in `generate_for_token`: `"" => fixtures::eql_v2_::spec().run().await,`. The match is exhaustive over the catalog — a catalog token with no arm fails the generator loudly. | +| `tests/sqlx/src/scalar_domains.rs` | `impl ScalarType for ` — `PG_TYPE` (the base PG type, e.g. `"int8"`) and `FIXTURE_VALUES = eql_scalars::_VALUES`. | +| `tests/sqlx/tests/encrypted_domain/scalars/.rs` | `ordered_numeric_matrix! { suite = , scalar = , eql_type = "eql_v2_" }`. | +| `tests/sqlx/tests/encrypted_domain/scalars/mod.rs` | `pub mod ;`. | + +Forget one and the matrix simply does not run for the type — the matrix +inventory cross-check (below) surfaces it, because the catalog has the type but +the binary has no `scalars::::` tests. (A future Phase-4 `scalar_types!` +registry, tracked separately, will collapse these into one declaration.) + +The coverage these registrations unlock comes from the `ordered_numeric_matrix!` +convention wrapper in `tests/sqlx/src/matrix.rs`: one `impl ScalarType` plus a +single invocation taking `suite`, `scalar`, and `eql_type`. The matrix derives +its comparison pivots — the scalar's `MIN`, `MAX`, and zero +(`Default::default()`) — from the type rather than a hand-written list, so the +invocation carries no pivot argument. Equality-only scalars use the sibling +`eq_only_scalar_matrix!`. The `matrix.rs` module header is the canonical, +current list of the categories the matrix emits (sanity, correctness, +cross-shape, supported-NULL, blocker raises, index engagement, ORDER BY, ORDER +BY USING) — read it rather than duplicating a count here. For ordered `int4`, +keep the assertion that distinct plaintext values produce distinct ORE blocks; +do not add assertions for term behaviour the catalog does not promise. + +### Matrix coverage inventory snapshot + +The *set of test names* the matrix emits is guarded by **one** committed, +token-normalized snapshot at `tests/sqlx/snapshots/matrix_tests.txt` — the +sorted inventory of every `scalars::::*` test name with the type token +replaced by the literal ``. (The per-type `_matrix_tests.txt` files are +gone: they were byte-identical modulo the token, so one canonical set plus a +per-type normalize-and-compare carries the same signal at a fraction of the +committed surface.) This is the guard that catches a silently dropped, renamed, +or `#[cfg]`-gated matrix test — a behaviour the SQLx assertions cannot see (a +deleted test just stops running). The snapshot is a committed test baseline, +**not** gitignored generated SQL. + +`mise run test:matrix:inventory` discovers the present scalar types from the +`encrypted_domain` binary's `--list`, normalizes each type's token to ``, +asserts every type's set equals the canonical snapshot, and cross-checks the +discovered type set against `cargo run -p eql-codegen -- list-types` (the +catalog is the single source). You do **not** edit a per-type snapshot or touch +`mise.toml` / the CI workflow — you only regenerate the one `matrix_tests.txt` +when the macro's emitted name set itself changes. A catalog type missing its +matrix wiring fails the cross-check. The CI `matrix-coverage` job gates it. +**`tests/sqlx/snapshots/README.md` is the source of truth** for the mechanics +(pinned feature set, the catalog cross-check, the CI diff, and when to +regenerate). + +--- + +## 4. Regenerate, snapshot & verify + +Regeneration is deterministic: identical catalog + renderers produce +byte-identical SQL. If `mise run build` produces unexpected output, the change +is in `crates/eql-scalars/src` (catalog/terms) or `crates/eql-codegen/src` +(renderers) — not run-to-run variation. + +Run, in order: + +- `cargo run -p eql-codegen` (optional; refreshes all generated SQL from the + catalog before a full build) +- `mise run test:codegen` (`cargo test -p eql-scalars -p eql-codegen`) +- `mise run test:matrix:inventory` (matrix inventory + catalog cross-check; no + database) +- `mise run clean && mise run build` (regenerates every type's SQL from the + catalog first, then builds the release artefacts — a bare build can leave + stale `release/*.sql`) +- the relevant SQLx suites +- `mise run test` across supported PostgreSQL versions +- `mise run --output prefix test:splinter --postgres 17` after a PostgreSQL 17 + install has built EQL + +The CI codegen job is a prerequisite of the PostgreSQL test matrix, so +generated-SQL drift is caught before database tests run. + +**Why no per-type golden baseline.** Do **not** add a +`tests/codegen/reference//` baseline. `int4` is the sole golden master for +the type-generic generator: the templates are pure token substitution, so a +per-type baseline can only fail where `int4`'s already would. Drift protection +for a new type comes from the `int4` reference (shared templates + `Term` enum), +the catalog `values_tests` pinning the materialised `_VALUES`, the +catalog/generator `#[test]`s, and the `ordered_numeric_matrix!` SQLx suite +(behaviour, not bytes). + +--- + +## 5. The generated surface — what correct output looks like + +This is the contract the generated SQL satisfies. You normally never read it to +*add* a type — read it when a test fails or you're extending the surface. + +### Domains and CHECK constraints + +The generator emits `src/encrypted_domain//_types.sql` (gitignored; +materialised on every build) with one idempotent `DO $$ ... $$` block. Every +domain is a concrete domain over `jsonb` in the `eql_v3` schema — **never** +`CREATE DOMAIN a AS b` over another generated domain (PostgreSQL resolves +operators against the underlying base type, bypassing the fixed surface). Each +domain's `CHECK` requires: + +- fixed envelope keys `v` and `i`; +- ciphertext key `c`; +- catalog JSON keys for the listed terms; +- the envelope version value `VALUE->>'v' = '2'`, matching the repo-wide + `eql_v2._encrypted_check_v` rule (`src/encrypted/constraints.sql`). + +So a domain with `&[Term::Ore]` requires `v`, `i`, `c`, and `ob` present, with +`v` pinned to `2`. Beyond key presence and the version value, a malformed term +can still fail later inside its extractor. + +### Extractors, wrappers, and blockers + +Extractor names and return types come from the `Term` enum. Generated extractors +and supported comparison wrappers are inline-friendly SQL functions: + +```sql +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT ... $$; +``` + +They must **not** carry a pinned `search_path` — a `SET` clause disables +inlining and reverts index-backed queries to seq scans. The build tooling +recognises these functions structurally, so the generator emits no +`eql-inline-critical` markers. (Aggregate state functions are the one deliberate +exception — see below.) + +Unsupported operators route to **blockers**, which are `LANGUAGE plpgsql`, +`IMMUTABLE`, `PARALLEL SAFE`, and intentionally **not `STRICT`**: + +- **`plpgsql`, not `sql`.** A `LANGUAGE sql` body is inlinable, and the planner + could elide the call when the result is provably unused (dead `CASE` branch, + folded predicate), letting a blocked operator appear to succeed. `plpgsql` is + opaque to the planner, so the call — and its `RAISE` — always survives. +- **Not `STRICT`.** A `STRICT` blocker lets PostgreSQL skip the body and return + `NULL` on a `NULL` argument, silently bypassing the unsupported-operator + exception. + +### Operators + +Every generated domain declares supported scalar comparison operators plus +blockers for the native `jsonb` operator surface PostgreSQL could otherwise +reach through domain-to-base-type fallback. The surface is a fixed 20 operators +(`crates/eql-codegen/src/operator_surface.rs`, `OPERATORS`), each with its +PostgreSQL-shaped signatures, summing to **44 `CREATE OPERATOR` statements per +domain**: + +| Operators | Forms | +|---|---| +| `=` `<>` `<` `<=` `>` `>=` `@>` `<@` | `(domain, domain)` · `(domain, jsonb)` · `(jsonb, domain)` | +| `->` `->>` | `(domain, text)` · `(domain, integer)` · `(jsonb, domain)` | +| `?` | `(domain, text)` | +| `?\|` `?&` | `(domain, text[])` | +| `@?` `@@` | `(domain, jsonpath)` | +| `#>` `#>>` `#-` | `(domain, text[])` | +| `-` | `(domain, text)` · `(domain, integer)` · `(domain, text[])` | +| `\|\|` | `(domain, domain)` · `(domain, jsonb)` · `(jsonb, domain)` | + +Whether an operator routes to a wrapper or a blocker is a per-domain decision +driven by the domain's terms (`Term::operators_for_terms`), not a property of +the operator. Supported operators are emitted with full planner metadata +(`COMMUTATOR`, `NEGATOR`, `RESTRICT`, `JOIN` selectivity estimators) backing +onto inlinable wrappers; everything else carries minimal metadata backing onto +blockers. Path operators always back onto blockers — neither current term +enables them — and the native `jsonb` operators are blocker-only. + +The wrapper/blocker split per domain (the 44-operator total never moves): + +| Domain terms | Extractors | Wrappers | Blockers | Functions | Operators | +| ---------------- | ---------: | -------: | -------: | --------: | --------: | +| none | 0 | 0 | 44 | 44 | 44 | +| `&[Term::Hm]` | 1 (`eq_term`) | 6 | 38 | 45 | 44 | +| `&[Term::Ore]` | 1 (`ord_term`) | 18 | 26 | 45 | 44 | + +Six wrappers for `Hm` = `=` and `<>` × three shapes; eighteen for `Ore` = six +operators × three shapes. + +**Untyped-literal resolver edge.** PostgreSQL's operator resolver still prefers +the built-in `jsonb` operator for untyped string literals in forms such as +`payload::eql_v3.int4 ? 'c'`. Use typed parameters or explicit casts +(`? 'c'::text`, bound text parameters) to route those forms to the generated +blocker. A live-DB structural guard +(`tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs`) queries +`pg_operator` for every operator with a `jsonb` argument and asserts the set is +a subset of the enumerated surface, so a future PostgreSQL version that adds a +`jsonb` operator nobody enumerated fails the test rather than silently routing an +encrypted column to native plaintext-`jsonb` semantics. + +### Aggregates + +Each ordered (ord-capable) domain additionally gets a generated +`_aggregates.sql`: two state functions (`eql_v3.min_sfunc`, +`eql_v3.max_sfunc`) and two aggregates (`eql_v3.min()`, +`eql_v3.max()`). Comparison routes through the domain's `<` / `>` +operator (the ORE block term — no decryption). The state functions are `LANGUAGE +plpgsql IMMUTABLE STRICT PARALLEL SAFE` **with** a pinned `SET search_path` — +the one place the "no pinned `search_path`" rule does not apply, because +aggregate transition functions are never index expressions. `STRICT` makes +PostgreSQL seed the running state with the first non-NULL value and skip NULLs, +so an all-NULL group returns NULL. Each `CREATE AGGREGATE` declares +`combinefunc = ` and `parallel = safe`: min/max are associative, so the +state function doubles as the combine function, enabling partial and parallel +aggregation on large `GROUP BY` ORE workloads with no decryption. Storage-only +and equality-only domains have no comparator and emit no aggregate file. + +### Indexing + +Do not create operator classes on generated domains. Index through the +extractor, whose return type already carries a default opclass: + +```sql +CREATE INDEX ... ON table_name USING btree (eql_v3.ord_term(col)); +CREATE INDEX ... ON table_name USING hash (eql_v3.eq_term(col)); +``` + +`ore` depends on `src/ore_block_u64_8_256/functions.sql` and +`src/ore_block_u64_8_256/operators.sql`; `hm` depends on +`src/hmac_256/functions.sql`. + +### Extension files + +Optional hand-written SQL beyond the fixed surface belongs in +`src/encrypted_domain//_extensions.sql`. The generator never creates, +lists, headers, or cleans it; it must declare its own `-- REQUIRE:` edges +(usually to `_types.sql` and whichever generated function or operator file it +extends). Use it for cross-domain casts, helper functions, or type-specific +constraints. Unlike the generated siblings, **`_extensions.sql` IS +committed.** (Neither `int4` nor `int2` ships one today.) + +`tasks/pin_search_path.sql` describes the fallback marker for inline-critical +extension functions that take no domain argument and so escape the structural +skip: + +```sql +COMMENT ON FUNCTION eql_v2.my_helper(...) IS 'eql-inline-critical: ...'; +``` + +The generator never emits this marker; every function it produces takes a domain +argument and is covered by the structural skip intrinsically. + +### Invariants the generator enforces + +The generator's job is partly to write SQL and partly to make incorrect SQL +unreachable. Invariants encoded in the renderers / templates and guarded by +`#[test]`s in `crates/eql-codegen/src/generate.rs`: + +- **Blockers are never `STRICT` and always `plpgsql`** — the + unsupported-operator template emits each blocker as `IMMUTABLE PARALLEL SAFE` / + `LANGUAGE plpgsql` without `STRICT` + (`blockers_are_never_strict_and_always_plpgsql`). +- **Wrappers and extractors are inlinable SQL** — `LANGUAGE sql IMMUTABLE STRICT + PARALLEL SAFE`, single-statement `SELECT`, no `SET search_path` + (`inlinable_functions_have_no_set_search_path`). +- **Aggregate state functions are the deliberate exception** — `plpgsql` *with* + a pinned `SET search_path` (`aggregate_state_functions_are_plpgsql_not_inlinable`). +- **SQL-literal injection is structurally prevented** — every interpolated + single-quoted literal passes through `sql_str` + (`crates/eql-codegen/src/consts.rs`), which doubles embedded single quotes. +- **No domain-over-domain** — every domain is `CREATE DOMAIN eql_v3. AS + jsonb` (`types_file_has_all_four_domains`). +- **No operator class on a domain** — the generator emits operators, not + operator classes. +- **Ownership boundary** — `is_generated` recognises owned files by their header + marker; `ensure_generated_paths_writable` refuses to overwrite anything else, + and `clean_generated_files` deletes only marked files + (`crates/eql-codegen/src/writer.rs`). A hand-written file at a generated path + is a hard error, not a silent clobber. + +### Lint and test integration + +Two pieces of build tooling recognise the generated output without per-type +edits: + +- **`tasks/pin_search_path.sql`** — structural skip identifies encrypted-domain + functions by language (`sql`), volatility (`IMMUTABLE`), and a jsonb-backed + `DOMAIN` argument in the `eql_v3` schema. New scalar types need no edit. +- **`tasks/test/splinter.sh`** — name-based allowlist. The converged wrapper / + extractor names (`eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `eq_term`, `ord_term`) + plus the generated `min` / `max` aggregates are already covered by + `eql_v3`-schema entries. A new scalar type inherits coverage; **only a new + term whose extractor has a new name requires a splinter entry.** + +--- + +## 6. Generator internals — the machine + +You need this section only when **modifying the generator itself**, not when +adding a type. + +### Why a generator + +A single scalar type emits several hundred SQL declarations across eleven files: +four domains, three extractors, dozens of wrappers and blockers, 176 `CREATE +OPERATOR` statements (44 per domain), and MIN/MAX aggregates per ordered domain. +The shape is mechanical and the invariants are unforgiving — a `STRICT` blocker +silently bypasses its exception; a pinned `search_path` reverts queries to seq +scans. The generator exists so each new type adds one `CATALOG` row rather than +ninety hand-written declarations that must agree with each other and with +`pin_search_path.sql`, `tasks/test/splinter.sh`, and +`src/encrypted_domain/functions.sql`. + +### Pipeline + +`eql-codegen` is a small Rust crate with a binary entry point. The generator +runs as `cargo run -p eql-codegen` (no subcommand), which calls +`generate::generate_all` (`crates/eql-codegen/src/generate.rs`) over every row of +`eql_scalars::CATALOG`, writing each type's SQL into +`src/encrypted_domain//`. A second subcommand, `cargo run -p eql-codegen +-- list-types`, prints the catalog tokens one per line (consumed by the fixture +and matrix-inventory enumeration). `main` (`crates/eql-codegen/src/main.rs`) +recognises exactly these two forms; any other argument is a usage error. + +`tasks/build.sh` runs `cargo run -p eql-codegen` at the start of every `mise run +build`, so the generated SQL is never checked in. (The build first sweeps every +generated `*_{types,functions,operators,aggregates}.sql` under +`src/encrypted_domain` so a type removed from `CATALOG` cannot leave orphans the +`src/**/*.sql` build glob would pick up; hand-written `*_extensions.sql` is +preserved by the name patterns.) + +Stages, in order (`generate_all` → `generate_type`): + +1. **Read the catalog.** `eql_scalars::CATALOG` is the in-binary source of truth + — a `&[ScalarSpec]`. There is no parse/validate stage at generation time: the + catalog is validated at compile time (an undefined `Term` or unknown + `ScalarKind` does not compile) and by the catalog `#[test]`s, so the data is + already well-formed by the time `generate_all` runs. +2. **Resolve terms.** For each `DomainSpec`, the `Term` enum's `impl` methods + supply the extractor name, return type, JSON envelope key, supported + operators, and the SQL `-- REQUIRE:` edges those terms imply + (`Term::operators_for_terms`, `term_json_keys`, `term_requires`, + `extractor_for_operator`, `role_for_terms`). +3. **Render.** `render_types_file`, `render_functions_file`, + `render_operators_file`, and `render_aggregates_file` (the last only for + ordered domains) build the context structs in + `crates/eql-codegen/src/context.rs` and render them through embedded + **minijinja** templates (`crates/eql-codegen/templates/*.j2`, compiled in via + `include_str!` — no runtime file IO). The structural shape of each declaration + is split between the context builders (Rust) and the templates (Jinja). +4. **Write.** `clean_generated_files` first deletes every generated `.sql` in the + target directory (recognised by the header marker) so an abandoned domain + disappears on the next regeneration; `ensure_generated_paths_writable` then + refuses to proceed if any target path is a hand-written file lacking the + marker; `write_generated_file` writes each rendered body verbatim + (`crates/eql-codegen/src/writer.rs`). The template emits the `-- AUTOMATICALLY + GENERATED FILE.` marker as its own first line, so the writer does not prepend + a header — it only uses the marker to recognise files it owns. + +There is no caching layer and no incremental mode. Each run regenerates every +output for every catalog type from scratch. + +### Generated outputs + +For a type with `D` domains of which `A` are ordered, the generator writes `1 + +2D + A` SQL files into `src/encrypted_domain//`. For `int4` (`D = 4`, `A = +2`): eleven SQL files. The outputs are gitignored +(`.gitignore` excludes `src/encrypted_domain/*/*_{types,functions,operators,aggregates}.sql`) +and regenerated at the start of every build. + +| File | Content | +| --------------------------------- | ---------------------------------------------------------------------------------------- | +| `_types.sql` | Single idempotent `DO` block creating every domain; each `CHECK` pins the payload version (`VALUE->>'v' = '2'`) and required envelope/ciphertext/term keys; one `--! @brief` per domain | +| `_functions.sql` | One extractor per unique term, then 44 wrappers-or-blockers covering the surface | +| `_operators.sql` | 44 `CREATE OPERATOR` statements with planner metadata on supported ops | +| `_aggregates.sql` | MIN/MAX state functions + `CREATE AGGREGATE`; emitted only for ordered domains | + +Every file opens with the `-- AUTOMATICALLY GENERATED FILE.` marker (the +project-wide marker `docs:validate` greps on to skip generated SQL — +`crates/eql-codegen/src/consts.rs`), declares its `-- REQUIRE:` edges in +dependency order (types files require `src/schema-v3.sql`; function files require +both `src/schema.sql` and `src/schema-v3.sql`, the types file, and +`src/encrypted_domain/functions.sql` plus each term's `requires` set; operator +files require `src/schema-v3.sql`, the types file, and their domain's function +file; aggregate files require `src/schema-v3.sql`, the types file, and their +domain's function and operator files), and carries Doxygen `--! @file` / +`--! @brief` headers. + +### Generator tests and the parity gate + +The generator's tests are Rust, run by `mise run test:codegen` (`cargo test -p +eql-scalars -p eql-codegen`) — no database. `mise run test:crates` adds `cargo +clippy ... -D warnings`. + +- **`eql-scalars` unit tests** — `rust_tests`, `term_tests`, + `term_helper_tests`, `fixture_tests`, `catalog_tests`, `invariant_tests`, + `values_tests` over `CATALOG`, the `Term` / `ScalarKind` / `Fixture` impls, and + the materialised `_VALUES` consts. +- **`eql-codegen` unit tests** — file counts, language/volatility invariants, + escaping guards, and twin byte-identity + (`crates/eql-codegen/src/generate.rs` `#[cfg(test)]`). +- **The parity gate** — `mise run codegen:parity` (`tasks/codegen-parity.sh`). + It runs the generator into the real tree, then (1) compares the int4 generated + SQL **file set** against the golden under `tests/codegen/reference/int4/*.sql`, + excluding committed hand-written files (`comm -23` of `ls` against `git + ls-files`), so an extra or dropped generated file fails; and (2) diffs each + golden file **byte-for-byte** against its generated counterpart, after dropping + the golden's single leading `-- REFERENCE:` provenance line (`tail -n +2`). The + same byte-for-byte assertion runs in-crate as + `crates/eql-codegen/tests/parity.rs` + (`rust_generator_matches_int4_golden_files`). The golden reference — not any + Python oracle — is the sole contract that survives generator refactors. + +CI runs these in three jobs in `.github/workflows/test-eql.yml`: `rust-crates` +(`Rust workspace crates`, runs `mise run test:crates`), `codegen` +(`Encrypted-domain codegen`, runs `mise run codegen:parity`), and +`matrix-coverage` (`Matrix coverage inventory`, runs `mise run +test:matrix:inventory`). The codegen job is a prerequisite of the PostgreSQL +test matrix. + +Adding a new **term** is a bigger move than adding a type: edit the `Term` enum's +`impl` methods, add `#[test]`s, audit `splinter.sh` for a name collision if the +extractor name is new, and — because it changes the int4 surface — update the +golden reference under `tests/codegen/reference/int4/`. + +--- + +## 7. Out of scope — `text` and `jsonb` + +`text` and `jsonb` are **not** materialised through this generator. The +`ScalarKind` enum carries `Text` / `Numeric` / `Jsonb` variants and the +`Fixture` enum carries their string-backed shapes at the capability layer, but +`CATALOG` declares only the integer scalars today, so no `text` / `jsonb` SQL +surface is generated. Text and JSONB encrypted behaviour lives on the composite +`eql_v2_encrypted` type and its hand-written operator surface in `src/encrypted/` +and `src/operators/`, not the scalar materializer. `jsonb` in particular needs a +separate SQL design beyond this ordered-scalar materializer. diff --git a/docs/reference/encrypted-domain-generator.md b/docs/reference/encrypted-domain-generator.md deleted file mode 100644 index 770cbeaf..00000000 --- a/docs/reference/encrypted-domain-generator.md +++ /dev/null @@ -1,483 +0,0 @@ -# Encrypted-Domain Code Generator - -How the Rust `eql-codegen` crate turns the `eql-scalars` catalog into the -SQL surface for a scalar encrypted-domain type. This document describes -the generator itself — its inputs, stages, outputs, and the invariants it -enforces. The contract those outputs must satisfy is in -[`encrypted-domain-implementation-spec.md`](./encrypted-domain-implementation-spec.md); -this file describes the machine that produces them. - -The reference type is `eql_v3.int4`. `text` and `jsonb` are outside scope. - -The generator is **Rust, not Python**. There is no TOML manifest, no -`tasks/codegen/` package, no `terms.py`/`templates.py`/`spec.py`. The -source of truth is the `CATALOG` const in -[`crates/eql-scalars/src/lib.rs`](../../crates/eql-scalars/src/lib.rs); -the renderers live in [`crates/eql-codegen/`](../../crates/eql-codegen/). -Adding a scalar type is adding a `ScalarSpec` row to `CATALOG`, validated -by the compiler plus catalog `#[test]`s — never an edit to free-form -manifest data. - -## 1. Why a generator - -A single scalar encrypted-domain type emits several hundred SQL -declarations across eleven files: four domains, three extractors, dozens -of comparison wrappers and blockers, 176 `CREATE OPERATOR` statements (44 -per domain), and MIN/MAX aggregates for every ordered domain. The shape -is mechanical and the invariants are unforgiving — a `STRICT` blocker -silently bypasses its exception, a pinned `search_path` disables inlining -and reverts queries to seq scans. The generator exists so each new scalar -type adds one `CATALOG` row rather than ninety hand-written declarations -that must agree with each other and with `pin_search_path.sql`, -`tasks/test/splinter.sh`, and `src/encrypted_domain/functions.sql`. - -## 2. Pipeline - -`eql-codegen` is a small Rust crate with a binary entry point. The -generator runs as `cargo run -p eql-codegen` (no subcommand), which calls -`generate::generate_all` (`crates/eql-codegen/src/generate.rs`) over every -row of `eql_scalars::CATALOG`, writing each type's SQL into -`src/encrypted_domain//`. A second subcommand, -`cargo run -p eql-codegen -- list-types`, prints the catalog tokens one per -line (consumed by the fixture and matrix-inventory enumeration). The -binary's `main` (`crates/eql-codegen/src/main.rs`) recognises exactly these -two forms; any other argument is a usage error. - -`tasks/build.sh` runs `cargo run -p eql-codegen` at the start of every -`mise run build`, so the generated SQL is never checked in — the catalog -is the source of truth. (The build first sweeps every generated -`*_{types,functions,operators,aggregates}.sql` under `src/encrypted_domain` -so a type removed from `CATALOG` cannot leave orphans the `src/**/*.sql` -build glob would pick up; hand-written `*_extensions.sql` is preserved by -the name patterns.) - -Stages, in order (`generate_all` → `generate_type`): - -1. **Read the catalog.** `eql_scalars::CATALOG` is the in-binary source of - truth — a `&[ScalarSpec]`, each row a `token`, a `ScalarKind`, an - ordered `&[DomainSpec]`, and a `&[Fixture]` list - (`crates/eql-scalars/src/lib.rs`). There is no parse/validate stage at - generation time: the catalog is validated at compile time (an undefined - `Term` or unknown `ScalarKind` does not compile) and by the catalog - `#[test]`s, so by the time `generate_all` runs the data is already - well-formed. -2. **Resolve terms.** For each `DomainSpec`, the `Term` enum's `impl` - methods supply the extractor name, return type, JSON envelope key, - supported operators, and the SQL `-- REQUIRE:` edges those terms imply - (`Term::operators_for_terms`, `term_json_keys`, `term_requires`, - `extractor_for_operator`, `role_for_terms` — `crates/eql-scalars/src/lib.rs`). -3. **Render.** `render_types_file`, `render_functions_file`, - `render_operators_file`, and `render_aggregates_file` (the last only for - ordered domains) build the context structs in - `crates/eql-codegen/src/context.rs` and render them through embedded - **minijinja** templates (`crates/eql-codegen/templates/*.j2`, - compiled in via `include_str!` — no runtime file IO). The structural - shape of each declaration is split between the context builders (Rust) - and the templates (Jinja). -4. **Write.** `clean_generated_files` first deletes every generated `.sql` - in the target directory (recognised by the header marker) so an - abandoned domain disappears on the next regeneration; - `ensure_generated_paths_writable` then refuses to proceed if any target - path is a hand-written file lacking the marker; `write_generated_file` - writes each rendered body verbatim (`crates/eql-codegen/src/writer.rs`). - The template emits the `-- AUTOMATICALLY GENERATED FILE.` marker as its - own first line, so the writer does not prepend a header — it only uses - the marker to recognise files it owns. - -There is no caching layer and no incremental mode. Each `cargo run -p -eql-codegen` regenerates every output for every catalog type from scratch. -Regeneration is deterministic: identical catalog + renderers produce -byte-identical SQL. - -## 3. Catalog format - -A scalar type is one `ScalarSpec` row -(`crates/eql-scalars/src/lib.rs`): - -```rust -ScalarSpec { - token: "int4", - kind: ScalarKind::I32, - domains: &[ - DomainSpec { suffix: "", terms: &[] }, - DomainSpec { suffix: "_eq", terms: &[Term::Hm] }, - DomainSpec { suffix: "_ord_ore", terms: &[Term::Ore] }, - DomainSpec { suffix: "_ord", terms: &[Term::Ore] }, - ], - fixtures: INT4_FIXTURES, -} -``` - -Structural rules, enforced by the type system and the catalog `#[test]`s -rather than a runtime validator: - -- `token` supplies the **type token** (`int4` here). Each domain's full - name is `token` + `suffix`; `ScalarSpec::domain_name` makes the old - "domain name must start with the token" rule structural, and - `every_domain_name_starts_with_its_token` pins it. -- `kind` is a `ScalarKind` (`I16` / `I32` / `I64` / `Numeric` / `Text` / - `Jsonb`), which carries the Rust type name, the `MIN`/`MAX`/zero symbols, - and the numeric bounds. Only the integer kinds have an i128 range with - `Min`/`Max`/`Zero` sentinels; the bounded accessors `panic!` on the - others (a misuse guard, gated by `is_int()`). -- `domains` is a non-empty `&[DomainSpec]` (pinned by - `every_type_has_at_least_one_domain`). Each `DomainSpec` is a `suffix` - plus a `&[Term]`; the storage domain is `suffix: ""` with no terms. -- `fixtures` is a `&[Fixture]` (see §3a). - -The `DomainSpec` declares nothing else — no extractor names, no operator -lists, no REQUIRE edges. Every behavioural fact comes from the `Term` -enum. - -Domains may be **twinned** (`int4_ord` and `int4_ord_ore` both carry -`&[Term::Ore]`). The generator emits them as independent domains with -byte-identical SQL modulo type name (`ordered_files_byte_identical_modulo_typename`). -Twins exist so callers can choose a name that documents intent ("ordered, -regardless of mechanism" vs "ordered via ORE block") without committing to -one term family in a future migration. - -Catalog order is significant. The generator iterates `CATALOG` in order -(driving generation order), and iterates each spec's `domains` slice in -order — that order shows up in the generated `_types.sql` `DO` block. - -### 3a. The `fixtures` field - -The `fixtures` field is an ordered `&[Fixture]` — the single source of -truth for the type's plaintext fixture list, consumed by the SQLx fixture -generator and the matrix oracle. A `Fixture` is value-kind tagged: -`Min` / `Max` / `Zero` (the integer matrix pivots, resolved per-kind), -`Int(i128)` (an integer literal), and `Numeric`/`Text`/`Jsonb` string -variants. The `fixtures!` macro range-checks each `Int` literal against the -kind at compile time (`N(-40000)` for an `i16` kind does not compile): - -```rust -const INT4_FIXTURES: &[Fixture] = fixtures!(int i32; - Min, N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), N(25), - N(42), N(50), N(100), N(250), N(1000), N(9999), Max); -``` - -Catalog `#[test]`s enforce a **distinct-plaintext contract** plus the -matrix-pivot requirement: `fixture_values_are_distinct_by_resolved_number` -rejects duplicates against the resolved value (so both copy-paste dups and -sentinel/literal aliases fail), `fixtures_include_min_max_and_zero` requires -`Min`, `Max`, and zero for integer kinds, and -`every_fixture_value_is_within_kind_bounds` keeps every resolved value in -range. These are the compile/test-time analogue of the old `load_spec` -validation. - -The plaintext value list is **not** rendered to a generated file. The -`int_values!` macro (next to `CATALOG`) materialises a `Fixture` list into -a typed `pub const _VALUES: &[]` at compile time -(`INT4_VALUES`, `INT2_VALUES`). Both consumers reference that single symbol -— the fixture generator and the matrix oracle's `FIXTURE_VALUES` — so the -oracle cannot drift from the values the generator encrypts. There is no -committed `_values.rs`: a Rust source of truth does not round-trip -through generated Rust. (The old generated, committed file is gone.) The -exact materialised list is pinned by the catalog's `values_tests`. - -## 4. Term catalog - -The `Term` enum (`crates/eql-scalars/src/lib.rs`) defines every term the -materializer recognises. The `json_key`/`extractor`/`returns`/`ctor` -values are the cross-schema SQL contract — changing one is a generated-SQL -behaviour change, not a refactor. - -| Term | JSON key | Extractor | Returns | Operators | -| ----- | -------- | ----------- | -------------------------------- | -------------------------- | -| `Hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` `<>` | -| `Ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` `<>` `<` `<=` `>` `>=` | - -The index-term return types (`eql_v2.hmac_256`, -`eql_v2.ore_block_u64_8_256`) live in `eql_v2` and are referenced -cross-schema; the domains, extractors, and wrappers live in `eql_v3`. - -Adding a term is a code change to the `Term` enum's `impl` methods -(`json_key`, `extractor`, `returns`, `ctor`, `role`, `operators`, -`requires`) with matching `#[test]`s (`term_tests` / `term_helper_tests`) -— never a free-form catalog field. The `Term` enum is the only source of -operator support, extractor identity, and REQUIRE edges; a `DomainSpec` is -a thin selector over it. - -## 5. The operator surface - -`crates/eql-codegen/src/operator_surface.rs` enumerates the 20-operator -surface every generated domain declares (`OPERATORS`): - -- **Comparison operators**: `=` `<>` `<` `<=` `>` `>=` `@>` `<@` -- **Path-selector operators**: `->` `->>` -- **Native `jsonb` operators**: `?` `?|` `?&` `@?` `@@` `#>` `#>>` `-` `#-` `||` - -Each operator carries its PostgreSQL-shaped signatures. The comparison -operators use the three symmetric shapes — `(domain, domain)`, -`(domain, jsonb)`, `(jsonb, domain)`; the path and native operators use -only the shapes PostgreSQL exposes for `jsonb` itself. Summed across all -20 operators, that is **44 `CREATE OPERATOR` statements per domain** -(`operators_file_has_forty_four`). - -Whether an operator routes to a wrapper or a blocker is a per-domain -decision driven by the domain's terms (`Term::operators_for_terms`), not a -property of the operator. Supported operators are emitted with full planner -metadata (`COMMUTATOR`, `NEGATOR`, `RESTRICT`, `JOIN` selectivity -estimators) and back onto inlinable wrappers; unsupported operators carry -minimal metadata and back onto blockers (`operator_entry` only renders -metadata when the operator is supported on that domain). - -Path operators always back onto blockers — neither current term enables -them. The native `jsonb` operators are blocker-only. Untyped string -literals are a PostgreSQL resolver edge: `? 'c'` can still select the -built-in `jsonb` operator, while `? 'c'::text` and bound text parameters -select the generated blocker. - -A live-DB structural guard -(`tests/sqlx/tests/encrypted_domain/family/jsonb_operator_surface.rs`) -queries `pg_operator` for every operator with a `jsonb` argument and -asserts the set is a subset of the surface this module enumerates, so a -future PostgreSQL version that adds a `jsonb` operator nobody enumerated -here fails the test rather than silently routing an encrypted column to -native plaintext-`jsonb` semantics. The `operator_surface` unit tests pin -the Rust surface (20 operators, signatures, metadata); the live-DB test -mirrors it. - -## 6. Generated outputs - -For a type with `D` domains of which `A` are ordered (ord-capable), the -generator writes `1 + 2D + A` SQL files into -`src/encrypted_domain//`. For `int4` (`D = 4`, `A = 2`): eleven SQL -files. The SQL outputs are **gitignored** — -`.gitignore` excludes `src/encrypted_domain/*/*_{types,functions,operators,aggregates}.sql`, -and `tasks/build.sh` regenerates them at the start of every build. There is -**no per-type codegen task**: one `cargo run -p eql-codegen` regenerates -every catalog type in a single deterministic run. - -| File | Content | -| --------------------------------- | ---------------------------------------------------------------------------------------- | -| `_types.sql` | Single idempotent `DO` block creating every domain; each domain `CHECK` pins the payload version (`VALUE->>'v' = '2'`) and required envelope/ciphertext/term keys; one `--! @brief` per domain | -| `_functions.sql` | One extractor per unique term, then 44 wrappers-or-blockers covering the surface | -| `_operators.sql` | 44 `CREATE OPERATOR` statements with planner metadata on supported ops | -| `_aggregates.sql` | MIN/MAX state functions + `CREATE AGGREGATE`; emitted only for ordered (ord-capable) domains | - -Every file: - -- Opens with the `-- AUTOMATICALLY GENERATED FILE.` marker (the project-wide - marker `docs:validate` greps on to skip generated SQL — - `crates/eql-codegen/src/consts.rs`). -- Declares its `-- REQUIRE:` edges in dependency order — types files - require `src/schema-v3.sql`; function files require schema, types, and - `src/encrypted_domain/functions.sql` plus each term's `requires` set; - operator files require `src/schema-v3.sql`, types, and their domain's - function file; aggregate files require `src/schema-v3.sql`, types, and - their domain's function and operator files. -- Carries Doxygen `--! @file` / `--! @brief` headers describing its role. - -### Function-count totals per domain - -| Domain terms | Extractors | Wrappers | Blockers | Functions | Operators | -| ---------------- | ---------: | -------: | -------: | --------: | --------: | -| none | 0 | 0 | 44 | 44 | 44 | -| `&[Term::Hm]` | 1 | 6 | 38 | 45 | 44 | -| `&[Term::Ore]` | 1 | 18 | 26 | 45 | 44 | - -Six wrappers for `Hm` = `=` and `<>` × three shapes. Eighteen for `Ore` -= six operators × three shapes. The 44-operator total never moves; the -wrapper/blocker split is what shifts, and native `jsonb` fallback -operators are always blockers. (Pinned by `storage_functions_file_is_all_blockers`, -`eq_functions_file_counts`, `ore_functions_file_counts`.) - -The table above covers `_functions.sql` only. Ordered domains -additionally emit `_aggregates.sql` — two state functions -(`min_sfunc`, `max_sfunc`) and two `CREATE AGGREGATE` declarations -(`eql_v3.min`, `eql_v3.max`). Each aggregate declares -`combinefunc = ` and `parallel = safe`: min/max are associative, so -the state function doubles as the combine function, enabling partial and -parallel aggregation on large `GROUP BY` ORE workloads with no decryption. - -## 7. Invariants the generator enforces - -The generator's job is partly to write SQL and partly to make incorrect -SQL unreachable. Invariants encoded in the renderers / templates and -guarded by `#[test]`s in `crates/eql-codegen/src/generate.rs`: - -- **Blockers are never `STRICT` and always `plpgsql`.** The - unsupported-operator template emits each blocker as `IMMUTABLE PARALLEL - SAFE` / `LANGUAGE plpgsql` without `STRICT`, so a `NULL` argument still - reaches the `RAISE`. `blockers_are_never_strict_and_always_plpgsql` - asserts the storage domain (all blockers) contains no `STRICT` and as - many `LANGUAGE plpgsql` as `CREATE FUNCTION`. A `LANGUAGE sql` blocker - would be inlinable and could be elided when the result is provably - unused; `plpgsql` is opaque to the planner so the `RAISE` survives. -- **Wrappers and extractors are inlinable SQL.** They emit `LANGUAGE sql - IMMUTABLE STRICT PARALLEL SAFE` with a single-statement `SELECT` and **no - `SET search_path`** (`inlinable_functions_have_no_set_search_path`). A - pinned `search_path` disables inlining. `tasks/pin_search_path.sql` - recognises these functions structurally — by language (`sql`), volatility - (`IMMUTABLE`), and a jsonb-backed `DOMAIN` argument in the `eql_v3` - schema — and leaves them unpinned, with no per-type edit. -- **Aggregate state functions are the deliberate exception.** `min_sfunc` / - `max_sfunc` are `LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE` *with* - a pinned `SET search_path` (`aggregate_state_functions_are_plpgsql_not_inlinable`). - They are aggregate transition functions, not index expressions, so - pinning is correct; the generated `min` / `max` aggregates are - allowlisted by name in `splinter.sh`. -- **SQL-literal injection is structurally prevented.** Every string - interpolated into a single-quoted SQL literal — payload keys, operator - symbols, domain names in `RAISE` messages — passes through `sql_str` - (`crates/eql-codegen/src/consts.rs`), which doubles embedded single - quotes. Today's catalog strings are all quote-free so it is a no-op, but - it guarantees a future quote-bearing string cannot break out of its - literal (`unsupported_entry_preserves_operator_literal_and_domain_lit_is_escaped`, - `domain_block_escapes_quote_bearing_name`). -- **No domain-over-domain.** Every domain is `CREATE DOMAIN eql_v3. - AS jsonb`, never `AS ` (`types_file_has_all_four_domains`). - PostgreSQL resolves operators against the underlying base type; a derived - domain would silently bypass the fixed operator surface. -- **No operator class on a domain.** The generator emits operators, not - operator classes. Callers index through the extractor function (e.g. - `USING btree (eql_v3.ord_term(col))`), whose return type already carries - a default opclass. -- **Ownership boundary.** `is_generated` recognises owned files by their - header marker; `ensure_generated_paths_writable` refuses to overwrite - anything else, and `clean_generated_files` deletes only files carrying - the marker (`crates/eql-codegen/src/writer.rs`). A hand-written file at a - generated path is a hard error, not a silent clobber. Stale generated - files for removed domains are cleaned before the new files land. - -## 8. Extension files - -`_extensions.sql` is the hand-written sibling. The generator never -creates, lists, or cleans it; it has no auto-generated header and must -declare its own `-- REQUIRE:` edges. Use it for behaviour that's specific -to the type and not part of the fixed surface — e.g. cross-domain casts, -helper functions, type-specific constraints. Unlike the generated -siblings, `_extensions.sql` IS committed. (Neither `int4` nor `int2` -ships one today — there is no committed `*_extensions.sql` in the tree.) - -`tasks/pin_search_path.sql` describes the fallback marker for -inline-critical extension functions that take no domain argument and so -escape the structural skip: - -```sql -COMMENT ON FUNCTION eql_v2.my_helper(...) IS 'eql-inline-critical: ...'; -``` - -The generator does **not** emit this marker; every function it produces -takes a domain argument and is covered by the structural skip -intrinsically. - -## 9. Lint and test integration - -The generator depends on two pieces of build tooling recognising its -output without per-type edits: - -- **`tasks/pin_search_path.sql`** — structural skip identifies - encrypted-domain functions by language (`sql`), volatility (`IMMUTABLE`), - and the presence of at least one argument typed as a jsonb-backed - `DOMAIN` in the `eql_v3` schema. New scalar types need no edit here. -- **`tasks/test/splinter.sh`** — name-based allowlist. The converged - wrapper / extractor names (`eq`, `neq`, `lt`, `lte`, `gt`, `gte`, - `eq_term`, `ord_term`) plus the generated `min` / `max` aggregates are - covered by `eql_v3`-schema entries. Splinter matches by name only, so a - new scalar type that uses the catalog extractors inherits coverage. - Adding a new term whose extractor has a new name requires a splinter - entry. - -## 10. Tests - -The generator's tests are Rust, run by `mise run test:codegen` -(`cargo test -p eql-scalars -p eql-codegen`) — no database required. The -broader `mise run test:crates` adds `cargo clippy ... -D warnings`. - -- **`eql-scalars` unit tests** — `rust_tests`, `term_tests`, - `term_helper_tests`, `fixture_tests`, `catalog_tests`, `invariant_tests`, - `values_tests` over `CATALOG`, the `Term`/`ScalarKind`/`Fixture` impls, - and the materialised `_VALUES` consts - (`crates/eql-scalars/src/lib.rs`). -- **`eql-codegen` unit tests** — file counts, language/volatility - invariants, escaping guards, and twin byte-identity - (`crates/eql-codegen/src/generate.rs` `#[cfg(test)]` module). -- **The parity gate** — `mise run codegen:parity` - (`tasks/codegen-parity.sh`). It runs `cargo run -p eql-codegen` into the - real tree, then: - 1. compares the int4 generated SQL **file set** against the golden under - `tests/codegen/reference/int4/*.sql`, excluding committed hand-written - files (`comm -23` of `ls` against `git ls-files`), so an extra or - dropped generated file fails; and - 2. diffs each golden file **byte-for-byte** against its generated - counterpart, after dropping the golden's single leading - `-- REFERENCE:` provenance line (`tail -n +2`). Both bodies start with - the `-- AUTOMATICALLY GENERATED FILE.` marker, so no header strip is - needed. - The same byte-for-byte assertion runs in-crate as - `crates/eql-codegen/tests/parity.rs` (`rust_generator_matches_int4_golden_files`) - and in the `generate.rs` golden tests. The golden reference — not any - Python oracle — is the sole contract that survives generator refactors. - -CI runs these in three jobs in `.github/workflows/test-eql.yml`: the -`test:crates` job (`Rust workspace crates`) compiles/lints/tests the -crates, the `codegen` job (`Encrypted-domain codegen`) runs `mise run -codegen:parity`, and the `matrix-coverage` job runs `mise run -test:matrix:inventory`. The codegen job is a prerequisite of the -PostgreSQL test matrix, so generated-SQL drift fails CI before any database -test runs. - -## 11. Adding a new scalar type - -From a generator perspective: - -1. **Add a `ScalarSpec` row to `eql_scalars::CATALOG`** - (`crates/eql-scalars/src/lib.rs`) — `token`, `kind`, the `domains` - slice, and the `fixtures` list. Term names must be `Term` variants and - the kind must be a `ScalarKind` variant, or it does not compile. If the - type needs a new scalar width, add a `ScalarKind` variant (with its - rust-type name, `MIN`/`MAX`/zero symbols, and bounds) and unit-test its - `impl`. New term behaviour belongs in the `Term` enum's `impl`, not in - catalog data. -2. **Materialise the value list** with `int_values!(_VALUES, , - );` next to `CATALOG`, and pin it with a `values_tests` - assertion. This is the single source the SQLx matrix reads as - `FIXTURE_VALUES`. There is nothing to regenerate-and-commit on the test - side — it is a compile-time const, not a generated file. -3. **Regenerate.** `cargo run -p eql-codegen` (or just `mise run build` — - the build runs the generator first). One run regenerates every catalog - type; there is no per-type codegen task. The generated - `*_{types,functions,operators,aggregates}.sql` are gitignored and never - committed. -4. **Hand-write** `_extensions.sql` if the type needs SQL beyond the - fixed surface, with explicit `-- REQUIRE:` edges. This file IS committed. -5. **Do not add a `tests/codegen/reference//` baseline.** `int4` is - the sole golden master for the type-generic generator: the templates are - pure token substitution, so a per-type baseline can only fail where - `int4`'s already would. Drift protection for the new type comes from the - `int4` reference (shared templates + `Term` enum), the catalog - `values_tests` pinning the materialised `_VALUES`, the - catalog/generator `#[test]`s, and the `ordered_numeric_matrix!` SQLx - suite (behaviour, not bytes). -6. **Wire the SQLx matrix oracle and snapshot the inventory.** The - implementation spec §2 lists the hand-maintained registration files. - Then run `mise run test:matrix:inventory`: it normalizes each present - type's `scalars::::*` test-name set to ``, asserts it equals - the single canonical `tests/sqlx/snapshots/matrix_tests.txt`, and - cross-checks the present type set against `cargo run -p eql-codegen -- - list-types`. There is **no per-type snapshot** — the per-type - `_matrix_tests.txt` files were collapsed into one token-normalized - snapshot. You only regenerate `matrix_tests.txt` when the macro's - emitted name set itself changes. A catalog type added without its matrix - wiring fails the cross-check (catalog has the type, binary has no - `scalars::::` tests). See `tests/sqlx/snapshots/README.md` and - the implementation spec §2 / §8. - -Adding a new **term** is a bigger move — edit the `Term` enum's `impl` -methods, add `#[test]`s, audit `splinter.sh` for a name collision if the -extractor name is new, and (because it changes the int4 surface) update the -golden reference under `tests/codegen/reference/int4/`. - -## 12. Out of scope - -`text` and `jsonb` are not materialised through this generator. The -`ScalarKind` enum carries `Text`/`Numeric`/`Jsonb` variants and the -`Fixture` enum carries their string-backed shapes at the capability layer, -but `CATALOG` declares only the integer scalars today, so no `text`/`jsonb` -SQL surface is generated. Text and JSONB encrypted behaviour lives on the -composite `eql_v2_encrypted` type and its hand-written operator surface in -`src/encrypted/` and `src/operators/`, not the scalar materializer. -`jsonb` in particular needs a separate SQL design beyond this -ordered-scalar materializer. diff --git a/docs/reference/encrypted-domain-implementation-spec.md b/docs/reference/encrypted-domain-implementation-spec.md deleted file mode 100644 index cf1a4b19..00000000 --- a/docs/reference/encrypted-domain-implementation-spec.md +++ /dev/null @@ -1,400 +0,0 @@ -# Encrypted Domain Type Implementation Spec - -This is the scalar encrypted-domain generator contract used by `int4`. -It applies to scalar domains whose searchable payloads are represented by -the fixed `Term` catalog in `crates/eql-scalars/src`. - -`text` and `jsonb` are outside this scalar materializer. - -## 1. Model - -Each generated domain is a concrete `jsonb` domain in the `eql_v3` -schema named `eql_v3.` (dropped by `DROP SCHEMA eql_v3 CASCADE`; -survives an `eql_v2` uninstall). A type's catalog row is intentionally -small — a `ScalarSpec` whose `domains` field lists each generated domain -as a `DomainSpec` (a `suffix` plus the fixed terms it carries): - -```rust -ScalarSpec { - token: "int4", - kind: ScalarKind::I32, - domains: &[ - DomainSpec { suffix: "", terms: &[] }, - DomainSpec { suffix: "_eq", terms: &[Term::Hm] }, - DomainSpec { suffix: "_ord_ore", terms: &[Term::Ore] }, - DomainSpec { suffix: "_ord", terms: &[Term::Ore] }, - ], - fixtures: &[/* see §9 */], -} -``` - -The `token` supplies the type token; each domain's full name is `token` -+ `suffix`. The generator emits domains in the order the `domains` slice -declares them, so order the slice the way you want the generated output to -read. Term capabilities are fixed by the `Term` enum -(`crates/eql-scalars/src`): - -| Term | JSON key | Extractor | Return type | Supported operators | -|---|---|---|---|---| -| `Hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` / `<>` | -| `Ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` / `<>` / `<` / `<=` / `>` / `>=` | - -For current `int4`, domains carrying `Ore` use JSON key `ob`, extractor -`ord_term`, and the ORE block supports equality plus ordering. A type -that needs a non-ORE equality term on an ordered domain needs a new -`Term` design, not a catalog flag. - -The row above declares two ordered domains, `int4_ord` and -`int4_ord_ore`, carrying the same term. They are intentional twins: the -generator emits byte-identical SQL (modulo type name) so callers can pick -a name that documents intent without committing to a term family in a -future migration. - -## 2. Checklist - -- [ ] Add a row to the Rust catalog `eql-scalars::CATALOG` - (`crates/eql-scalars/src/lib.rs`). A `ScalarSpec` declares: - - - `token` — the type token (e.g. `int8`); supplies `` everywhere. - - `kind` — the `ScalarKind` (`I16` / `I32` / `I64`), which carries the - Rust type name, the `MIN`/`MAX`/zero symbols, and the numeric bounds. - - `domains` — a `&[DomainSpec]`, each a `suffix` + the fixed `Term`s it - carries. The storage domain is suffix `""` with no terms; `_eq => [Hm]`; - `_ord` and `_ord_ore => [Ore]`. - - `fixtures` — the `Fixture` value list (see §9). It MUST include `Min`, - `Max`, and zero. - - Terms determine operator support: `Hm` provides `=` / `<>`; `Ore` - provides `=` / `<>` / `<` / `<=` / `>` / `>=`. There is no TOML manifest - and no Python: the catalog is the source of truth, validated by the - compiler (an undefined `Term` or unknown `ScalarKind` is a compile error) - plus catalog `#[test]`s over `CATALOG`. -- [ ] Materialise the type's plaintext fixture list as a typed const next to - `CATALOG`: add `int_values!(_VALUES, , );` (e.g. - `int_values!(INT8_VALUES, i64, INT8);`). The macro resolves the row's - `Fixture` list into a compile-time `&'static []` — the single source the - SQLx matrix reads as `FIXTURE_VALUES`. Pin the exact list with a - `values_tests` assertion. This replaces the old generated, committed - `_values.rs`. -- [ ] **If `` needs a new scalar width**, add a `ScalarKind` enum variant in - `crates/eql-scalars/src/lib.rs` with its rust-type name, `MIN`/`MAX`/zero - symbols, and numeric bounds, and unit-test its `impl` methods. New term - behaviour likewise belongs in the `Term` enum's `impl` methods with tests - — not in free-form catalog data. -- [ ] Run `cargo run -p eql-codegen` to materialise the generated SQL - (`src/encrypted_domain//_{types,functions,operators,aggregates}.sql`, - gitignored), or just `mise run build` — every build runs the generator - first. There is no per-type codegen task: one run generates every type from - `CATALOG`. The plaintext fixture list is **not** generated — it is - materialised from the catalog row at compile time (see the next step), so - there is nothing to regenerate-and-commit on the test side. -- [ ] Generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / - `*_aggregates.sql` are gitignored and never committed. The catalog - (`eql-scalars::CATALOG`) plus the `eql-codegen` renderers are the source - of truth. Change the catalog and rebuild; do not hand-edit generated SQL. -- [ ] Put optional hand-written SQL in - `src/encrypted_domain//_extensions.sql` with explicit - `-- REQUIRE:` edges. This file IS committed. -- [ ] Do **not** add a `tests/codegen/reference//` baseline. `int4` is the - single golden master for the type-generic generator: the SQL templates are - pure token substitution, so a per-type baseline can only fail when `int4`'s - already would. Drift protection for the new type comes from the `int4` - reference, the catalog `values_tests` pinning the materialised - `eql_scalars::_VALUES` const, the catalog/generator `#[test]`s - (`cargo test -p eql-scalars -p eql-codegen`), and the - `ordered_numeric_matrix!` SQLx suite (behaviour, not bytes). -- [ ] Wire the SQLx matrix oracle. The generated SQL is enough to install the - domains, but the `ordered_numeric_matrix!` suite only runs once the Rust - harness knows about the scalar. Copy each piece from the `int4` - reference — these are hand-maintained registration lists (the Phase-4 - `scalar_types!` registry, a separate plan, will collapse them): - - | File | Add | - |------|-----| - | `tests/sqlx/src/fixtures/eql_plaintext.rs` | A sealed `EqlPlaintext` impl for the scalar's Rust type: `impl Sealed for {}`, a `PlaintextSqlType` const for its base column type, `impl EqlPlaintext for ` (`CAST`, `PLAINTEXT_SQL_TYPE`, `to_plaintext` → the right `Plaintext` variant), plus the two `#[test]` casts. | - | `tests/sqlx/src/fixtures/eql_v2_.rs` | `use eql_scalars::_VALUES as VALUES;` then `crate::scalar_fixture!("eql_v2_", , VALUES);`. | - | `tests/sqlx/src/fixtures/mod.rs` | `pub mod eql_v2_;`. | - | `tests/sqlx/tests/generate_all_fixtures.rs` | An arm in `generate_for_token`: `"" => fixtures::eql_v2_::spec().run().await,`. The match is exhaustive over the catalog — a catalog token with no arm fails the generator loudly. | - | `tests/sqlx/src/scalar_domains.rs` | `impl ScalarType for ` — `PG_TYPE` (the base PG type, e.g. `"int8"`) and `FIXTURE_VALUES = eql_scalars::_VALUES`. | - | `tests/sqlx/tests/encrypted_domain/scalars/.rs` | `ordered_numeric_matrix! { suite = , scalar = , eql_type = "eql_v2_" }`. | - | `tests/sqlx/tests/encrypted_domain/scalars/mod.rs` | `pub mod ;`. | - - `` is the scalar's Rust type (`i32` for `int4`, `i16` for `int2`). - Forget one and the matrix simply does not run for the type — the matrix - inventory cross-check (next step) surfaces it, because the catalog has the - type but the binary has no `scalars::::` tests. -- [ ] Run `mise run test:matrix:inventory`. It verifies every present type's - token-normalized `scalars::::*` name set equals the single canonical - `tests/sqlx/snapshots/matrix_tests.txt`, and cross-checks the present type - set against `cargo run -p eql-codegen -- list-types`. You do **not** edit a - per-type snapshot — there is one canonical snapshot; you only regenerate it - when the macro's emitted name set itself changes. A catalog type missing - its matrix wiring fails the cross-check. See §8 and - `tests/sqlx/snapshots/README.md`. -- [ ] Run `mise run test:codegen` (`cargo test -p eql-scalars -p eql-codegen`), - the relevant SQLx suites, and the PostgreSQL matrix before merging. - -## 3. Domain Generation - -The generator emits `src/encrypted_domain//_types.sql` (gitignored; -materialised on every `mise run build` and every `cargo run -p eql-codegen`) -with one idempotent `DO $$ ... $$` block. Domain `CHECK` -constraints always require: - -- fixed envelope keys `v` and `i`; -- ciphertext key `c`; -- catalog JSON keys for the listed terms; -- the envelope version value: `VALUE->>'v' = '2'`, matching the repo-wide - `eql_v2._encrypted_check_v` rule (`src/encrypted/constraints.sql`). - -For example, a domain with `["ore"]` requires `v`, `i`, `c`, and `ob` present, -with `v` pinned to `2`. Beyond key presence and the version value, a malformed -term can still fail later inside its extractor unless a future catalog design -adds stronger validation. - -Every generated domain is a concrete domain over `jsonb` in the `eql_v3` -schema. Do not define one generated domain over another generated domain; -PostgreSQL resolves operators against the underlying base type in ways -that bypass the fixed operator surface. - -## 4. Extractors And Wrappers - -Extractor names and return types come from the `Term` enum -(`crates/eql-scalars/src`), not from catalog data. Generated extractors and -supported comparison wrappers are inline-friendly SQL functions: - -```sql -LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT ... $$; -``` - -Extractors and comparison wrappers must not carry a pinned `search_path` -— a `SET` clause disables inlining and reverts index-backed queries to -seq scans. The build tooling recognises these generated functions -structurally, so the generator does not emit `eql-inline-critical` -markers. Aggregate state functions are the one deliberate exception — see -§5 — because they are never index expressions. - -Unsupported operators route to blockers. Blockers are `plpgsql`, -`IMMUTABLE`, `PARALLEL SAFE`, and intentionally not `STRICT`. Both -choices are deliberate: - -- **`plpgsql`, not `sql`.** A `LANGUAGE sql` body would be inlinable, and - the planner could elide the call when the result is provably unused - (dead `CASE` branch, folded predicate), letting a blocked operator - appear to succeed. `plpgsql` is opaque to the planner, so the call — - and its `RAISE` — always survives. -- **Not `STRICT`.** A `STRICT` blocker lets PostgreSQL skip the body and - return `NULL` on a `NULL` argument, silently bypassing the - unsupported-operator exception. - -## 5. Operators - -Every generated domain declares supported scalar comparison operators plus -blockers for the native `jsonb` operator surface that PostgreSQL could -otherwise reach through domain-to-base-type fallback. Each domain emits -44 `CREATE OPERATOR` statements. Supported operators route to wrappers; -everything else routes to blockers. - -| Operators | Forms | -|---|---| -| `=` `<>` `<` `<=` `>` `>=` `@>` `<@` | `(domain, domain)` · `(domain, jsonb)` · `(jsonb, domain)` | -| `->` `->>` | `(domain, text)` · `(domain, integer)` · `(jsonb, domain)` | -| `?` | `(domain, text)` | -| `?\|` `?&` | `(domain, text[])` | -| `@?` `@@` | `(domain, jsonpath)` | -| `#>` `#>>` `#-` | `(domain, text[])` | -| `-` | `(domain, text)` · `(domain, integer)` · `(domain, text[])` | -| `\|\|` | `(domain, domain)` · `(domain, jsonb)` · `(jsonb, domain)` | - -Function counts: - -| Domain terms | Extractors | Wrappers | Blockers | Functions | Operators | -|---|---:|---:|---:|---:|---:| -| none | 0 | 0 | 44 | 44 | 44 | -| `hm` | 1 (`eq_term`) | 6 | 38 | 45 | 44 | -| `ore` | 1 (`ord_term`) | 18 | 26 | 45 | 44 | - -Supported comparison operators carry planner metadata such as -`COMMUTATOR`, `NEGATOR`, `RESTRICT`, and `JOIN`. Blocker operators keep -minimal metadata because they should never be planner-visible supported -paths. - -PostgreSQL's operator resolver still prefers the built-in `jsonb` operator -for untyped string literals in forms such as `payload::eql_v3.int4 ? 'c'`. -Use typed parameters or explicit casts (`'c'::text`) to route those forms -to the generated blocker. The generated surface blocks the typed native -operator shapes exposed by the catalog. - -### Aggregates - -Each ordered (ord-capable) domain additionally gets a generated -`_aggregates.sql` file declaring `MIN` / `MAX`: - -- two state functions, `eql_v3.min_sfunc` and `eql_v3.max_sfunc`, and -- two aggregates, `eql_v3.min()` and `eql_v3.max()`. - -Comparison routes through the domain's `<` / `>` operator (the ORE block -term — no decryption). The state functions are `LANGUAGE plpgsql -IMMUTABLE STRICT PARALLEL SAFE` **with** a pinned `SET search_path`. This is -the one place the "no pinned `search_path`" rule of §4 does not apply: -aggregate transition functions are never index expressions, so pinning is -correct. `STRICT` makes PostgreSQL seed the running state with the first -non-NULL value and skip NULLs, so an all-NULL group returns NULL. - -Each `CREATE AGGREGATE` declares `combinefunc = ` and -`parallel = safe`: min/max are associative, so the state function doubles as -the combine function, and with a `PARALLEL SAFE` sfunc/combinefunc -PostgreSQL can use partial and parallel aggregation on the large `GROUP BY` -ORE workloads these aggregates exist to serve — still with no decryption. -Storage-only and equality-only domains have no comparator and emit no -aggregate file. - -## 6. Extension Files - -Optional hand-written SQL beyond the fixed scalar surface belongs in: - -```text -src/encrypted_domain//_extensions.sql -``` - -The generator must not create this file, list it in the catalog, add an -auto-generated header, or clean it during regeneration. The file must -declare its own `-- REQUIRE:` edges, usually to `_types.sql` and -whichever generated function or operator file it extends. Unlike the -generated siblings, `_extensions.sql` IS committed. - -## 7. Indexing - -Do not create operator classes on generated domains. Index through -the extractor: - -```sql -CREATE INDEX ... ON table_name USING btree (eql_v3.ord_term(col)); -CREATE INDEX ... ON table_name USING hash (eql_v3.eq_term(col)); -``` - -The extractor return type must already have the needed PostgreSQL access -method support. `ore` depends on -`src/ore_block_u64_8_256/functions.sql` and -`src/ore_block_u64_8_256/operators.sql`; `hm` depends on -`src/hmac_256/functions.sql`. - -## 8. Tests - -Cover each generated domain with SQLx tests appropriate to its terms: - -- supported operators return correct rows for all argument forms; -- unsupported operators raise the expected error for all forms; -- blockers raise on `NULL` input; -- supported wrappers return `NULL` for `NULL` operands; -- functional indexes engage and return correct rows; -- constant-on-left comparisons engage the index where applicable; -- domain `CHECK` rejects non-object and under-populated payloads; -- real typed columns are tested, not only cast literals; -- generated ordered-domain twins remain byte-identical modulo type name - (the shared generator is anchored by the `int4` golden master in - `tests/codegen/reference/int4/` via the eql-codegen parity test; - new types add no baseline of their own — see §2). - -For ordered numeric scalars this coverage is generated by the -`ordered_numeric_matrix!` convention wrapper in `tests/sqlx/src/matrix.rs`: -one `impl ScalarType` (`tests/sqlx/src/scalar_domains.rs`) plus a single -invocation taking `suite`, `scalar`, and `eql_type`. The matrix derives -its comparison pivots — the scalar's `MIN`, `MAX`, and zero -(`Default::default()`) — from the type rather than a hand-written list, so -the invocation carries no pivot argument. Equality-only scalars use the -sibling `eq_only_scalar_matrix!`. The `matrix.rs` module header is the -canonical, current list of the test categories the matrix emits (sanity, -correctness, cross-shape, supported-NULL, blocker raises, index engagement, -ORDER BY, ORDER BY USING) — read it rather than maintaining a duplicate -count here. - -For ordered `int4`, keep the assertion that distinct plaintext values -produce distinct ORE blocks. Do not add assertions for term behavior that -the catalog does not promise. - -### Matrix coverage inventory snapshot - -The *set of test names* the matrix emits is guarded by ONE committed, -token-normalized snapshot at `tests/sqlx/snapshots/matrix_tests.txt` — the -sorted inventory of every `scalars::::*` test name with the type token -replaced by the literal ``. (The per-type `_matrix_tests.txt` files are -gone: they were byte-identical modulo the token, so one canonical set plus a -per-type normalize-and-compare carries the same signal at a fraction of the -committed surface.) This is the guard that catches a silently dropped, renamed, -or `#[cfg]`-gated matrix test, a behaviour the SQLx assertions above cannot see. -The snapshot is a committed test baseline, **not** gitignored generated SQL. - -`mise run test:matrix:inventory` discovers the present scalar types from the -`encrypted_domain` binary's `--list`, normalizes each type's token to ``, -asserts every type's set equals the canonical snapshot, and cross-checks the -discovered type set against `cargo run -p eql-codegen -- list-types` (the catalog -is the single source). The CI `matrix-coverage` job gates it. **`tests/sqlx/snapshots/README.md` -is the source of truth** for the mechanics (pinned feature set, the catalog -cross-check, the CI diff, and when to regenerate); see it rather than -duplicating the detail here. - -## 9. Fixtures - -Fixture generation should use real encrypted payloads produced through -CipherStash Proxy. A single payload table may carry every term needed by -the generated domains for that type. For `int4`, the payloads carry `c`, -`hm`, and `ob`; the equality domain reads `hm`, and ordered domains read -`ob`. - -Choose values so range operators produce distinguishable result counts, -include useful boundaries, and cover omitted-term negative cases. For a -scalar driven by `ordered_numeric_matrix!`, the fixture **must** include -the type's `MIN`, `MAX`, and zero (`Default::default()`): the matrix uses -those three as comparison pivots and fetches each one's ciphertext from the -fixture via `fetch_fixture_payload`, which fails loudly if the row is -absent. - -### Single-sourcing the value list - -The plaintext value list is declared **once**, in the catalog row's `fixtures` -field, and materialised into a typed Rust const — never hand-maintained in two -places: - -```rust -fixtures: &[Fixture::Min, Fixture::N(-100), Fixture::N(-1), Fixture::Zero, - Fixture::N(1), Fixture::N(2), Fixture::N(5), Fixture::N(10), - Fixture::N(17), Fixture::N(25), Fixture::N(42), Fixture::N(50), - Fixture::N(100), Fixture::N(250), Fixture::N(1000), - Fixture::N(9999), Fixture::Max], -``` - -`Fixture::Min` / `Fixture::Max` / `Fixture::Zero` resolve to the scalar's Rust -named consts (for `int4`: `i32::MIN`, `i32::MAX`, `0`); every `Fixture::N(_)` is -a numeric literal validated against the `ScalarKind`'s representable range by a -catalog `#[test]` (`numeric_value` is infallible, so the range check is the -explicit invariant `every_fixture_value_is_within_kind_bounds`). The same test -enforces the matrix invariant: the set **must** include `Min`, `Max`, and zero, -or the test fails (the compile-time analogue of the old `load_spec` validation). - -The `int_values!` macro (in `crates/eql-scalars/src/lib.rs`) materialises that -`Fixture` list into a `pub const _VALUES: &[]` at compile -time, sitting next to `CATALOG`. Both consumers reference that single symbol — -the fixture generator (`fixtures::eql_v2_::spec`) and the matrix oracle -(`impl ScalarType for { const FIXTURE_VALUES = eql_scalars::_VALUES }`) -— so the oracle cannot drift from the values the generator encrypts. There is no -generated `_values.rs`: a Rust source of truth does not round-trip through -generated Rust. The exact list is pinned by a `values_tests` assertion, and the -`Fixture`-list invariants (`Min`/`Max`/zero present, in-bounds) by the catalog -`#[test]`s. - -## 10. Build And Verification - -- `cargo run -p eql-codegen` (optional; refreshes all generated SQL from the - catalog before a full build) -- `mise run test:codegen` (`cargo test -p eql-scalars -p eql-codegen`) -- `mise run clean && mise run build` (regenerates every type's SQL from - the catalog first, then builds the release artefacts) -- relevant SQLx suites -- `mise run test` across supported PostgreSQL versions -- `mise run --output prefix test:splinter --postgres 17` after a - PostgreSQL 17 install has built EQL - -The CI codegen job should remain a prerequisite of the PostgreSQL test -matrix so generated SQL drift is caught before database tests run. diff --git a/docs/reference/eql-functions.md b/docs/reference/eql-functions.md index e517e63e..cfca800c 100644 --- a/docs/reference/eql-functions.md +++ b/docs/reference/eql-functions.md @@ -426,7 +426,7 @@ eql_v2.ste_vec(val jsonb) RETURNS eql_v2_encrypted[] Extract the equality (`hm`) or ordering (`ob`) index term from a scalar encrypted-domain value. Generated per eq/ord-capable variant of every -scalar type — see [Encrypted-Domain Code Generator](./encrypted-domain-generator.md). +scalar type — see [Adding a Scalar Encrypted-Domain Type](./adding-a-scalar-encrypted-domain-type.md). The argument type selects the overload, and both are inlinable so a functional index built on the extractor engages. The extractors live in the `eql_v3` schema; their return types remain the core `eql_v2` @@ -449,7 +449,7 @@ CREATE INDEX ON users USING btree (eql_v3.ord_term(salary_encrypted)); > The full per-domain operator/wrapper/blocker surface (and the > `eql_v3.` / `_eq` / `_ord` / `_ord_ore` domain types themselves) is > documented in [SQL support](./sql-support.md#encrypted-domain-scalar-types-eql_v3t) -> and the [generator reference](./encrypted-domain-generator.md). +> and the [scalar encrypted-domain type reference](./adding-a-scalar-encrypted-domain-type.md). --- diff --git a/docs/reference/sql-support.md b/docs/reference/sql-support.md index d15be2ee..c5204850 100644 --- a/docs/reference/sql-support.md +++ b/docs/reference/sql-support.md @@ -61,7 +61,7 @@ Use the equivalent [`jsonb_path_query`](#jsonb-functions-and-selectors-enabled-b ## Encrypted-domain scalar types (`eql_v3.`) -Scalar encrypted-domain types (e.g. `eql_v3.int4`; see the [generator reference](./encrypted-domain-generator.md)) are a different access model from the matrix above. Instead of configuring a search index on an `eql_v2_encrypted` column, you type the column as a specific domain *variant* whose operator surface is fixed at generation time. The index terms travel in the payload; there is no `add_search_config` step. The domains and their operator surface live in the `eql_v3` schema (dropped by `DROP SCHEMA eql_v3 CASCADE`, and they survive an `eql_v2` uninstall); their extracted index-term types remain the core `eql_v2` types. +Scalar encrypted-domain types (e.g. `eql_v3.int4`; see [Adding a Scalar Encrypted-Domain Type](./adding-a-scalar-encrypted-domain-type.md)) are a different access model from the matrix above. Instead of configuring a search index on an `eql_v2_encrypted` column, you type the column as a specific domain *variant* whose operator surface is fixed at generation time. The index terms travel in the payload; there is no `add_search_config` step. The domains and their operator surface live in the `eql_v3` schema (dropped by `DROP SCHEMA eql_v3 CASCADE`, and they survive an `eql_v2` uninstall); their extracted index-term types remain the core `eql_v2` types. Each scalar type `` generates one storage-only variant plus eq/ord query variants: diff --git a/tests/codegen/reference/README.md b/tests/codegen/reference/README.md index ae7204ab..186d6d5c 100644 --- a/tests/codegen/reference/README.md +++ b/tests/codegen/reference/README.md @@ -12,7 +12,7 @@ The parity gate runs the generator (`cargo run -p eql-codegen`, which writes the The golden reference, not any retired generator, is the sole oracle. If the generator diverges, either it regressed (fix `crates/eql-codegen`) or the reference is being updated deliberately (commit the new `int4` reference in the same PR). -See `docs/reference/encrypted-domain-generator.md` for the full generator story (manifest-free catalog, templates, term capabilities). +See `docs/reference/adding-a-scalar-encrypted-domain-type.md` §6 for the full generator story (catalog source of truth, minijinja templates, term capabilities). ## No committed fixture values diff --git a/tests/sqlx/snapshots/README.md b/tests/sqlx/snapshots/README.md index 213b034f..ed363608 100644 --- a/tests/sqlx/snapshots/README.md +++ b/tests/sqlx/snapshots/README.md @@ -60,8 +60,9 @@ catalog cross-check) fails the job. ## When you must update this - **Adding a new scalar type** → add the catalog row in - `eql-scalars::CATALOG`, wire the SQLx matrix oracle (see the implementation - spec §2), then run `mise run test:matrix:inventory`. If the new type's + `eql-scalars::CATALOG`, wire the SQLx matrix oracle (see + `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3), then run + `mise run test:matrix:inventory`. If the new type's normalized name set matches the canonical snapshot (it will, for a standard `ordered_numeric_matrix!` type), no snapshot edit is needed — the cross-check just confirms the type is wired. @@ -76,4 +77,4 @@ catalog cross-check) fails the job. | sed -e 's/^scalars::int4::/scalars::::/' -e 's/_int4_/__/g' | LC_ALL=C sort > snapshots/matrix_tests.txt ``` -See `docs/reference/encrypted-domain-implementation-spec.md` §2 and §8. +See `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3 (matrix oracle + inventory snapshot). From d0f78b7c11a079e791fe2d5851af11fdcdcf11f6 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 12:14:51 +1000 Subject: [PATCH 59/93] refactor(scalars): generate SQLx matrix harness from one declarative list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the seven per-type hand registrations with a single `scalar_harness!` list expanded by the new `eql-tests-macros` proc-macros: the `ScalarType` impls, `eql_v2_` fixture modules, `ordered_numeric_matrix!` suites, and the `generate_for_token` dispatch are all generated from that one list. Derive `EqlPlaintext`'s `CAST` / `PLAINTEXT_SQL_TYPE` from a single `const KIND: ScalarKind` (via `cast_for_kind` / `plaintext_sql_type_for_kind` const-fn defaults), collapsing each impl to `KIND` + `to_plaintext`. Add `eql-tests-macros` to `test:crates` (clippy -D warnings + unit tests). Update the guide §3: adding a type drops from seven harness edits to one `scalar_harness!` line. Test-harness/tooling only — no change to generated SQL or the public API. --- Cargo.lock | 10 + Cargo.toml | 3 + crates/eql-tests-macros/Cargo.toml | 13 + crates/eql-tests-macros/src/lib.rs | 326 ++++++++++++++++++ .../adding-a-scalar-encrypted-domain-type.md | 34 +- mise.toml | 16 +- tests/sqlx/Cargo.toml | 1 + tests/sqlx/src/fixtures/cipherstash.rs | 3 +- tests/sqlx/src/fixtures/eql_plaintext.rs | 47 ++- tests/sqlx/src/fixtures/eql_v2_int2.rs | 12 - tests/sqlx/src/fixtures/eql_v2_int4.rs | 11 - tests/sqlx/src/fixtures/mod.rs | 14 +- tests/sqlx/src/lib.rs | 8 + tests/sqlx/src/scalar_domains.rs | 34 +- tests/sqlx/src/scalar_harness.rs | 63 ++++ .../tests/encrypted_domain/scalars/int2.rs | 14 - .../tests/encrypted_domain/scalars/int4.rs | 14 - .../tests/encrypted_domain/scalars/mod.rs | 12 +- tests/sqlx/tests/generate_all_fixtures.rs | 21 +- 19 files changed, 528 insertions(+), 128 deletions(-) create mode 100644 crates/eql-tests-macros/Cargo.toml create mode 100644 crates/eql-tests-macros/src/lib.rs delete mode 100644 tests/sqlx/src/fixtures/eql_v2_int2.rs delete mode 100644 tests/sqlx/src/fixtures/eql_v2_int4.rs create mode 100644 tests/sqlx/src/scalar_harness.rs delete mode 100644 tests/sqlx/tests/encrypted_domain/scalars/int2.rs delete mode 100644 tests/sqlx/tests/encrypted_domain/scalars/int4.rs diff --git a/Cargo.lock b/Cargo.lock index 2d902e93..3f44eee0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1168,6 +1168,15 @@ dependencies = [ name = "eql-scalars" version = "0.1.0" +[[package]] +name = "eql-tests-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "eql_tests" version = "0.1.0" @@ -1175,6 +1184,7 @@ dependencies = [ "anyhow", "cipherstash-client", "eql-scalars", + "eql-tests-macros", "hex", "jsonschema", "paste", diff --git a/Cargo.toml b/Cargo.toml index 0baa1aad..6e0b6760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ # truth for the Rust generator (Plan 2) and the SQLx test # harness (Plan 3). # crates/eql-codegen — the SQL generator binary (stub here; Plan 2 fills it in). +# crates/eql-tests-macros — proc-macros expanding the single scalar-harness +# list into the per-type SQLx-matrix wiring. # tests/sqlx — the existing `eql_tests` SQLx integration crate. # # resolver = "2" keeps the heavy test-crate feature set (sqlx/tokio/cipherstash- @@ -17,6 +19,7 @@ resolver = "2" members = [ "crates/eql-scalars", "crates/eql-codegen", + "crates/eql-tests-macros", "tests/sqlx", ] default-members = ["tests/sqlx"] diff --git a/crates/eql-tests-macros/Cargo.toml b/crates/eql-tests-macros/Cargo.toml new file mode 100644 index 00000000..8c352a9a --- /dev/null +++ b/crates/eql-tests-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "eql-tests-macros" +version = "0.1.0" +edition = "2021" +description = "Proc-macros that expand a single scalar-harness list into the per-type SQLx-matrix wiring (ScalarType impls, fixture modules, matrix suites, fixture dispatch)." + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/crates/eql-tests-macros/src/lib.rs b/crates/eql-tests-macros/src/lib.rs new file mode 100644 index 00000000..a2ceb198 --- /dev/null +++ b/crates/eql-tests-macros/src/lib.rs @@ -0,0 +1,326 @@ +//! Proc-macros that expand ONE declarative scalar-harness list into all the +//! per-type SQLx-matrix wiring that used to be hand-maintained across four +//! locations. +//! +//! # Why a proc-macro (and why more than one) +//! +//! Adding a scalar encrypted-domain type used to require editing several +//! files in lock-step (see +//! `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3). The harness +//! wiring — everything *except* the catalog row in `eql-scalars` and the +//! `EqlPlaintext` impl, which are owned by a separate task — is now driven by +//! a SINGLE list, e.g.: +//! +//! ```ignore +//! eql_tests::scalar_harness! { +//! int4 => i32, +//! int2 => i16, +//! int8 => i64, +//! } +//! ``` +//! +//! Rust macros emit code into the crate/module where they are invoked, and the +//! harness pieces live in three *different* compilation contexts: +//! +//! 1. the `ScalarType` impls + the fixture modules live in the `eql-tests` +//! **library** (`src/`); +//! 2. the `ordered_numeric_matrix!` suites live in the `encrypted_domain` +//! **integration-test binary** (`tests/`), a separate crate target; +//! 3. the `generate_all_fixtures` dispatch lives in *another* integration-test +//! binary (`tests/generate_all_fixtures.rs`). +//! +//! A single macro invocation cannot emit into all three at once, so the design +//! is: ONE canonical list, captured by the `macro_rules! scalar_harness!` +//! re-exported from `eql-tests` (see `tests/sqlx/src/scalar_harness.rs`), which +//! forwards that same list to whichever proc-macro is appropriate for the call +//! site. Each proc-macro below parses the identical `token => rust_type` list +//! and emits only the items that belong where it is invoked. The list itself is +//! the single source of truth; the four emitters are pure functions of it. +//! +//! Each entry is `token => rust_type` where `token` is the Postgres type token +//! (e.g. `int4`, also the fixture/domain name suffix) and `rust_type` is the +//! Rust plaintext type (`i32`). The catalog value const is derived by +//! upper-casing the token and appending `_VALUES` (`int4` -> `INT4_VALUES`), +//! matching `eql_scalars::INT4_VALUES`. +//! +//! The four emitters are split into thin `#[proc_macro]` shims and pure +//! `proc_macro2::TokenStream` core functions (`*_tokens`) so the core logic is +//! unit-testable without a consumer crate — see the `tests` module. + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{Ident, Token, Type}; + +/// One `token => rust_type` entry from the harness list. +struct ScalarEntry { + /// The Postgres type token (`int4`), also the fixture/domain name suffix + /// and the `suite` ident in the matrix invocation. + token: Ident, + /// The Rust plaintext type (`i32`). + rust_type: Type, +} + +impl Parse for ScalarEntry { + fn parse(input: ParseStream) -> syn::Result { + let token: Ident = input.parse()?; + input.parse::]>()?; + let rust_type: Type = input.parse()?; + Ok(ScalarEntry { token, rust_type }) + } +} + +/// The whole comma-separated list, with optional trailing comma. +struct ScalarList { + entries: Vec, +} + +impl Parse for ScalarList { + fn parse(input: ParseStream) -> syn::Result { + let punctuated = Punctuated::::parse_terminated(input)?; + Ok(ScalarList { + entries: punctuated.into_iter().collect(), + }) + } +} + +/// `int4` -> `INT4_VALUES` — the catalog value const for a token. Mirrors the +/// `int_values!(INT4_VALUES, ...)` naming in `eql_scalars`. +fn values_const_ident(token: &Ident) -> Ident { + format_ident!("{}_VALUES", token.to_string().to_uppercase()) +} + +// --------------------------------------------------------------------------- +// Core token generators (pure, unit-testable). +// --------------------------------------------------------------------------- + +/// Emit one `impl ScalarType for ` per entry. See +/// [`emit_scalar_type_impls`]. +fn scalar_type_impls_tokens(list: &ScalarList) -> TokenStream2 { + let impls = list.entries.iter().map(|e| { + let token_str = e.token.to_string(); + let rust_type = &e.rust_type; + let values = values_const_ident(&e.token); + quote! { + impl ScalarType for #rust_type { + const PG_TYPE: &'static str = #token_str; + /// Single-sourced from the matching row in `eql-scalars::CATALOG` + /// (`eql_scalars::*_VALUES`, materialised from its `Fixture` + /// list) — the same list the fixture generator encrypts, so the + /// oracle cannot drift from the fixture. + const FIXTURE_VALUES: &'static [#rust_type] = ::eql_scalars::#values; + } + } + }); + quote! { #(#impls)* } +} + +/// Emit one `pub mod eql_v2_ { ... }` per entry. See +/// [`emit_scalar_fixture_modules`]. +fn scalar_fixture_modules_tokens(list: &ScalarList) -> TokenStream2 { + let mods = list.entries.iter().map(|e| { + let token_str = e.token.to_string(); + let rust_type = &e.rust_type; + let values = values_const_ident(&e.token); + let mod_ident = format_ident!("eql_v2_{}", e.token); + let fixture_name = format!("eql_v2_{}", token_str); + quote! { + #[doc = concat!("`eql_v2_", #token_str, "` scalar fixture — generated by `scalar_harness!`.")] + pub mod #mod_ident { + use ::eql_scalars::#values as VALUES; + // `scalar_fixture!` is `#[macro_export]`ed from this crate + // (`eql-tests`), so `crate::scalar_fixture!` resolves here since + // the modules are emitted into the `eql-tests` lib. + crate::scalar_fixture!(#fixture_name, #rust_type, VALUES); + } + } + }); + quote! { #(#mods)* } +} + +/// Emit the `generate_for_token` dispatch fn. See [`emit_fixture_dispatch`]. +fn fixture_dispatch_tokens(list: &ScalarList) -> TokenStream2 { + let arms = list.entries.iter().map(|e| { + let token_str = e.token.to_string(); + let mod_ident = format_ident!("eql_v2_{}", e.token); + quote! { + #token_str => ::eql_tests::fixtures::#mod_ident::spec().run().await, + } + }); + quote! { + /// Map a catalog token to its fixture generator and run it. Generated by + /// `scalar_harness!` from the single harness list. A token present in the + /// catalog but absent from that list hits the catch-all below and fails + /// loudly so a new scalar type cannot silently skip fixture generation. + async fn generate_for_token(token: &str) -> anyhow::Result<()> { + match token { + #(#arms)* + other => anyhow::bail!( + "no fixture generator wired for catalog token '{other}'. \ + Add it to the scalar_harness! list (tests/sqlx/src/scalar_harness.rs). \ + See the encrypted-domain spec §3." + ), + } + } + } +} + +/// Emit one `pub mod { ordered_numeric_matrix! { ... } }` per entry. +/// See [`emit_scalar_matrix_suites`]. +fn scalar_matrix_suites_tokens(list: &ScalarList) -> TokenStream2 { + let mods = list.entries.iter().map(|e| { + let token = &e.token; + let token_str = e.token.to_string(); + let rust_type = &e.rust_type; + let eql_type = format!("eql_v2_{}", token_str); + quote! { + #[doc = concat!("`eql_v2_", #token_str, "` matrix suite — generated by `scalar_harness!`.")] + pub mod #token { + ::eql_tests::ordered_numeric_matrix! { + suite = #token, + scalar = #rust_type, + eql_type = #eql_type, + } + } + } + }); + quote! { #(#mods)* } +} + +// --------------------------------------------------------------------------- +// Proc-macro shims. +// --------------------------------------------------------------------------- + +/// Emit one `impl ScalarType for ` per entry. +/// +/// Invoked (via `scalar_harness!`) inside `tests/sqlx/src/scalar_domains.rs`, +/// so the impls land in the `eql-tests` library next to the trait. Replaces the +/// three hand-written impls. `PG_TYPE` is the token string; `FIXTURE_VALUES` +/// is the catalog const `eql_scalars::_VALUES` — single-sourced +/// from the catalog so the oracle cannot drift from the fixture. +#[proc_macro] +pub fn emit_scalar_type_impls(input: TokenStream) -> TokenStream { + let list = syn::parse_macro_input!(input as ScalarList); + scalar_type_impls_tokens(&list).into() +} + +/// Emit one `pub mod eql_v2_ { ... }` per entry. +/// +/// Invoked (via `scalar_harness!`) inside `tests/sqlx/src/fixtures/mod.rs`, so +/// the modules land at `crate::fixtures::eql_v2_` — the path the matrix +/// and the fixture dispatch reference. Each module body is exactly what the old +/// per-type `fixtures/eql_v2_.rs` file contained: a `use` of the catalog +/// value const plus a `scalar_fixture!` invocation. The per-type files are +/// therefore deleted. +#[proc_macro] +pub fn emit_scalar_fixture_modules(input: TokenStream) -> TokenStream { + let list = syn::parse_macro_input!(input as ScalarList); + scalar_fixture_modules_tokens(&list).into() +} + +/// Emit the `generate_for_token` dispatch as a single function driven by the +/// list. +/// +/// Invoked (via `scalar_harness!`) inside `tests/generate_all_fixtures.rs`. +/// Emits an `async fn generate_for_token(token: &str) -> anyhow::Result<()>` +/// with one match arm per entry plus a loud catch-all, replacing the +/// hand-written match. A catalog token with no entry here (i.e. not in the +/// harness list) hits the catch-all and fails the generator loudly — preserving +/// the "a catalog type with no harness wiring fails loudly" guarantee at +/// generation time (the matrix-inventory cross-check enforces it at test time). +#[proc_macro] +pub fn emit_fixture_dispatch(input: TokenStream) -> TokenStream { + let list = syn::parse_macro_input!(input as ScalarList); + fixture_dispatch_tokens(&list).into() +} + +/// Emit one `pub mod { ordered_numeric_matrix! { ... } }` per entry. +/// +/// Invoked (via `scalar_harness!`) inside +/// `tests/sqlx/tests/encrypted_domain/scalars/mod.rs`, so the matrix suites land +/// in the `encrypted_domain` integration-test binary — the only place +/// `#[sqlx::test]` suites belong. This is a SEPARATE proc-macro from the +/// lib-side ones because that binary is a different crate target: a single macro +/// invocation in `src/` could not emit into `tests/`. The generated module + +/// `ordered_numeric_matrix!` invocation are byte-equivalent to the old per-type +/// `scalars/.rs` files, so the emitted test names (`scalars:::: +/// matrix_*`) are unchanged and the token-normalized `matrix_tests.txt` snapshot +/// keeps matching. +#[proc_macro] +pub fn emit_scalar_matrix_suites(input: TokenStream) -> TokenStream { + let list = syn::parse_macro_input!(input as ScalarList); + scalar_matrix_suites_tokens(&list).into() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample() -> ScalarList { + syn::parse_str::("int4 => i32, int8 => i64,").unwrap() + } + + /// The token-stream comparison is on the normalized `to_string()` form so + /// whitespace/formatting differences don't make the assertions brittle. + fn norm(ts: &TokenStream2) -> String { + ts.to_string() + } + + #[test] + fn values_const_name_is_uppercased_with_suffix() { + let token: Ident = syn::parse_str("int8").unwrap(); + assert_eq!(values_const_ident(&token).to_string(), "INT8_VALUES"); + } + + #[test] + fn scalar_type_impls_emit_pg_type_and_fixture_values() { + let out = norm(&scalar_type_impls_tokens(&sample())); + // One impl per entry, with the right PG_TYPE string and catalog const. + assert!(out.contains("impl ScalarType for i32")); + assert!(out.contains("impl ScalarType for i64")); + assert!(out.contains(r#"const PG_TYPE : & 'static str = "int4""#)); + assert!(out.contains(r#"const PG_TYPE : & 'static str = "int8""#)); + assert!(out.contains(":: eql_scalars :: INT4_VALUES")); + assert!(out.contains(":: eql_scalars :: INT8_VALUES")); + } + + #[test] + fn fixture_modules_emit_named_mods_with_scalar_fixture() { + let out = norm(&scalar_fixture_modules_tokens(&sample())); + assert!(out.contains("pub mod eql_v2_int4")); + assert!(out.contains("pub mod eql_v2_int8")); + assert!(out.contains("crate :: scalar_fixture !")); + assert!(out.contains(r#""eql_v2_int4""#)); + assert!(out.contains(":: eql_scalars :: INT4_VALUES as VALUES")); + } + + #[test] + fn fixture_dispatch_emits_one_arm_per_token_and_a_catch_all() { + let out = norm(&fixture_dispatch_tokens(&sample())); + assert!(out.contains("async fn generate_for_token")); + assert!(out.contains(r#""int4" =>"#)); + assert!(out.contains(r#""int8" =>"#)); + assert!(out.contains(":: eql_tests :: fixtures :: eql_v2_int4 :: spec")); + // Loud catch-all preserved. + assert!(out.contains("other =>")); + assert!(out.contains("no fixture generator wired")); + } + + #[test] + fn matrix_suites_emit_mods_with_unchanged_suite_and_eql_type() { + let out = norm(&scalar_matrix_suites_tokens(&sample())); + assert!(out.contains("pub mod int4")); + assert!(out.contains("pub mod int8")); + assert!(out.contains(":: eql_tests :: ordered_numeric_matrix !")); + // suite/scalar/eql_type must match what the old per-type files used so + // the generated test names (and the snapshot) are unchanged. + assert!(out.contains("suite = int4")); + assert!(out.contains("scalar = i32")); + assert!(out.contains(r#"eql_type = "eql_v2_int4""#)); + assert!(out.contains("suite = int8")); + assert!(out.contains("scalar = i64")); + assert!(out.contains(r#"eql_type = "eql_v2_int8""#)); + } +} diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index ee304a3d..041bc999 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -184,24 +184,28 @@ generated Rust. Pin the exact materialised list with a `values_tests` assertion. The generated SQL is enough to *install* the domains, but the `ordered_numeric_matrix!` suite only runs once the Rust harness knows about the -scalar. These are hand-maintained registration lists — copy each piece from the -`int4` reference. `` is the scalar's Rust type (`i32` for `int4`, `i16` for -`int2`): +scalar. `` is the scalar's Rust type (`i32` for `int4`, `i16` for `int2`). +There are now **two** registrations: | File | Add | |------|-----| -| `tests/sqlx/src/fixtures/eql_plaintext.rs` | A sealed `EqlPlaintext` impl for ``: `impl Sealed for {}`, a `PlaintextSqlType` const for its base column type, `impl EqlPlaintext for ` (`CAST`, `PLAINTEXT_SQL_TYPE`, `to_plaintext` → the right `Plaintext` variant), plus the two `#[test]` casts. | -| `tests/sqlx/src/fixtures/eql_v2_.rs` | `use eql_scalars::_VALUES as VALUES;` then `crate::scalar_fixture!("eql_v2_", , VALUES);`. | -| `tests/sqlx/src/fixtures/mod.rs` | `pub mod eql_v2_;`. | -| `tests/sqlx/tests/generate_all_fixtures.rs` | An arm in `generate_for_token`: `"" => fixtures::eql_v2_::spec().run().await,`. The match is exhaustive over the catalog — a catalog token with no arm fails the generator loudly. | -| `tests/sqlx/src/scalar_domains.rs` | `impl ScalarType for ` — `PG_TYPE` (the base PG type, e.g. `"int8"`) and `FIXTURE_VALUES = eql_scalars::_VALUES`. | -| `tests/sqlx/tests/encrypted_domain/scalars/.rs` | `ordered_numeric_matrix! { suite = , scalar = , eql_type = "eql_v2_" }`. | -| `tests/sqlx/tests/encrypted_domain/scalars/mod.rs` | `pub mod ;`. | - -Forget one and the matrix simply does not run for the type — the matrix -inventory cross-check (below) surfaces it, because the catalog has the type but -the binary has no `scalars::::` tests. (A future Phase-4 `scalar_types!` -registry, tracked separately, will collapse these into one declaration.) +| `tests/sqlx/src/scalar_harness.rs` | One ` => ` line in the `scalar_harness!` list (e.g. `int8 => i64,`). This single line drives the `impl ScalarType`, the `eql_v2_` fixture module, the `ordered_numeric_matrix!` suite, and the `generate_for_token` arm — all generated by the `eql-tests-macros` proc-macros. | +| `tests/sqlx/src/fixtures/eql_plaintext.rs` | A sealed `EqlPlaintext` impl for ``: `impl Sealed for {}` and `impl EqlPlaintext for ` carrying just `const KIND: ScalarKind` plus the value-typed `to_plaintext` → the right `Plaintext` variant. `CAST` and `PLAINTEXT_SQL_TYPE` are **derived** from `KIND` via the `cast_for_kind` / `plaintext_sql_type_for_kind` `const fn` defaults, so a brand-new integer kind needs an arm in those two helpers — not a per-type const. Keep the three `#[test]`s (cast / sql-type / to_plaintext) mirroring the existing ones. | + +The single ` => ` line in `scalar_harness.rs` is the harness source of +truth. The four code-generators (`emit_scalar_type_impls`, +`emit_scalar_fixture_modules`, `emit_scalar_matrix_suites`, +`emit_fixture_dispatch`) are pure functions of that list, invoked at each call +site via `scalar_harness!()`; there are four because proc-macros emit into +the crate/module where they're invoked and the pieces span the `eql-tests` lib, +the `encrypted_domain` test binary, and the `generate_all_fixtures` test binary. +See the `scalar_harness.rs` module docs and `crates/eql-tests-macros/src/lib.rs`. + +Forget the harness line and the matrix simply does not run for the type — the +matrix inventory cross-check (below) surfaces it, because the catalog has the +type but the binary has no `scalars::::` tests. A catalog token absent from +the `scalar_harness!` list also fails the `generate_for_token` catch-all loudly +at fixture-generation time. The coverage these registrations unlock comes from the `ordered_numeric_matrix!` convention wrapper in `tests/sqlx/src/matrix.rs`: one `impl ScalarType` plus a diff --git a/mise.toml b/mise.toml index 7f3e4d9c..ccd458fc 100644 --- a/mise.toml +++ b/mise.toml @@ -110,18 +110,20 @@ description = "Compile, lint and test the std-only Rust workspace crates (no dat dir = "{{config_root}}" run = """ #!/usr/bin/env bash -# eql-scalars / eql-codegen are the lean workspace members. Scope explicitly to -# them (NOT --workspace): a workspace-wide test would drag in tests/sqlx, whose -# suite needs Postgres + CS_* secrets and is already covered by the `test` job. -# clippy is likewise scoped — a workspace clippy recompiles the heavy -# sqlx/tokio/cipherstash-client tree for no added coverage of these crates. +# eql-scalars / eql-codegen / eql-tests-macros are the lean workspace members. +# Scope explicitly to them (NOT --workspace): a workspace-wide test would drag +# in tests/sqlx, whose suite needs Postgres + CS_* secrets and is already +# covered by the `test` job. eql-tests-macros only pulls syn/quote/proc-macro2, +# so it stays in the lean set. clippy is likewise scoped — a workspace clippy +# recompiles the heavy sqlx/tokio/cipherstash-client tree for no added coverage +# of these crates. # bash is pinned via the `#!/usr/bin/env bash` shebang above (mise honors a # `#!` first line), so `set -o pipefail` is available regardless of the runner's # /bin/sh (dash on the CI images). set -euo pipefail cargo fmt --check -cargo clippy -p eql-scalars -p eql-codegen --all-targets -- -D warnings -cargo test -p eql-scalars -p eql-codegen +cargo clippy -p eql-scalars -p eql-codegen -p eql-tests-macros --all-targets -- -D warnings +cargo test -p eql-scalars -p eql-codegen -p eql-tests-macros """ [tasks."test:matrix:inventory"] diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index c7a8525a..11217726 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -14,6 +14,7 @@ jsonschema = { version = "0.46.4", default-features = false } cipherstash-client = { version = "0.35", features = ["tokio"] } paste = "1" eql-scalars = { path = "../../crates/eql-scalars" } +eql-tests-macros = { path = "../../crates/eql-tests-macros" } [dev-dependencies] # None needed - tests live in this crate diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs index 303e3997..d43cfeaf 100644 --- a/tests/sqlx/src/fixtures/cipherstash.rs +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -294,7 +294,8 @@ mod live_tests { /// Assert the well-formed Store shape: the payload is a JSON object /// with non-null `v`, `c`, `hm`, `ob`, and `i` fields. Mirrors the - /// per-key assertions in `tests/sqlx/tests/encrypted_domain/scalars/int4.rs`. + /// per-key assertions in the generated `scalars::int4` matrix suite + /// (emitted from the `scalar_harness!` list in `scalar_harness.rs`). fn assert_store_shape(payload: &Value) { let obj = payload.as_object().expect("payload must be a JSON object"); for key in ["v", "c", "hm", "ob", "i"] { diff --git a/tests/sqlx/src/fixtures/eql_plaintext.rs b/tests/sqlx/src/fixtures/eql_plaintext.rs index 36b348fd..3fde4393 100644 --- a/tests/sqlx/src/fixtures/eql_plaintext.rs +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -14,6 +14,7 @@ use std::fmt; use cipherstash_client::encryption::Plaintext; +use eql_scalars::ScalarKind; /// The `cast_as` argument for `eql_v2.add_search_config`. The field is /// private so the allowlist is the set of `pub const`s below. @@ -66,6 +67,34 @@ impl fmt::Display for PlaintextSqlType { } } +/// The EQL `cast_as` for a scalar kind, drawn from the `Cast` allowlist. +/// +/// Only the integer kinds have `EqlPlaintext` impls, so only those resolve; +/// the non-integer kinds mirror the `eql_scalars` accessor convention and +/// `panic!`, since no impl can ever reach them. +const fn cast_for_kind(kind: ScalarKind) -> Cast { + match kind { + ScalarKind::I32 => Cast::INT, + ScalarKind::I16 => Cast::SMALL_INT, + ScalarKind::I64 | ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + panic!("EqlPlaintext is only implemented for integer scalar kinds") + } + } +} + +/// The `plaintext` oracle column SQL type for a scalar kind, drawn from the +/// `PlaintextSqlType` allowlist. As with `cast_for_kind`, only integer kinds +/// resolve. +const fn plaintext_sql_type_for_kind(kind: ScalarKind) -> PlaintextSqlType { + match kind { + ScalarKind::I32 => PlaintextSqlType::INTEGER, + ScalarKind::I16 => PlaintextSqlType::SMALLINT, + ScalarKind::I64 | ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + panic!("EqlPlaintext is only implemented for integer scalar kinds") + } + } +} + mod sealed { pub trait Sealed {} impl Sealed for i32 {} @@ -75,9 +104,17 @@ mod sealed { /// A Rust type usable as a fixture `plaintext` value, carrying its EQL cast /// and the SQL type of the `plaintext` column. Sealed; only this crate may /// add impls. +/// +/// Each impl supplies a single `KIND`; the EQL cast and `plaintext` column +/// SQL type are derived from it via `cast_for_kind` / +/// `plaintext_sql_type_for_kind`, so they cannot drift from the kind. pub trait EqlPlaintext: sealed::Sealed { - const CAST: Cast; - const PLAINTEXT_SQL_TYPE: PlaintextSqlType; + /// The scalar kind this plaintext type maps to. The single source of + /// truth from which `CAST` and `PLAINTEXT_SQL_TYPE` are derived. + const KIND: ScalarKind; + + const CAST: Cast = cast_for_kind(Self::KIND); + const PLAINTEXT_SQL_TYPE: PlaintextSqlType = plaintext_sql_type_for_kind(Self::KIND); /// Lift the Rust value into the cipherstash-client `Plaintext` enum the /// EQL encryption pipeline consumes. The mapping is total — every @@ -90,8 +127,7 @@ pub trait EqlPlaintext: sealed::Sealed { } impl EqlPlaintext for i32 { - const CAST: Cast = Cast::INT; - const PLAINTEXT_SQL_TYPE: PlaintextSqlType = PlaintextSqlType::INTEGER; + const KIND: ScalarKind = ScalarKind::I32; fn to_plaintext(&self) -> Plaintext { Plaintext::Int(Some(*self)) @@ -99,8 +135,7 @@ impl EqlPlaintext for i32 { } impl EqlPlaintext for i16 { - const CAST: Cast = Cast::SMALL_INT; - const PLAINTEXT_SQL_TYPE: PlaintextSqlType = PlaintextSqlType::SMALLINT; + const KIND: ScalarKind = ScalarKind::I16; fn to_plaintext(&self) -> Plaintext { Plaintext::SmallInt(Some(*self)) diff --git a/tests/sqlx/src/fixtures/eql_v2_int2.rs b/tests/sqlx/src/fixtures/eql_v2_int2.rs deleted file mode 100644 index 661e4475..00000000 --- a/tests/sqlx/src/fixtures/eql_v2_int2.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! The `eql_v2_int2` fixture — the int4 reference, clamped to 16 bits. -//! -//! 19 integers spanning a negative boundary, the i16 signed extremes -//! (`MIN`/`MAX`), zero, a pair near the ±32767 boundary, and -//! small/medium/large magnitudes. The generated -//! `tests/sqlx/fixtures/eql_v2_int2.sql` is a plain `jsonb`-payload table with -//! no EQL dependency; the `eql_v3.int2` domain is layered on top by casting -//! `payload` per query. - -use eql_scalars::INT2_VALUES as VALUES; - -crate::scalar_fixture!("eql_v2_int2", i16, VALUES); diff --git a/tests/sqlx/src/fixtures/eql_v2_int4.rs b/tests/sqlx/src/fixtures/eql_v2_int4.rs deleted file mode 100644 index facb69c4..00000000 --- a/tests/sqlx/src/fixtures/eql_v2_int4.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! The `eql_v2_int4` fixture — the framework's reference example and proof. -//! -//! 17 integers spanning a negative boundary, the i32 signed extremes -//! (`MIN`/`MAX`), zero, and small/medium/large magnitudes. The generated -//! `tests/sqlx/fixtures/eql_v2_int4.sql` is a plain `jsonb`-payload table with -//! no EQL dependency; #225 layers the `eql_v3.int4` domain on top by casting -//! `payload` per query. - -use eql_scalars::INT4_VALUES as VALUES; - -crate::scalar_fixture!("eql_v2_int4", i32, VALUES); diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index 9a78189e..bd67fb68 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -26,9 +26,11 @@ pub mod cipherstash; pub mod driver; -/// Scalar fixtures read their plaintext value lists directly from the catalog -/// (`eql_scalars::INT4_VALUES` / `INT2_VALUES`) — see `scalar_fixture!`. There -/// is no generated `_values.rs` module any more. -pub mod eql_v2_int4; - -pub mod eql_v2_int2; +// The per-type scalar fixture modules (`eql_v2_int4`, `eql_v2_int2`, +// `eql_v2_int8`, …) are generated from the single harness list in +// `scalar_harness.rs`. Each expands to `pub mod eql_v2_ { … scalar_fixture! +// … }` — the same three items the old per-type `eql_v2_.rs` files held. +// Scalar fixtures read their plaintext value lists directly from the catalog +// (`eql_scalars::_VALUES`) — see `scalar_fixture!`. There is no +// generated `_values.rs` module. +crate::scalar_harness!(fixture_modules); diff --git a/tests/sqlx/src/lib.rs b/tests/sqlx/src/lib.rs index c37c176e..3c90a567 100644 --- a/tests/sqlx/src/lib.rs +++ b/tests/sqlx/src/lib.rs @@ -10,6 +10,8 @@ pub mod helpers; pub mod index_types; pub mod matrix; pub mod scalar_domains; +#[macro_use] +pub mod scalar_harness; pub mod selectors; // Re-export `paste` under a stable path so the `scalar_domain_matrix!` macro @@ -18,6 +20,12 @@ pub mod selectors; #[doc(hidden)] pub use paste; +// Re-export the harness proc-macro crate under a stable path so the +// `scalar_harness!` macro can refer to `$crate::eql_tests_macros::!` +// without each call site depending on the proc-macro crate directly. +#[doc(hidden)] +pub use eql_tests_macros; + pub use assertions::{assert_db_error, QueryAssertion}; pub use helpers::{ analyze_table, assert_no_seq_scan, assert_sequential_ids, assert_uses_index, diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index 39f5c079..7205b44d 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -1,11 +1,13 @@ //! Type-generic substrate for the encrypted-scalar-domain test matrix. //! //! Adding a new encrypted scalar type (e.g. `i64` for int8, `f64` for -//! float8) is a 4-line `impl ScalarType` plus a Proxy-encrypted fixture. -//! Everything else — the four `eql_v2_{,_eq,_ord,_ord_ore}` domains, -//! per-domain payload shapes, supported operators, index extractor -//! expressions, ground-truth result sets — is derived from -//! `T::PG_TYPE`, `T::FIXTURE_VALUES`, and the `Variant` enum. +//! float8) is one ` => ` line in the `scalar_harness!` list +//! (`scalar_harness.rs`) plus an `EqlPlaintext` impl and a catalog row. +//! The `impl ScalarType` below is generated from that list. Everything +//! else — the four `eql_v2_{,_eq,_ord,_ord_ore}` domains, per-domain +//! payload shapes, supported operators, index extractor expressions, +//! ground-truth result sets — is derived from `T::PG_TYPE`, +//! `T::FIXTURE_VALUES`, and the `Variant` enum. use anyhow::{bail, Context, Result}; use sqlx::PgPool; @@ -72,23 +74,11 @@ pub trait ScalarType: } } -impl ScalarType for i32 { - const PG_TYPE: &'static str = "int4"; - /// Single-sourced from the `int4` row in `eql-scalars::CATALOG` - /// (`eql_scalars::INT4_VALUES`, materialised from its `Fixture` list) — the - /// same list the fixture generator encrypts, so the oracle cannot drift from - /// the fixture. Spans the negative boundary, the i32 signed extremes, and zero. - const FIXTURE_VALUES: &'static [i32] = eql_scalars::INT4_VALUES; -} - -impl ScalarType for i16 { - const PG_TYPE: &'static str = "int2"; - /// Single-sourced from the `int2` row in `eql-scalars::CATALOG` - /// (`eql_scalars::INT2_VALUES`, materialised from its `Fixture` list) — the - /// same list the fixture generator encrypts, so the oracle cannot drift from - /// the fixture. Spans the negative boundary, the i16 signed extremes, and zero. - const FIXTURE_VALUES: &'static [i16] = eql_scalars::INT2_VALUES; -} +// The per-type `impl ScalarType` blocks (one per scalar, each carrying its +// `PG_TYPE` token string and `FIXTURE_VALUES = eql_scalars::_VALUES`) +// are generated from the single harness list in `scalar_harness.rs`. To add a +// type, add a `token => rust_type` line there — not an impl here. +crate::scalar_harness!(scalar_type_impls); /// Per-domain capability + payload shape. Storage carries no terms, `Eq` /// adds `hm`, `Ord`/`OrdOre` add `ob`. `Ord` and `OrdOre` are deliberate diff --git a/tests/sqlx/src/scalar_harness.rs b/tests/sqlx/src/scalar_harness.rs new file mode 100644 index 00000000..3172191a --- /dev/null +++ b/tests/sqlx/src/scalar_harness.rs @@ -0,0 +1,63 @@ +//! The single declarative scalar-harness list — the harness source of truth. +//! +//! Adding a scalar encrypted-domain type to the SQLx matrix used to require +//! editing several files in lock-step. That wiring is now generated from the +//! ONE list embedded in the `scalar_harness!` macro below. To add a type, add +//! one `token => rust_type` line here (plus the catalog row in `eql-scalars` +//! and the `EqlPlaintext` impl, which are owned separately — see +//! `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3). +//! +//! # How it works +//! +//! Proc-macros emit into the crate/module where they're invoked, and the +//! harness pieces live in three different compilation contexts (the `eql-tests` +//! lib, the `encrypted_domain` integration-test binary, and the +//! `generate_all_fixtures` integration-test binary). One proc-macro invocation +//! can't reach all three. So the canonical list is held *here once*, inside the +//! `scalar_harness!` `macro_rules!`, and each call site invokes +//! `scalar_harness!()` to forward that same list to the +//! `eql_tests_macros` proc-macro appropriate for that site: +//! +//! - `scalar_harness!(scalar_type_impls)` — in `scalar_domains.rs` (lib): the +//! `impl ScalarType` block. +//! - `scalar_harness!(fixture_modules)` — in `fixtures/mod.rs` (lib): the +//! `pub mod eql_v2_` fixture modules. +//! - `scalar_harness!(matrix_suites)` — in +//! `tests/encrypted_domain/scalars/mod.rs` (test binary): the +//! `ordered_numeric_matrix!` suites. +//! - `scalar_harness!(fixture_dispatch)` — in `tests/generate_all_fixtures.rs` +//! (test binary): the `generate_for_token` dispatch fn. +//! +//! The list appears once; the four mode arms are pure expansions of it, so the +//! list is genuinely the single source of truth. The matrix-inventory +//! cross-check (`mise run test:matrix:inventory`) still compares the type set +//! the binary actually emits against `eql-codegen list-types`, so a catalog +//! type missing from this list fails loudly. + +/// Forward the single canonical scalar-harness list to the `eql_tests_macros` +/// proc-macro selected by `$mode`. See the module docs for the call sites. +/// +/// THE LIST. This is the only place the harness token set is declared. Keep it +/// in sync with `eql-scalars::CATALOG` — the matrix-inventory cross-check +/// enforces that they agree. +#[macro_export] +macro_rules! scalar_harness { + (scalar_type_impls) => { + $crate::scalar_harness!(@dispatch emit_scalar_type_impls); + }; + (fixture_modules) => { + $crate::scalar_harness!(@dispatch emit_scalar_fixture_modules); + }; + (matrix_suites) => { + $crate::scalar_harness!(@dispatch emit_scalar_matrix_suites); + }; + (fixture_dispatch) => { + $crate::scalar_harness!(@dispatch emit_fixture_dispatch); + }; + (@dispatch $emitter:ident) => { + $crate::eql_tests_macros::$emitter! { + int4 => i32, + int2 => i16, + } + }; +} diff --git a/tests/sqlx/tests/encrypted_domain/scalars/int2.rs b/tests/sqlx/tests/encrypted_domain/scalars/int2.rs deleted file mode 100644 index 7a8e9331..00000000 --- a/tests/sqlx/tests/encrypted_domain/scalars/int2.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! `eql_v2_int2` — the int4 reference scalar, clamped to 16 bits. -//! -//! Adding a new ordered numeric scalar (i64, f64, date, ...) is one -//! `impl ScalarType` in `tests/sqlx/src/scalar_domains.rs` plus an -//! `ordered_numeric_matrix!` invocation like this one. The matrix covers -//! everything generic over `T: ScalarType`. - -use eql_tests::ordered_numeric_matrix; - -ordered_numeric_matrix! { - suite = int2, - scalar = i16, - eql_type = "eql_v2_int2", -} diff --git a/tests/sqlx/tests/encrypted_domain/scalars/int4.rs b/tests/sqlx/tests/encrypted_domain/scalars/int4.rs deleted file mode 100644 index 6ec665d3..00000000 --- a/tests/sqlx/tests/encrypted_domain/scalars/int4.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! `eql_v2_int4` — the reference scalar implementation. -//! -//! Adding a new ordered numeric scalar (i64, f64, date, ...) is one -//! `impl ScalarType` in `tests/sqlx/src/scalar_domains.rs` plus an -//! `ordered_numeric_matrix!` invocation like this one. The matrix covers -//! everything generic over `T: ScalarType`. - -use eql_tests::ordered_numeric_matrix; - -ordered_numeric_matrix! { - suite = int4, - scalar = i32, - eql_type = "eql_v2_int4", -} diff --git a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs index f42cfb5f..0ba4ddc5 100644 --- a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs +++ b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs @@ -1,6 +1,8 @@ -//! Per-scalar tests. Each subdirectory targets one scalar type; future -//! additions (`int8`, `bool`, `date`, …) become sibling modules here. +//! Per-scalar matrix suites. Each `pub mod ` targets one scalar type and +//! holds its `ordered_numeric_matrix!` invocation. +//! +//! The modules are generated from the single harness list in +//! `tests/sqlx/src/scalar_harness.rs` — adding a type there adds its suite here +//! automatically. The old per-type `scalars/.rs` files are gone. -pub mod int4; - -pub mod int2; +eql_tests::scalar_harness!(matrix_suites); diff --git a/tests/sqlx/tests/generate_all_fixtures.rs b/tests/sqlx/tests/generate_all_fixtures.rs index bbc1f595..e81ab9d0 100644 --- a/tests/sqlx/tests/generate_all_fixtures.rs +++ b/tests/sqlx/tests/generate_all_fixtures.rs @@ -14,22 +14,13 @@ #![cfg(feature = "fixture-gen")] use eql_scalars::CATALOG; -use eql_tests::fixtures; -/// Map a catalog token to its fixture generator and run it. A token present in -/// the catalog but missing here is a wiring gap — fail loudly so a new scalar -/// type cannot silently skip fixture generation. -async fn generate_for_token(token: &str) -> anyhow::Result<()> { - match token { - "int2" => fixtures::eql_v2_int2::spec().run().await, - "int4" => fixtures::eql_v2_int4::spec().run().await, - other => anyhow::bail!( - "no fixture generator wired for catalog token '{other}'. \ - Add an arm to generate_for_token in tests/sqlx/tests/generate_all_fixtures.rs \ - (and the eql_v2_{other} fixture module). See the encrypted-domain spec §9." - ), - } -} +// `generate_for_token(token: &str) -> anyhow::Result<()>` is generated from the +// single harness list in `tests/sqlx/src/scalar_harness.rs`: one match arm per +// token (`"int4" => fixtures::eql_v2_int4::spec().run().await`) plus a loud +// catch-all. A catalog token absent from that list hits the catch-all and fails +// the generator loudly, so a new scalar type cannot silently skip generation. +eql_tests::scalar_harness!(fixture_dispatch); #[tokio::test] #[ignore = "generator — run via `mise run fixture:generate:all`"] From 9b4f8807f58273213c771d6620d971446c5e28ac Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 12:28:49 +1000 Subject: [PATCH 60/93] test(scalars): abstract per-type catalog tests into generic checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-type catalog tests mostly restated the catalog literal (kind, domain count, specific fixtures) or duplicated the whole fixture list as a hardcoded golden array — double-bookkeeping that a wrong fixture wouldn't even fail (the distinctness / bounds / min-max-zero invariants catch that). Replace them with checks generic over CATALOG, which cover every type (including future ones) with no per-type code: - `all_types_share_the_same_domain_shape` now also asserts each domain's terms (subsumes `_domain_terms_match_manifest`). - `every_int_kind_matches_its_rust_type` pins token->kind once (the kind drives the Min/Max pivot resolution, so a wrong kind would otherwise silently corrupt the matrix pivots without failing any test). - `materialised_values_match_resolved_fixtures` computes the expected values from each row's fixtures (no hardcoded array), subsuming the per-type `_values_materialise_to_typed_array` goldens and the `_track_` test. Adding a scalar now needs only one `check(&INTx, INTx_VALUES)` line, not a duplicated golden list. --- crates/eql-scalars/src/lib.rs | 179 ++++++++++------------------------ 1 file changed, 53 insertions(+), 126 deletions(-) diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index f606a07b..07bb45e8 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -760,44 +760,47 @@ mod catalog_tests { assert_eq!(tokens, vec!["int4", "int2"]); } - #[test] - fn int4_maps_to_i32_with_four_domains() { - let s = scalar("int4"); - assert_eq!(s.kind, ScalarKind::I32); - let suffixes: Vec<&str> = s.domains.iter().map(|d| d.suffix).collect(); - // File order from int4.toml: storage, _eq, _ord_ore, _ord. - assert_eq!(suffixes, vec!["", "_eq", "_ord_ore", "_ord"]); - } - - #[test] - fn int4_domain_terms_match_manifest() { - let s = scalar("int4"); - assert_eq!(s.domains[0].terms, &[] as &[Term]); // storage - assert_eq!(s.domains[1].terms, &[Term::Hm]); // _eq - assert_eq!(s.domains[2].terms, &[Term::Ore]); // _ord_ore - assert_eq!(s.domains[3].terms, &[Term::Ore]); // _ord - } - - #[test] - fn int2_rust_type() { - assert_eq!(scalar("int2").kind, ScalarKind::I16); - } - #[test] fn all_types_share_the_same_domain_shape() { // Every scalar declares the same four domains with the same terms; // only the token differs (the matrix-snapshot collapse depends on this). + // Generic over CATALOG, so it covers every type — including new ones — + // and subsumes the old per-type `_maps_to_*_with_four_domains` / + // `_domain_terms_match_manifest` tests (which only restated the + // catalog literal for one token). for s in CATALOG { - let suffixes: Vec<&str> = s.domains.iter().map(|d| d.suffix).collect(); + let shape: Vec<(&str, &[Term])> = + s.domains.iter().map(|d| (d.suffix, d.terms)).collect(); assert_eq!( - suffixes, - vec!["", "_eq", "_ord_ore", "_ord"], - "{} has unexpected domain set", + shape, + vec![ + ("", &[] as &[Term]), + ("_eq", &[Term::Hm][..]), + ("_ord_ore", &[Term::Ore][..]), + ("_ord", &[Term::Ore][..]), + ], + "{} has unexpected domain shape", s.token ); } } + #[test] + fn every_int_kind_matches_its_rust_type() { + // The kind↔rust-type pairing for every integer scalar, generic over + // CATALOG. Replaces the per-type `_maps_to_iNN` / `_rust_type` + // restatements. + for s in CATALOG.iter().filter(|s| s.kind.is_int()) { + let expected = match s.token { + "int2" => ScalarKind::I16, + "int4" => ScalarKind::I32, + "int8" => ScalarKind::I64, + other => panic!("unmapped integer scalar token {other}"), + }; + assert_eq!(s.kind, expected, "{} maps to the wrong kind", s.token); + } + } + #[test] fn domain_name_concatenates_token_and_suffix() { let s = scalar("int4"); @@ -805,116 +808,40 @@ mod catalog_tests { assert_eq!(s.domain_name(&s.domains[1]), "int4_eq"); assert_eq!(s.domain_name(&s.domains[3]), "int4_ord"); } - - #[test] - fn int4_fixtures_match_manifest() { - let s = scalar("int4"); - // From int4.toml [fixture] values, in order. - let expected = vec![ - Fixture::Min, - Fixture::Int(-100), - Fixture::Int(-1), - Fixture::Zero, - Fixture::Int(1), - Fixture::Int(2), - Fixture::Int(5), - Fixture::Int(10), - Fixture::Int(17), - Fixture::Int(25), - Fixture::Int(42), - Fixture::Int(50), - Fixture::Int(100), - Fixture::Int(250), - Fixture::Int(1000), - Fixture::Int(9999), - Fixture::Max, - ]; - assert_eq!(s.fixtures, expected.as_slice()); - } - - #[test] - fn int2_fixtures_match_manifest() { - let s = scalar("int2"); - // From int2.toml [fixture] values, in order — includes the wide - // ±30000 values that exercise the i16 bounds. - assert!(s.fixtures.contains(&Fixture::Int(-30000))); - assert!(s.fixtures.contains(&Fixture::Int(30000))); - assert_eq!(s.fixtures.first(), Some(&Fixture::Min)); - assert_eq!(s.fixtures.last(), Some(&Fixture::Max)); - } } #[cfg(test)] mod values_tests { use super::*; - // The exact typed lists the SQLx matrix consumes. These pin the values the - // deleted golden `int4_values.rs` / committed `_values.rs` used to pin: - // a catalog edit that changes a fixture must update these assertions. - #[test] - fn int4_values_materialise_to_typed_array() { - assert_eq!( - INT4_VALUES, - &[ - i32::MIN, - -100, - -1, - 0, - 1, - 2, - 5, - 10, - 17, - 25, - 42, - 50, - 100, - 250, - 1000, - 9999, - i32::MAX - ] - ); - } - - #[test] - fn int2_values_materialise_to_typed_array() { + /// Every materialised `_VALUES` array equals its catalog row's fixtures, + /// resolved per kind, in order. Computed from the fixtures — no hardcoded + /// expected array — so it cannot drift and adding a type needs only one + /// `check(&INTx, INTx_VALUES)` line, not a duplicated golden list. Subsumes + /// the old per-type `_values_materialise_to_typed_array` goldens and + /// `materialised_values_track_their_fixture_lists`. + fn check>(spec: &ScalarSpec, values: &[T]) { assert_eq!( - INT2_VALUES, - &[ - i16::MIN, - -30000, - -100, - -1, - 0, - 1, - 2, - 5, - 10, - 17, - 25, - 42, - 50, - 100, - 250, - 1000, - 9999, - 30000, - i16::MAX - ] + values.len(), + spec.fixtures.len(), + "{}: value count != fixture count", + spec.token ); + for (i, (v, f)) in values.iter().zip(spec.fixtures).enumerate() { + assert_eq!( + (*v).into(), + f.numeric_value(spec.kind) + .expect("integer scalar fixture resolves to a number"), + "{}: value[{i}] does not match resolved fixture {f:?}", + spec.token + ); + } } #[test] - fn materialised_values_track_their_fixture_lists() { - // One value per fixture, in catalog order; sentinels resolve to extremes. - assert_eq!(INT4_VALUES.len(), INT4_FIXTURES.len()); - assert_eq!(INT2_VALUES.len(), INT2_FIXTURES.len()); - assert_eq!(INT4_VALUES.first(), Some(&i32::MIN)); - assert_eq!(INT4_VALUES.last(), Some(&i32::MAX)); - assert_eq!(INT2_VALUES.first(), Some(&i16::MIN)); - assert_eq!(INT2_VALUES.last(), Some(&i16::MAX)); - assert!(INT4_VALUES.contains(&0) && INT2_VALUES.contains(&0)); + fn materialised_values_match_resolved_fixtures() { + check(&INT4, INT4_VALUES); + check(&INT2, INT2_VALUES); } } From 147ef1f1ca4c4691d4edaa9565a04c501c028219 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 13:01:51 +1000 Subject: [PATCH 61/93] refactor(tests): rename scalar_harness! to scalar_types! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macro/file is the declarative registry of scalar types under matrix test; 'scalar_types' names what it is (the type list), where 'harness' was opaque. Pure rename — file, macro, call sites, and docs. --- crates/eql-tests-macros/src/lib.rs | 22 +++++++------- .../adding-a-scalar-encrypted-domain-type.md | 10 +++---- tests/sqlx/src/fixtures/cipherstash.rs | 2 +- tests/sqlx/src/fixtures/mod.rs | 4 +-- tests/sqlx/src/lib.rs | 4 +-- tests/sqlx/src/scalar_domains.rs | 8 ++--- .../{scalar_harness.rs => scalar_types.rs} | 29 ++++++++++--------- .../tests/encrypted_domain/scalars/mod.rs | 4 +-- tests/sqlx/tests/generate_all_fixtures.rs | 4 +-- 9 files changed, 44 insertions(+), 43 deletions(-) rename tests/sqlx/src/{scalar_harness.rs => scalar_types.rs} (67%) diff --git a/crates/eql-tests-macros/src/lib.rs b/crates/eql-tests-macros/src/lib.rs index a2ceb198..af74c1b2 100644 --- a/crates/eql-tests-macros/src/lib.rs +++ b/crates/eql-tests-macros/src/lib.rs @@ -12,7 +12,7 @@ //! a SINGLE list, e.g.: //! //! ```ignore -//! eql_tests::scalar_harness! { +//! eql_tests::scalar_types! { //! int4 => i32, //! int2 => i16, //! int8 => i64, @@ -30,8 +30,8 @@ //! binary (`tests/generate_all_fixtures.rs`). //! //! A single macro invocation cannot emit into all three at once, so the design -//! is: ONE canonical list, captured by the `macro_rules! scalar_harness!` -//! re-exported from `eql-tests` (see `tests/sqlx/src/scalar_harness.rs`), which +//! is: ONE canonical list, captured by the `macro_rules! scalar_types!` +//! re-exported from `eql-tests` (see `tests/sqlx/src/scalar_types.rs`), which //! forwards that same list to whichever proc-macro is appropriate for the call //! site. Each proc-macro below parses the identical `token => rust_type` list //! and emits only the items that belong where it is invoked. The list itself is @@ -127,7 +127,7 @@ fn scalar_fixture_modules_tokens(list: &ScalarList) -> TokenStream2 { let mod_ident = format_ident!("eql_v2_{}", e.token); let fixture_name = format!("eql_v2_{}", token_str); quote! { - #[doc = concat!("`eql_v2_", #token_str, "` scalar fixture — generated by `scalar_harness!`.")] + #[doc = concat!("`eql_v2_", #token_str, "` scalar fixture — generated by `scalar_types!`.")] pub mod #mod_ident { use ::eql_scalars::#values as VALUES; // `scalar_fixture!` is `#[macro_export]`ed from this crate @@ -151,7 +151,7 @@ fn fixture_dispatch_tokens(list: &ScalarList) -> TokenStream2 { }); quote! { /// Map a catalog token to its fixture generator and run it. Generated by - /// `scalar_harness!` from the single harness list. A token present in the + /// `scalar_types!` from the single harness list. A token present in the /// catalog but absent from that list hits the catch-all below and fails /// loudly so a new scalar type cannot silently skip fixture generation. async fn generate_for_token(token: &str) -> anyhow::Result<()> { @@ -159,7 +159,7 @@ fn fixture_dispatch_tokens(list: &ScalarList) -> TokenStream2 { #(#arms)* other => anyhow::bail!( "no fixture generator wired for catalog token '{other}'. \ - Add it to the scalar_harness! list (tests/sqlx/src/scalar_harness.rs). \ + Add it to the scalar_types! list (tests/sqlx/src/scalar_types.rs). \ See the encrypted-domain spec §3." ), } @@ -176,7 +176,7 @@ fn scalar_matrix_suites_tokens(list: &ScalarList) -> TokenStream2 { let rust_type = &e.rust_type; let eql_type = format!("eql_v2_{}", token_str); quote! { - #[doc = concat!("`eql_v2_", #token_str, "` matrix suite — generated by `scalar_harness!`.")] + #[doc = concat!("`eql_v2_", #token_str, "` matrix suite — generated by `scalar_types!`.")] pub mod #token { ::eql_tests::ordered_numeric_matrix! { suite = #token, @@ -195,7 +195,7 @@ fn scalar_matrix_suites_tokens(list: &ScalarList) -> TokenStream2 { /// Emit one `impl ScalarType for ` per entry. /// -/// Invoked (via `scalar_harness!`) inside `tests/sqlx/src/scalar_domains.rs`, +/// Invoked (via `scalar_types!`) inside `tests/sqlx/src/scalar_domains.rs`, /// so the impls land in the `eql-tests` library next to the trait. Replaces the /// three hand-written impls. `PG_TYPE` is the token string; `FIXTURE_VALUES` /// is the catalog const `eql_scalars::_VALUES` — single-sourced @@ -208,7 +208,7 @@ pub fn emit_scalar_type_impls(input: TokenStream) -> TokenStream { /// Emit one `pub mod eql_v2_ { ... }` per entry. /// -/// Invoked (via `scalar_harness!`) inside `tests/sqlx/src/fixtures/mod.rs`, so +/// Invoked (via `scalar_types!`) inside `tests/sqlx/src/fixtures/mod.rs`, so /// the modules land at `crate::fixtures::eql_v2_` — the path the matrix /// and the fixture dispatch reference. Each module body is exactly what the old /// per-type `fixtures/eql_v2_.rs` file contained: a `use` of the catalog @@ -223,7 +223,7 @@ pub fn emit_scalar_fixture_modules(input: TokenStream) -> TokenStream { /// Emit the `generate_for_token` dispatch as a single function driven by the /// list. /// -/// Invoked (via `scalar_harness!`) inside `tests/generate_all_fixtures.rs`. +/// Invoked (via `scalar_types!`) inside `tests/generate_all_fixtures.rs`. /// Emits an `async fn generate_for_token(token: &str) -> anyhow::Result<()>` /// with one match arm per entry plus a loud catch-all, replacing the /// hand-written match. A catalog token with no entry here (i.e. not in the @@ -238,7 +238,7 @@ pub fn emit_fixture_dispatch(input: TokenStream) -> TokenStream { /// Emit one `pub mod { ordered_numeric_matrix! { ... } }` per entry. /// -/// Invoked (via `scalar_harness!`) inside +/// Invoked (via `scalar_types!`) inside /// `tests/sqlx/tests/encrypted_domain/scalars/mod.rs`, so the matrix suites land /// in the `encrypted_domain` integration-test binary — the only place /// `#[sqlx::test]` suites belong. This is a SEPARATE proc-macro from the diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index 041bc999..68d913e8 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -189,22 +189,22 @@ There are now **two** registrations: | File | Add | |------|-----| -| `tests/sqlx/src/scalar_harness.rs` | One ` => ` line in the `scalar_harness!` list (e.g. `int8 => i64,`). This single line drives the `impl ScalarType`, the `eql_v2_` fixture module, the `ordered_numeric_matrix!` suite, and the `generate_for_token` arm — all generated by the `eql-tests-macros` proc-macros. | +| `tests/sqlx/src/scalar_types.rs` | One ` => ` line in the `scalar_types!` list (e.g. `int8 => i64,`). This single line drives the `impl ScalarType`, the `eql_v2_` fixture module, the `ordered_numeric_matrix!` suite, and the `generate_for_token` arm — all generated by the `eql-tests-macros` proc-macros. | | `tests/sqlx/src/fixtures/eql_plaintext.rs` | A sealed `EqlPlaintext` impl for ``: `impl Sealed for {}` and `impl EqlPlaintext for ` carrying just `const KIND: ScalarKind` plus the value-typed `to_plaintext` → the right `Plaintext` variant. `CAST` and `PLAINTEXT_SQL_TYPE` are **derived** from `KIND` via the `cast_for_kind` / `plaintext_sql_type_for_kind` `const fn` defaults, so a brand-new integer kind needs an arm in those two helpers — not a per-type const. Keep the three `#[test]`s (cast / sql-type / to_plaintext) mirroring the existing ones. | -The single ` => ` line in `scalar_harness.rs` is the harness source of +The single ` => ` line in `scalar_types.rs` is the harness source of truth. The four code-generators (`emit_scalar_type_impls`, `emit_scalar_fixture_modules`, `emit_scalar_matrix_suites`, `emit_fixture_dispatch`) are pure functions of that list, invoked at each call -site via `scalar_harness!()`; there are four because proc-macros emit into +site via `scalar_types!()`; there are four because proc-macros emit into the crate/module where they're invoked and the pieces span the `eql-tests` lib, the `encrypted_domain` test binary, and the `generate_all_fixtures` test binary. -See the `scalar_harness.rs` module docs and `crates/eql-tests-macros/src/lib.rs`. +See the `scalar_types.rs` module docs and `crates/eql-tests-macros/src/lib.rs`. Forget the harness line and the matrix simply does not run for the type — the matrix inventory cross-check (below) surfaces it, because the catalog has the type but the binary has no `scalars::::` tests. A catalog token absent from -the `scalar_harness!` list also fails the `generate_for_token` catch-all loudly +the `scalar_types!` list also fails the `generate_for_token` catch-all loudly at fixture-generation time. The coverage these registrations unlock comes from the `ordered_numeric_matrix!` diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs index d43cfeaf..adb12173 100644 --- a/tests/sqlx/src/fixtures/cipherstash.rs +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -295,7 +295,7 @@ mod live_tests { /// Assert the well-formed Store shape: the payload is a JSON object /// with non-null `v`, `c`, `hm`, `ob`, and `i` fields. Mirrors the /// per-key assertions in the generated `scalars::int4` matrix suite - /// (emitted from the `scalar_harness!` list in `scalar_harness.rs`). + /// (emitted from the `scalar_types!` list in `scalar_types.rs`). fn assert_store_shape(payload: &Value) { let obj = payload.as_object().expect("payload must be a JSON object"); for key in ["v", "c", "hm", "ob", "i"] { diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index bd67fb68..bbcd6cce 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -28,9 +28,9 @@ pub mod driver; // The per-type scalar fixture modules (`eql_v2_int4`, `eql_v2_int2`, // `eql_v2_int8`, …) are generated from the single harness list in -// `scalar_harness.rs`. Each expands to `pub mod eql_v2_ { … scalar_fixture! +// `scalar_types.rs`. Each expands to `pub mod eql_v2_ { … scalar_fixture! // … }` — the same three items the old per-type `eql_v2_.rs` files held. // Scalar fixtures read their plaintext value lists directly from the catalog // (`eql_scalars::_VALUES`) — see `scalar_fixture!`. There is no // generated `_values.rs` module. -crate::scalar_harness!(fixture_modules); +crate::scalar_types!(fixture_modules); diff --git a/tests/sqlx/src/lib.rs b/tests/sqlx/src/lib.rs index 3c90a567..4386222b 100644 --- a/tests/sqlx/src/lib.rs +++ b/tests/sqlx/src/lib.rs @@ -11,7 +11,7 @@ pub mod index_types; pub mod matrix; pub mod scalar_domains; #[macro_use] -pub mod scalar_harness; +pub mod scalar_types; pub mod selectors; // Re-export `paste` under a stable path so the `scalar_domain_matrix!` macro @@ -21,7 +21,7 @@ pub mod selectors; pub use paste; // Re-export the harness proc-macro crate under a stable path so the -// `scalar_harness!` macro can refer to `$crate::eql_tests_macros::!` +// `scalar_types!` macro can refer to `$crate::eql_tests_macros::!` // without each call site depending on the proc-macro crate directly. #[doc(hidden)] pub use eql_tests_macros; diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index 7205b44d..e9f91e3b 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -1,8 +1,8 @@ //! Type-generic substrate for the encrypted-scalar-domain test matrix. //! //! Adding a new encrypted scalar type (e.g. `i64` for int8, `f64` for -//! float8) is one ` => ` line in the `scalar_harness!` list -//! (`scalar_harness.rs`) plus an `EqlPlaintext` impl and a catalog row. +//! float8) is one ` => ` line in the `scalar_types!` list +//! (`scalar_types.rs`) plus an `EqlPlaintext` impl and a catalog row. //! The `impl ScalarType` below is generated from that list. Everything //! else — the four `eql_v2_{,_eq,_ord,_ord_ore}` domains, per-domain //! payload shapes, supported operators, index extractor expressions, @@ -76,9 +76,9 @@ pub trait ScalarType: // The per-type `impl ScalarType` blocks (one per scalar, each carrying its // `PG_TYPE` token string and `FIXTURE_VALUES = eql_scalars::_VALUES`) -// are generated from the single harness list in `scalar_harness.rs`. To add a +// are generated from the single harness list in `scalar_types.rs`. To add a // type, add a `token => rust_type` line there — not an impl here. -crate::scalar_harness!(scalar_type_impls); +crate::scalar_types!(scalar_type_impls); /// Per-domain capability + payload shape. Storage carries no terms, `Eq` /// adds `hm`, `Ord`/`OrdOre` add `ob`. `Ord` and `OrdOre` are deliberate diff --git a/tests/sqlx/src/scalar_harness.rs b/tests/sqlx/src/scalar_types.rs similarity index 67% rename from tests/sqlx/src/scalar_harness.rs rename to tests/sqlx/src/scalar_types.rs index 3172191a..0e57a39c 100644 --- a/tests/sqlx/src/scalar_harness.rs +++ b/tests/sqlx/src/scalar_types.rs @@ -1,8 +1,9 @@ -//! The single declarative scalar-harness list — the harness source of truth. +//! The single declarative list of scalar types under matrix test — the +//! harness source of truth. //! //! Adding a scalar encrypted-domain type to the SQLx matrix used to require //! editing several files in lock-step. That wiring is now generated from the -//! ONE list embedded in the `scalar_harness!` macro below. To add a type, add +//! ONE list embedded in the `scalar_types!` macro below. To add a type, add //! one `token => rust_type` line here (plus the catalog row in `eql-scalars` //! and the `EqlPlaintext` impl, which are owned separately — see //! `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3). @@ -14,18 +15,18 @@ //! lib, the `encrypted_domain` integration-test binary, and the //! `generate_all_fixtures` integration-test binary). One proc-macro invocation //! can't reach all three. So the canonical list is held *here once*, inside the -//! `scalar_harness!` `macro_rules!`, and each call site invokes -//! `scalar_harness!()` to forward that same list to the +//! `scalar_types!` `macro_rules!`, and each call site invokes +//! `scalar_types!()` to forward that same list to the //! `eql_tests_macros` proc-macro appropriate for that site: //! -//! - `scalar_harness!(scalar_type_impls)` — in `scalar_domains.rs` (lib): the +//! - `scalar_types!(scalar_type_impls)` — in `scalar_domains.rs` (lib): the //! `impl ScalarType` block. -//! - `scalar_harness!(fixture_modules)` — in `fixtures/mod.rs` (lib): the +//! - `scalar_types!(fixture_modules)` — in `fixtures/mod.rs` (lib): the //! `pub mod eql_v2_` fixture modules. -//! - `scalar_harness!(matrix_suites)` — in +//! - `scalar_types!(matrix_suites)` — in //! `tests/encrypted_domain/scalars/mod.rs` (test binary): the //! `ordered_numeric_matrix!` suites. -//! - `scalar_harness!(fixture_dispatch)` — in `tests/generate_all_fixtures.rs` +//! - `scalar_types!(fixture_dispatch)` — in `tests/generate_all_fixtures.rs` //! (test binary): the `generate_for_token` dispatch fn. //! //! The list appears once; the four mode arms are pure expansions of it, so the @@ -34,25 +35,25 @@ //! the binary actually emits against `eql-codegen list-types`, so a catalog //! type missing from this list fails loudly. -/// Forward the single canonical scalar-harness list to the `eql_tests_macros` +/// Forward the single canonical scalar-type list to the `eql_tests_macros` /// proc-macro selected by `$mode`. See the module docs for the call sites. /// /// THE LIST. This is the only place the harness token set is declared. Keep it /// in sync with `eql-scalars::CATALOG` — the matrix-inventory cross-check /// enforces that they agree. #[macro_export] -macro_rules! scalar_harness { +macro_rules! scalar_types { (scalar_type_impls) => { - $crate::scalar_harness!(@dispatch emit_scalar_type_impls); + $crate::scalar_types!(@dispatch emit_scalar_type_impls); }; (fixture_modules) => { - $crate::scalar_harness!(@dispatch emit_scalar_fixture_modules); + $crate::scalar_types!(@dispatch emit_scalar_fixture_modules); }; (matrix_suites) => { - $crate::scalar_harness!(@dispatch emit_scalar_matrix_suites); + $crate::scalar_types!(@dispatch emit_scalar_matrix_suites); }; (fixture_dispatch) => { - $crate::scalar_harness!(@dispatch emit_fixture_dispatch); + $crate::scalar_types!(@dispatch emit_fixture_dispatch); }; (@dispatch $emitter:ident) => { $crate::eql_tests_macros::$emitter! { diff --git a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs index 0ba4ddc5..f900af3e 100644 --- a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs +++ b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs @@ -2,7 +2,7 @@ //! holds its `ordered_numeric_matrix!` invocation. //! //! The modules are generated from the single harness list in -//! `tests/sqlx/src/scalar_harness.rs` — adding a type there adds its suite here +//! `tests/sqlx/src/scalar_types.rs` — adding a type there adds its suite here //! automatically. The old per-type `scalars/.rs` files are gone. -eql_tests::scalar_harness!(matrix_suites); +eql_tests::scalar_types!(matrix_suites); diff --git a/tests/sqlx/tests/generate_all_fixtures.rs b/tests/sqlx/tests/generate_all_fixtures.rs index e81ab9d0..72ebbdf0 100644 --- a/tests/sqlx/tests/generate_all_fixtures.rs +++ b/tests/sqlx/tests/generate_all_fixtures.rs @@ -16,11 +16,11 @@ use eql_scalars::CATALOG; // `generate_for_token(token: &str) -> anyhow::Result<()>` is generated from the -// single harness list in `tests/sqlx/src/scalar_harness.rs`: one match arm per +// single harness list in `tests/sqlx/src/scalar_types.rs`: one match arm per // token (`"int4" => fixtures::eql_v2_int4::spec().run().await`) plus a loud // catch-all. A catalog token absent from that list hits the catch-all and fails // the generator loudly, so a new scalar type cannot silently skip generation. -eql_tests::scalar_harness!(fixture_dispatch); +eql_tests::scalar_types!(fixture_dispatch); #[tokio::test] #[ignore = "generator — run via `mise run fixture:generate:all`"] From c49e1644f7e776a5f53fcfecb631eb88381889e9 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 13:03:40 +1000 Subject: [PATCH 62/93] feat(int8): add eql_v3.int8 encrypted-domain type family One ScalarSpec row in eql-scalars::CATALOG, one `EqlPlaintext for i64` impl, and one `int8 => i64` line in the scalar_types! list. The generated SQL surface, the ScalarType impl, the fixture module, the matrix suite, and the fixture dispatch all follow from those. No bespoke catalog tests: the generic CATALOG checks cover int8; only the order-pin assertion and one `check(&INT8, INT8_VALUES)` line are touched. Generated SQL is byte-identical to the int4 reference modulo token. --- CHANGELOG.md | 1 + crates/eql-scalars/src/lib.rs | 22 +++++++++++++-- tests/sqlx/src/fixtures/eql_plaintext.rs | 36 ++++++++++++++++++++++-- tests/sqlx/src/scalar_types.rs | 1 + 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76700194..7969bf32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Each entry that ships in a published release links to the PR that introduced it. - **`eql_v3` encrypted-domain schema, with the `int4` family as its first member.** Encrypted-domain type families now live in a new, additional `eql_v3` schema (the existing `eql_v2` schema is unchanged — it keeps the core types/operators and stays the documented public API). Four jsonb-backed domains for encrypted `int4` columns: `eql_v3.int4` (storage-only), `eql_v3.int4_eq` (`=` / `<>` via HMAC), and `eql_v3.int4_ord` / `eql_v3.int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. The extractors still return the core `eql_v2.hmac_256` / `eql_v2.ore_block_u64_8_256` index-term types, which remain in `eql_v2` and are referenced cross-schema. Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`, namespaced under its own schema. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) - **`eql_v3.int2` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int2` columns — `eql_v3.int2` (storage-only), `eql_v3.int2_eq` (`=` / `<>` via HMAC), and `eql_v3.int2_ord` / `eql_v3.int2_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from the `int2` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `smallint` column, proving the scalar generator generalizes beyond the `int4` reference. ([#243](https://github.com/cipherstash/encrypt-query-language/pull/243)) +- **`eql_v3.int8` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int8` columns — `eql_v3.int8` (storage-only), `eql_v3.int8_eq` (`=` / `<>` via HMAC), and `eql_v3.int8_ord` / `eql_v3.int8_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from the `int8` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `bigint` column, extending the scalar generator across the full 64-bit integer width. ([#253](https://github.com/cipherstash/encrypt-query-language/pull/253)) - **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v3.min(eql_v3._ord)` / `eql_v3.max(eql_v3._ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) ### Changed diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index 07bb45e8..e41d20f4 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -371,6 +371,13 @@ const INT2_FIXTURES: &[Fixture] = fixtures!(int i16; Min, N(-30000), N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), N(25), N(42), N(50), N(100), N(250), N(1000), N(9999), N(30000), Max); +/// int8 fixture plaintexts — the int4 set plus two values beyond the i32 range +/// (`±5_000_000_000`) so the matrix exercises the full 64-bit width. `N(..)` +/// literals are range-checked against `i64` at compile time. +const INT8_FIXTURES: &[Fixture] = fixtures!(int i64; + Min, N(-5000000000), N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), + N(25), N(42), N(50), N(100), N(250), N(1000), N(9999), N(5000000000), Max); + const INT4: ScalarSpec = ScalarSpec { token: "int4", kind: ScalarKind::I32, @@ -385,9 +392,16 @@ const INT2: ScalarSpec = ScalarSpec { fixtures: INT2_FIXTURES, }; +const INT8: ScalarSpec = ScalarSpec { + token: "int8", + kind: ScalarKind::I64, + domains: ORDERED_INT_DOMAINS, + fixtures: INT8_FIXTURES, +}; + /// The scalar catalog — the single source of truth. Order is significant (it /// drives generation order). New types are appended as their SQL surface lands. -pub const CATALOG: &[ScalarSpec] = &[INT4, INT2]; +pub const CATALOG: &[ScalarSpec] = &[INT4, INT2, INT8]; /// Materialise an integer scalar's fixtures into a typed `&'static` slice at /// compile time. This is the **single-sourced** plaintext list the SQLx test @@ -425,6 +439,7 @@ macro_rules! int_values { int_values!(INT4_VALUES, i32, INT4); int_values!(INT2_VALUES, i16, INT2); +int_values!(INT8_VALUES, i64, INT8); #[cfg(test)] mod rust_tests { @@ -755,9 +770,9 @@ mod catalog_tests { } #[test] - fn catalog_has_int4_int2_in_order() { + fn catalog_has_int4_int2_int8_in_order() { let tokens: Vec<&str> = CATALOG.iter().map(|s| s.token).collect(); - assert_eq!(tokens, vec!["int4", "int2"]); + assert_eq!(tokens, vec!["int4", "int2", "int8"]); } #[test] @@ -842,6 +857,7 @@ mod values_tests { fn materialised_values_match_resolved_fixtures() { check(&INT4, INT4_VALUES); check(&INT2, INT2_VALUES); + check(&INT8, INT8_VALUES); } } diff --git a/tests/sqlx/src/fixtures/eql_plaintext.rs b/tests/sqlx/src/fixtures/eql_plaintext.rs index 3fde4393..72c5b2a0 100644 --- a/tests/sqlx/src/fixtures/eql_plaintext.rs +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -55,6 +55,7 @@ pub struct PlaintextSqlType(&'static str); impl PlaintextSqlType { pub const INTEGER: PlaintextSqlType = PlaintextSqlType("integer"); pub const SMALLINT: PlaintextSqlType = PlaintextSqlType("smallint"); + pub const BIGINT: PlaintextSqlType = PlaintextSqlType("bigint"); pub fn as_str(&self) -> &'static str { self.0 @@ -76,7 +77,8 @@ const fn cast_for_kind(kind: ScalarKind) -> Cast { match kind { ScalarKind::I32 => Cast::INT, ScalarKind::I16 => Cast::SMALL_INT, - ScalarKind::I64 | ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + ScalarKind::I64 => Cast::BIG_INT, + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { panic!("EqlPlaintext is only implemented for integer scalar kinds") } } @@ -89,7 +91,8 @@ const fn plaintext_sql_type_for_kind(kind: ScalarKind) -> PlaintextSqlType { match kind { ScalarKind::I32 => PlaintextSqlType::INTEGER, ScalarKind::I16 => PlaintextSqlType::SMALLINT, - ScalarKind::I64 | ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + ScalarKind::I64 => PlaintextSqlType::BIGINT, + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { panic!("EqlPlaintext is only implemented for integer scalar kinds") } } @@ -99,6 +102,7 @@ mod sealed { pub trait Sealed {} impl Sealed for i32 {} impl Sealed for i16 {} + impl Sealed for i64 {} } /// A Rust type usable as a fixture `plaintext` value, carrying its EQL cast @@ -142,6 +146,14 @@ impl EqlPlaintext for i16 { } } +impl EqlPlaintext for i64 { + const KIND: ScalarKind = ScalarKind::I64; + + fn to_plaintext(&self) -> Plaintext { + Plaintext::BigInt(Some(*self)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -191,4 +203,24 @@ mod tests { other => panic!("expected Plaintext::SmallInt(Some(42)), got {other:?}"), } } + + #[test] + fn i64_casts_to_big_int() { + assert_eq!(::CAST.as_str(), "big_int"); + } + + #[test] + fn i64_plaintext_sql_type_is_bigint() { + assert_eq!(::PLAINTEXT_SQL_TYPE.as_str(), "bigint"); + } + + #[test] + fn i64_to_plaintext_wraps_in_big_int_variant() { + // i64 must lift into the BigInt variant so the fixture driver + // encrypts it under the `big_int` cast, not `int`. + match 42_i64.to_plaintext() { + Plaintext::BigInt(Some(value)) => assert_eq!(value, 42), + other => panic!("expected Plaintext::BigInt(Some(42)), got {other:?}"), + } + } } diff --git a/tests/sqlx/src/scalar_types.rs b/tests/sqlx/src/scalar_types.rs index 0e57a39c..96dc7cf5 100644 --- a/tests/sqlx/src/scalar_types.rs +++ b/tests/sqlx/src/scalar_types.rs @@ -59,6 +59,7 @@ macro_rules! scalar_types { $crate::eql_tests_macros::$emitter! { int4 => i32, int2 => i16, + int8 => i64, } }; } From e8bbe81d2888d553d15ad147b2b0be1fb025ae14 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:11:07 +1000 Subject: [PATCH 63/93] =?UTF-8?q?build:=20always=20run=20codegen=20?= =?UTF-8?q?=E2=80=94=20drop=20mise=20sources/outputs=20incremental=20skip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `#MISE sources`/`outputs` declarations let mise skip the `build` task when it judged the inputs unchanged and the release artefacts already present. Because the encrypted-domain SQL is gitignored and regenerated by `cargo run -p eql-codegen`, a skipped build could ship stale `release/*.sql`. Drop them so the build always regenerates. --- tasks/build.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tasks/build.sh b/tasks/build.sh index 311dfbc7..ead83184 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash #MISE description="Build SQL into single release file" #MISE alias="b" -#MISE sources=["src/**/*.sql", "tasks/pin_search_path.sql", "tasks/uninstall.sql", "tasks/uninstall-protect.sql", "crates/eql-scalars/src/**/*.rs", "crates/eql-codegen/src/**/*.rs"] -#MISE outputs=["release/cipherstash-encrypt.sql","release/cipherstash-encrypt-uninstall.sql","release/cipherstash-encrypt-protect.sql","release/cipherstash-encrypt-protect-uninstall.sql"] #USAGE flag "--version " help="Specify release version of EQL" default="DEV" #!/bin/bash From f0a03d168ead2b91c4d4caf0390aa5f1b31bf16e Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:28:30 +1000 Subject: [PATCH 64/93] docs(tests): tighten scalar-harness macro comments Condense verbose module/item docs on the scalar_types! macro wiring, keeping the load-bearing facts (single source of truth, three compilation contexts, token => rust_type format, matrix-inventory cross-check). Comment-only change. --- crates/eql-tests-macros/src/lib.rs | 141 +++++++++++------------------ tests/sqlx/src/fixtures/mod.rs | 11 +-- tests/sqlx/src/scalar_types.rs | 58 +++++------- 3 files changed, 80 insertions(+), 130 deletions(-) diff --git a/crates/eql-tests-macros/src/lib.rs b/crates/eql-tests-macros/src/lib.rs index af74c1b2..537a8312 100644 --- a/crates/eql-tests-macros/src/lib.rs +++ b/crates/eql-tests-macros/src/lib.rs @@ -1,51 +1,31 @@ -//! Proc-macros that expand ONE declarative scalar-harness list into all the -//! per-type SQLx-matrix wiring that used to be hand-maintained across four -//! locations. +//! Proc-macros that expand one declarative scalar-type list into the per-type +//! SQLx-matrix wiring that used to be hand-maintained across four locations. //! -//! # Why a proc-macro (and why more than one) -//! -//! Adding a scalar encrypted-domain type used to require editing several -//! files in lock-step (see -//! `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3). The harness -//! wiring — everything *except* the catalog row in `eql-scalars` and the -//! `EqlPlaintext` impl, which are owned by a separate task — is now driven by -//! a SINGLE list, e.g.: +//! The list lives once in the `scalar_types!` `macro_rules!` in +//! `tests/sqlx/src/scalar_types.rs`: //! //! ```ignore //! eql_tests::scalar_types! { //! int4 => i32, //! int2 => i16, -//! int8 => i64, //! } //! ``` //! -//! Rust macros emit code into the crate/module where they are invoked, and the -//! harness pieces live in three *different* compilation contexts: -//! -//! 1. the `ScalarType` impls + the fixture modules live in the `eql-tests` -//! **library** (`src/`); -//! 2. the `ordered_numeric_matrix!` suites live in the `encrypted_domain` -//! **integration-test binary** (`tests/`), a separate crate target; -//! 3. the `generate_all_fixtures` dispatch lives in *another* integration-test -//! binary (`tests/generate_all_fixtures.rs`). -//! -//! A single macro invocation cannot emit into all three at once, so the design -//! is: ONE canonical list, captured by the `macro_rules! scalar_types!` -//! re-exported from `eql-tests` (see `tests/sqlx/src/scalar_types.rs`), which -//! forwards that same list to whichever proc-macro is appropriate for the call -//! site. Each proc-macro below parses the identical `token => rust_type` list -//! and emits only the items that belong where it is invoked. The list itself is -//! the single source of truth; the four emitters are pure functions of it. +//! The harness pieces live in three separate compilation contexts — the +//! `eql-tests` lib, the `encrypted_domain` integration-test binary, and the +//! `generate_all_fixtures` integration-test binary — so no single invocation +//! can emit them all. `scalar_types!` forwards the same list to whichever +//! proc-macro below fits the call site; each parses the list and emits only the +//! items belonging there. The list is the single source of truth; the four +//! emitters are pure functions of it. //! -//! Each entry is `token => rust_type` where `token` is the Postgres type token -//! (e.g. `int4`, also the fixture/domain name suffix) and `rust_type` is the -//! Rust plaintext type (`i32`). The catalog value const is derived by -//! upper-casing the token and appending `_VALUES` (`int4` -> `INT4_VALUES`), -//! matching `eql_scalars::INT4_VALUES`. +//! Each entry is `token => rust_type`: `token` is the Postgres type token +//! (`int4`, also the fixture/domain suffix), `rust_type` is the Rust plaintext +//! type (`i32`). The catalog value const is the upper-cased token plus +//! `_VALUES` (`int4` -> `eql_scalars::INT4_VALUES`). //! -//! The four emitters are split into thin `#[proc_macro]` shims and pure -//! `proc_macro2::TokenStream` core functions (`*_tokens`) so the core logic is -//! unit-testable without a consumer crate — see the `tests` module. +//! Each emitter is split into a thin `#[proc_macro]` shim and a pure `*_tokens` +//! core so the core is unit-testable without a consumer crate. use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; @@ -54,12 +34,12 @@ use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{Ident, Token, Type}; -/// One `token => rust_type` entry from the harness list. +/// One `token => rust_type` entry. struct ScalarEntry { - /// The Postgres type token (`int4`), also the fixture/domain name suffix - /// and the `suite` ident in the matrix invocation. + /// Postgres type token (`int4`); also the fixture/domain suffix and the + /// matrix `suite` ident. token: Ident, - /// The Rust plaintext type (`i32`). + /// Rust plaintext type (`i32`). rust_type: Type, } @@ -72,7 +52,7 @@ impl Parse for ScalarEntry { } } -/// The whole comma-separated list, with optional trailing comma. +/// The comma-separated list (optional trailing comma). struct ScalarList { entries: Vec, } @@ -86,8 +66,7 @@ impl Parse for ScalarList { } } -/// `int4` -> `INT4_VALUES` — the catalog value const for a token. Mirrors the -/// `int_values!(INT4_VALUES, ...)` naming in `eql_scalars`. +/// `int4` -> `INT4_VALUES`, the catalog value const in `eql_scalars`. fn values_const_ident(token: &Ident) -> Ident { format_ident!("{}_VALUES", token.to_string().to_uppercase()) } @@ -106,10 +85,9 @@ fn scalar_type_impls_tokens(list: &ScalarList) -> TokenStream2 { quote! { impl ScalarType for #rust_type { const PG_TYPE: &'static str = #token_str; - /// Single-sourced from the matching row in `eql-scalars::CATALOG` - /// (`eql_scalars::*_VALUES`, materialised from its `Fixture` - /// list) — the same list the fixture generator encrypts, so the - /// oracle cannot drift from the fixture. + /// The catalog `eql_scalars::*_VALUES` list — the same values + /// the fixture generator encrypts, so the oracle can't drift + /// from the fixture. const FIXTURE_VALUES: &'static [#rust_type] = ::eql_scalars::#values; } } @@ -130,9 +108,8 @@ fn scalar_fixture_modules_tokens(list: &ScalarList) -> TokenStream2 { #[doc = concat!("`eql_v2_", #token_str, "` scalar fixture — generated by `scalar_types!`.")] pub mod #mod_ident { use ::eql_scalars::#values as VALUES; - // `scalar_fixture!` is `#[macro_export]`ed from this crate - // (`eql-tests`), so `crate::scalar_fixture!` resolves here since - // the modules are emitted into the `eql-tests` lib. + // `scalar_fixture!` is `#[macro_export]`ed by `eql-tests`; + // these modules expand into that lib, so `crate::` resolves it. crate::scalar_fixture!(#fixture_name, #rust_type, VALUES); } } @@ -150,10 +127,9 @@ fn fixture_dispatch_tokens(list: &ScalarList) -> TokenStream2 { } }); quote! { - /// Map a catalog token to its fixture generator and run it. Generated by - /// `scalar_types!` from the single harness list. A token present in the - /// catalog but absent from that list hits the catch-all below and fails - /// loudly so a new scalar type cannot silently skip fixture generation. + /// Map a catalog token to its fixture generator and run it. A token in + /// the catalog but absent from the harness list hits the catch-all and + /// fails loudly, so a new scalar type can't silently skip generation. async fn generate_for_token(token: &str) -> anyhow::Result<()> { match token { #(#arms)* @@ -195,11 +171,9 @@ fn scalar_matrix_suites_tokens(list: &ScalarList) -> TokenStream2 { /// Emit one `impl ScalarType for ` per entry. /// -/// Invoked (via `scalar_types!`) inside `tests/sqlx/src/scalar_domains.rs`, -/// so the impls land in the `eql-tests` library next to the trait. Replaces the -/// three hand-written impls. `PG_TYPE` is the token string; `FIXTURE_VALUES` -/// is the catalog const `eql_scalars::_VALUES` — single-sourced -/// from the catalog so the oracle cannot drift from the fixture. +/// Invoked via `scalar_types!` in `tests/sqlx/src/scalar_domains.rs`, so the +/// impls land in the `eql-tests` lib next to the trait. `PG_TYPE` is the token +/// string; `FIXTURE_VALUES` is the catalog const `eql_scalars::_VALUES`. #[proc_macro] pub fn emit_scalar_type_impls(input: TokenStream) -> TokenStream { let list = syn::parse_macro_input!(input as ScalarList); @@ -208,28 +182,23 @@ pub fn emit_scalar_type_impls(input: TokenStream) -> TokenStream { /// Emit one `pub mod eql_v2_ { ... }` per entry. /// -/// Invoked (via `scalar_types!`) inside `tests/sqlx/src/fixtures/mod.rs`, so -/// the modules land at `crate::fixtures::eql_v2_` — the path the matrix -/// and the fixture dispatch reference. Each module body is exactly what the old -/// per-type `fixtures/eql_v2_.rs` file contained: a `use` of the catalog -/// value const plus a `scalar_fixture!` invocation. The per-type files are -/// therefore deleted. +/// Invoked via `scalar_types!` in `tests/sqlx/src/fixtures/mod.rs`, so the +/// modules land at `crate::fixtures::eql_v2_` — the path the matrix and +/// fixture dispatch reference. Each body is a `use` of the catalog value const +/// plus a `scalar_fixture!` invocation. #[proc_macro] pub fn emit_scalar_fixture_modules(input: TokenStream) -> TokenStream { let list = syn::parse_macro_input!(input as ScalarList); scalar_fixture_modules_tokens(&list).into() } -/// Emit the `generate_for_token` dispatch as a single function driven by the -/// list. +/// Emit the `generate_for_token` dispatch fn. /// -/// Invoked (via `scalar_types!`) inside `tests/generate_all_fixtures.rs`. -/// Emits an `async fn generate_for_token(token: &str) -> anyhow::Result<()>` -/// with one match arm per entry plus a loud catch-all, replacing the -/// hand-written match. A catalog token with no entry here (i.e. not in the -/// harness list) hits the catch-all and fails the generator loudly — preserving -/// the "a catalog type with no harness wiring fails loudly" guarantee at -/// generation time (the matrix-inventory cross-check enforces it at test time). +/// Invoked via `scalar_types!` in `tests/generate_all_fixtures.rs`. Emits an +/// `async fn generate_for_token(token: &str)` with one match arm per entry plus +/// a loud catch-all, so a catalog token missing from the harness list fails the +/// generator loudly. (The matrix-inventory cross-check enforces the same at +/// test time.) #[proc_macro] pub fn emit_fixture_dispatch(input: TokenStream) -> TokenStream { let list = syn::parse_macro_input!(input as ScalarList); @@ -238,16 +207,12 @@ pub fn emit_fixture_dispatch(input: TokenStream) -> TokenStream { /// Emit one `pub mod { ordered_numeric_matrix! { ... } }` per entry. /// -/// Invoked (via `scalar_types!`) inside +/// Invoked via `scalar_types!` in /// `tests/sqlx/tests/encrypted_domain/scalars/mod.rs`, so the matrix suites land -/// in the `encrypted_domain` integration-test binary — the only place -/// `#[sqlx::test]` suites belong. This is a SEPARATE proc-macro from the -/// lib-side ones because that binary is a different crate target: a single macro -/// invocation in `src/` could not emit into `tests/`. The generated module + -/// `ordered_numeric_matrix!` invocation are byte-equivalent to the old per-type -/// `scalars/.rs` files, so the emitted test names (`scalars:::: -/// matrix_*`) are unchanged and the token-normalized `matrix_tests.txt` snapshot -/// keeps matching. +/// in the `encrypted_domain` integration-test binary where `#[sqlx::test]` +/// suites belong. Separate from the lib-side macros because that binary is a +/// different crate target. The emitted test names (`scalars::::matrix_*`) +/// match the old per-type files, so the `matrix_tests.txt` snapshot still holds. #[proc_macro] pub fn emit_scalar_matrix_suites(input: TokenStream) -> TokenStream { let list = syn::parse_macro_input!(input as ScalarList); @@ -262,8 +227,8 @@ mod tests { syn::parse_str::("int4 => i32, int8 => i64,").unwrap() } - /// The token-stream comparison is on the normalized `to_string()` form so - /// whitespace/formatting differences don't make the assertions brittle. + /// Normalize to the `to_string()` form so whitespace differences don't make + /// assertions brittle. fn norm(ts: &TokenStream2) -> String { ts.to_string() } @@ -314,8 +279,8 @@ mod tests { assert!(out.contains("pub mod int4")); assert!(out.contains("pub mod int8")); assert!(out.contains(":: eql_tests :: ordered_numeric_matrix !")); - // suite/scalar/eql_type must match what the old per-type files used so - // the generated test names (and the snapshot) are unchanged. + // suite/scalar/eql_type must match the old per-type files so test names + // (and the snapshot) are unchanged. assert!(out.contains("suite = int4")); assert!(out.contains("scalar = i32")); assert!(out.contains(r#"eql_type = "eql_v2_int4""#)); diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index bbcd6cce..65cdacbc 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -26,11 +26,8 @@ pub mod cipherstash; pub mod driver; -// The per-type scalar fixture modules (`eql_v2_int4`, `eql_v2_int2`, -// `eql_v2_int8`, …) are generated from the single harness list in -// `scalar_types.rs`. Each expands to `pub mod eql_v2_ { … scalar_fixture! -// … }` — the same three items the old per-type `eql_v2_.rs` files held. -// Scalar fixtures read their plaintext value lists directly from the catalog -// (`eql_scalars::_VALUES`) — see `scalar_fixture!`. There is no -// generated `_values.rs` module. +// The per-type scalar fixture modules (`eql_v2_int4`, `eql_v2_int2`, …) are +// generated from the harness list in `scalar_types.rs`. Each expands to +// `pub mod eql_v2_ { … scalar_fixture! … }`, reading its plaintext values +// directly from the catalog (`eql_scalars::_VALUES`). crate::scalar_types!(fixture_modules); diff --git a/tests/sqlx/src/scalar_types.rs b/tests/sqlx/src/scalar_types.rs index 0e57a39c..0d6a70a8 100644 --- a/tests/sqlx/src/scalar_types.rs +++ b/tests/sqlx/src/scalar_types.rs @@ -1,46 +1,34 @@ -//! The single declarative list of scalar types under matrix test — the -//! harness source of truth. +//! The single declarative list of scalar types under matrix test — the harness +//! source of truth. //! -//! Adding a scalar encrypted-domain type to the SQLx matrix used to require -//! editing several files in lock-step. That wiring is now generated from the -//! ONE list embedded in the `scalar_types!` macro below. To add a type, add -//! one `token => rust_type` line here (plus the catalog row in `eql-scalars` -//! and the `EqlPlaintext` impl, which are owned separately — see +//! To add a scalar encrypted-domain type to the SQLx matrix, add one +//! `token => rust_type` line below (plus the catalog row in `eql-scalars` and +//! the `EqlPlaintext` impl, owned separately — see //! `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3). //! -//! # How it works +//! The harness pieces live in three separate compilation contexts (the +//! `eql-tests` lib, the `encrypted_domain` integration-test binary, and the +//! `generate_all_fixtures` integration-test binary), so no single proc-macro +//! invocation can reach all three. The list is held here once, inside the +//! `scalar_types!` `macro_rules!`; each call site invokes `scalar_types!()` +//! to forward it to the matching `eql_tests_macros` proc-macro: //! -//! Proc-macros emit into the crate/module where they're invoked, and the -//! harness pieces live in three different compilation contexts (the `eql-tests` -//! lib, the `encrypted_domain` integration-test binary, and the -//! `generate_all_fixtures` integration-test binary). One proc-macro invocation -//! can't reach all three. So the canonical list is held *here once*, inside the -//! `scalar_types!` `macro_rules!`, and each call site invokes -//! `scalar_types!()` to forward that same list to the -//! `eql_tests_macros` proc-macro appropriate for that site: +//! - `scalar_type_impls` — `scalar_domains.rs` (lib): the `impl ScalarType` block. +//! - `fixture_modules` — `fixtures/mod.rs` (lib): the `pub mod eql_v2_` modules. +//! - `matrix_suites` — `tests/encrypted_domain/scalars/mod.rs` (test binary): +//! the `ordered_numeric_matrix!` suites. +//! - `fixture_dispatch` — `tests/generate_all_fixtures.rs` (test binary): the +//! `generate_for_token` dispatch fn. //! -//! - `scalar_types!(scalar_type_impls)` — in `scalar_domains.rs` (lib): the -//! `impl ScalarType` block. -//! - `scalar_types!(fixture_modules)` — in `fixtures/mod.rs` (lib): the -//! `pub mod eql_v2_` fixture modules. -//! - `scalar_types!(matrix_suites)` — in -//! `tests/encrypted_domain/scalars/mod.rs` (test binary): the -//! `ordered_numeric_matrix!` suites. -//! - `scalar_types!(fixture_dispatch)` — in `tests/generate_all_fixtures.rs` -//! (test binary): the `generate_for_token` dispatch fn. -//! -//! The list appears once; the four mode arms are pure expansions of it, so the -//! list is genuinely the single source of truth. The matrix-inventory -//! cross-check (`mise run test:matrix:inventory`) still compares the type set -//! the binary actually emits against `eql-codegen list-types`, so a catalog +//! The matrix-inventory cross-check (`mise run test:matrix:inventory`) compares +//! the type set the binary emits against `eql-codegen list-types`, so a catalog //! type missing from this list fails loudly. -/// Forward the single canonical scalar-type list to the `eql_tests_macros` -/// proc-macro selected by `$mode`. See the module docs for the call sites. +/// Forward the canonical scalar-type list to the `eql_tests_macros` proc-macro +/// selected by `$mode` (see module docs for call sites). /// -/// THE LIST. This is the only place the harness token set is declared. Keep it -/// in sync with `eql-scalars::CATALOG` — the matrix-inventory cross-check -/// enforces that they agree. +/// This is the only place the harness token set is declared. Keep it in sync +/// with `eql-scalars::CATALOG`; the matrix-inventory cross-check enforces it. #[macro_export] macro_rules! scalar_types { (scalar_type_impls) => { From 05e6b8199db783988a0f7531d288b03d33126dd6 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:17:58 +1000 Subject: [PATCH 65/93] refactor(codegen): target eql_v3 schema and src/v3/scalars output paths Flip CORE_SCHEMA to eql_v3, qualify the extractor RETURNS type with the core schema (D12), repoint generated output paths and REQUIRE edges to src/v3, drop the vestigial src/schema.sql edge (D10), and repoint term REQUIRE edges to src/v3/sem. Golden reference regenerated in the follow-up commit. --- .gitignore | 17 ++++++++----- crates/eql-codegen/src/consts.rs | 2 +- crates/eql-codegen/src/context.rs | 7 ++++- crates/eql-codegen/src/generate.rs | 31 +++++++++++------------ crates/eql-codegen/templates/types.sql.j2 | 4 +-- crates/eql-codegen/tests/parity.rs | 2 +- crates/eql-scalars/src/lib.rs | 18 ++++++------- tasks/codegen-parity.sh | 4 +-- 8 files changed, 46 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index bdc8e444..83ee4c42 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ deps-ordered.txt deps-supabase.txt deps-ordered-supabase.txt +src/deps-v3.txt +src/deps-ordered-v3.txt + # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore src/version.sql @@ -223,13 +226,13 @@ tests/sqlx/migrations/001_install_eql.sql # never commit — stale fixtures hide bugs) tests/sqlx/fixtures/eql_v2* -# Generated encrypted-domain SQL — regenerated by `tasks/build.sh` from -# tasks/codegen/types/.toml on every build (or `mise run codegen:domain -# ` to refresh manually). Hand-written *_extensions.sql stays committed. -src/encrypted_domain/*/*_types.sql -src/encrypted_domain/*/*_functions.sql -src/encrypted_domain/*/*_operators.sql -src/encrypted_domain/*/*_aggregates.sql +# Generated encrypted-domain SQL — regenerated by `tasks/build.sh` from the +# eql-scalars::CATALOG via `cargo run -p eql-codegen` on every build. The +# hand-written src/v3/scalars/functions.sql (no type subdir) stays committed. +src/v3/scalars/*/*_types.sql +src/v3/scalars/*/*_functions.sql +src/v3/scalars/*/*_operators.sql +src/v3/scalars/*/*_aggregates.sql # Large generated test data files tests/ste_vec_vast.sql diff --git a/crates/eql-codegen/src/consts.rs b/crates/eql-codegen/src/consts.rs index 64d2e438..635c4908 100644 --- a/crates/eql-codegen/src/consts.rs +++ b/crates/eql-codegen/src/consts.rs @@ -7,7 +7,7 @@ pub(crate) const AUTO_GENERATED_HEADER: &str = "-- AUTOMATICALLY GENERATED FILE. /// Schema housing the encrypted-domain families. pub(crate) const DOMAIN_SCHEMA: &str = "eql_v3"; /// Schema owning the core index-term types/constructors. -pub(crate) const CORE_SCHEMA: &str = "eql_v2"; +pub(crate) const CORE_SCHEMA: &str = "eql_v3"; /// Always-present payload keys checked for presence in every domain CHECK, in /// order: envelope version (`v`), ident (`i`), ciphertext (`c`). Term-specific diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs index 01e80c6c..225c6cda 100644 --- a/crates/eql-codegen/src/context.rs +++ b/crates/eql-codegen/src/context.rs @@ -132,9 +132,14 @@ pub struct FunctionsContext { } /// Build the inlinable index-extractor entry for a domain term. +/// +/// The `RETURNS` type name equals the constructor name (`hmac_256`, +/// `ore_block_u64_8_256`); qualify it with `CORE_SCHEMA` here so flipping the +/// core schema moves BOTH the body's constructor call and the declared return +/// type together (design D12). `Term::returns()` is intentionally not used. pub fn extractor_entry(term: Term) -> FnEntry { FnEntry::Extractor { - ret: term.returns().to_string(), + ret: format!("{CORE_SCHEMA}.{}", term.ctor()), extractor: term.extractor().to_string(), ctor: term.ctor().to_string(), } diff --git a/crates/eql-codegen/src/generate.rs b/crates/eql-codegen/src/generate.rs index 581f0244..f7157971 100644 --- a/crates/eql-codegen/src/generate.rs +++ b/crates/eql-codegen/src/generate.rs @@ -25,7 +25,7 @@ fn arg_b_name(symbol: &str) -> &'static str { /// REQUIRE path for a type's _types.sql. Port of `_types_path`. fn types_path(token: &str) -> String { - format!("src/encrypted_domain/{token}/{token}_types.sql") + format!("src/v3/scalars/{token}/{token}_types.sql") } /// Body for _types.sql: every domain in one idempotent DO block. @@ -50,10 +50,9 @@ pub fn render_types_file(spec: &ScalarSpec) -> String { /// REQUIRE edges for a domain's _functions.sql. Port of `_functions_requires`. fn functions_requires(token: &str, terms: &[Term]) -> Vec { let mut reqs = vec![ - "src/schema.sql".to_string(), - "src/schema-v3.sql".to_string(), + "src/v3/schema.sql".to_string(), types_path(token), - "src/encrypted_domain/functions.sql".to_string(), + "src/v3/scalars/functions.sql".to_string(), ]; for extra in Term::term_requires(terms) { if !reqs.iter().any(|r| r == extra) { @@ -163,9 +162,9 @@ pub fn render_operators_file(token: &str, domain: &DomainSpec) -> String { let ctx = OperatorsContext { requires: vec![ - "src/schema-v3.sql".to_string(), + "src/v3/schema.sql".to_string(), types_path(token), - format!("src/encrypted_domain/{token}/{name}_functions.sql"), + format!("src/v3/scalars/{token}/{name}_functions.sql"), ], token: token.to_string(), name, @@ -190,10 +189,10 @@ pub fn render_aggregates_file(token: &str, domain: &DomainSpec) -> Option Result, pub fn generate_all(out_root: &Path) -> Result { for spec in eql_scalars::CATALOG { let token = spec.token; - let out_dir = out_root.join("src").join("encrypted_domain").join(token); + let out_dir = out_root.join("src").join("v3").join("scalars").join(token); let written = generate_type(spec, &out_dir)?; for p in &written { @@ -475,7 +474,7 @@ mod tests { #[test] fn types_file_has_all_four_domains() { let sql = render_types_file(spec("int4")); - assert!(sql.contains("-- REQUIRE: src/schema-v3.sql")); + assert!(sql.contains("-- REQUIRE: src/v3/schema.sql")); for dom in ["int4", "int4_eq", "int4_ord_ore", "int4_ord"] { assert!( sql.contains(&format!("CREATE DOMAIN eql_v3.{dom} AS jsonb")), @@ -504,7 +503,7 @@ mod tests { let sql = render_functions_file(s.token, domain(s, "_eq")); assert_eq!(sql.matches("CREATE FUNCTION").count(), 45); assert!(sql.contains("CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq)")); - assert!(sql.contains("RETURNS eql_v2.hmac_256")); + assert!(sql.contains("RETURNS eql_v3.hmac_256")); assert_eq!( sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") .count(), @@ -520,7 +519,7 @@ mod tests { let sql = render_functions_file(s.token, domain(s, "_ord")); assert_eq!(sql.matches("CREATE FUNCTION").count(), 45); assert!(sql.contains("CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord)")); - assert!(sql.contains("RETURNS eql_v2.ore_block_u64_8_256")); + assert!(sql.contains("RETURNS eql_v3.ore_block_u64_8_256")); assert_eq!( sql.matches("LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE") .count(), @@ -553,9 +552,9 @@ mod tests { assert_eq!(sql.matches("CREATE AGGREGATE").count(), 2); assert!(sql.contains("eql_v3.min_sfunc")); assert!(sql.contains("eql_v3.max_sfunc")); - assert!(sql.contains("-- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql")); - assert!(sql.contains("-- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql")); - assert!(sql.contains("-- REQUIRE: src/encrypted_domain/int4/int4_types.sql")); + assert!(sql.contains("-- REQUIRE: src/v3/scalars/int4/int4_ord_operators.sql")); + assert!(sql.contains("-- REQUIRE: src/v3/scalars/int4/int4_ord_functions.sql")); + assert!(sql.contains("-- REQUIRE: src/v3/scalars/int4/int4_types.sql")); } #[test] diff --git a/crates/eql-codegen/templates/types.sql.j2 b/crates/eql-codegen/templates/types.sql.j2 index 99636ac0..b8263e24 100644 --- a/crates/eql-codegen/templates/types.sql.j2 +++ b/crates/eql-codegen/templates/types.sql.j2 @@ -1,7 +1,7 @@ -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql +-- REQUIRE: src/v3/schema.sql ---! @file encrypted_domain/{{ token }}/{{ token }}_types.sql +--! @file v3/scalars/{{ token }}/{{ token }}_types.sql --! @brief Encrypted-domain types for {{ token }}. DO $$ diff --git a/crates/eql-codegen/tests/parity.rs b/crates/eql-codegen/tests/parity.rs index a59ed8dc..d5efb193 100644 --- a/crates/eql-codegen/tests/parity.rs +++ b/crates/eql-codegen/tests/parity.rs @@ -36,7 +36,7 @@ fn rust_generator_matches_int4_golden_files() { eql_codegen::generate::generate_all(&out).expect("rust generate_all"); let ref_dir = root.join("tests/codegen/reference/int4"); - let gen_dir = out.join("src/encrypted_domain/int4"); + let gen_dir = out.join("src/v3/scalars/int4"); for entry in fs::read_dir(&ref_dir).unwrap() { let path = entry.unwrap().path(); if path.extension().and_then(|e| e.to_str()) != Some("sql") { diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index 07bb45e8..7dedc2f4 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -180,10 +180,10 @@ impl Term { /// SQL `-- REQUIRE:` edges this term pulls in, in catalog order. pub const fn requires(self) -> &'static [&'static str] { match self { - Term::Hm => &["src/hmac_256/functions.sql"], + Term::Hm => &["src/v3/sem/hmac_256/functions.sql"], Term::Ore => &[ - "src/ore_block_u64_8_256/functions.sql", - "src/ore_block_u64_8_256/operators.sql", + "src/v3/sem/ore_block_u64_8_256/functions.sql", + "src/v3/sem/ore_block_u64_8_256/operators.sql", ], } } @@ -517,7 +517,7 @@ mod term_tests { assert_eq!(hm.ctor(), "hmac_256"); assert_eq!(hm.role(), "eq"); assert_eq!(hm.operators(), &["=", "<>"]); - assert_eq!(hm.requires(), &["src/hmac_256/functions.sql"]); + assert_eq!(hm.requires(), &["src/v3/sem/hmac_256/functions.sql"]); } #[test] @@ -532,8 +532,8 @@ mod term_tests { assert_eq!( ore.requires(), &[ - "src/ore_block_u64_8_256/functions.sql", - "src/ore_block_u64_8_256/operators.sql", + "src/v3/sem/ore_block_u64_8_256/functions.sql", + "src/v3/sem/ore_block_u64_8_256/operators.sql", ] ); } @@ -571,9 +571,9 @@ mod term_helper_tests { assert_eq!( Term::term_requires(&[Term::Ore, Term::Ore, Term::Hm]), vec![ - "src/ore_block_u64_8_256/functions.sql", - "src/ore_block_u64_8_256/operators.sql", - "src/hmac_256/functions.sql", + "src/v3/sem/ore_block_u64_8_256/functions.sql", + "src/v3/sem/ore_block_u64_8_256/operators.sql", + "src/v3/sem/hmac_256/functions.sql", ] ); assert!(Term::term_requires(&[]).is_empty()); diff --git a/tasks/codegen-parity.sh b/tasks/codegen-parity.sh index 2de923be..13e8bead 100755 --- a/tasks/codegen-parity.sh +++ b/tasks/codegen-parity.sh @@ -17,7 +17,7 @@ echo "==> Comparing int4 generated SQL file SET vs golden (catches extra/dropped # "Generated" excludes any committed, hand-written SQL (e.g. int4_extensions.sql), # which lives in this dir but has no golden counterpart; git-tracked == hand-written. golden_set=$(cd tests/codegen/reference/int4 && ls *.sql | LC_ALL=C sort) -gen_set=$(cd src/encrypted_domain/int4 \ +gen_set=$(cd src/v3/scalars/int4 \ && comm -23 <(ls *.sql | LC_ALL=C sort) \ <(git ls-files . | sed 's#.*/##' | LC_ALL=C sort)) if [ "$golden_set" != "$gen_set" ]; then @@ -33,7 +33,7 @@ for f in tests/codegen/reference/int4/*.sql; do # bytes EXACTLY. Both the reference body (from line 2) and the whole generated # file start with the template-owned `-- AUTOMATICALLY GENERATED FILE.` marker, # so no header strip is needed — any whitespace or blank-line drift fails here. - diff <(tail -n +2 "$f") "src/encrypted_domain/int4/$name" + diff <(tail -n +2 "$f") "src/v3/scalars/int4/$name" done echo "PARITY OK: Rust generator matches the int4 golden (byte-for-byte)." From cf514f29d39102c9eee3008f6b6c447db3527df0 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:18:48 +1000 Subject: [PATCH 66/93] test(codegen): regenerate int4 golden for the eql_v3 schema + src/v3 paths --- .../reference/int4/int4_eq_functions.sql | 17 ++++++++--------- .../reference/int4/int4_eq_operators.sql | 8 ++++---- .../codegen/reference/int4/int4_functions.sql | 9 ++++----- .../codegen/reference/int4/int4_operators.sql | 8 ++++---- .../reference/int4/int4_ord_aggregates.sql | 10 +++++----- .../reference/int4/int4_ord_functions.sql | 19 +++++++++---------- .../reference/int4/int4_ord_operators.sql | 8 ++++---- .../int4/int4_ord_ore_aggregates.sql | 10 +++++----- .../reference/int4/int4_ord_ore_functions.sql | 19 +++++++++---------- .../reference/int4/int4_ord_ore_operators.sql | 8 ++++---- tests/codegen/reference/int4/int4_types.sql | 6 +++--- 11 files changed, 59 insertions(+), 63 deletions(-) diff --git a/tests/codegen/reference/int4/int4_eq_functions.sql b/tests/codegen/reference/int4/int4_eq_functions.sql index 1b244577..54a7c048 100644 --- a/tests/codegen/reference/int4/int4_eq_functions.sql +++ b/tests/codegen/reference/int4/int4_eq_functions.sql @@ -1,21 +1,20 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema.sql --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/functions.sql --- REQUIRE: src/hmac_256/functions.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/hmac_256/functions.sql --! @file encrypted_domain/int4/int4_eq_functions.sql --! @brief Functions for eql_v3.int4_eq. --! @brief Index extractor for eql_v3.int4_eq. --! @param a eql_v3.int4_eq ---! @return eql_v2.hmac_256 +--! @return eql_v3.hmac_256 CREATE FUNCTION eql_v3.eq_term(a eql_v3.int4_eq) -RETURNS eql_v2.hmac_256 +RETURNS eql_v3.hmac_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.hmac_256(a::jsonb) $$; +AS $$ SELECT eql_v3.hmac_256(a::jsonb) $$; --! @brief Operator wrapper for eql_v3.int4_eq. --! @param a eql_v3.int4_eq diff --git a/tests/codegen/reference/int4/int4_eq_operators.sql b/tests/codegen/reference/int4/int4_eq_operators.sql index a2190e16..9da0ce32 100644 --- a/tests/codegen/reference/int4/int4_eq_operators.sql +++ b/tests/codegen/reference/int4/int4_eq_operators.sql @@ -1,8 +1,8 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/int4/int4_eq_functions.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/int4/int4_eq_functions.sql --! @file encrypted_domain/int4/int4_eq_operators.sql --! @brief Operators for eql_v3.int4_eq. diff --git a/tests/codegen/reference/int4/int4_functions.sql b/tests/codegen/reference/int4/int4_functions.sql index 6dae8388..dc142163 100644 --- a/tests/codegen/reference/int4/int4_functions.sql +++ b/tests/codegen/reference/int4/int4_functions.sql @@ -1,9 +1,8 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema.sql --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/functions.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/functions.sql --! @file encrypted_domain/int4/int4_functions.sql --! @brief Functions for eql_v3.int4. diff --git a/tests/codegen/reference/int4/int4_operators.sql b/tests/codegen/reference/int4/int4_operators.sql index e461c3b7..fb6c03f9 100644 --- a/tests/codegen/reference/int4/int4_operators.sql +++ b/tests/codegen/reference/int4/int4_operators.sql @@ -1,8 +1,8 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/int4/int4_functions.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/int4/int4_functions.sql --! @file encrypted_domain/int4/int4_operators.sql --! @brief Operators for eql_v3.int4. diff --git a/tests/codegen/reference/int4/int4_ord_aggregates.sql b/tests/codegen/reference/int4/int4_ord_aggregates.sql index 08cdc10d..95ce5d3b 100644 --- a/tests/codegen/reference/int4/int4_ord_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_aggregates.sql @@ -1,9 +1,9 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql --- REQUIRE: src/encrypted_domain/int4/int4_ord_operators.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/int4/int4_ord_functions.sql +-- REQUIRE: src/v3/scalars/int4/int4_ord_operators.sql --! @file encrypted_domain/int4/int4_ord_aggregates.sql --! @brief Aggregates for eql_v3.int4_ord. diff --git a/tests/codegen/reference/int4/int4_ord_functions.sql b/tests/codegen/reference/int4/int4_ord_functions.sql index 2c0ee56b..4b170fcb 100644 --- a/tests/codegen/reference/int4/int4_ord_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_functions.sql @@ -1,22 +1,21 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema.sql --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/functions.sql --- REQUIRE: src/ore_block_u64_8_256/functions.sql --- REQUIRE: src/ore_block_u64_8_256/operators.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/functions.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/operators.sql --! @file encrypted_domain/int4/int4_ord_functions.sql --! @brief Functions for eql_v3.int4_ord. --! @brief Index extractor for eql_v3.int4_ord. --! @param a eql_v3.int4_ord ---! @return eql_v2.ore_block_u64_8_256 +--! @return eql_v3.ore_block_u64_8_256 CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord) -RETURNS eql_v2.ore_block_u64_8_256 +RETURNS eql_v3.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; +AS $$ SELECT eql_v3.ore_block_u64_8_256(a::jsonb) $$; --! @brief Operator wrapper for eql_v3.int4_ord. --! @param a eql_v3.int4_ord diff --git a/tests/codegen/reference/int4/int4_ord_operators.sql b/tests/codegen/reference/int4/int4_ord_operators.sql index a5321c62..52a52a12 100644 --- a/tests/codegen/reference/int4/int4_ord_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_operators.sql @@ -1,8 +1,8 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/int4/int4_ord_functions.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/int4/int4_ord_functions.sql --! @file encrypted_domain/int4/int4_ord_operators.sql --! @brief Operators for eql_v3.int4_ord. diff --git a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql index de5b0848..369a1938 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_aggregates.sql @@ -1,9 +1,9 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql --- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_operators.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/int4/int4_ord_ore_functions.sql +-- REQUIRE: src/v3/scalars/int4/int4_ord_ore_operators.sql --! @file encrypted_domain/int4/int4_ord_ore_aggregates.sql --! @brief Aggregates for eql_v3.int4_ord_ore. diff --git a/tests/codegen/reference/int4/int4_ord_ore_functions.sql b/tests/codegen/reference/int4/int4_ord_ore_functions.sql index 75f09fb9..e93c8491 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_functions.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_functions.sql @@ -1,22 +1,21 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema.sql --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/functions.sql --- REQUIRE: src/ore_block_u64_8_256/functions.sql --- REQUIRE: src/ore_block_u64_8_256/operators.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/functions.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/operators.sql --! @file encrypted_domain/int4/int4_ord_ore_functions.sql --! @brief Functions for eql_v3.int4_ord_ore. --! @brief Index extractor for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore ---! @return eql_v2.ore_block_u64_8_256 +--! @return eql_v3.ore_block_u64_8_256 CREATE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord_ore) -RETURNS eql_v2.ore_block_u64_8_256 +RETURNS eql_v3.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) $$; +AS $$ SELECT eql_v3.ore_block_u64_8_256(a::jsonb) $$; --! @brief Operator wrapper for eql_v3.int4_ord_ore. --! @param a eql_v3.int4_ord_ore diff --git a/tests/codegen/reference/int4/int4_ord_ore_operators.sql b/tests/codegen/reference/int4/int4_ord_ore_operators.sql index 52f363cf..73e57f63 100644 --- a/tests/codegen/reference/int4/int4_ord_ore_operators.sql +++ b/tests/codegen/reference/int4/int4_ord_ore_operators.sql @@ -1,8 +1,8 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql --- REQUIRE: src/encrypted_domain/int4/int4_types.sql --- REQUIRE: src/encrypted_domain/int4/int4_ord_ore_functions.sql +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/int4/int4_types.sql +-- REQUIRE: src/v3/scalars/int4/int4_ord_ore_functions.sql --! @file encrypted_domain/int4/int4_ord_ore_operators.sql --! @brief Operators for eql_v3.int4_ord_ore. diff --git a/tests/codegen/reference/int4/int4_types.sql b/tests/codegen/reference/int4/int4_types.sql index ba4d9d89..01082ea2 100644 --- a/tests/codegen/reference/int4/int4_types.sql +++ b/tests/codegen/reference/int4/int4_types.sql @@ -1,8 +1,8 @@ --- REFERENCE: hand-written parity baseline for crates/eql-codegen — see ../README.md +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md -- AUTOMATICALLY GENERATED FILE. --- REQUIRE: src/schema-v3.sql +-- REQUIRE: src/v3/schema.sql ---! @file encrypted_domain/int4/int4_types.sql +--! @file v3/scalars/int4/int4_types.sql --! @brief Encrypted-domain types for int4. DO $$ From 44d2dbaef5d35a02298a0dcc0d83124ee4bfd155 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:20:00 +1000 Subject: [PATCH 67/93] feat(v3): relocate schema and fork crypto/common into src/v3 (D7, D8) --- src/schema-v3.sql | 22 -------------------- src/v3/common.sql | 38 +++++++++++++++++++++++++++++++++ src/v3/crypto.sql | 53 +++++++++++++++++++++++++++++++++++++++++++++++ src/v3/schema.sql | 23 ++++++++++++++++++++ 4 files changed, 114 insertions(+), 22 deletions(-) delete mode 100644 src/schema-v3.sql create mode 100644 src/v3/common.sql create mode 100644 src/v3/crypto.sql create mode 100644 src/v3/schema.sql diff --git a/src/schema-v3.sql b/src/schema-v3.sql deleted file mode 100644 index 06df8d38..00000000 --- a/src/schema-v3.sql +++ /dev/null @@ -1,22 +0,0 @@ ---! @file schema-v3.sql ---! @brief EQL v3 schema creation ---! ---! Creates the eql_v3 schema, which houses the encrypted-domain type ---! families (eql_v3.int4 and future scalar domains): their domains, index-term ---! extractors, comparison wrappers, blockers, and aggregates. The core ---! index-term types these reuse (eql_v2.hmac_256, eql_v2.ore_block_u64_8_256) ---! remain in the eql_v2 schema and are referenced cross-schema. ---! ---! Drops existing schema if present to support clean reinstallation. ---! ---! @warning DROP SCHEMA CASCADE will remove all objects in the schema ---! @note eql_v3 is a new, additional schema for domain families; the eql_v2 ---! schema name is unchanged. - ---! @brief Drop existing EQL v3 schema ---! @warning CASCADE will drop all dependent objects -DROP SCHEMA IF EXISTS eql_v3 CASCADE; - ---! @brief Create EQL v3 schema ---! @note Houses the encrypted-domain type families -CREATE SCHEMA eql_v3; diff --git a/src/v3/common.sql b/src/v3/common.sql new file mode 100644 index 00000000..b30366fc --- /dev/null +++ b/src/v3/common.sql @@ -0,0 +1,38 @@ +-- REQUIRE: src/v3/schema.sql + +--! @file v3/common.sql +--! @brief Common utility functions for the self-contained eql_v3 surface. +--! +--! Forked from src/common.sql (design D7) so the eql_v3 ORE constructor owns the +--! one transitive helper it needs without reaching into another schema. The +--! eql_v2 original is unchanged. + +--! @brief Convert JSONB hex array to bytea array +--! @internal +--! +--! Converts a JSONB array of hex-encoded strings into a PostgreSQL bytea array. +--! Used for deserializing binary data (like ORE terms) from JSONB storage. +--! +--! @param val jsonb JSONB array of hex-encoded strings +--! @return bytea[] Array of decoded binary values +--! +--! @note Returns NULL if input is JSON null +--! @note Each array element is hex-decoded to bytea +CREATE FUNCTION eql_v3.jsonb_array_to_bytea_array(val jsonb) +RETURNS bytea[] + SET search_path = pg_catalog, extensions, public +AS $$ +DECLARE + terms_arr bytea[]; +BEGIN + IF jsonb_typeof(val) = 'null' THEN + RETURN NULL; + END IF; + + SELECT array_agg(decode(value::text, 'hex')::bytea) + INTO terms_arr + FROM jsonb_array_elements_text(val) AS value; + + RETURN terms_arr; +END; +$$ LANGUAGE plpgsql; diff --git a/src/v3/crypto.sql b/src/v3/crypto.sql new file mode 100644 index 00000000..bd99b162 --- /dev/null +++ b/src/v3/crypto.sql @@ -0,0 +1,53 @@ +-- REQUIRE: src/v3/schema.sql + +--! @file v3/crypto.sql +--! @brief PostgreSQL pgcrypto extension enablement (eql_v3 fork) +--! +--! Forked from src/crypto.sql (design D8) so the entire eql_v3 dependency +--! closure lives under src/v3/. Enables the pgcrypto extension which provides +--! cryptographic functions used by the eql_v3 ORE comparison path. +--! +--! Installs pgcrypto into the `extensions` schema (Supabase convention) to +--! avoid the `extension_in_public` lint. Every EQL function that uses pgcrypto +--! has `pg_catalog, extensions, public` on its `search_path`, so a pre-existing +--! install in `public` keeps working — and a pre-existing install anywhere else +--! will be rejected at install time. The body is idempotent +--! (`CREATE SCHEMA IF NOT EXISTS`, `pg_extension` guard), so running it +--! alongside the eql_v2 copy in a combined install is safe. +--! +--! @note pgcrypto provides functions like digest(), hmac(), gen_random_bytes() + +--! @brief Create extensions schema (Supabase convention) +CREATE SCHEMA IF NOT EXISTS extensions; + +--! @brief Enable pgcrypto extension and validate its schema +DO $$ +DECLARE + pgcrypto_schema name; +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto') THEN + CREATE EXTENSION pgcrypto WITH SCHEMA extensions; + END IF; + + SELECT n.nspname INTO pgcrypto_schema + FROM pg_extension e + JOIN pg_namespace n ON n.oid = e.extnamespace + WHERE e.extname = 'pgcrypto'; + + IF pgcrypto_schema = 'extensions' THEN + -- expected location, nothing to say + NULL; + ELSIF pgcrypto_schema = 'public' THEN + RAISE NOTICE + 'pgcrypto is installed in the `public` schema. EQL works against this layout, ' + 'but Supabase splinter will flag it as `extension_in_public`. Move it with: ' + 'ALTER EXTENSION pgcrypto SET SCHEMA extensions'; + ELSE + RAISE EXCEPTION + 'pgcrypto is installed in schema `%`, which is not on the EQL function search_path ' + '(pg_catalog, extensions, public). EQL cryptographic operations would fail at ' + 'runtime. Relocate the extension before installing EQL: ' + 'ALTER EXTENSION pgcrypto SET SCHEMA extensions', + pgcrypto_schema; + END IF; +END $$; diff --git a/src/v3/schema.sql b/src/v3/schema.sql new file mode 100644 index 00000000..41be4d40 --- /dev/null +++ b/src/v3/schema.sql @@ -0,0 +1,23 @@ +--! @file v3/schema.sql +--! @brief EQL v3 schema creation +--! +--! Creates the eql_v3 schema, which houses the self-contained encrypted-domain +--! type families (eql_v3.int4, eql_v3.int8, and future scalar domains): their +--! jsonb-backed domains, the searchable-encrypted-metadata (SEM) index-term +--! types they use (eql_v3.hmac_256, eql_v3.ore_block_u64_8_256), the index-term +--! extractors, comparison wrappers, blockers, and aggregates. The v3 surface is +--! self-contained — it owns every type it needs and has no runtime dependency +--! on another EQL schema. +--! +--! Drops existing schema if present to support clean reinstallation. +--! +--! @warning DROP SCHEMA CASCADE will remove all objects in the schema +--! @note eql_v3 is a new, additional schema for the encrypted-domain families. + +--! @brief Drop existing EQL v3 schema +--! @warning CASCADE will drop all dependent objects +DROP SCHEMA IF EXISTS eql_v3 CASCADE; + +--! @brief Create EQL v3 schema +--! @note Houses the encrypted-domain type families +CREATE SCHEMA eql_v3; From f2f43c9973ed589937e343ed4959aadc165667c4 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:20:53 +1000 Subject: [PATCH 68/93] feat(v3): add self-contained eql_v3.hmac_256 SEM type (jsonb-only) --- src/v3/sem/hmac_256/functions.sql | 42 +++++++++++++++++++++++++++++++ src/v3/sem/hmac_256/types.sql | 12 +++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/v3/sem/hmac_256/functions.sql create mode 100644 src/v3/sem/hmac_256/types.sql diff --git a/src/v3/sem/hmac_256/functions.sql b/src/v3/sem/hmac_256/functions.sql new file mode 100644 index 00000000..9b2592cc --- /dev/null +++ b/src/v3/sem/hmac_256/functions.sql @@ -0,0 +1,42 @@ +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/sem/hmac_256/types.sql + +--! @file v3/sem/hmac_256/functions.sql +--! @brief HMAC-SHA256 index-term extraction from a jsonb payload (eql_v3 SEM). +--! +--! jsonb-only subset of src/hmac_256/functions.sql. The encrypted-column and +--! ste_vec-entry overloads are intentionally omitted — the eql_v3 scalar +--! domains extract from the jsonb payload directly via a cast to the domain. +--! (Doc comments deliberately avoid naming eql_v2 symbols so the +--! self-containment grep stays clean.) + +--! @brief Extract HMAC-SHA256 index term from JSONB payload +--! +--! Inlinable single-statement SQL — the planner can fold this into the calling +--! query so functional hash/btree indexes built on `eql_v3.eq_term(col)` +--! (which calls this) engage structurally. +--! +--! @param val jsonb containing encrypted EQL payload +--! @return eql_v3.hmac_256 HMAC-SHA256 hash value, or NULL when `hm` is absent +CREATE FUNCTION eql_v3.hmac_256(val jsonb) + RETURNS eql_v3.hmac_256 + LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + SELECT (val ->> 'hm')::eql_v3.hmac_256 +$$; + + +--! @brief Check if JSONB payload contains HMAC-SHA256 index term +--! +--! @param val jsonb containing encrypted EQL payload +--! @return boolean True if 'hm' field is present and non-null +CREATE FUNCTION eql_v3.has_hmac_256(val jsonb) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE + SET search_path = pg_catalog, extensions, public +AS $$ + BEGIN + RETURN val ->> 'hm' IS NOT NULL; + END; +$$ LANGUAGE plpgsql; diff --git a/src/v3/sem/hmac_256/types.sql b/src/v3/sem/hmac_256/types.sql new file mode 100644 index 00000000..92987935 --- /dev/null +++ b/src/v3/sem/hmac_256/types.sql @@ -0,0 +1,12 @@ +-- REQUIRE: src/v3/schema.sql + +--! @file v3/sem/hmac_256/types.sql +--! @brief HMAC-SHA256 index term type (eql_v3 SEM) +--! +--! Domain type representing HMAC-SHA256 hash values. Used for exact-match +--! encrypted searches. The hash is stored in the 'hm' field of encrypted data +--! payloads. Self-contained eql_v3 copy (design D1/D3); the eql_v2 original is +--! unchanged. +--! +--! @note Transient type used only during query execution. +CREATE DOMAIN eql_v3.hmac_256 AS text; From cf7ce98ed506d2407d81d9824f30b576319312a5 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:22:25 +1000 Subject: [PATCH 69/93] feat(v3): add self-contained eql_v3.ore_block_u64_8_256 SEM type (jsonb-only) --- src/v3/sem/ore_block_u64_8_256/functions.sql | 206 ++++++++++++++++++ .../ore_block_u64_8_256/operator_class.sql | 26 +++ src/v3/sem/ore_block_u64_8_256/operators.sql | 145 ++++++++++++ src/v3/sem/ore_block_u64_8_256/types.sql | 26 +++ 4 files changed, 403 insertions(+) create mode 100644 src/v3/sem/ore_block_u64_8_256/functions.sql create mode 100644 src/v3/sem/ore_block_u64_8_256/operator_class.sql create mode 100644 src/v3/sem/ore_block_u64_8_256/operators.sql create mode 100644 src/v3/sem/ore_block_u64_8_256/types.sql diff --git a/src/v3/sem/ore_block_u64_8_256/functions.sql b/src/v3/sem/ore_block_u64_8_256/functions.sql new file mode 100644 index 00000000..f86ef8a4 --- /dev/null +++ b/src/v3/sem/ore_block_u64_8_256/functions.sql @@ -0,0 +1,206 @@ +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/crypto.sql +-- REQUIRE: src/v3/common.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/types.sql + +--! @file v3/sem/ore_block_u64_8_256/functions.sql +--! @brief ORE block construction, extraction, and comparison (eql_v3 SEM). +--! +--! jsonb-only subset of src/ore_block_u64_8_256/functions.sql. The +--! encrypted-column overloads are omitted; the helper jsonb_array_to_bytea_array +--! and pgcrypto encrypt() are reached via the forked src/v3/common.sql and +--! src/v3/crypto.sql so the whole closure stays under src/v3. (Doc comments +--! deliberately avoid naming eql_v2 symbols so the self-containment grep stays +--! clean.) + +--! @brief Convert JSONB array to ORE block composite type +--! @internal +--! @param val jsonb Array of hex-encoded ORE block terms +--! @return eql_v3.ore_block_u64_8_256 ORE block composite, or NULL if input is null +CREATE FUNCTION eql_v3.jsonb_array_to_ore_block_u64_8_256(val jsonb) +RETURNS eql_v3.ore_block_u64_8_256 + SET search_path = pg_catalog, extensions, public +AS $$ +DECLARE + terms eql_v3.ore_block_u64_8_256_term[]; +BEGIN + IF jsonb_typeof(val) = 'null' THEN + RETURN NULL; + END IF; + + SELECT array_agg(ROW(b)::eql_v3.ore_block_u64_8_256_term) + INTO terms + FROM unnest(eql_v3.jsonb_array_to_bytea_array(val)) AS b; + + RETURN ROW(terms)::eql_v3.ore_block_u64_8_256; +END; +$$ LANGUAGE plpgsql; + + +--! @brief Extract ORE block index term from JSONB payload +--! @param val jsonb containing encrypted EQL payload +--! @return eql_v3.ore_block_u64_8_256 ORE block index term +--! @throws Exception if 'ob' field is missing +CREATE FUNCTION eql_v3.ore_block_u64_8_256(val jsonb) + RETURNS eql_v3.ore_block_u64_8_256 + IMMUTABLE STRICT PARALLEL SAFE + SET search_path = pg_catalog, extensions, public +AS $$ + BEGIN + IF val IS NULL THEN + RETURN NULL; + END IF; + + IF eql_v3.has_ore_block_u64_8_256(val) THEN + RETURN eql_v3.jsonb_array_to_ore_block_u64_8_256(val->'ob'); + END IF; + RAISE 'Expected an ore index (ob) value in json: %', val; + END; +$$ LANGUAGE plpgsql; + + +--! @brief Check if JSONB payload contains ORE block index term +--! @param val jsonb containing encrypted EQL payload +--! @return boolean True if 'ob' field is present and non-null +CREATE FUNCTION eql_v3.has_ore_block_u64_8_256(val jsonb) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE + SET search_path = pg_catalog, extensions, public +AS $$ + BEGIN + RETURN val ->> 'ob' IS NOT NULL; + END; +$$ LANGUAGE plpgsql; + + +--! @brief Compare two ORE block terms using cryptographic comparison +--! @internal +--! @param a eql_v3.ore_block_u64_8_256_term First ORE term +--! @param b eql_v3.ore_block_u64_8_256_term Second ORE term +--! @return integer -1 if a < b, 0 if a = b, 1 if a > b +--! @throws Exception if ciphertexts are different lengths +CREATE FUNCTION eql_v3.compare_ore_block_u64_8_256_term(a eql_v3.ore_block_u64_8_256_term, b eql_v3.ore_block_u64_8_256_term) + RETURNS integer + SET search_path = pg_catalog, extensions, public +AS $$ + DECLARE + eq boolean := true; + unequal_block smallint := 0; + hash_key bytea; + data_block bytea; + encrypt_block bytea; + target_block bytea; + + left_block_size CONSTANT smallint := 16; + right_block_size CONSTANT smallint := 32; + right_offset CONSTANT smallint := 136; -- 8 * 17 + + indicator smallint := 0; + BEGIN + IF a IS NULL AND b IS NULL THEN + RETURN 0; + END IF; + + IF a IS NULL THEN + RETURN -1; + END IF; + + IF b IS NULL THEN + RETURN 1; + END IF; + + IF bit_length(a.bytes) != bit_length(b.bytes) THEN + RAISE EXCEPTION 'Ciphertexts are different lengths'; + END IF; + + FOR block IN 0..7 LOOP + IF + substr(a.bytes, 1 + block, 1) != substr(b.bytes, 1 + block, 1) + OR substr(a.bytes, 9 + left_block_size * block, left_block_size) != substr(b.bytes, 9 + left_block_size * BLOCK, left_block_size) + THEN + IF eq THEN + unequal_block := block; + END IF; + eq = false; + END IF; + END LOOP; + + IF eq THEN + RETURN 0::integer; + END IF; + + hash_key := substr(b.bytes, right_offset + 1, 16); + + target_block := substr(b.bytes, right_offset + 17 + (unequal_block * right_block_size), right_block_size); + + data_block := substr(a.bytes, 9 + (left_block_size * unequal_block), left_block_size); + + encrypt_block := encrypt(data_block::bytea, hash_key::bytea, 'aes-ecb'); + + indicator := ( + get_bit( + encrypt_block, + 0 + ) + get_bit(target_block, get_byte(a.bytes, unequal_block))) % 2; + + IF indicator = 1 THEN + RETURN 1::integer; + ELSE + RETURN -1::integer; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--! @brief Compare arrays of ORE block terms recursively +--! @internal +--! @param a eql_v3.ore_block_u64_8_256_term[] First array +--! @param b eql_v3.ore_block_u64_8_256_term[] Second array +--! @return integer -1/0/1, or NULL if either array is NULL +CREATE FUNCTION eql_v3.compare_ore_block_u64_8_256_terms(a eql_v3.ore_block_u64_8_256_term[], b eql_v3.ore_block_u64_8_256_term[]) +RETURNS integer + SET search_path = pg_catalog, extensions, public +AS $$ + DECLARE + cmp_result integer; + BEGIN + IF a IS NULL OR b IS NULL THEN + RETURN NULL; + END IF; + + IF cardinality(a) = 0 AND cardinality(b) = 0 THEN + RETURN 0; + END IF; + + IF (cardinality(a) = 0) AND cardinality(b) > 0 THEN + RETURN -1; + END IF; + + IF cardinality(a) > 0 AND (cardinality(b) = 0) THEN + RETURN 1; + END IF; + + cmp_result := eql_v3.compare_ore_block_u64_8_256_term(a[1], b[1]); + + IF cmp_result = 0 THEN + RETURN eql_v3.compare_ore_block_u64_8_256_terms(a[2:array_length(a,1)], b[2:array_length(b,1)]); + END IF; + + RETURN cmp_result; + END +$$ LANGUAGE plpgsql; + + +--! @brief Compare ORE block composite types +--! @internal +--! @param a eql_v3.ore_block_u64_8_256 First ORE block +--! @param b eql_v3.ore_block_u64_8_256 Second ORE block +--! @return integer -1/0/1 +CREATE FUNCTION eql_v3.compare_ore_block_u64_8_256_terms(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) +RETURNS integer + SET search_path = pg_catalog, extensions, public +AS $$ + BEGIN + RETURN eql_v3.compare_ore_block_u64_8_256_terms(a.terms, b.terms); + END +$$ LANGUAGE plpgsql; diff --git a/src/v3/sem/ore_block_u64_8_256/operator_class.sql b/src/v3/sem/ore_block_u64_8_256/operator_class.sql new file mode 100644 index 00000000..b367c8f6 --- /dev/null +++ b/src/v3/sem/ore_block_u64_8_256/operator_class.sql @@ -0,0 +1,26 @@ +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/types.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/functions.sql + +--! @file v3/sem/ore_block_u64_8_256/operator_class.sql +--! @brief B-tree operator family + default class on eql_v3.ore_block_u64_8_256. +--! +--! Gives the composite type its DEFAULT btree opclass so the recommended +--! functional index `CREATE INDEX ON t (eql_v3.ord_term(col))` engages without +--! an explicit opclass annotation (design D4). Excluded from the Supabase build +--! variant by the `**/*operator_class.sql` glob. + +--! @brief B-tree operator family for ORE block types +CREATE OPERATOR FAMILY eql_v3.ore_block_u64_8_256_operator_family USING btree; + +--! @brief B-tree operator class for ORE block encrypted values +--! +--! Supports operators: <, <=, =, >=, >. Uses comparison function +--! compare_ore_block_u64_8_256_terms. +CREATE OPERATOR CLASS eql_v3.ore_block_u64_8_256_operator_class DEFAULT FOR TYPE eql_v3.ore_block_u64_8_256 USING btree FAMILY eql_v3.ore_block_u64_8_256_operator_family AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 eql_v3.compare_ore_block_u64_8_256_terms(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256); diff --git a/src/v3/sem/ore_block_u64_8_256/operators.sql b/src/v3/sem/ore_block_u64_8_256/operators.sql new file mode 100644 index 00000000..78364a19 --- /dev/null +++ b/src/v3/sem/ore_block_u64_8_256/operators.sql @@ -0,0 +1,145 @@ +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/types.sql +-- REQUIRE: src/v3/sem/ore_block_u64_8_256/functions.sql + +--! @file v3/sem/ore_block_u64_8_256/operators.sql +--! @brief Comparison operators on eql_v3.ore_block_u64_8_256. +--! +--! The six backing functions are inlinable single-statement SQL so the planner +--! can fold the eql_v3 comparison wrappers through to functional-index matching. + +--! @brief Equality backing function for ORE block types +--! @internal +CREATE FUNCTION eql_v3.ore_block_u64_8_256_eq(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) +RETURNS boolean + LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + SELECT eql_v3.compare_ore_block_u64_8_256_terms(a, b) = 0 +$$; + +--! @brief Not-equal backing function for ORE block types +--! @internal +CREATE FUNCTION eql_v3.ore_block_u64_8_256_neq(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) +RETURNS boolean + LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + SELECT eql_v3.compare_ore_block_u64_8_256_terms(a, b) <> 0 +$$; + +--! @brief Less-than backing function for ORE block types +--! @internal +CREATE FUNCTION eql_v3.ore_block_u64_8_256_lt(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) +RETURNS boolean + LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + SELECT eql_v3.compare_ore_block_u64_8_256_terms(a, b) = -1 +$$; + +--! @brief Less-than-or-equal backing function for ORE block types +--! @internal +CREATE FUNCTION eql_v3.ore_block_u64_8_256_lte(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) +RETURNS boolean + LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + SELECT eql_v3.compare_ore_block_u64_8_256_terms(a, b) != 1 +$$; + +--! @brief Greater-than backing function for ORE block types +--! @internal +CREATE FUNCTION eql_v3.ore_block_u64_8_256_gt(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) +RETURNS boolean + LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + SELECT eql_v3.compare_ore_block_u64_8_256_terms(a, b) = 1 +$$; + +--! @brief Greater-than-or-equal backing function for ORE block types +--! @internal +CREATE FUNCTION eql_v3.ore_block_u64_8_256_gte(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) +RETURNS boolean + LANGUAGE sql + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + SELECT eql_v3.compare_ore_block_u64_8_256_terms(a, b) != -1 +$$; + + +--! @brief = operator for ORE block types +--! +--! COMMUTATOR is the operator itself: equality is symmetric. Required for the +--! MERGES flag — without it the planner raises "could not find commutator" the +--! first time an ore_block equality is used as a join qual (e.g. via the inlined +--! eql_v3._ord_ore equality wrappers). +CREATE OPERATOR = ( + FUNCTION=eql_v3.ore_block_u64_8_256_eq, + LEFTARG=eql_v3.ore_block_u64_8_256, + RIGHTARG=eql_v3.ore_block_u64_8_256, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +--! @brief <> operator for ORE block types +CREATE OPERATOR <> ( + FUNCTION=eql_v3.ore_block_u64_8_256_neq, + LEFTARG=eql_v3.ore_block_u64_8_256, + RIGHTARG=eql_v3.ore_block_u64_8_256, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +--! @brief > operator for ORE block types +CREATE OPERATOR > ( + FUNCTION=eql_v3.ore_block_u64_8_256_gt, + LEFTARG=eql_v3.ore_block_u64_8_256, + RIGHTARG=eql_v3.ore_block_u64_8_256, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +--! @brief < operator for ORE block types +CREATE OPERATOR < ( + FUNCTION=eql_v3.ore_block_u64_8_256_lt, + LEFTARG=eql_v3.ore_block_u64_8_256, + RIGHTARG=eql_v3.ore_block_u64_8_256, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +--! @brief <= operator for ORE block types +CREATE OPERATOR <= ( + FUNCTION=eql_v3.ore_block_u64_8_256_lte, + LEFTARG=eql_v3.ore_block_u64_8_256, + RIGHTARG=eql_v3.ore_block_u64_8_256, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +--! @brief >= operator for ORE block types +CREATE OPERATOR >= ( + FUNCTION=eql_v3.ore_block_u64_8_256_gte, + LEFTARG=eql_v3.ore_block_u64_8_256, + RIGHTARG=eql_v3.ore_block_u64_8_256, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); diff --git a/src/v3/sem/ore_block_u64_8_256/types.sql b/src/v3/sem/ore_block_u64_8_256/types.sql new file mode 100644 index 00000000..f7e44dd0 --- /dev/null +++ b/src/v3/sem/ore_block_u64_8_256/types.sql @@ -0,0 +1,26 @@ +-- REQUIRE: src/v3/schema.sql + +--! @file v3/sem/ore_block_u64_8_256/types.sql +--! @brief ORE block index-term types (eql_v3 SEM). +--! +--! Self-contained eql_v3 copies of the Order-Revealing Encryption block types +--! (design D1/D3). The eql_v2 originals are unchanged. + +--! @brief ORE block term type for Order-Revealing Encryption +--! +--! Composite type representing a single ORE block term. Stores encrypted data +--! as bytea that enables range comparisons without decryption. +CREATE TYPE eql_v3.ore_block_u64_8_256_term AS ( + bytes bytea +); + + +--! @brief ORE block index term type for range queries +--! +--! Composite type containing an array of ORE block terms. The array is stored +--! in the 'ob' field of encrypted data payloads. +--! +--! @note Transient type used only during query execution. +CREATE TYPE eql_v3.ore_block_u64_8_256 AS ( + terms eql_v3.ore_block_u64_8_256_term[] +); From bbf09cc902e2557ff51837c069c4f61dce670900 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:37:54 +1000 Subject: [PATCH 70/93] feat(v3): move shared blocker to src/v3/scalars; remove src/encrypted_domain Also repoint src/lint/lints.sql's REQUIRE from the moved src/schema-v3.sql to src/v3/schema.sql so the combined build's dependency graph still resolves. --- src/lint/lints.sql | 2 +- src/{encrypted_domain => v3/scalars}/functions.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/{encrypted_domain => v3/scalars}/functions.sql (93%) diff --git a/src/lint/lints.sql b/src/lint/lints.sql index f7870abd..08c4a7de 100644 --- a/src/lint/lints.sql +++ b/src/lint/lints.sql @@ -1,5 +1,5 @@ -- REQUIRE: src/schema.sql --- REQUIRE: src/schema-v3.sql +-- REQUIRE: src/v3/schema.sql --! @brief EQL lint: detect non-inlinable operator implementation functions --! diff --git a/src/encrypted_domain/functions.sql b/src/v3/scalars/functions.sql similarity index 93% rename from src/encrypted_domain/functions.sql rename to src/v3/scalars/functions.sql index 71a070a1..ab997a48 100644 --- a/src/encrypted_domain/functions.sql +++ b/src/v3/scalars/functions.sql @@ -1,6 +1,6 @@ --- REQUIRE: src/schema-v3.sql +-- REQUIRE: src/v3/schema.sql ---! @file encrypted_domain/functions.sql +--! @file v3/scalars/functions.sql --! @brief Shared blocker helper for the eql_v3 encrypted-domain families. --! --! Per-domain wrapper functions live in src/encrypted_domain//. From 0ae5c1031db7bf74e62e66e5e247c7bd12252d4c Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:39:21 +1000 Subject: [PATCH 71/93] build(v3): emit self-contained release/cipherstash-encrypt-v3.sql variant (D9, D13) Also add the v3 installer/uninstaller to the build task's MISE outputs list (and tasks/uninstall-v3.sql to sources) so the incremental cache tracks them. --- tasks/build.sh | 44 +++++++++++++++++++++++++++++++++++++++--- tasks/uninstall-v3.sql | 3 +++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 tasks/uninstall-v3.sql diff --git a/tasks/build.sh b/tasks/build.sh index ead83184..4d10e752 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -8,7 +8,7 @@ set -euo pipefail # Regenerate encrypted-domain SQL from the Rust catalog before building. -# Generated files (src/encrypted_domain//_*.sql) are gitignored; the +# Generated files (src/v3/scalars//_*.sql) are gitignored; the # catalog at crates/eql-scalars/src (eql-scalars::CATALOG) is the source of # truth, rendered by the eql-codegen binary. # @@ -17,8 +17,8 @@ set -euo pipefail # pick up. eql-codegen cleans within a directory it regenerates, but never # runs for a type no longer in the catalog. Hand-written *_extensions.sql is # preserved by the name patterns; -mindepth 2 keeps the type-agnostic -# src/encrypted_domain/functions.sql safe. -find src/encrypted_domain -mindepth 2 -type f \ +# src/v3/scalars/functions.sql safe. +find src/v3/scalars -mindepth 2 -type f \ \( -name '*_types.sql' -o -name '*_functions.sql' -o -name '*_operators.sql' \ -o -name '*_aggregates.sql' \) \ -delete 2>/dev/null || true @@ -59,6 +59,9 @@ rm -f release/cipherstash-encrypt-supabase.sql rm -f release/cipherstash-encrypt-protect.sql rm -f release/cipherstash-encrypt-protect-uninstall.sql +rm -f release/cipherstash-encrypt-v3.sql +rm -f release/cipherstash-encrypt-v3-uninstall.sql + rm -f dbdev/eql--0.0.0.sql rm -f src/version.sql @@ -68,6 +71,8 @@ rm -f src/deps-supabase.txt rm -f src/deps-ordered-supabase.txt rm -f src/deps-protect.txt rm -f src/deps-ordered-protect.txt +rm -f src/deps-v3.txt +rm -f src/deps-ordered-v3.txt RELEASE_VERSION=${usage_version:-DEV} @@ -163,6 +168,37 @@ cat tasks/pin_search_path.sql >> release/cipherstash-encrypt-protect.sql cat tasks/uninstall-protect.sql >> release/cipherstash-encrypt-protect-uninstall.sql +# v3-only build (design D9): the self-contained eql_v3 surface — schema, SEM +# types, scalar domains — globbed from src/v3 ONLY. This is the unit the +# self-containment gate greps; it is the only artifact that can be "free of +# eql_v2", because the combined variants glob all of src/. It deliberately does +# NOT append tasks/pin_search_path.sql (D11): that script is eql_v2-coupled +# (raises if public.eql_v2_encrypted / eql_v2.ste_vec_entry are absent and only +# ever pins eql_v2 functions), so appending it would both fail a clean v3 +# install and break the self-containment grep. +find src/v3 -type f -path "*.sql" ! -path "*_test.sql" | while IFS= read -r sql_file; do + echo $sql_file + + echo "$sql_file $sql_file" >> src/deps-v3.txt + + while IFS= read -r line; do + if [[ "$line" == *"-- REQUIRE:"* ]]; then + deps=${line#*-- REQUIRE: } + for dep in $deps; do + echo "$sql_file $dep" >> src/deps-v3.txt + done + fi + done < "$sql_file" +done + +cat src/deps-v3.txt | tsort | tac > src/deps-ordered-v3.txt +verify_deps_exist src/deps-ordered-v3.txt + +cat src/deps-ordered-v3.txt | xargs cat | grep -v REQUIRE >> release/cipherstash-encrypt-v3.sql + +cat tasks/uninstall-v3.sql >> release/cipherstash-encrypt-v3-uninstall.sql + + echo echo '###############################################' echo "# ✅Build succeeded" @@ -172,8 +208,10 @@ echo 'Installer:' echo ' release/cipherstash-encrypt.sql' echo ' release/cipherstash-encrypt-supabase.sql' echo ' release/cipherstash-encrypt-protect.sql' +echo ' release/cipherstash-encrypt-v3.sql' echo echo 'Uninstaller:' echo ' release/cipherstash-encrypt-uninstall.sql' echo ' release/cipherstash-encrypt-uninstall-supabase.sql' echo ' release/cipherstash-encrypt-protect-uninstall.sql' +echo ' release/cipherstash-encrypt-v3-uninstall.sql' diff --git a/tasks/uninstall-v3.sql b/tasks/uninstall-v3.sql new file mode 100644 index 00000000..ce680dc9 --- /dev/null +++ b/tasks/uninstall-v3.sql @@ -0,0 +1,3 @@ +-- Uninstall the standalone eql_v3 surface. CASCADE removes the domains, SEM +-- types, operators, opclass, and any columns typed with the eql_v3 domains. +DROP SCHEMA IF EXISTS eql_v3 CASCADE; From 6e4cff2decbb74f00f3d0766a0387b4874ba048a Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:49:46 +1000 Subject: [PATCH 72/93] fix(v3): keep eql_v3 SEM ore_block/hmac_256 inlinable in the combined build The combined-build pin_search_path allowlist is eql_v2-scoped; the self-contained eql_v3 SEM functions (composite/jsonb args) would be pinned, silently breaking v3 functional-index inlining. Mirror the eql_v2 treatment, allowlist them in splinter, and add a direct regression guard. --- tasks/pin_search_path.sql | 26 +++++++++++- tasks/test/splinter.sh | 11 ++++- .../encrypted_domain/family/inlinability.rs | 41 +++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/tasks/pin_search_path.sql b/tasks/pin_search_path.sql index 774be740..1abf9c7d 100644 --- a/tasks/pin_search_path.sql +++ b/tasks/pin_search_path.sql @@ -99,7 +99,8 @@ BEGIN SELECT pg_catalog.array_agg(p.oid) INTO inline_critical_oids FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - WHERE n.nspname = 'eql_v2' + WHERE ( + n.nspname = 'eql_v2' AND ( -- Same-type (encrypted, encrypted) operators that must inline. -- `like`/`ilike` are the SQL helpers that `~~`/`~~*` delegate to; @@ -244,7 +245,28 @@ BEGIN OR p.proargtypes[0] = (SELECT t.oid FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = 'eql_v2' AND t.typname = 'stevec_query'))) - ); + ) + ) + OR ( + -- eql_v3 SEM index-term functions (self-contained fork). These mirror the + -- eql_v2 ore_block / hmac_256 inline-critical clauses above: the + -- comparison-wrapper inlining for the eql_v3 *_ord domains and eq_term only + -- reaches functional-index matching if these inner functions stay inlinable + -- (no SET, IMMUTABLE). The generated extractors/wrappers themselves are + -- spared by the jsonb-DOMAIN structural skip below; these SEM functions take + -- a composite (ore_block) or raw jsonb (hmac_256) arg, so they need an + -- explicit entry here. + n.nspname = 'eql_v3' + AND ( + (p.pronargs = 2 + AND p.proname IN ('ore_block_u64_8_256_eq', 'ore_block_u64_8_256_neq', + 'ore_block_u64_8_256_lt', 'ore_block_u64_8_256_lte', + 'ore_block_u64_8_256_gt', 'ore_block_u64_8_256_gte')) + OR (p.pronargs = 1 + AND p.proname = 'hmac_256' + AND p.proargtypes[0] = jsonb_oid) + ) + ); FOR fn_oid IN SELECT p.oid diff --git a/tasks/test/splinter.sh b/tasks/test/splinter.sh index a01de51a..9d8c3cb1 100755 --- a/tasks/test/splinter.sh +++ b/tasks/test/splinter.sh @@ -107,8 +107,8 @@ function_search_path_mutable eql_v2 grouped_value function Aggregate: same as mi # encrypted-type operators above; splinter matches by (schema, name, type), so # they need their own rows. The plpgsql blockers are pinned by # tasks/pin_search_path.sql and do not surface here. -function_search_path_mutable eql_v3 eq_term function HMAC equality term extractor for the eql_v3 *_eq domains: returns eql_v2.hmac_256. Must inline so `eql_v3.eq_term(col)` folds into the calling query and matches the functional hash/btree index built on the same expression. SET search_path would disable SQL function inlining (see PostgreSQL inline_function). -function_search_path_mutable eql_v3 ord_term function ORE-block order term extractor for the eql_v3 ordered domains: returns eql_v2.ore_block_u64_8_256 (carrying the main DEFAULT btree opclass). Used inside the inlinable comparison wrappers and as the functional-index expression USING btree (eql_v3.ord_term(col)); must inline. Covers both ord_term overloads (eql_v3.int4_ord, eql_v3.int4_ord_ore). +function_search_path_mutable eql_v3 eq_term function HMAC equality term extractor for the eql_v3 *_eq domains: returns eql_v3.hmac_256. Must inline so `eql_v3.eq_term(col)` folds into the calling query and matches the functional hash/btree index built on the same expression. SET search_path would disable SQL function inlining (see PostgreSQL inline_function). +function_search_path_mutable eql_v3 ord_term function ORE-block order term extractor for the eql_v3 ordered domains: returns eql_v3.ore_block_u64_8_256 (carrying the main DEFAULT btree opclass). Used inside the inlinable comparison wrappers and as the functional-index expression USING btree (eql_v3.ord_term(col)); must inline. Covers both ord_term overloads (eql_v3.int4_ord, eql_v3.int4_ord_ore). function_search_path_mutable eql_v3 eq function Equality comparison wrapper on the eql_v3 domains. Inlines to `eq_term(a) = eq_term(b)`; must reach the functional index on eql_v3.eq_term(col) for bare-form equality to engage Index Scan. Covers the converged eq wrappers on the eql_v3 int4 variants. function_search_path_mutable eql_v3 neq function Inequality comparison wrapper on the eql_v3 domains. Same rationale as eql_v3.eq. function_search_path_mutable eql_v3 lt function Less-than comparison wrapper on the eql_v3 ordered domains. Inlines to `ord_term(a) < ord_term(b)`; must reach the functional btree index on eql_v3.ord_term(col) for range queries to engage Index Scan. @@ -117,6 +117,13 @@ function_search_path_mutable eql_v3 gt function Greater-than comparison wrapper function_search_path_mutable eql_v3 gte function Greater-than-or-equal comparison wrapper on the eql_v3 ordered domains. Same rationale as eql_v3.lt. function_search_path_mutable eql_v3 min function Per-domain MIN aggregate on the eql_v3 ordered domains (splinter labels aggregates type=function): ALTER AGGREGATE has no SET configuration_parameter syntax, and ALTER ROUTINE/FUNCTION reject aggregates. The aggregate's SFUNC carries a pinned search_path. function_search_path_mutable eql_v3 max function Per-domain MAX aggregate on the eql_v3 ordered domains. Same as eql_v3.min. +function_search_path_mutable eql_v3 ore_block_u64_8_256_eq function Inner comparator for the eql_v3 ore_block_u64_8_256 type's `=` operator (self-contained SEM fork). The eql_v3 *_ord comparison wrappers inline to `ord_term(a) op ord_term(b)`; the planner only carries that through to the functional ORE index if this inner function is also inlinable (no SET, IMMUTABLE). Mirrors eql_v2.ore_block_u64_8_256_eq. +function_search_path_mutable eql_v3 ore_block_u64_8_256_neq function Inner comparator for the eql_v3 ore_block_u64_8_256 `<>` operator. Same rationale as eql_v3.ore_block_u64_8_256_eq. +function_search_path_mutable eql_v3 ore_block_u64_8_256_lt function Inner comparator for the eql_v3 ore_block_u64_8_256 `<` operator. Same rationale as eql_v3.ore_block_u64_8_256_eq. +function_search_path_mutable eql_v3 ore_block_u64_8_256_lte function Inner comparator for the eql_v3 ore_block_u64_8_256 `<=` operator. Same rationale as eql_v3.ore_block_u64_8_256_eq. +function_search_path_mutable eql_v3 ore_block_u64_8_256_gt function Inner comparator for the eql_v3 ore_block_u64_8_256 `>` operator. Same rationale as eql_v3.ore_block_u64_8_256_eq. +function_search_path_mutable eql_v3 ore_block_u64_8_256_gte function Inner comparator for the eql_v3 ore_block_u64_8_256 `>=` operator. Same rationale as eql_v3.ore_block_u64_8_256_eq. +function_search_path_mutable eql_v3 hmac_256 function HMAC equality extractor for the eql_v3 SEM fork: inlinable SQL (jsonb) constructor used inside eql_v3.eq_term. Must inline so the functional hash/btree index on eql_v3.eq_term(col) engages. Mirrors eql_v2.hmac_256. ALLOW # Wrap splinter (a single bare SELECT expression) into a subquery we can diff --git a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs index 3cacb605..7e17e265 100644 --- a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs +++ b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs @@ -87,6 +87,47 @@ async fn no_encrypted_domain_inline_critical_function_is_pinned(pool: PgPool) -> Ok(()) } +/// Direct guard for the self-contained eql_v3 SEM index-term functions. Unlike +/// the structural guard above (which covers jsonb-domain-arg functions), these +/// take a composite (ore_block_u64_8_256) or raw jsonb (hmac_256) arg, so they +/// are NOT caught by the structural pin-skip and need explicit inline_critical +/// allowlisting. If pin_search_path.sql pins any of them, v3 functional-index +/// inlining silently regresses to Seq Scan — this test fails instead. +#[sqlx::test] +async fn eql_v3_sem_inline_critical_functions_are_unpinned(pool: PgPool) -> Result<()> { + let rows: Vec<(String,)> = sqlx::query_as( + r#" + SELECT p.proname || '(' || pg_catalog.pg_get_function_arguments(p.oid) || ')' + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + WHERE n.nspname = 'eql_v3' + AND ( + (p.pronargs = 2 AND p.proname IN ( + 'ore_block_u64_8_256_eq','ore_block_u64_8_256_neq', + 'ore_block_u64_8_256_lt','ore_block_u64_8_256_lte', + 'ore_block_u64_8_256_gt','ore_block_u64_8_256_gte')) + OR (p.pronargs = 1 AND p.proname = 'hmac_256') + ) + AND ( + -- offender: pinned search_path, or not inlinable SQL/IMMUTABLE + EXISTS (SELECT 1 FROM unnest(coalesce(p.proconfig,'{}'::text[])) c WHERE c LIKE 'search_path=%') + OR p.provolatile <> 'i' + OR p.prolang <> (SELECT l.oid FROM pg_catalog.pg_language l WHERE l.lanname = 'sql') + ) + ORDER BY 1 + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + rows.is_empty(), + "eql_v3 SEM inline-critical functions must stay unpinned + inlinable SQL; offenders: {:?}", + rows.iter().map(|r| &r.0).collect::>() + ); + Ok(()) +} + #[sqlx::test] async fn every_inline_critical_eligible_domain_has_inline_critical_functions( pool: PgPool, From 94d01775f346e75ed37c1edfd4724b630a810c55 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:50:44 +1000 Subject: [PATCH 73/93] test(v3): add self-containment gate (symbol + file + artifact) and CI job --- .github/workflows/test-eql.yml | 28 ++++++++++++++++++++ tasks/test/self_contained_v3.sh | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100755 tasks/test/self_contained_v3.sh diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index d74f2946..76062cfe 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -127,6 +127,34 @@ jobs: run: | mise run codegen:parity + self-contained-v3: + name: "eql_v3 self-containment" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4 + with: + version: 2026.4.0 + install: true + cache: true + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + workspaces: . + shared-key: sqlx-tests + + # Build to materialise release/cipherstash-encrypt-v3.sql and + # src/deps-ordered-v3.txt, then assert no eql_v2 symbol/file leakage. + - name: Build EQL + run: mise run --force build + + - name: Assert eql_v3 is self-contained + run: mise run test:self_contained_v3 + matrix-coverage: name: "Matrix coverage inventory" runs-on: ubuntu-latest diff --git a/tasks/test/self_contained_v3.sh b/tasks/test/self_contained_v3.sh new file mode 100755 index 00000000..59373cc9 --- /dev/null +++ b/tasks/test/self_contained_v3.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +#MISE description="Assert the eql_v3 surface is self-contained (no eql_v2 symbol/file leakage)" + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +fail=0 + +# Symbol level (design goal 1): no eql_v2. anywhere under src/v3 — the +# hand-written SEM + foundation files plus the gitignored generated scalar +# surface (present because build runs codegen). Run `mise run build` first. +echo "==> Symbol gate: no 'eql_v2.' under src/v3" +if grep -rn 'eql_v2\.' src/v3; then + echo "ERROR: eql_v2. reference found in src/v3 (must be self-contained)" >&2 + fail=1 +fi + +# File level (design goal 2): the v3-only dependency closure pulls in no file +# outside src/v3/. tsort output is one path per line. +if [[ ! -f src/deps-ordered-v3.txt ]]; then + echo "ERROR: src/deps-ordered-v3.txt missing — run 'mise run build' first" >&2 + exit 2 +fi +echo "==> File gate: every path in src/deps-ordered-v3.txt is under src/v3/" +if grep -v '^src/v3/' src/deps-ordered-v3.txt; then + echo "ERROR: v3 dep closure pulls in a path outside src/v3/ (eql_v2 file leak)" >&2 + fail=1 +fi + +# Belt-and-braces: the assembled artifact carries no eql_v2 symbol. +echo "==> Artifact gate: release/cipherstash-encrypt-v3.sql has no 'eql_v2.'" +if [[ ! -f release/cipherstash-encrypt-v3.sql ]]; then + echo "ERROR: release/cipherstash-encrypt-v3.sql missing — run 'mise run build' first" >&2 + exit 2 +fi +if grep -n 'eql_v2\.' release/cipherstash-encrypt-v3.sql; then + echo "ERROR: assembled v3 artifact contains an eql_v2. reference" >&2 + fail=1 +fi + +if [[ $fail -ne 0 ]]; then + echo "self-containment gate FAILED" >&2 + exit 1 +fi +echo "self-containment gate OK" From ae2bac559fa2876668edc4bb49c4ac85db8d94ce Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:51:28 +1000 Subject: [PATCH 74/93] test(v3): assert the v3 artifact is self-contained and v2-decoupled --- tests/sqlx/tests/build_validation_tests.rs | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/sqlx/tests/build_validation_tests.rs b/tests/sqlx/tests/build_validation_tests.rs index 264f770b..d23ba5a5 100644 --- a/tests/sqlx/tests/build_validation_tests.rs +++ b/tests/sqlx/tests/build_validation_tests.rs @@ -138,3 +138,52 @@ fn protect_variant_is_smaller_than_full() { full.len() ); } + +// ============================================================================= +// v3-only Variant Tests (design D9/D11 — self-contained eql_v3 surface) +// ============================================================================= + +#[test] +fn v3_variant_file_exists() { + assert!( + Path::new("../../release/cipherstash-encrypt-v3.sql").exists(), + "v3-only variant installer should exist" + ); +} + +#[test] +fn v3_uninstaller_exists() { + assert!( + Path::new("../../release/cipherstash-encrypt-v3-uninstall.sql").exists(), + "v3-only variant uninstaller should exist" + ); +} + +#[test] +fn v3_variant_creates_eql_v3_schema() { + let sql = read_release_sql("cipherstash-encrypt-v3.sql"); + assert!( + sql.contains("CREATE SCHEMA eql_v3"), + "v3 variant must create the eql_v3 schema" + ); +} + +#[test] +fn v3_variant_has_no_eql_v2_symbol() { + let sql = read_release_sql("cipherstash-encrypt-v3.sql"); + assert!( + !sql.contains("eql_v2."), + "v3 variant must be self-contained (no eql_v2. reference)" + ); +} + +#[test] +fn v3_variant_omits_v2_coupled_pin_search_path() { + // D11: the v3 artifact must NOT append tasks/pin_search_path.sql, which is + // eql_v2-coupled (references eql_v2_encrypted / ste_vec_entry). + let sql = read_release_sql("cipherstash-encrypt-v3.sql"); + assert!( + !sql.contains("ste_vec_entry") && !sql.contains("eql_v2_encrypted"), + "v3 variant must not carry the eql_v2-coupled pin_search_path script" + ); +} From 15370c8a4eb537a3353cfb6df1f642ca8a034d14 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:54:01 +1000 Subject: [PATCH 75/93] test(v3): clean-DB install + functional-index smoke (proves D11, D4) --- .github/workflows/test-eql.yml | 5 +++ tasks/test/clean_install_v3.sh | 70 ++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100755 tasks/test/clean_install_v3.sh diff --git a/.github/workflows/test-eql.yml b/.github/workflows/test-eql.yml index 76062cfe..776d701a 100644 --- a/.github/workflows/test-eql.yml +++ b/.github/workflows/test-eql.yml @@ -244,6 +244,11 @@ jobs: rustup component add --toolchain ${active_rust_toolchain} rustfmt clippy mise run --output prefix test --postgres ${POSTGRES_VERSION} + - name: Clean-DB v3 install smoke (Postgres ${{ matrix.postgres-version }}) + run: | + mise run build + mise run test:clean_install_v3 + splinter: name: "Supabase splinter" runs-on: ubuntu-latest-m diff --git a/tasks/test/clean_install_v3.sh b/tasks/test/clean_install_v3.sh new file mode 100755 index 00000000..7c771da8 --- /dev/null +++ b/tasks/test/clean_install_v3.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +#MISE description="Install release/cipherstash-encrypt-v3.sql into a scratch DB with NO eql_v2 and smoke-test it (D11, D4)" +#USAGE flag "--port " help="Postgres port" default="7432" +#USAGE flag "--user " help="Postgres user" default="cipherstash" + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +PG_PORT="${usage_port:-7432}" +PG_USER="${usage_user:-cipherstash}" +export PGPASSWORD="${POSTGRES_PASSWORD:-password}" +SCRATCH_DB="cipherstash_v3_clean" + +ADMIN=(psql -U "$PG_USER" -h localhost -p "$PG_PORT" -d postgres -v ON_ERROR_STOP=1 -q) +RUN=(psql -U "$PG_USER" -h localhost -p "$PG_PORT" -d "$SCRATCH_DB" -v ON_ERROR_STOP=1 -q) + +test -f release/cipherstash-encrypt-v3.sql || { echo "Build first: release/cipherstash-encrypt-v3.sql missing" >&2; exit 2; } + +echo "==> (re)creating scratch database $SCRATCH_DB (no eql_v2 installed)" +"${ADMIN[@]}" -c "DROP DATABASE IF EXISTS ${SCRATCH_DB} WITH (FORCE);" +"${ADMIN[@]}" -c "CREATE DATABASE ${SCRATCH_DB};" + +cleanup() { "${ADMIN[@]}" -c "DROP DATABASE IF EXISTS ${SCRATCH_DB} WITH (FORCE);" >/dev/null 2>&1 || true; } +trap cleanup EXIT + +echo "==> installing the standalone eql_v3 surface" +"${RUN[@]}" -f release/cipherstash-encrypt-v3.sql + +echo "==> asserting NO eql_v2 schema exists (proves no v2 dependency)" +"${RUN[@]}" -c "DO \$\$ BEGIN IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'eql_v2') THEN RAISE EXCEPTION 'eql_v2 schema unexpectedly present'; END IF; END \$\$;" + +echo "==> smoke: domains, SEM types, extractors, opclass functional index (D4)" +"${RUN[@]}" <<'SQL' +-- Domains and SEM types exist in eql_v3. +SELECT 'eql_v3.int4_ord'::regtype; +SELECT 'eql_v3.hmac_256'::regtype; +SELECT 'eql_v3.ore_block_u64_8_256'::regtype; + +-- A real ordered-domain column + the documented functional index. This is the +-- D4 proof: it fails outright if the ported operator_class is absent. +CREATE TABLE v3_smoke (c eql_v3.int4_ord); +CREATE INDEX v3_smoke_ord ON v3_smoke (eql_v3.ord_term(c)); +DROP TABLE v3_smoke; +SQL + +echo "==> smoke: the shared blocker is reachable and raises" +"${RUN[@]}" <<'SQL' +DO $$ +DECLARE + raised boolean := false; +BEGIN + -- The blocker always RAISEs; catch it and assert we got the expected message. + BEGIN + PERFORM eql_v3.encrypted_domain_unsupported_bool('eql_v3.int4', '<'); + EXCEPTION WHEN OTHERS THEN + raised := true; + IF SQLERRM <> 'operator < is not supported for eql_v3.int4' THEN + RAISE EXCEPTION 'blocker raised an unexpected message: %', SQLERRM; + END IF; + END; + + IF NOT raised THEN + RAISE EXCEPTION 'blocker eql_v3.encrypted_domain_unsupported_bool did not raise'; + END IF; +END $$; +SQL + +echo "clean v3 install OK (D11 + D4 proven)" From fa091cd38edf8a221b084c51277d0d3f70b1c858 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 14:58:30 +1000 Subject: [PATCH 76/93] docs(v3): document the self-contained eql_v3 schema and v3-only installer --- CHANGELOG.md | 3 +- CLAUDE.md | 10 ++-- .../adding-a-scalar-encrypted-domain-type.md | 49 +++++++++++-------- tests/codegen/reference/README.md | 2 +- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76700194..1404e61d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,9 +22,10 @@ Each entry that ships in a published release links to the PR that introduced it. ### Added -- **`eql_v3` encrypted-domain schema, with the `int4` family as its first member.** Encrypted-domain type families now live in a new, additional `eql_v3` schema (the existing `eql_v2` schema is unchanged — it keeps the core types/operators and stays the documented public API). Four jsonb-backed domains for encrypted `int4` columns: `eql_v3.int4` (storage-only), `eql_v3.int4_eq` (`=` / `<>` via HMAC), and `eql_v3.int4_ord` / `eql_v3.int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. The extractors still return the core `eql_v2.hmac_256` / `eql_v2.ore_block_u64_8_256` index-term types, which remain in `eql_v2` and are referenced cross-schema. Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`, namespaced under its own schema. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) +- **`eql_v3` encrypted-domain schema, with the `int4` family as its first member.** Encrypted-domain type families now live in a new, additional `eql_v3` schema (the existing `eql_v2` schema is unchanged — it keeps the core types/operators and stays the documented public API). Four jsonb-backed domains for encrypted `int4` columns: `eql_v3.int4` (storage-only), `eql_v3.int4_eq` (`=` / `<>` via HMAC), and `eql_v3.int4_ord` / `eql_v3.int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. The extractors return the searchable-encrypted-metadata index-term types `eql_v3.hmac_256` / `eql_v3.ore_block_u64_8_256`, which `eql_v3` owns directly (see the self-contained `eql_v3` schema entry below). Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`, namespaced under its own schema. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) - **`eql_v3.int2` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int2` columns — `eql_v3.int2` (storage-only), `eql_v3.int2_eq` (`=` / `<>` via HMAC), and `eql_v3.int2_ord` / `eql_v3.int2_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from the `int2` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `smallint` column, proving the scalar generator generalizes beyond the `int4` reference. ([#243](https://github.com/cipherstash/encrypt-query-language/pull/243)) - **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v3.min(eql_v3._ord)` / `eql_v3.max(eql_v3._ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) +- **Self-contained `eql_v3` schema + standalone `release/cipherstash-encrypt-v3.sql` installer.** The `eql_v3` encrypted-domain surface no longer depends on `eql_v2` at runtime: it now owns its own copies of the searchable-encrypted-metadata (SEM) index-term types — `eql_v3.hmac_256` and `eql_v3.ore_block_u64_8_256` (with its btree operator class) — so the `eql_v3.eq_term` / `eql_v3.ord_term` extractors return `eql_v3` types and no `eql_v2.` appears anywhere in the v3 SQL. The whole v3 surface relocated under a single `src/v3/` tree (`src/v3/sem/` for the hand-written SEM types, `src/v3/scalars/` for the generated domain families). A new build variant ships the `eql_v3` schema on its own as `release/cipherstash-encrypt-v3.sql`, installable into a database with no `eql_v2` present; a CI gate greps that artifact and its dependency closure to keep it `eql_v2`-free. Why: a clean foundation for the per-scalar encrypted-domain model to stand alone, ahead of it replacing the `eql_v2_encrypted` composite column type. This is additive — a new schema and a new artifact — and leaves `eql_v2` byte-for-byte unchanged. ([#255](https://github.com/cipherstash/encrypt-query-language/pull/255)) ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index 50766e33..9c9a08ca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,7 @@ This project uses `mise` for task management. Common commands: - `cipherstash-encrypt.sql` - Main installer - `cipherstash-encrypt-supabase.sql` - Supabase-compatible (excludes operator classes) - `cipherstash-encrypt-protect.sql` - ProtectJS variant (excludes config management) + - `cipherstash-encrypt-v3.sql` - Standalone, self-contained `eql_v3` surface only (globbed from `src/v3` alone; no `eql_v2`, installable into a DB with no `eql_v2` present) - Corresponding uninstallers for each variant #### Build Variants @@ -45,13 +46,14 @@ This project uses `mise` for task management. Common commands: | Main | Nothing | Full EQL with all features | | Supabase | Operator classes | Supabase compatibility | | Protect | `src/config/*`, `src/encryptindex/*` | ProtectJS (no database-side config) | +| v3-only | Everything outside `src/v3` (and `pin_search_path.sql`) | Self-contained `eql_v3` surface, `eql_v2`-free (gated by `mise run test:self_contained_v3`) | ## Project Architecture This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for searchable encryption. Key architectural components: ### Core Structure -- **Schema**: Core EQL functions/types are in the `eql_v2` PostgreSQL schema. The encrypted-domain type families (`int4` and future scalar domains) live in a separate `eql_v3` schema (see below); they reuse the core `eql_v2` index-term types cross-schema. `eql_v2` is unchanged and remains the documented public API. +- **Schema**: Core EQL functions/types are in the `eql_v2` PostgreSQL schema. The encrypted-domain type families (`int4` and future scalar domains) live in a separate `eql_v3` schema (see below). The `eql_v3` surface is **self-contained**: it owns its own copies of the searchable-encrypted-metadata (SEM) index-term types (`eql_v3.hmac_256`, `eql_v3.ore_block_u64_8_256`, hand-written under `src/v3/sem/`) and has no runtime dependency on `eql_v2`. `eql_v2` is unchanged and remains the documented public API. - **Main Type**: `eql_v2_encrypted` - composite type for encrypted columns (stored as JSONB) - **Configuration**: `eql_v2_configuration` table tracks encryption configs - **Index Types**: Various encrypted index types (blake3, hmac_256, bloom_filter, ore variants) @@ -62,7 +64,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search - `src/operators/` - SQL operators for encrypted data comparisons - `src/config/` - Configuration management functions - `src/blake3/`, `src/hmac_256/`, `src/bloom_filter/`, `src/ore_*` - Index implementations -- `src/encrypted_domain/` - Encrypted-domain type families (jsonb-backed PostgreSQL domains, one per operator/index capability) +- `src/v3/` - Self-contained `eql_v3` surface: `src/v3/schema.sql`, forked `src/v3/crypto.sql` / `src/v3/common.sql`, hand-written SEM index-term types under `src/v3/sem/` (`hmac_256`, `ore_block_u64_8_256`), and the generated scalar encrypted-domain families under `src/v3/scalars//` (plus the shared blocker `src/v3/scalars/functions.sql`) - `tasks/` - mise task scripts - `tests/sqlx/` - Rust/SQLx test framework (PostgreSQL 14-17 support) - `release/` - Generated SQL installation files @@ -76,9 +78,9 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search ### Encrypted-Domain Types -`src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, but the index-term types they return and construct (`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are referenced cross-schema. `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, `text`, and `jsonb` follow this materializer pattern. `text`, `numeric`, and `jsonb` are planned but have no generated SQL surface yet — `jsonb` in particular needs a separate SQL design beyond the ordered-scalar materializer. The `eql-scalars` fixture catalog (`crates/eql-scalars`) already models their fixture values ahead of the SQL surface. +`src/v3/scalars/` holds the generated **encrypted-domain type families** — jsonb-backed PostgreSQL domains in the **`eql_v3` schema**, one domain per operator/index capability (`eql_v3.` storage-only, `eql_v3._eq`, `eql_v3._ord`). The schema qualifier replaces the old version-prefixed name, so the domains are `eql_v3.int4`, `eql_v3.int4_eq`, `eql_v3.int4_ord`, `eql_v3.int4_ord_ore` — created in `eql_v3`, not `public`. Their extractors/wrappers/aggregates (`eql_v3.eq_term`, `eql_v3.ord_term`, `eql_v3.eq`/`lt`/…, `eql_v3.min`/`max`) also live in `eql_v3`, and the SEM index-term types they return and construct (`eql_v3.hmac_256`, `eql_v3.ore_block_u64_8_256`) are **also `eql_v3`** — hand-written under `src/v3/sem/` so the whole v3 surface is self-contained (no `eql_v2.` appears anywhere in v3 SQL; CI gates this via `mise run test:self_contained_v3` and the standalone `release/cipherstash-encrypt-v3.sql` installer). `eql_v3.int4` (PR #239, supersedes #225) is the reference scalar implementation; future scalar types such as `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, `text`, and `jsonb` follow this materializer pattern. `text`, `numeric`, and `jsonb` are planned but have no generated SQL surface yet — `jsonb` in particular needs a separate SQL design beyond the ordered-scalar materializer. The `eql-scalars` fixture catalog (`crates/eql-scalars`) already models their fixture values ahead of the SQL surface. -Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/encrypted_domain//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed. The per-type plaintext fixture lists the SQLx matrix consumes are **not** a generated file — they are materialised from each `CATALOG` row at compile time as `eql_scalars::INT4_VALUES` / `INT2_VALUES` (the `int_values!` macro) and read directly by `ScalarType::FIXTURE_VALUES`; a Rust source of truth no longer round-trips through a committed generated `.rs`. Generated SQL carries a `-- AUTOMATICALLY GENERATED FILE` header (the project-wide marker `docs:validate` greps on); change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/encrypted_domain//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. +Adding a scalar encrypted-domain type is one row in the Rust catalog `eql-scalars::CATALOG` (`crates/eql-scalars/src/lib.rs`): a `ScalarSpec` giving the type `token` (e.g. `int8`), its `ScalarKind` (the `kind` field), the `DomainSpec`s mapping each generated domain suffix to its fixed index `Term`s (`_eq => [Hm]`, `_ord`/`_ord_ore => [Ore]`), and the `Fixture` value list. Term capabilities are fixed in the `Term` enum's `impl` methods (with unit tests): `Hm` provides equality, and `Ore` provides equality plus ordering. There is no TOML manifest and no Python — the catalog is the source of truth, validated by the compiler (an undefined term or unknown scalar is a compile error) plus catalog `#[test]`s. `mise run build` runs `cargo run -p eql-codegen`, which regenerates the scalar SQL surface into `src/v3/scalars//` from `CATALOG` at the start of every build; that surface includes supported comparison wrappers plus blockers for native `jsonb` operators that would otherwise be reachable through domain fallback. `cargo run -p eql-codegen` regenerates every type at once (the same call `mise run build` uses; there is no per-type codegen task). The generated `*_types.sql` / `*_functions.sql` / `*_operators.sql` / `*_aggregates.sql` files are gitignored and never committed. The per-type plaintext fixture lists the SQLx matrix consumes are **not** a generated file — they are materialised from each `CATALOG` row at compile time as `eql_scalars::INT4_VALUES` / `INT2_VALUES` (the `int_values!` macro) and read directly by `ScalarType::FIXTURE_VALUES`; a Rust source of truth no longer round-trips through a committed generated `.rs`. Generated SQL carries a `-- AUTOMATICALLY GENERATED FILE` header (the project-wide marker `docs:validate` greps on); change the catalog and rebuild, never hand-edit. Hand-written SQL beyond the fixed surface goes in `src/v3/scalars//_extensions.sql` with no auto-generated header and explicit `-- REQUIRE:` edges — that file IS committed. `text` and `jsonb` are out of scope for this scalar materializer. **Adding a new encrypted-domain type: follow `docs/reference/adding-a-scalar-encrypted-domain-type.md`.** The mechanics are fixed for ordered scalar domains; the catalog row only declares the token, kind, domain suffixes, and terms. New term behavior belongs in the `Term` enum's `impl` methods in `crates/eql-scalars/src` with tests, not in free-form catalog data. diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index 68d913e8..e4ae242b 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -11,9 +11,11 @@ A scalar encrypted-domain type is a family of concrete `jsonb` domains in the **`eql_v3`** schema (`eql_v3.`, `eql_v3._eq`, `eql_v3._ord`, …), dropped by `DROP SCHEMA eql_v3 CASCADE` and surviving an `eql_v2` uninstall. Their extractors, comparison wrappers, and MIN/MAX -aggregates also live in `eql_v3`; the index-term types they return -(`eql_v2.hmac_256`, `eql_v2.ore_block_u64_8_256`) stay in `eql_v2` and are -referenced cross-schema. +aggregates also live in `eql_v3`; the searchable-encrypted-metadata (SEM) +index-term types they return (`eql_v3.hmac_256`, +`eql_v3.ore_block_u64_8_256`) are **also `eql_v3`** — hand-written under +`src/v3/sem/`. The whole v3 surface is self-contained: it owns every type it +needs and has no runtime dependency on `eql_v2` (CI gates this — see §6). The whole SQL surface is **generated** from a single Rust source of truth: the `CATALOG` const in [`crates/eql-scalars/src/lib.rs`](../../crates/eql-scalars/src/lib.rs), @@ -60,7 +62,7 @@ Things you do **not** do: has a new name (§5). Hand-written SQL beyond the fixed surface goes in -`src/encrypted_domain//_extensions.sql` with explicit `-- REQUIRE:` edges +`src/v3/scalars//_extensions.sql` with explicit `-- REQUIRE:` edges — and **that file IS committed** (§5). --- @@ -111,8 +113,8 @@ contract — changing one is a generated-SQL behaviour change, not a refactor: | Term | JSON key | Extractor | Returns | Operators | | ----- | -------- | ----------- | -------------------------------- | -------------------------- | -| `Hm` | `hm` | `eq_term` | `eql_v2.hmac_256` | `=` `<>` | -| `Ore` | `ob` | `ord_term` | `eql_v2.ore_block_u64_8_256` | `=` `<>` `<` `<=` `>` `>=` | +| `Hm` | `hm` | `eq_term` | `eql_v3.hmac_256` | `=` `<>` | +| `Ore` | `ob` | `ord_term` | `eql_v3.ore_block_u64_8_256` | `=` `<>` `<` `<=` `>` `>=` | A type that needs a non-ORE equality term on an ordered domain needs a **new `Term`**, not a catalog flag. Adding a term is a code change to the `Term` @@ -290,7 +292,7 @@ This is the contract the generated SQL satisfies. You normally never read it to ### Domains and CHECK constraints -The generator emits `src/encrypted_domain//_types.sql` (gitignored; +The generator emits `src/v3/scalars//_types.sql` (gitignored; materialised on every build) with one idempotent `DO $$ ... $$` block. Every domain is a concrete domain over `jsonb` in the `eql_v3` schema — **never** `CREATE DOMAIN a AS b` over another generated domain (PostgreSQL resolves @@ -411,14 +413,14 @@ CREATE INDEX ... ON table_name USING btree (eql_v3.ord_term(col)); CREATE INDEX ... ON table_name USING hash (eql_v3.eq_term(col)); ``` -`ore` depends on `src/ore_block_u64_8_256/functions.sql` and -`src/ore_block_u64_8_256/operators.sql`; `hm` depends on -`src/hmac_256/functions.sql`. +`ore` depends on `src/v3/sem/ore_block_u64_8_256/functions.sql` and +`src/v3/sem/ore_block_u64_8_256/operators.sql`; `hm` depends on +`src/v3/sem/hmac_256/functions.sql`. ### Extension files Optional hand-written SQL beyond the fixed surface belongs in -`src/encrypted_domain//_extensions.sql`. The generator never creates, +`src/v3/scalars//_extensions.sql`. The generator never creates, lists, headers, or cleans it; it must declare its own `-- REQUIRE:` edges (usually to `_types.sql` and whichever generated function or operator file it extends). Use it for cross-domain casts, helper functions, or type-specific @@ -495,7 +497,7 @@ silently bypasses its exception; a pinned `search_path` reverts queries to seq scans. The generator exists so each new type adds one `CATALOG` row rather than ninety hand-written declarations that must agree with each other and with `pin_search_path.sql`, `tasks/test/splinter.sh`, and -`src/encrypted_domain/functions.sql`. +`src/v3/scalars/functions.sql`. ### Pipeline @@ -503,15 +505,20 @@ ninety hand-written declarations that must agree with each other and with runs as `cargo run -p eql-codegen` (no subcommand), which calls `generate::generate_all` (`crates/eql-codegen/src/generate.rs`) over every row of `eql_scalars::CATALOG`, writing each type's SQL into -`src/encrypted_domain//`. A second subcommand, `cargo run -p eql-codegen +`src/v3/scalars//`. A second subcommand, `cargo run -p eql-codegen -- list-types`, prints the catalog tokens one per line (consumed by the fixture and matrix-inventory enumeration). `main` (`crates/eql-codegen/src/main.rs`) recognises exactly these two forms; any other argument is a usage error. +The generator targets the `eql_v3` schema throughout: `CORE_SCHEMA = "eql_v3"` +(`crates/eql-codegen/src/consts.rs`) qualifies both the domain families and the +SEM index-term types the extractors return (`eql_v3.hmac_256`, +`eql_v3.ore_block_u64_8_256`), so no generated SQL references `eql_v2`. + `tasks/build.sh` runs `cargo run -p eql-codegen` at the start of every `mise run build`, so the generated SQL is never checked in. (The build first sweeps every generated `*_{types,functions,operators,aggregates}.sql` under -`src/encrypted_domain` so a type removed from `CATALOG` cannot leave orphans the +`src/v3/scalars` so a type removed from `CATALOG` cannot leave orphans the `src/**/*.sql` build glob would pick up; hand-written `*_extensions.sql` is preserved by the name patterns.) @@ -549,9 +556,9 @@ output for every catalog type from scratch. ### Generated outputs For a type with `D` domains of which `A` are ordered, the generator writes `1 + -2D + A` SQL files into `src/encrypted_domain//`. For `int4` (`D = 4`, `A = +2D + A` SQL files into `src/v3/scalars//`. For `int4` (`D = 4`, `A = 2`): eleven SQL files. The outputs are gitignored -(`.gitignore` excludes `src/encrypted_domain/*/*_{types,functions,operators,aggregates}.sql`) +(`.gitignore` excludes `src/v3/scalars/*/*_{types,functions,operators,aggregates}.sql`) and regenerated at the start of every build. | File | Content | @@ -564,11 +571,11 @@ and regenerated at the start of every build. Every file opens with the `-- AUTOMATICALLY GENERATED FILE.` marker (the project-wide marker `docs:validate` greps on to skip generated SQL — `crates/eql-codegen/src/consts.rs`), declares its `-- REQUIRE:` edges in -dependency order (types files require `src/schema-v3.sql`; function files require -both `src/schema.sql` and `src/schema-v3.sql`, the types file, and -`src/encrypted_domain/functions.sql` plus each term's `requires` set; operator -files require `src/schema-v3.sql`, the types file, and their domain's function -file; aggregate files require `src/schema-v3.sql`, the types file, and their +dependency order (types files require `src/v3/schema.sql`; function files require +`src/v3/schema.sql`, the types file, and +`src/v3/scalars/functions.sql` plus each term's `requires` set; operator +files require `src/v3/schema.sql`, the types file, and their domain's function +file; aggregate files require `src/v3/schema.sql`, the types file, and their domain's function and operator files), and carries Doxygen `--! @file` / `--! @brief` headers. diff --git a/tests/codegen/reference/README.md b/tests/codegen/reference/README.md index 186d6d5c..fb2de720 100644 --- a/tests/codegen/reference/README.md +++ b/tests/codegen/reference/README.md @@ -4,7 +4,7 @@ The SQL files under `int4/` are the hand-maintained golden reference for the enc Each reference file's first line is a `-- REFERENCE:` provenance marker; everything after it is the generated body verbatim, starting with the template-owned `-- AUTOMATICALLY GENERATED FILE.` header. -The parity gate runs the generator (`cargo run -p eql-codegen`, which writes the real `src/encrypted_domain/int4/` tree) and asserts its output matches these files **byte-for-byte** after dropping that single provenance line. It runs three ways, all on the same reference: +The parity gate runs the generator (`cargo run -p eql-codegen`, which writes the real `src/v3/scalars/int4/` tree) and asserts its output matches these files **byte-for-byte** after dropping that single provenance line. It runs three ways, all on the same reference: - `mise run codegen:parity` (`tasks/codegen-parity.sh`) — the CI shell gate. It first compares the generated `int4` SQL *file set* against the golden `*.sql` set (`comm -23` against `git ls-files` excludes the committed, hand-written `int4_extensions.sql`, which has no golden counterpart) to catch extra/dropped files, then `diff`s each golden file against its generated counterpart after `tail -n +2` drops the provenance line. Any whitespace or blank-line drift fails — there is no normalization. - `crates/eql-codegen/tests/parity.rs` (`rust_generator_matches_int4_golden_files`) — runs `generate_all` into a temp dir and byte-compares the materialised `int4` SQL surface against the same golden. From eacf66998c7aa676744dd720e89f95d8179e0c8d Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 15:15:06 +1000 Subject: [PATCH 77/93] test(v3): update family mutation tests + drop-opclass fixture for the eql_v3 SEM fork The ord_term extractor now returns eql_v3.ore_block_u64_8_256 (D12), so the CREATE-OR-REPLACE mutation redefinitions must declare the eql_v3 return type and construct eql_v3 SEM terms; the eq-reroute mutations move to eql_v3 SEM too. The drop_operator_classes fixture now also drops the new eql_v3 ORE btree opclass, which the Supabase build's **/*operator_class.sql glob likewise excludes. --- tests/sqlx/fixtures/drop_operator_classes.sql | 7 +++++++ .../tests/encrypted_domain/family/mutations.rs | 16 ++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/sqlx/fixtures/drop_operator_classes.sql b/tests/sqlx/fixtures/drop_operator_classes.sql index 2c5632bb..094073aa 100644 --- a/tests/sqlx/fixtures/drop_operator_classes.sql +++ b/tests/sqlx/fixtures/drop_operator_classes.sql @@ -16,6 +16,13 @@ DROP OPERATOR FAMILY IF EXISTS eql_v2.encrypted_hash_operator_family USING hash DROP OPERATOR CLASS IF EXISTS eql_v2.ore_block_u64_8_256_operator_class USING btree CASCADE; DROP OPERATOR FAMILY IF EXISTS eql_v2.ore_block_u64_8_256_operator_family USING btree CASCADE; +-- Drop the self-contained eql_v3 ORE btree operator class too — its file carries +-- the `*operator_class.sql` suffix, so the Supabase build's `**/*operator_class.sql` +-- glob excludes it as well. Without this the unqualified-name opclass check below +-- still finds the eql_v3 copy. +DROP OPERATOR CLASS IF EXISTS eql_v3.ore_block_u64_8_256_operator_class USING btree CASCADE; +DROP OPERATOR FAMILY IF EXISTS eql_v3.ore_block_u64_8_256_operator_family USING btree CASCADE; + -- Drop ore_block_u64_8_256 operators (also excluded from Supabase build) DROP OPERATOR IF EXISTS = (eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) CASCADE; DROP OPERATOR IF EXISTS <> (eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) CASCADE; diff --git a/tests/sqlx/tests/encrypted_domain/family/mutations.rs b/tests/sqlx/tests/encrypted_domain/family/mutations.rs index 7be20853..36d394d8 100644 --- a/tests/sqlx/tests/encrypted_domain/family/mutations.rs +++ b/tests/sqlx/tests/encrypted_domain/family/mutations.rs @@ -143,14 +143,14 @@ async fn rerouting_ord_eq_through_hm_flips_ord_routes_arm(pool: PgPool) -> Resul "baseline: `_ord` `=` must match exactly the pivot via ob with hm stripped (got {baseline})" ); - // Mutation: reroute `_ord` `=` through HMAC. `eql_v2.hmac_256(jsonb)` is + // Mutation: reroute `_ord` `=` through HMAC. `eql_v3.hmac_256(jsonb)` is // STRICT and the `hm` key is absent, so it yields NULL and `=` matches // nothing. mutate( &pool, "CREATE OR REPLACE FUNCTION eql_v3.eq(a eql_v3.int4_ord, b eql_v3.int4_ord) \ RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ - AS $$ SELECT eql_v2.hmac_256(a::jsonb) = eql_v2.hmac_256(b::jsonb) $$", + AS $$ SELECT eql_v3.hmac_256(a::jsonb) = eql_v3.hmac_256(b::jsonb) $$", ) .await?; @@ -297,12 +297,12 @@ async fn rerouting_eq_eq_through_ob_flips_eq_arm(pool: PgPool) -> Result<()> { ); // Mutation: reroute `_eq` `=` through ORE. The `ob` key is absent, so - // `eql_v2.ore_block_u64_8_256(jsonb)` raises rather than matching. + // `eql_v3.ore_block_u64_8_256(jsonb)` raises rather than matching. mutate( &pool, "CREATE OR REPLACE FUNCTION eql_v3.eq(a eql_v3.int4_eq, b eql_v3.int4_eq) \ RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ - AS $$ SELECT eql_v2.ore_block_u64_8_256(a::jsonb) = eql_v2.ore_block_u64_8_256(b::jsonb) $$", + AS $$ SELECT eql_v3.ore_block_u64_8_256(a::jsonb) = eql_v3.ore_block_u64_8_256(b::jsonb) $$", ) .await?; @@ -354,8 +354,8 @@ async fn collapsing_ord_term_flips_order_by_arm(pool: PgPool) -> Result<()> { let const_payload = fetch_fixture_payload::(&pool, 0).await?; let ddl = format!( "CREATE OR REPLACE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord) \ - RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ - AS $mutbody$ SELECT eql_v2.ore_block_u64_8_256('{esc}'::jsonb) $mutbody$", + RETURNS eql_v3.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE \ + AS $mutbody$ SELECT eql_v3.ore_block_u64_8_256('{esc}'::jsonb) $mutbody$", esc = const_payload.replace('\'', "''"), ); mutate(&pool, &ddl).await?; @@ -409,8 +409,8 @@ async fn making_ord_term_non_strict_flips_order_by_nulls_arm(pool: PgPool) -> Re let const_payload = fetch_fixture_payload::(&pool, 0).await?; let ddl = format!( "CREATE OR REPLACE FUNCTION eql_v3.ord_term(a eql_v3.int4_ord) \ - RETURNS eql_v2.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE PARALLEL SAFE \ - AS $mutbody$ SELECT eql_v2.ore_block_u64_8_256(\ + RETURNS eql_v3.ore_block_u64_8_256 LANGUAGE sql IMMUTABLE PARALLEL SAFE \ + AS $mutbody$ SELECT eql_v3.ore_block_u64_8_256(\ coalesce(a, '{esc}'::jsonb::eql_v3.int4_ord)::jsonb) $mutbody$", esc = const_payload.replace('\'', "''"), ); From 973c5fe7341052b263c39988d224d51f3574905b Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 15:15:15 +1000 Subject: [PATCH 78/93] refactor(codegen): extract v3 path constants, drop dead Term::returns, fix blocker doc path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoist the src/v3 path strings into named constants in generate.rs (byte-identical output — parity holds). Remove the now-dead Term::returns() (codegen builds the extractor return type from CORE_SCHEMA + ctor) and its legacy eql_v2 test assertions. Repoint the shared blocker's doc comment to src/v3/scalars//. --- crates/eql-codegen/src/generate.rs | 32 +++++++++++++++++++++--------- crates/eql-scalars/src/lib.rs | 10 ---------- src/v3/scalars/functions.sql | 2 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/eql-codegen/src/generate.rs b/crates/eql-codegen/src/generate.rs index f7157971..622ca634 100644 --- a/crates/eql-codegen/src/generate.rs +++ b/crates/eql-codegen/src/generate.rs @@ -7,6 +7,20 @@ use eql_scalars::{DomainSpec, ScalarSpec, Term}; use crate::context::{domain_name, is_ord_capable}; use crate::operator_surface::OPERATORS; +/// REQUIRE edge for the v3 schema file — pulled in by every generated file. +const V3_SCHEMA: &str = "src/v3/schema.sql"; +/// REQUIRE edge for the hand-written shared blocker helper. +const V3_SCALARS_BLOCKER: &str = "src/v3/scalars/functions.sql"; +/// Root of the generated per-token scalar surface. The single place the tree +/// layout is spelled out — keeps `types_path`/`scalar_path` and the REQUIRE +/// vecs from drifting if the surface ever relocates again. +const V3_SCALARS_DIR: &str = "src/v3/scalars"; + +/// REQUIRE path for a generated file `file` under a token's scalar dir. +fn scalar_path(token: &str, file: &str) -> String { + format!("{V3_SCALARS_DIR}/{token}/{file}") +} + /// The full domain name (token + suffix). suffix "" => bare token. fn full_name(token: &str, suffix: &str) -> String { format!("{token}{suffix}") @@ -25,7 +39,7 @@ fn arg_b_name(symbol: &str) -> &'static str { /// REQUIRE path for a type's _types.sql. Port of `_types_path`. fn types_path(token: &str) -> String { - format!("src/v3/scalars/{token}/{token}_types.sql") + scalar_path(token, &format!("{token}_types.sql")) } /// Body for _types.sql: every domain in one idempotent DO block. @@ -50,9 +64,9 @@ pub fn render_types_file(spec: &ScalarSpec) -> String { /// REQUIRE edges for a domain's _functions.sql. Port of `_functions_requires`. fn functions_requires(token: &str, terms: &[Term]) -> Vec { let mut reqs = vec![ - "src/v3/schema.sql".to_string(), + V3_SCHEMA.to_string(), types_path(token), - "src/v3/scalars/functions.sql".to_string(), + V3_SCALARS_BLOCKER.to_string(), ]; for extra in Term::term_requires(terms) { if !reqs.iter().any(|r| r == extra) { @@ -162,9 +176,9 @@ pub fn render_operators_file(token: &str, domain: &DomainSpec) -> String { let ctx = OperatorsContext { requires: vec![ - "src/v3/schema.sql".to_string(), + V3_SCHEMA.to_string(), types_path(token), - format!("src/v3/scalars/{token}/{name}_functions.sql"), + scalar_path(token, &format!("{name}_functions.sql")), ], token: token.to_string(), name, @@ -189,10 +203,10 @@ pub fn render_aggregates_file(token: &str, domain: &DomainSpec) -> Option Result, pub fn generate_all(out_root: &Path) -> Result { for spec in eql_scalars::CATALOG { let token = spec.token; - let out_dir = out_root.join("src").join("v3").join("scalars").join(token); + let out_dir = out_root.join(V3_SCALARS_DIR).join(token); let written = generate_type(spec, &out_dir)?; for p in &written { diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index 7dedc2f4..1d40a7e1 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -145,14 +145,6 @@ impl Term { } } - /// Cross-schema return type of the extractor (in `eql_v2`). - pub const fn returns(self) -> &'static str { - match self { - Term::Hm => "eql_v2.hmac_256", - Term::Ore => "eql_v2.ore_block_u64_8_256", - } - } - /// Constructor name for the index-term type (unqualified). pub const fn ctor(self) -> &'static str { match self { @@ -513,7 +505,6 @@ mod term_tests { let hm = Term::Hm; assert_eq!(hm.json_key(), "hm"); assert_eq!(hm.extractor(), "eq_term"); - assert_eq!(hm.returns(), "eql_v2.hmac_256"); assert_eq!(hm.ctor(), "hmac_256"); assert_eq!(hm.role(), "eq"); assert_eq!(hm.operators(), &["=", "<>"]); @@ -525,7 +516,6 @@ mod term_tests { let ore = Term::Ore; assert_eq!(ore.json_key(), "ob"); assert_eq!(ore.extractor(), "ord_term"); - assert_eq!(ore.returns(), "eql_v2.ore_block_u64_8_256"); assert_eq!(ore.ctor(), "ore_block_u64_8_256"); assert_eq!(ore.role(), "ord"); assert_eq!(ore.operators(), &["=", "<>", "<", "<=", ">", ">="]); diff --git a/src/v3/scalars/functions.sql b/src/v3/scalars/functions.sql index ab997a48..53273023 100644 --- a/src/v3/scalars/functions.sql +++ b/src/v3/scalars/functions.sql @@ -3,7 +3,7 @@ --! @file v3/scalars/functions.sql --! @brief Shared blocker helper for the eql_v3 encrypted-domain families. --! ---! Per-domain wrapper functions live in src/encrypted_domain//. +--! Per-domain wrapper functions live in src/v3/scalars//. --! Blockers in those files delegate to encrypted_domain_unsupported_bool --! so every domain raises a uniform domain-specific error rather than --! letting an unsupported operator fall through to native jsonb From 0717d16e4155527a27562f3264e741283ef40966 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 15:33:47 +1000 Subject: [PATCH 79/93] test(v3): direct coverage for the eql_v3 ORE/HMAC SEM functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The eql_v3 SEM index-term functions are a hand-port of the eql_v2 originals. The scalar matrix already exercises the array comparator's happy path end-to-end against real ciphertext fixtures, but several branches are structurally unreachable there and were tested only on the eql_v2 copies. Add a sibling family test module covering them: - differential v2<->v3 parity on real `ob` fixtures (both sides routed through extractor -> composite -> compare_..._terms, so the schema prefix is the only variable) — the strongest guard against a faithful-port slip; - the 'Ciphertexts are different lengths' RAISE; - NULL-term ordering branches the STRICT wrappers bypass; - array NULL + empty/cardinality recursion base cases; - has_* presence checks, the missing-`ob` RAISE, and the NULL-jsonb short-circuit. Verified non-vacuous: a deliberately broken comparator fails T1/T2/T3 while the independent T4/T5 stay green. --- .../sqlx/tests/encrypted_domain/family/mod.rs | 1 + .../sqlx/tests/encrypted_domain/family/sem.rs | 214 ++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 tests/sqlx/tests/encrypted_domain/family/sem.rs diff --git a/tests/sqlx/tests/encrypted_domain/family/mod.rs b/tests/sqlx/tests/encrypted_domain/family/mod.rs index 6622e0f8..892842a2 100644 --- a/tests/sqlx/tests/encrypted_domain/family/mod.rs +++ b/tests/sqlx/tests/encrypted_domain/family/mod.rs @@ -4,4 +4,5 @@ pub mod inlinability; pub mod jsonb_operator_surface; pub mod mutations; +pub mod sem; pub mod support; diff --git a/tests/sqlx/tests/encrypted_domain/family/sem.rs b/tests/sqlx/tests/encrypted_domain/family/sem.rs new file mode 100644 index 00000000..4f30bac2 --- /dev/null +++ b/tests/sqlx/tests/encrypted_domain/family/sem.rs @@ -0,0 +1,214 @@ +//! Direct behavioural tests for the self-contained `eql_v3` searchable- +//! encrypted-metadata (SEM) index-term functions (`eql_v3.hmac_256`, +//! `eql_v3.ore_block_u64_8_256` and their comparators). +//! +//! These functions are a HAND-PORT of the `eql_v2` originals (`src/v3/sem/`). +//! The scalar matrix already exercises the happy path of the *array* comparator +//! end-to-end against real ciphertext fixtures (ordering, equality, min/max, +//! injectivity, index engagement). This file covers the branches the matrix +//! structurally cannot reach, and which are otherwise tested only on the +//! `eql_v2` copies (in `tests/index_compare_tests.rs`): +//! +//! - T1: differential v2↔v3 parity on real `ob` fixtures (the strongest guard +//! against a faithful-port slip — see below). +//! - T2: the `'Ciphertexts are different lengths'` RAISE (all real fixtures are +//! equal length, so the matrix never hits it). +//! - T3: NULL-term ordering inside `compare_ore_block_u64_8_256_term` — the +//! `STRICT` comparison wrappers short-circuit before these branches run. +//! - T4: array-level NULL + empty/cardinality base cases of the recursion. +//! - T5: presence checks (`has_*`) and the missing-`ob` RAISE. +//! +//! All migrations (`001`–`007`) auto-apply to every `#[sqlx::test]` pool, so the +//! real `ore` table (ids 1–1000) and both schemas are available with no setup. + +use std::collections::HashSet; + +use anyhow::Result; +use eql_tests::assert_raises; +use sqlx::PgPool; + +/// A single term built directly from hex — no encryption needed for the +/// structural/edge-case tests. +fn term(hex: &str) -> String { + format!("ROW(decode('{hex}', 'hex'))::eql_v3.ore_block_u64_8_256_term") +} + +/// T1 — Differential parity: the same real `ob` payload must compare identically +/// through the `eql_v2` and `eql_v3` array comparators. `eql_v2` is the trusted +/// oracle; `eql_v3` is the byte-port. Both sides route through the SAME path +/// (jsonb extractor → composite → `compare_ore_block_u64_8_256_terms`) so the +/// schema prefix is the only variable — any divergence is a genuine port bug. +/// v3 has no encrypted-arg `compare` overload, hence the extractor routing. +#[sqlx::test] +async fn ore_v2_v3_comparator_parity_on_real_fixtures(pool: PgPool) -> Result<()> { + // Pairs spanning equal and unequal ids. Plaintext order of the fixtures is + // undocumented, so we assert v2≡v3 agreement (not a specific sign). + let pairs = [ + (1i64, 1i64), + (1, 2), + (2, 1), + (1, 500), + (500, 1), + (42, 42), + (10, 900), + (900, 10), + ]; + + let sql = r#" + WITH a AS (SELECT e::jsonb AS j FROM ore WHERE id = $1), + b AS (SELECT e::jsonb AS j FROM ore WHERE id = $2) + SELECT + eql_v2.compare_ore_block_u64_8_256_terms( + eql_v2.ore_block_u64_8_256(a.j), eql_v2.ore_block_u64_8_256(b.j)) AS v2, + eql_v3.compare_ore_block_u64_8_256_terms( + eql_v3.ore_block_u64_8_256(a.j), eql_v3.ore_block_u64_8_256(b.j)) AS v3 + FROM a, b + "#; + + let mut v3_signs: HashSet = HashSet::new(); + for (x, y) in pairs { + let (v2, v3): (i32, i32) = sqlx::query_as(sql) + .bind(x) + .bind(y) + .fetch_one(&pool) + .await?; + assert_eq!( + v2, v3, + "eql_v2 and eql_v3 ORE comparators disagree on ids ({x},{y}): v2={v2} v3={v3}" + ); + v3_signs.insert(v3); + } + + // Non-triviality: the sample must have actually exercised lt, eq, and gt — + // otherwise the parity check could pass on a degenerate all-equal path. + assert!(v3_signs.contains(&0), "sample must include an equal pair (0)"); + assert!( + v3_signs.contains(&-1), + "sample must include a less-than pair (-1)" + ); + assert!( + v3_signs.contains(&1), + "sample must include a greater-than pair (1)" + ); + Ok(()) +} + +/// T2 — The term comparator must reject ciphertexts of different lengths. This +/// guard is unreachable via the matrix (every real fixture is equal length). +#[sqlx::test] +async fn ore_term_comparator_rejects_different_length_ciphertexts(pool: PgPool) -> Result<()> { + let sql = format!( + "SELECT eql_v3.compare_ore_block_u64_8_256_term({}, {})", + term("aabbccdd"), // 4 bytes + term("aabbccddee"), // 5 bytes + ); + assert_raises(&pool, &sql, &[], "Ciphertexts are different lengths").await?; + Ok(()) +} + +/// T3 — NULL-term ordering inside `compare_ore_block_u64_8_256_term`. The +/// function is intentionally NOT `STRICT`, so these defensive branches are +/// reachable by a direct call (the `STRICT` comparison wrappers never reach +/// them). Pins: `(NULL, t) = -1`, `(t, NULL) = 1`, `(NULL, NULL) = 0`. +#[sqlx::test] +async fn ore_term_comparator_null_ordering(pool: PgPool) -> Result<()> { + let t = term("aabb"); + let n = "NULL::eql_v3.ore_block_u64_8_256_term"; + + let cases = [ + ( + format!("SELECT eql_v3.compare_ore_block_u64_8_256_term({n}, {t})"), + -1, + ), + ( + format!("SELECT eql_v3.compare_ore_block_u64_8_256_term({t}, {n})"), + 1, + ), + ( + format!("SELECT eql_v3.compare_ore_block_u64_8_256_term({n}, {n})"), + 0, + ), + ]; + + for (sql, expected) in cases { + let got: i32 = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + assert_eq!(got, expected, "null-term ordering: {sql}"); + } + Ok(()) +} + +/// T4 — Array-level NULL and empty/cardinality base cases of the recursive +/// `compare_ore_block_u64_8_256_terms(term[], term[])`. NULL array → NULL; +/// both empty → 0; empty vs non-empty → -1; non-empty vs empty → 1. +#[sqlx::test] +async fn ore_terms_array_null_and_empty_base_cases(pool: PgPool) -> Result<()> { + let t = format!("ARRAY[{}]", term("aabb")); + let empty = "ARRAY[]::eql_v3.ore_block_u64_8_256_term[]"; + let null_arr = "NULL::eql_v3.ore_block_u64_8_256_term[]"; + + // NULL array operand → NULL result (the array overload returns NULL; it is + // not STRICT). Typed as Option; the shared `assert_null` helper only + // types Option, so query directly here. + for sql in [ + format!("SELECT eql_v3.compare_ore_block_u64_8_256_terms({null_arr}, {t})"), + format!("SELECT eql_v3.compare_ore_block_u64_8_256_terms({t}, {null_arr})"), + ] { + let got: Option = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + assert!(got.is_none(), "NULL array operand must yield NULL: {sql}"); + } + + let cases = [ + ( + format!("SELECT eql_v3.compare_ore_block_u64_8_256_terms({empty}, {empty})"), + 0, + ), + ( + format!("SELECT eql_v3.compare_ore_block_u64_8_256_terms({empty}, {t})"), + -1, + ), + ( + format!("SELECT eql_v3.compare_ore_block_u64_8_256_terms({t}, {empty})"), + 1, + ), + ]; + for (sql, expected) in cases { + let got: i32 = sqlx::query_scalar(&sql).fetch_one(&pool).await?; + assert_eq!(got, expected, "array base case: {sql}"); + } + Ok(()) +} + +/// T5 — SEM presence checks (`has_ore_block_u64_8_256`, `has_hmac_256`), the +/// extractor's missing-`ob` RAISE, and its NULL-jsonb short-circuit. +#[sqlx::test] +async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<()> { + let bool_cases = [ + (r#"SELECT eql_v3.has_ore_block_u64_8_256('{"ob":["aa"]}'::jsonb)"#, true), + (r#"SELECT eql_v3.has_ore_block_u64_8_256('{}'::jsonb)"#, false), + // json-null `ob` → `->>` yields NULL → absent. + (r#"SELECT eql_v3.has_ore_block_u64_8_256('{"ob":null}'::jsonb)"#, false), + (r#"SELECT eql_v3.has_hmac_256('{"hm":"abc"}'::jsonb)"#, true), + (r#"SELECT eql_v3.has_hmac_256('{}'::jsonb)"#, false), + ]; + for (sql, expected) in bool_cases { + let got: bool = sqlx::query_scalar(sql).fetch_one(&pool).await?; + assert_eq!(got, expected, "presence check: {sql}"); + } + + // Missing `ob` → RAISE. + assert_raises( + &pool, + r#"SELECT eql_v3.ore_block_u64_8_256('{"foo":1}'::jsonb)"#, + &[], + "Expected an ore index (ob) value", + ) + .await?; + + // NULL jsonb → NULL composite (STRICT short-circuit), NOT a raise. + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.ore_block_u64_8_256(NULL::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!(is_null, "NULL jsonb must extract to a NULL composite, not raise"); + Ok(()) +} From 9b9557dd8935108f6af1757096636e767828dceb Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 16:25:46 +1000 Subject: [PATCH 80/93] test(v3): harden self-containment checks to also reject bare eql_v2_ entity names Addresses CodeRabbit review: the artifact/test self-containment assertions only matched schema-qualified eql_v2. references; extend them to also reject eql_v2_ names (eql_v2_encrypted, eql_v2_configuration) while still allowing prose mentions of eql_v2 in doc comments. Verified the v3 artifact and src/v3 contain zero eql_v2_ occurrences. --- tasks/test/self_contained_v3.sh | 15 +++++++++------ tests/sqlx/tests/build_validation_tests.rs | 7 +++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/tasks/test/self_contained_v3.sh b/tasks/test/self_contained_v3.sh index 59373cc9..72466624 100755 --- a/tasks/test/self_contained_v3.sh +++ b/tasks/test/self_contained_v3.sh @@ -11,9 +11,12 @@ fail=0 # Symbol level (design goal 1): no eql_v2. anywhere under src/v3 — the # hand-written SEM + foundation files plus the gitignored generated scalar # surface (present because build runs codegen). Run `mise run build` first. -echo "==> Symbol gate: no 'eql_v2.' under src/v3" -if grep -rn 'eql_v2\.' src/v3; then - echo "ERROR: eql_v2. reference found in src/v3 (must be self-contained)" >&2 +# Match both schema-qualified refs (`eql_v2.`) and bare v2 entity names +# (`eql_v2_encrypted`, `eql_v2_configuration`, …). Prose like "the eql_v2 +# original is unchanged" in doc comments is intentionally still allowed. +echo "==> Symbol gate: no 'eql_v2.' / 'eql_v2_' under src/v3" +if grep -rnE 'eql_v2[._]' src/v3; then + echo "ERROR: eql_v2 symbol/entity reference found in src/v3 (must be self-contained)" >&2 fail=1 fi @@ -30,13 +33,13 @@ if grep -v '^src/v3/' src/deps-ordered-v3.txt; then fi # Belt-and-braces: the assembled artifact carries no eql_v2 symbol. -echo "==> Artifact gate: release/cipherstash-encrypt-v3.sql has no 'eql_v2.'" +echo "==> Artifact gate: release/cipherstash-encrypt-v3.sql has no 'eql_v2.' / 'eql_v2_'" if [[ ! -f release/cipherstash-encrypt-v3.sql ]]; then echo "ERROR: release/cipherstash-encrypt-v3.sql missing — run 'mise run build' first" >&2 exit 2 fi -if grep -n 'eql_v2\.' release/cipherstash-encrypt-v3.sql; then - echo "ERROR: assembled v3 artifact contains an eql_v2. reference" >&2 +if grep -nE 'eql_v2[._]' release/cipherstash-encrypt-v3.sql; then + echo "ERROR: assembled v3 artifact contains an eql_v2 symbol/entity reference" >&2 fail=1 fi diff --git a/tests/sqlx/tests/build_validation_tests.rs b/tests/sqlx/tests/build_validation_tests.rs index d23ba5a5..691a5d4a 100644 --- a/tests/sqlx/tests/build_validation_tests.rs +++ b/tests/sqlx/tests/build_validation_tests.rs @@ -171,9 +171,12 @@ fn v3_variant_creates_eql_v3_schema() { #[test] fn v3_variant_has_no_eql_v2_symbol() { let sql = read_release_sql("cipherstash-encrypt-v3.sql"); + // Reject both schema-qualified refs (`eql_v2.`) and bare v2 entity names + // (`eql_v2_encrypted`, `eql_v2_configuration`, …). Prose mentions like + // "the eql_v2 original is unchanged" in doc comments are still allowed. assert!( - !sql.contains("eql_v2."), - "v3 variant must be self-contained (no eql_v2. reference)" + !sql.contains("eql_v2.") && !sql.contains("eql_v2_"), + "v3 variant must be self-contained (no eql_v2. or eql_v2_ reference)" ); } From 19fe0496e902408137df6d11de64c293d6c9d932 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 16:40:53 +1000 Subject: [PATCH 81/93] refactor(codegen): collapse CORE/DOMAIN schema constants into a single SCHEMA With eql_v3 fully self-contained, the encrypted-domain families and the SEM index-term types they call live in one schema, so there is no second schema to point the core types at. Replace the CORE_SCHEMA/DOMAIN_SCHEMA pair with one SCHEMA = "eql_v3" constant; the templates read it via the {{ schema }} global. Output is byte-identical (golden parity holds). --- crates/eql-codegen/src/consts.rs | 10 ++++++---- crates/eql-codegen/src/context.rs | 19 +++++++++---------- .../eql-codegen/templates/aggregates.sql.j2 | 8 ++++---- .../templates/functions/extractor.sql.j2 | 4 ++-- .../templates/functions/unsupported.sql.j2 | 2 +- .../templates/functions/wrapper.sql.j2 | 2 +- crates/eql-codegen/templates/operators.sql.j2 | 2 +- crates/eql-codegen/templates/types.sql.j2 | 6 +++--- 8 files changed, 27 insertions(+), 26 deletions(-) diff --git a/crates/eql-codegen/src/consts.rs b/crates/eql-codegen/src/consts.rs index 635c4908..0f7a1ef7 100644 --- a/crates/eql-codegen/src/consts.rs +++ b/crates/eql-codegen/src/consts.rs @@ -4,10 +4,12 @@ /// the writer uses it only to recognise files it owns (overwrite/clean safety). pub(crate) const AUTO_GENERATED_HEADER: &str = "-- AUTOMATICALLY GENERATED FILE.\n"; -/// Schema housing the encrypted-domain families. -pub(crate) const DOMAIN_SCHEMA: &str = "eql_v3"; -/// Schema owning the core index-term types/constructors. -pub(crate) const CORE_SCHEMA: &str = "eql_v3"; +/// The single schema housing the self-contained `eql_v3` surface: the +/// encrypted-domain families AND the SEM index-term types/constructors they +/// call. v3 has zero dependency on `eql_v2`, so domains and core index-term +/// types share one schema by construction — there is no second schema to point +/// the core types at. +pub(crate) const SCHEMA: &str = "eql_v3"; /// Always-present payload keys checked for presence in every domain CHECK, in /// order: envelope version (`v`), ident (`i`), ciphertext (`c`). Term-specific diff --git a/crates/eql-codegen/src/context.rs b/crates/eql-codegen/src/context.rs index 225c6cda..78b50883 100644 --- a/crates/eql-codegen/src/context.rs +++ b/crates/eql-codegen/src/context.rs @@ -49,8 +49,7 @@ pub fn environment() -> minijinja::Environment<'static> { include_str!("../templates/aggregates.sql.j2"), ) .expect("aggregates.sql template"); - env.add_global("domain_schema", DOMAIN_SCHEMA); - env.add_global("core_schema", CORE_SCHEMA); + env.add_global("schema", SCHEMA); env } @@ -104,7 +103,7 @@ pub enum FnEntry { Extractor { ret: String, // e.g. eql_v2.hmac_256 (selection STAYS in Rust) extractor: String, // e.g. eq_term - ctor: String, // e.g. hmac_256 (called as {{ core_schema }}.{{ ctor }}) + ctor: String, // e.g. hmac_256 (called as {{ schema }}.{{ ctor }}) }, Wrapper { op: String, // SQL operator used in the body, e.g. = @@ -134,12 +133,12 @@ pub struct FunctionsContext { /// Build the inlinable index-extractor entry for a domain term. /// /// The `RETURNS` type name equals the constructor name (`hmac_256`, -/// `ore_block_u64_8_256`); qualify it with `CORE_SCHEMA` here so flipping the -/// core schema moves BOTH the body's constructor call and the declared return -/// type together (design D12). `Term::returns()` is intentionally not used. +/// `ore_block_u64_8_256`); qualify it with `SCHEMA` — the same schema as the +/// body's constructor call — so the declared return type and the call stay in +/// lockstep. `Term::returns()` is intentionally not used. pub fn extractor_entry(term: Term) -> FnEntry { FnEntry::Extractor { - ret: format!("{CORE_SCHEMA}.{}", term.ctor()), + ret: format!("{SCHEMA}.{}", term.ctor()), extractor: term.extractor().to_string(), ctor: term.ctor().to_string(), } @@ -229,7 +228,7 @@ pub struct AggregatesContext { /// The schema-qualified SQL domain type name, e.g. `eql_v3.int4_eq`. /// Port of `domain_name`. pub fn domain_name(name: &str) -> String { - format!("{DOMAIN_SCHEMA}.{name}") + format!("{SCHEMA}.{name}") } /// The full domain name from a token + suffix (suffix "" => bare token). @@ -241,9 +240,9 @@ pub fn full_domain_name(token: &str, suffix: &str) -> String { /// Port of `_extract_arg`. `dom` is the schema-qualified domain name. pub fn extract_arg(arg_type: &str, extractor: &str, dom: &str, arg: &str) -> String { if arg_type == "jsonb" { - format!("{DOMAIN_SCHEMA}.{extractor}({arg}::{dom})") + format!("{SCHEMA}.{extractor}({arg}::{dom})") } else { - format!("{DOMAIN_SCHEMA}.{extractor}({arg})") + format!("{SCHEMA}.{extractor}({arg})") } } diff --git a/crates/eql-codegen/templates/aggregates.sql.j2 b/crates/eql-codegen/templates/aggregates.sql.j2 index 9660917d..d85ab283 100644 --- a/crates/eql-codegen/templates/aggregates.sql.j2 +++ b/crates/eql-codegen/templates/aggregates.sql.j2 @@ -9,7 +9,7 @@ --! @param state {{ dom }} --! @param value {{ dom }} --! @return {{ dom }} -CREATE FUNCTION {{ domain_schema }}.{{ a.name }}_sfunc(state {{ dom }}, value {{ dom }}) +CREATE FUNCTION {{ schema }}.{{ a.name }}_sfunc(state {{ dom }}, value {{ dom }}) RETURNS {{ dom }} LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE SET search_path = pg_catalog, extensions, public @@ -25,10 +25,10 @@ $$; --! @brief {{ a.name }} aggregate for {{ dom }}. --! @param input {{ dom }} --! @return {{ dom }} -CREATE AGGREGATE {{ domain_schema }}.{{ a.name }}({{ dom }}) ( - sfunc = {{ domain_schema }}.{{ a.name }}_sfunc, +CREATE AGGREGATE {{ schema }}.{{ a.name }}({{ dom }}) ( + sfunc = {{ schema }}.{{ a.name }}_sfunc, stype = {{ dom }}, - combinefunc = {{ domain_schema }}.{{ a.name }}_sfunc, + combinefunc = {{ schema }}.{{ a.name }}_sfunc, parallel = safe ); {% endfor -%} diff --git a/crates/eql-codegen/templates/functions/extractor.sql.j2 b/crates/eql-codegen/templates/functions/extractor.sql.j2 index da649b21..9044f890 100644 --- a/crates/eql-codegen/templates/functions/extractor.sql.j2 +++ b/crates/eql-codegen/templates/functions/extractor.sql.j2 @@ -1,7 +1,7 @@ --! @brief Index extractor for {{ dom }}. --! @param a {{ dom }} --! @return {{ e.ret }} -CREATE FUNCTION {{ domain_schema }}.{{ e.extractor }}(a {{ dom }}) +CREATE FUNCTION {{ schema }}.{{ e.extractor }}(a {{ dom }}) RETURNS {{ e.ret }} LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE -AS $$ SELECT {{ core_schema }}.{{ e.ctor }}(a::jsonb) $$; +AS $$ SELECT {{ schema }}.{{ e.ctor }}(a::jsonb) $$; diff --git a/crates/eql-codegen/templates/functions/unsupported.sql.j2 b/crates/eql-codegen/templates/functions/unsupported.sql.j2 index f33dab6c..5ec85aed 100644 --- a/crates/eql-codegen/templates/functions/unsupported.sql.j2 +++ b/crates/eql-codegen/templates/functions/unsupported.sql.j2 @@ -2,7 +2,7 @@ --! @param {{ e.args[0].name }} {{ e.args[0].ty }} --! @param {{ e.args[1].name }} {{ e.args[1].ty }} --! @return {{ e.returns }} -CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) +CREATE FUNCTION {{ schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) RETURNS {{ e.returns }} IMMUTABLE PARALLEL SAFE AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '{{ e.operator_lit }}', '{{ domain_lit }}'; END; $$ LANGUAGE plpgsql; diff --git a/crates/eql-codegen/templates/functions/wrapper.sql.j2 b/crates/eql-codegen/templates/functions/wrapper.sql.j2 index 243f2465..1e0b43ac 100644 --- a/crates/eql-codegen/templates/functions/wrapper.sql.j2 +++ b/crates/eql-codegen/templates/functions/wrapper.sql.j2 @@ -2,6 +2,6 @@ --! @param {{ e.args[0].name }} {{ e.args[0].ty }} --! @param {{ e.args[1].name }} {{ e.args[1].ty }} --! @return boolean -CREATE FUNCTION {{ domain_schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) +CREATE FUNCTION {{ schema }}.{{ e.function_name }}({{ e.args[0].name }} {{ e.args[0].ty }}, {{ e.args[1].name }} {{ e.args[1].ty }}) RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT {{ e.call_a }} {{ e.op }} {{ e.call_b }} $$; diff --git a/crates/eql-codegen/templates/operators.sql.j2 b/crates/eql-codegen/templates/operators.sql.j2 index 1dc1360c..bb33ff52 100644 --- a/crates/eql-codegen/templates/operators.sql.j2 +++ b/crates/eql-codegen/templates/operators.sql.j2 @@ -6,7 +6,7 @@ --! @brief Operators for {{ dom }}. {% for o in operators %} CREATE OPERATOR {{ o.symbol }} ( - FUNCTION = {{ domain_schema }}.{{ o.function_name }}, + FUNCTION = {{ schema }}.{{ o.function_name }}, LEFTARG = {{ o.leftarg }}, RIGHTARG = {{ o.rightarg }}{% if o.metadata %}, {{ o.metadata }}{% endif %} ); diff --git a/crates/eql-codegen/templates/types.sql.j2 b/crates/eql-codegen/templates/types.sql.j2 index b8263e24..f46f6616 100644 --- a/crates/eql-codegen/templates/types.sql.j2 +++ b/crates/eql-codegen/templates/types.sql.j2 @@ -7,12 +7,12 @@ DO $$ BEGIN {%- for d in domains %} - --! @brief Encrypted domain {{ domain_schema }}.{{ d.name }}. + --! @brief Encrypted domain {{ schema }}.{{ d.name }}. IF NOT EXISTS ( SELECT 1 FROM pg_type - WHERE typname = '{{ d.typname }}' AND typnamespace = '{{ domain_schema }}'::regnamespace + WHERE typname = '{{ d.typname }}' AND typnamespace = '{{ schema }}'::regnamespace ) THEN - CREATE DOMAIN {{ domain_schema }}.{{ d.name }} AS jsonb + CREATE DOMAIN {{ schema }}.{{ d.name }} AS jsonb CHECK ( jsonb_typeof(VALUE) = 'object' {%- for k in d.keys %} From ba37472c4e28b96e7411b0187639a289e0842aef Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 17:10:13 +1000 Subject: [PATCH 82/93] fix(v3): address code-review findings on stale docs/comments + test hardening - Cargo.toml: update stale fixture command to fixture:generate:all - eql-functions.md: index examples use distinct _eq/_ord columns (a column carries a single domain, so eq_term/ord_term apply to different columns) - scalars/mod.rs: header describes the scalar_types!(matrix_suites) layout - codegen-parity.sh: ls *.sql -> portable find (no set -e abort on empty dir) - inlinability.rs: narrow arity-1 hmac_256 match to the jsonb overload --- docs/reference/eql-functions.md | 8 +++++--- tasks/codegen-parity.sh | 7 +++++-- tests/sqlx/Cargo.toml | 4 ++-- tests/sqlx/tests/encrypted_domain/family/inlinability.rs | 3 ++- tests/sqlx/tests/encrypted_domain/scalars/mod.rs | 5 +++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/reference/eql-functions.md b/docs/reference/eql-functions.md index cfca800c..20c6f958 100644 --- a/docs/reference/eql-functions.md +++ b/docs/reference/eql-functions.md @@ -441,9 +441,11 @@ eql_v3.ord_term(a eql_v3.int4_ord_ore) RETURNS eql_v2.ore_block_u64_8_256 **Example:** ```sql --- Functional indexes on the extracted terms (see Database Indexes) -CREATE INDEX ON users USING hash (eql_v3.eq_term(salary_encrypted)); -CREATE INDEX ON users USING btree (eql_v3.ord_term(salary_encrypted)); +-- Functional indexes on the extracted terms (see Database Indexes). +-- A column carries a single domain type, so `eq_term` and `ord_term` +-- apply to different columns (an `_eq` column vs an `_ord`/`_ord_ore` one). +CREATE INDEX ON users USING hash (eql_v3.eq_term(salary_eq)); +CREATE INDEX ON users USING btree (eql_v3.ord_term(salary_ord)); ``` > The full per-domain operator/wrapper/blocker surface (and the diff --git a/tasks/codegen-parity.sh b/tasks/codegen-parity.sh index 13e8bead..0445802f 100755 --- a/tasks/codegen-parity.sh +++ b/tasks/codegen-parity.sh @@ -16,9 +16,12 @@ echo "==> Comparing int4 generated SQL file SET vs golden (catches extra/dropped # is never iterated. Assert the sets are equal first to close that blind spot. # "Generated" excludes any committed, hand-written SQL (e.g. int4_extensions.sql), # which lives in this dir but has no golden counterpart; git-tracked == hand-written. -golden_set=$(cd tests/codegen/reference/int4 && ls *.sql | LC_ALL=C sort) +# find (not `ls *.sql`) so an empty dir yields zero lines instead of aborting +# under `set -e`; `-maxdepth 1` + sed strips the leading `./` for bare names. +golden_set=$(cd tests/codegen/reference/int4 \ + && find . -maxdepth 1 -name '*.sql' | sed 's#.*/##' | LC_ALL=C sort) gen_set=$(cd src/v3/scalars/int4 \ - && comm -23 <(ls *.sql | LC_ALL=C sort) \ + && comm -23 <(find . -maxdepth 1 -name '*.sql' | sed 's#.*/##' | LC_ALL=C sort) \ <(git ls-files . | sed 's#.*/##' | LC_ALL=C sort)) if [ "$golden_set" != "$gen_set" ]; then echo "int4 generated SQL file set differs from golden (< golden, > generated):" >&2 diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index 11217726..208f6d86 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -39,6 +39,6 @@ scale = [] # in the process env, BOTH a ZeroKMS auth credential (`CS_CLIENT_ACCESS_KEY` # + `CS_WORKSPACE_CRN`, via AutoStrategy) AND a client key (`CS_CLIENT_ID` + # `CS_CLIENT_KEY`, via EnvKeyProvider) — the two pairs are not alternatives. -# Run one with: -# mise run fixture:generate +# Regenerate all fixtures with: +# mise run fixture:generate:all fixture-gen = [] diff --git a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs index 7e17e265..1a250ef9 100644 --- a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs +++ b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs @@ -106,7 +106,8 @@ async fn eql_v3_sem_inline_critical_functions_are_unpinned(pool: PgPool) -> Resu 'ore_block_u64_8_256_eq','ore_block_u64_8_256_neq', 'ore_block_u64_8_256_lt','ore_block_u64_8_256_lte', 'ore_block_u64_8_256_gt','ore_block_u64_8_256_gte')) - OR (p.pronargs = 1 AND p.proname = 'hmac_256') + OR (p.pronargs = 1 AND p.proname = 'hmac_256' + AND p.proargtypes[0] = 'jsonb'::regtype) ) AND ( -- offender: pinned search_path, or not inlinable SQL/IMMUTABLE diff --git a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs index f900af3e..72c64f87 100644 --- a/tests/sqlx/tests/encrypted_domain/scalars/mod.rs +++ b/tests/sqlx/tests/encrypted_domain/scalars/mod.rs @@ -1,5 +1,6 @@ -//! Per-scalar matrix suites. Each `pub mod ` targets one scalar type and -//! holds its `ordered_numeric_matrix!` invocation. +//! Per-scalar matrix suites, generated by the `scalar_types!(matrix_suites)` +//! invocation below — one module per scalar type, each holding its +//! `ordered_numeric_matrix!` suite. //! //! The modules are generated from the single harness list in //! `tests/sqlx/src/scalar_types.rs` — adding a type there adds its suite here From fb98a09d3fcb9ac8b18e1905d3eaf97242147956 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 20:29:25 +1000 Subject: [PATCH 83/93] docs(v3): add missing @param/@return tags to ore_block_u64_8_256 operators The six comparison operator backing functions (_eq, _neq, _lt, _lte, _gt, _gte) were missing required @param and @return Doxygen tags, failing docs:validate:required-tags with 6 errors and 6 warnings. Tags follow the eql_v2 sibling src/ore_block_u64_8_256/operators.sql convention. --- src/v3/sem/ore_block_u64_8_256/operators.sql | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/v3/sem/ore_block_u64_8_256/operators.sql b/src/v3/sem/ore_block_u64_8_256/operators.sql index 78364a19..beca9da8 100644 --- a/src/v3/sem/ore_block_u64_8_256/operators.sql +++ b/src/v3/sem/ore_block_u64_8_256/operators.sql @@ -10,6 +10,12 @@ --! @brief Equality backing function for ORE block types --! @internal +--! +--! @param a eql_v3.ore_block_u64_8_256 Left operand +--! @param b eql_v3.ore_block_u64_8_256 Right operand +--! @return boolean True if the ORE blocks are equal +--! +--! @see eql_v3.compare_ore_block_u64_8_256_terms CREATE FUNCTION eql_v3.ore_block_u64_8_256_eq(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) RETURNS boolean LANGUAGE sql @@ -20,6 +26,12 @@ $$; --! @brief Not-equal backing function for ORE block types --! @internal +--! +--! @param a eql_v3.ore_block_u64_8_256 Left operand +--! @param b eql_v3.ore_block_u64_8_256 Right operand +--! @return boolean True if the ORE blocks are not equal +--! +--! @see eql_v3.compare_ore_block_u64_8_256_terms CREATE FUNCTION eql_v3.ore_block_u64_8_256_neq(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) RETURNS boolean LANGUAGE sql @@ -30,6 +42,12 @@ $$; --! @brief Less-than backing function for ORE block types --! @internal +--! +--! @param a eql_v3.ore_block_u64_8_256 Left operand +--! @param b eql_v3.ore_block_u64_8_256 Right operand +--! @return boolean True if the left operand is less than the right operand +--! +--! @see eql_v3.compare_ore_block_u64_8_256_terms CREATE FUNCTION eql_v3.ore_block_u64_8_256_lt(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) RETURNS boolean LANGUAGE sql @@ -40,6 +58,12 @@ $$; --! @brief Less-than-or-equal backing function for ORE block types --! @internal +--! +--! @param a eql_v3.ore_block_u64_8_256 Left operand +--! @param b eql_v3.ore_block_u64_8_256 Right operand +--! @return boolean True if the left operand is less than or equal to the right operand +--! +--! @see eql_v3.compare_ore_block_u64_8_256_terms CREATE FUNCTION eql_v3.ore_block_u64_8_256_lte(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) RETURNS boolean LANGUAGE sql @@ -50,6 +74,12 @@ $$; --! @brief Greater-than backing function for ORE block types --! @internal +--! +--! @param a eql_v3.ore_block_u64_8_256 Left operand +--! @param b eql_v3.ore_block_u64_8_256 Right operand +--! @return boolean True if the left operand is greater than the right operand +--! +--! @see eql_v3.compare_ore_block_u64_8_256_terms CREATE FUNCTION eql_v3.ore_block_u64_8_256_gt(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) RETURNS boolean LANGUAGE sql @@ -60,6 +90,12 @@ $$; --! @brief Greater-than-or-equal backing function for ORE block types --! @internal +--! +--! @param a eql_v3.ore_block_u64_8_256 Left operand +--! @param b eql_v3.ore_block_u64_8_256 Right operand +--! @return boolean True if the left operand is greater than or equal to the right operand +--! +--! @see eql_v3.compare_ore_block_u64_8_256_terms CREATE FUNCTION eql_v3.ore_block_u64_8_256_gte(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256) RETURNS boolean LANGUAGE sql From eafaa17599b1fa4624c51b4b29a08a76d20bdbbc Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 20:50:13 +1000 Subject: [PATCH 84/93] style(test): rustfmt encrypted_domain/family/sem.rs cargo fmt --check was failing CI (test:lint and test:crates jobs) on this file; apply rustfmt with no logic changes. --- .../sqlx/tests/encrypted_domain/family/sem.rs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/sqlx/tests/encrypted_domain/family/sem.rs b/tests/sqlx/tests/encrypted_domain/family/sem.rs index 4f30bac2..3d12bdd8 100644 --- a/tests/sqlx/tests/encrypted_domain/family/sem.rs +++ b/tests/sqlx/tests/encrypted_domain/family/sem.rs @@ -67,11 +67,7 @@ async fn ore_v2_v3_comparator_parity_on_real_fixtures(pool: PgPool) -> Result<() let mut v3_signs: HashSet = HashSet::new(); for (x, y) in pairs { - let (v2, v3): (i32, i32) = sqlx::query_as(sql) - .bind(x) - .bind(y) - .fetch_one(&pool) - .await?; + let (v2, v3): (i32, i32) = sqlx::query_as(sql).bind(x).bind(y).fetch_one(&pool).await?; assert_eq!( v2, v3, "eql_v2 and eql_v3 ORE comparators disagree on ids ({x},{y}): v2={v2} v3={v3}" @@ -81,7 +77,10 @@ async fn ore_v2_v3_comparator_parity_on_real_fixtures(pool: PgPool) -> Result<() // Non-triviality: the sample must have actually exercised lt, eq, and gt — // otherwise the parity check could pass on a degenerate all-equal path. - assert!(v3_signs.contains(&0), "sample must include an equal pair (0)"); + assert!( + v3_signs.contains(&0), + "sample must include an equal pair (0)" + ); assert!( v3_signs.contains(&-1), "sample must include a less-than pair (-1)" @@ -183,10 +182,19 @@ async fn ore_terms_array_null_and_empty_base_cases(pool: PgPool) -> Result<()> { #[sqlx::test] async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<()> { let bool_cases = [ - (r#"SELECT eql_v3.has_ore_block_u64_8_256('{"ob":["aa"]}'::jsonb)"#, true), - (r#"SELECT eql_v3.has_ore_block_u64_8_256('{}'::jsonb)"#, false), + ( + r#"SELECT eql_v3.has_ore_block_u64_8_256('{"ob":["aa"]}'::jsonb)"#, + true, + ), + ( + r#"SELECT eql_v3.has_ore_block_u64_8_256('{}'::jsonb)"#, + false, + ), // json-null `ob` → `->>` yields NULL → absent. - (r#"SELECT eql_v3.has_ore_block_u64_8_256('{"ob":null}'::jsonb)"#, false), + ( + r#"SELECT eql_v3.has_ore_block_u64_8_256('{"ob":null}'::jsonb)"#, + false, + ), (r#"SELECT eql_v3.has_hmac_256('{"hm":"abc"}'::jsonb)"#, true), (r#"SELECT eql_v3.has_hmac_256('{}'::jsonb)"#, false), ]; @@ -209,6 +217,9 @@ async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<() sqlx::query_scalar("SELECT eql_v3.ore_block_u64_8_256(NULL::jsonb) IS NULL") .fetch_one(&pool) .await?; - assert!(is_null, "NULL jsonb must extract to a NULL composite, not raise"); + assert!( + is_null, + "NULL jsonb must extract to a NULL composite, not raise" + ); Ok(()) } From b265e43dddd10f6a7ba1e02d1039d1ce781781ce Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 20:57:29 +1000 Subject: [PATCH 85/93] refactor(tests): generalise scalar matrix harness off integer-inherent consts Make the SQLx scalar-matrix harness type-agnostic ahead of the first non-integer scalar, without adding one. Three integer assumptions are lifted: - `ScalarType::FIXTURE_VALUES` (a `const`) becomes `fn fixture_values()`, so a scalar whose values can't be const-constructed can return a borrow of a lazily-built `Vec` instead. Integer impls still hand back their `eql_scalars::_VALUES` const. - New `min_pivot()` / `max_pivot()` trait methods replace the matrix's direct `::MIN` / `::MAX` pivot references, so a scalar without an inherent `::MIN`/`::MAX` const can supply an explicit sentinel. - The ORDER BY arms build their `WHERE` clause from `to_sql_literal(zero)` instead of a hardcoded `> 0`, so a non-integer plaintext column typechecks. Behaviour-preserving for the existing `int4` / `int2` types: the integer `min_pivot`/`max_pivot` resolve to `Self::MIN`/`Self::MAX`, `to_sql_literal(0)` renders `0`, and the generated test names are unchanged. The int4 cargo-expand snapshot is regenerated to track the method-based bodies. --- crates/eql-tests-macros/src/lib.rs | 26 +- tests/sqlx/snapshots/int4_expanded.rs | 1860 +++++++++-------- tests/sqlx/src/matrix.rs | 78 +- tests/sqlx/src/scalar_domains.rs | 38 +- .../encrypted_domain/family/mutations.rs | 4 +- 5 files changed, 1080 insertions(+), 926 deletions(-) diff --git a/crates/eql-tests-macros/src/lib.rs b/crates/eql-tests-macros/src/lib.rs index 537a8312..408648ae 100644 --- a/crates/eql-tests-macros/src/lib.rs +++ b/crates/eql-tests-macros/src/lib.rs @@ -85,10 +85,26 @@ fn scalar_type_impls_tokens(list: &ScalarList) -> TokenStream2 { quote! { impl ScalarType for #rust_type { const PG_TYPE: &'static str = #token_str; + /// The catalog `eql_scalars::*_VALUES` list — the same values /// the fixture generator encrypts, so the oracle can't drift - /// from the fixture. - const FIXTURE_VALUES: &'static [#rust_type] = ::eql_scalars::#values; + /// from the fixture. A method (not a `const`) so non-integer + /// scalars whose values can't be `const`-constructed can return + /// a borrow of a lazily-built `Vec`; integer scalars hand back + /// their catalog const directly. + fn fixture_values() -> &'static [#rust_type] { + ::eql_scalars::#values + } + + /// Integer scalars pivot on their inherent `MIN`/`MAX` consts; + /// the fixture lists include both (`fixtures!(int …; Min, …, Max)`). + fn min_pivot() -> #rust_type { + <#rust_type>::MIN + } + + fn max_pivot() -> #rust_type { + <#rust_type>::MAX + } } } }); @@ -249,6 +265,12 @@ mod tests { assert!(out.contains(r#"const PG_TYPE : & 'static str = "int8""#)); assert!(out.contains(":: eql_scalars :: INT4_VALUES")); assert!(out.contains(":: eql_scalars :: INT8_VALUES")); + // const→fn: fixture values is a method now, plus the integer pivots. + assert!(out.contains("fn fixture_values")); + assert!(out.contains("fn min_pivot")); + assert!(out.contains("fn max_pivot")); + assert!(out.contains("MIN")); + assert!(out.contains("MAX")); } #[test] diff --git a/tests/sqlx/snapshots/int4_expanded.rs b/tests/sqlx/snapshots/int4_expanded.rs index 3441b01b..f2997d2a 100644 --- a/tests/sqlx/snapshots/int4_expanded.rs +++ b/tests/sqlx/snapshots/int4_expanded.rs @@ -1,11 +1,5 @@ +///`eql_v2_int4` matrix suite — generated by `scalar_types!`. pub mod int4 { - //! `eql_v2_int4` — the reference scalar implementation. - //! - //! Adding a new ordered numeric scalar (i64, f64, date, ...) is one - //! `impl ScalarType` in `tests/sqlx/src/scalar_domains.rs` plus an - //! `ordered_numeric_matrix!` invocation like this one. The matrix covers - //! everything generic over `T: ScalarType`. - use eql_tests::ordered_numeric_matrix; extern crate test; #[rustc_test_marker = "scalars::int4::matrix_int4_storage_sanity"] #[doc(hidden)] @@ -14,10 +8,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_storage_sanity"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 451usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 454usize, start_col: 26usize, - end_line: 451usize, + end_line: 454usize, end_col: 60usize, compile_fail: false, no_run: false, @@ -91,10 +85,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_sanity"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 451usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 454usize, start_col: 26usize, - end_line: 451usize, + end_line: 454usize, end_col: 60usize, compile_fail: false, no_run: false, @@ -168,10 +162,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_ord_sanity"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 451usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 454usize, start_col: 26usize, - end_line: 451usize, + end_line: 454usize, end_col: 60usize, compile_fail: false, no_run: false, @@ -245,10 +239,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_ord_ore_sanity"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 451usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 454usize, start_col: 26usize, - end_line: 451usize, + end_line: 454usize, end_col: 60usize, compile_fail: false, no_run: false, @@ -324,10 +318,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -347,7 +341,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -423,10 +417,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -446,7 +440,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -522,10 +516,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -621,10 +615,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -644,7 +638,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -720,10 +714,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -743,7 +737,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -819,10 +813,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -918,10 +912,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -941,7 +935,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -1017,10 +1011,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1040,7 +1034,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -1116,10 +1110,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1215,10 +1209,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1238,7 +1232,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -1314,10 +1308,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1337,7 +1331,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -1413,10 +1407,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1512,10 +1506,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1535,7 +1529,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -1611,10 +1605,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1634,7 +1628,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -1710,10 +1704,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1809,10 +1803,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1832,7 +1826,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -1908,10 +1902,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -1931,7 +1925,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -2007,10 +2001,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2106,10 +2100,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2129,7 +2123,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -2205,10 +2199,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2228,7 +2222,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -2304,10 +2298,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2403,10 +2397,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2426,7 +2420,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -2502,10 +2496,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2525,7 +2519,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -2601,10 +2595,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2700,10 +2694,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2723,7 +2717,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -2799,10 +2793,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2822,7 +2816,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -2898,10 +2892,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -2997,10 +2991,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3020,7 +3014,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -3096,10 +3090,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3119,7 +3113,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -3195,10 +3189,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3294,10 +3288,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3317,7 +3311,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -3393,10 +3387,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3416,7 +3410,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -3492,10 +3486,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3591,10 +3585,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3614,7 +3608,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -3690,10 +3684,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3713,7 +3707,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -3789,10 +3783,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3888,10 +3882,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -3911,7 +3905,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -3987,10 +3981,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4010,7 +4004,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -4086,10 +4080,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4185,10 +4179,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4208,7 +4202,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -4284,10 +4278,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4307,7 +4301,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -4383,10 +4377,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 587usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 590usize, start_col: 22usize, - end_line: 587usize, + end_line: 590usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4482,10 +4476,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4505,7 +4499,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -4648,10 +4642,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4671,7 +4665,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -4814,10 +4808,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -4980,10 +4974,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -5003,7 +4997,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -5146,10 +5140,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -5169,7 +5163,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Eq); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -5312,10 +5306,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -5478,10 +5472,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -5501,7 +5495,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -5644,10 +5638,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -5667,7 +5661,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -5810,10 +5804,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -5976,10 +5970,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -5999,7 +5993,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -6142,10 +6136,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -6165,7 +6159,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -6308,10 +6302,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -6474,10 +6468,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -6497,7 +6491,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -6640,10 +6634,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -6663,7 +6657,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -6806,10 +6800,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -6972,10 +6966,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -6995,7 +6989,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -7138,10 +7132,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -7161,7 +7155,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -7304,10 +7298,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -7470,10 +7464,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -7493,7 +7487,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -7636,10 +7630,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -7659,7 +7653,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -7802,10 +7796,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -7968,10 +7962,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -7991,7 +7985,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -8134,10 +8128,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -8157,7 +8151,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -8300,10 +8294,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -8466,10 +8460,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -8489,7 +8483,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -8632,10 +8626,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -8655,7 +8649,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -8798,10 +8792,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -8964,10 +8958,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -8987,7 +8981,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -9130,10 +9124,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -9153,7 +9147,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -9296,10 +9290,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -9462,10 +9456,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -9485,7 +9479,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -9628,10 +9622,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -9651,7 +9645,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -9794,10 +9788,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -9960,10 +9954,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -9983,7 +9977,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -10126,10 +10120,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -10149,7 +10143,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -10292,10 +10286,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -10458,10 +10452,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -10481,7 +10475,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -10624,10 +10618,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -10647,7 +10641,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -10790,10 +10784,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -10956,10 +10950,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -10979,7 +10973,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MIN; + let pivot: i32 = ::min_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -11122,10 +11116,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -11145,7 +11139,7 @@ pub mod int4 { let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let pivot: i32 = ::MAX; + let pivot: i32 = ::max_pivot(); let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -11288,10 +11282,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 628usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 631usize, start_col: 22usize, - end_line: 628usize, + end_line: 631usize, end_col: 96usize, compile_fail: false, no_run: false, @@ -11454,10 +11448,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -11547,10 +11541,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -11640,10 +11634,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -11733,10 +11727,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -11826,10 +11820,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -11919,10 +11913,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12012,10 +12006,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12105,10 +12099,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12198,10 +12192,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12291,10 +12285,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12384,10 +12378,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12477,10 +12471,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12570,10 +12564,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12663,10 +12657,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 680usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 683usize, start_col: 22usize, - end_line: 680usize, + end_line: 683usize, end_col: 79usize, compile_fail: false, no_run: false, @@ -12754,10 +12748,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_storage_eq_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -12889,10 +12883,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_storage_neq_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13024,10 +13018,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_storage_lt_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13159,10 +13153,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_storage_lte_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13294,10 +13288,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_storage_gt_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13429,10 +13423,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_storage_gte_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13566,10 +13560,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13703,10 +13697,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13838,10 +13832,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_lt_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -13971,10 +13965,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_lte_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -14104,10 +14098,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_gt_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -14237,10 +14231,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_gte_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -14370,10 +14364,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_contains_blocker"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -14507,10 +14501,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -14644,10 +14638,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -14781,10 +14775,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -14918,10 +14912,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -15055,10 +15049,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 744usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 747usize, start_col: 22usize, - end_line: 744usize, + end_line: 747usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -15192,10 +15186,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 811usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 814usize, start_col: 22usize, - end_line: 811usize, + end_line: 814usize, end_col: 67usize, compile_fail: false, no_run: false, @@ -15332,10 +15326,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_payload_check"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 811usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 814usize, start_col: 22usize, - end_line: 811usize, + end_line: 814usize, end_col: 67usize, compile_fail: false, no_run: false, @@ -15470,10 +15464,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_ord_payload_check"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 811usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 814usize, start_col: 22usize, - end_line: 811usize, + end_line: 814usize, end_col: 67usize, compile_fail: false, no_run: false, @@ -15612,10 +15606,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 811usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 814usize, start_col: 22usize, - end_line: 811usize, + end_line: 814usize, end_col: 67usize, compile_fail: false, no_run: false, @@ -15754,10 +15748,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 884usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 887usize, start_col: 22usize, - end_line: 884usize, + end_line: 887usize, end_col: 70usize, compile_fail: false, no_run: false, @@ -15855,10 +15849,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_path_op_blockers"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 884usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 887usize, start_col: 22usize, - end_line: 884usize, + end_line: 887usize, end_col: 70usize, compile_fail: false, no_run: false, @@ -15958,10 +15952,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 884usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 887usize, start_col: 22usize, - end_line: 884usize, + end_line: 887usize, end_col: 70usize, compile_fail: false, no_run: false, @@ -16061,10 +16055,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 884usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 887usize, start_col: 22usize, - end_line: 884usize, + end_line: 887usize, end_col: 70usize, compile_fail: false, no_run: false, @@ -16164,10 +16158,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 941usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 944usize, start_col: 22usize, - end_line: 941usize, + end_line: 944usize, end_col: 71usize, compile_fail: false, no_run: false, @@ -16253,10 +16247,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 941usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 944usize, start_col: 22usize, - end_line: 941usize, + end_line: 944usize, end_col: 71usize, compile_fail: false, no_run: false, @@ -16342,10 +16336,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 941usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 944usize, start_col: 22usize, - end_line: 941usize, + end_line: 944usize, end_col: 71usize, compile_fail: false, no_run: false, @@ -16431,10 +16425,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 941usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 944usize, start_col: 22usize, - end_line: 941usize, + end_line: 944usize, end_col: 71usize, compile_fail: false, no_run: false, @@ -16520,10 +16514,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 994usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 997usize, start_col: 22usize, - end_line: 994usize, + end_line: 997usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -16891,10 +16885,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 994usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 997usize, start_col: 22usize, - end_line: 994usize, + end_line: 997usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -17192,10 +17186,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 994usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 997usize, start_col: 22usize, - end_line: 994usize, + end_line: 997usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -17353,10 +17347,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 994usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 997usize, start_col: 22usize, - end_line: 994usize, + end_line: 997usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -17514,10 +17508,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1075usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1078usize, start_col: 22usize, - end_line: 1075usize, + end_line: 1078usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -17549,7 +17543,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (\'{1}\'::regtype = o.oprleft OR \'{1}\'::regtype = o.oprright)\n ", op_list, d, ), @@ -17678,10 +17672,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1075usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1078usize, start_col: 22usize, - end_line: 1075usize, + end_line: 1078usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -17713,7 +17707,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (\'{1}\'::regtype = o.oprleft OR \'{1}\'::regtype = o.oprright)\n ", op_list, d, ), @@ -17842,10 +17836,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1075usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1078usize, start_col: 22usize, - end_line: 1075usize, + end_line: 1078usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -17877,7 +17871,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (\'{1}\'::regtype = o.oprleft OR \'{1}\'::regtype = o.oprright)\n ", op_list, d, ), @@ -18006,10 +18000,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1075usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1078usize, start_col: 22usize, - end_line: 1075usize, + end_line: 1078usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -18041,7 +18035,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (\'{1}\'::regtype = o.oprleft OR \'{1}\'::regtype = o.oprright)\n ", op_list, d, ), @@ -18170,10 +18164,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1075usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1078usize, start_col: 22usize, - end_line: 1075usize, + end_line: 1078usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -18205,7 +18199,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (lt.typname = \'{1}\' OR rt.typname = \'{1}\')\n ", + "\n SELECT o.oprname,\n lt.typname AS lhs,\n rt.typname AS rhs,\n o.oprcom <> 0 AS has_commutator,\n o.oprnegate <> 0 AS has_negator,\n o.oprrest::oid <> 0 AS has_restrict,\n o.oprjoin::oid <> 0 AS has_join\n FROM pg_catalog.pg_operator o\n JOIN pg_catalog.pg_type lt ON lt.oid = o.oprleft\n JOIN pg_catalog.pg_type rt ON rt.oid = o.oprright\n WHERE o.oprname IN ({0})\n AND (\'{1}\'::regtype = o.oprleft OR \'{1}\'::regtype = o.oprright)\n ", op_list, d, ), @@ -18334,10 +18328,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1641usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1644usize, start_col: 22usize, - end_line: 1641usize, + end_line: 1644usize, end_col: 75usize, compile_fail: false, no_run: false, @@ -18395,7 +18389,7 @@ pub mod int4 { format_args!( "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", "btree", - "eql_v2.eq_term", + "eql_v3.eq_term", index, table, ), @@ -18412,7 +18406,7 @@ pub mod int4 { .execute(&mut *tx) .await?; sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; - let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot: i32 = ::fixture_values()[0]; let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -18508,10 +18502,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1641usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1644usize, start_col: 22usize, - end_line: 1641usize, + end_line: 1644usize, end_col: 75usize, compile_fail: false, no_run: false, @@ -18569,7 +18563,7 @@ pub mod int4 { format_args!( "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", "hash", - "eql_v2.eq_term", + "eql_v3.eq_term", index, table, ), @@ -18586,7 +18580,7 @@ pub mod int4 { .execute(&mut *tx) .await?; sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; - let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot: i32 = ::fixture_values()[0]; let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -18682,10 +18676,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1641usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1644usize, start_col: 22usize, - end_line: 1641usize, + end_line: 1644usize, end_col: 75usize, compile_fail: false, no_run: false, @@ -18743,7 +18737,7 @@ pub mod int4 { format_args!( "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", "btree", - "eql_v2.ord_term", + "eql_v3.ord_term", index, table, ), @@ -18760,7 +18754,7 @@ pub mod int4 { .execute(&mut *tx) .await?; sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; - let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot: i32 = ::fixture_values()[0]; let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -18976,10 +18970,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1641usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1644usize, start_col: 22usize, - end_line: 1641usize, + end_line: 1644usize, end_col: 75usize, compile_fail: false, no_run: false, @@ -19037,7 +19031,7 @@ pub mod int4 { format_args!( "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", "btree", - "eql_v2.ord_term", + "eql_v3.ord_term", index, table, ), @@ -19054,7 +19048,7 @@ pub mod int4 { .execute(&mut *tx) .await?; sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; - let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot: i32 = ::fixture_values()[0]; let payload = ::eql_tests::scalar_domains::fetch_fixture_payload::< i32, >(&pool, pivot) @@ -19270,10 +19264,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1263usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1266usize, start_col: 22usize, - end_line: 1263usize, + end_line: 1266usize, end_col: 86usize, compile_fail: false, no_run: false, @@ -19297,7 +19291,7 @@ pub mod int4 { let d = &spec.sql_domain; let table = "matrix_int4_ord_scaledef_btree"; let index = "matrix_int4_ord_scaledef_btree_idx"; - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 2) { return ::anyhow::__private::Err({ let error = ::anyhow::__private::format_err( @@ -19366,7 +19360,7 @@ pub mod int4 { format_args!( "CREATE INDEX {2} ON {3} USING {0} ({1}(value))", "btree", - "eql_v2.ord_term", + "eql_v3.ord_term", index, table, ), @@ -19451,10 +19445,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_fixture_shape"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1341usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1344usize, start_col: 22usize, - end_line: 1341usize, + end_line: 1344usize, end_col: 55usize, compile_fail: false, no_run: false, @@ -19471,7 +19465,7 @@ pub mod int4 { { use ::eql_tests::scalar_domains::ScalarType; let table = ::fixture_table_name(); - let expected: &[i32] = ::FIXTURE_VALUES; + let expected: &[i32] = ::fixture_values(); let n = expected.len() as i64; let count: i64 = sqlx::query_scalar( &::alloc::__export::must_use({ @@ -19706,10 +19700,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1444usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1447usize, start_col: 22usize, - end_line: 1444usize, + end_line: 1447usize, end_col: 75usize, compile_fail: false, no_run: false, @@ -19733,7 +19727,7 @@ pub mod int4 { let table = "matrix_int4_ord_no_hm"; let index = "matrix_int4_ord_no_hm_idx"; let fixture_table = ::fixture_table_name(); - let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot: i32 = ::fixture_values()[0]; let pivot_lit = ::to_sql_literal( pivot, ); @@ -19790,7 +19784,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "CREATE INDEX {0} ON {1} USING btree (eql_v2.ord_term(value))", + "CREATE INDEX {0} ON {1} USING btree (eql_v3.ord_term(value))", index, table, ), @@ -19845,7 +19839,7 @@ pub mod int4 { error }); } - let expected_neq = ::FIXTURE_VALUES + let expected_neq = ::fixture_values() .len() as i64 - eq_count; let neq_count: i64 = sqlx::query_scalar( &::alloc::__export::must_use({ @@ -19887,7 +19881,7 @@ pub mod int4 { ) }), index, - "= must engage the eql_v2.ord_term functional btree with no hm", + "= must engage the eql_v3.ord_term functional btree with no hm", ) .await?; tx.commit().await?; @@ -19944,10 +19938,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1444usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1447usize, start_col: 22usize, - end_line: 1444usize, + end_line: 1447usize, end_col: 75usize, compile_fail: false, no_run: false, @@ -19971,7 +19965,7 @@ pub mod int4 { let table = "matrix_int4_ord_ore_no_hm"; let index = "matrix_int4_ord_ore_no_hm_idx"; let fixture_table = ::fixture_table_name(); - let pivot: i32 = ::FIXTURE_VALUES[0]; + let pivot: i32 = ::fixture_values()[0]; let pivot_lit = ::to_sql_literal( pivot, ); @@ -20028,7 +20022,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "CREATE INDEX {0} ON {1} USING btree (eql_v2.ord_term(value))", + "CREATE INDEX {0} ON {1} USING btree (eql_v3.ord_term(value))", index, table, ), @@ -20083,7 +20077,7 @@ pub mod int4 { error }); } - let expected_neq = ::FIXTURE_VALUES + let expected_neq = ::fixture_values() .len() as i64 - eq_count; let neq_count: i64 = sqlx::query_scalar( &::alloc::__export::must_use({ @@ -20125,7 +20119,7 @@ pub mod int4 { ) }), index, - "= must engage the eql_v2.ord_term functional btree with no hm", + "= must engage the eql_v3.ord_term functional btree with no hm", ) .await?; tx.commit().await?; @@ -20182,10 +20176,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1578usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1581usize, start_col: 22usize, - end_line: 1578usize, + end_line: 1581usize, end_col: 69usize, compile_fail: false, no_run: false, @@ -20282,10 +20276,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_ord_aggregate_min"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2087usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2099usize, start_col: 22usize, - end_line: 2087usize, + end_line: 2099usize, end_col: 73usize, compile_fail: false, no_run: false, @@ -20308,7 +20302,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let extremum: i32 = ::FIXTURE_VALUES + let extremum: i32 = ::fixture_values() .iter() .copied() .min() @@ -20331,7 +20325,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "SELECT eql_v3.{0}(payload::{1})::text FROM {2}", "min", d, fixture, @@ -20351,7 +20345,7 @@ pub mod int4 { &*right_val, ::core::option::Option::Some( format_args!( - "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "eql_v3.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", "min", d, extremum, @@ -20366,7 +20360,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "SELECT eql_v3.ord_term(eql_v3.{0}(payload::{1})) = eql_v3.ord_term($1::jsonb::{1}) FROM {2}", "min", d, fixture, @@ -20383,7 +20377,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "eql_v3.ord_term(eql_v3.{0}({1})) must equal eql_v3.ord_term() for plaintext={2:?}", "min", d, extremum, @@ -20446,10 +20440,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2145usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2157usize, start_col: 22usize, - end_line: 2145usize, + end_line: 2157usize, end_col: 80usize, compile_fail: false, no_run: false, @@ -20487,7 +20481,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "SELECT eql_v3.{0}(value)::text FROM empty_agg", "min", ), ) @@ -20501,7 +20495,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "empty rowset to eql_v3.{0} on {1} must return NULL, got {2:?}", "min", d, result, @@ -20558,10 +20552,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2170usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2182usize, start_col: 22usize, - end_line: 2170usize, + end_line: 2182usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -20585,7 +20579,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "SELECT eql_v3.{0}(NULL::{1})::text FROM generate_series(1, 3)", "min", d, ), @@ -20600,7 +20594,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "all-NULL input to eql_v3.{0} on {1} must return NULL, got {2:?}; SQL={3}", "min", d, result, @@ -20657,10 +20651,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2196usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2208usize, start_col: 22usize, - end_line: 2196usize, + end_line: 2208usize, end_col: 85usize, compile_fail: false, no_run: false, @@ -20683,7 +20677,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 2) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -20753,7 +20747,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "SELECT eql_v3.{0}(value)::text FROM mixed_null", "min", ), ) @@ -20769,7 +20763,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "eql_v3.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", "min", "min", expected_plaintext, @@ -20833,10 +20827,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_ord_aggregate_max"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2087usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2099usize, start_col: 22usize, - end_line: 2087usize, + end_line: 2099usize, end_col: 73usize, compile_fail: false, no_run: false, @@ -20859,7 +20853,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let extremum: i32 = ::FIXTURE_VALUES + let extremum: i32 = ::fixture_values() .iter() .copied() .max() @@ -20882,7 +20876,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "SELECT eql_v3.{0}(payload::{1})::text FROM {2}", "max", d, fixture, @@ -20902,7 +20896,7 @@ pub mod int4 { &*right_val, ::core::option::Option::Some( format_args!( - "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "eql_v3.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", "max", d, extremum, @@ -20917,7 +20911,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "SELECT eql_v3.ord_term(eql_v3.{0}(payload::{1})) = eql_v3.ord_term($1::jsonb::{1}) FROM {2}", "max", d, fixture, @@ -20934,7 +20928,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "eql_v3.ord_term(eql_v3.{0}({1})) must equal eql_v3.ord_term() for plaintext={2:?}", "max", d, extremum, @@ -20997,10 +20991,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2145usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2157usize, start_col: 22usize, - end_line: 2145usize, + end_line: 2157usize, end_col: 80usize, compile_fail: false, no_run: false, @@ -21038,7 +21032,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "SELECT eql_v3.{0}(value)::text FROM empty_agg", "max", ), ) @@ -21052,7 +21046,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "empty rowset to eql_v3.{0} on {1} must return NULL, got {2:?}", "max", d, result, @@ -21109,10 +21103,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2170usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2182usize, start_col: 22usize, - end_line: 2170usize, + end_line: 2182usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -21136,7 +21130,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "SELECT eql_v3.{0}(NULL::{1})::text FROM generate_series(1, 3)", "max", d, ), @@ -21151,7 +21145,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "all-NULL input to eql_v3.{0} on {1} must return NULL, got {2:?}; SQL={3}", "max", d, result, @@ -21208,10 +21202,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2196usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2208usize, start_col: 22usize, - end_line: 2196usize, + end_line: 2208usize, end_col: 85usize, compile_fail: false, no_run: false, @@ -21234,7 +21228,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 2) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -21304,7 +21298,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "SELECT eql_v3.{0}(value)::text FROM mixed_null", "max", ), ) @@ -21320,7 +21314,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "eql_v3.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", "max", "max", expected_plaintext, @@ -21386,10 +21380,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2087usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2099usize, start_col: 22usize, - end_line: 2087usize, + end_line: 2099usize, end_col: 73usize, compile_fail: false, no_run: false, @@ -21412,7 +21406,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let extremum: i32 = ::FIXTURE_VALUES + let extremum: i32 = ::fixture_values() .iter() .copied() .min() @@ -21435,7 +21429,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "SELECT eql_v3.{0}(payload::{1})::text FROM {2}", "min", d, fixture, @@ -21455,7 +21449,7 @@ pub mod int4 { &*right_val, ::core::option::Option::Some( format_args!( - "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "eql_v3.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", "min", d, extremum, @@ -21470,7 +21464,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "SELECT eql_v3.ord_term(eql_v3.{0}(payload::{1})) = eql_v3.ord_term($1::jsonb::{1}) FROM {2}", "min", d, fixture, @@ -21487,7 +21481,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "eql_v3.ord_term(eql_v3.{0}({1})) must equal eql_v3.ord_term() for plaintext={2:?}", "min", d, extremum, @@ -21550,10 +21544,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2145usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2157usize, start_col: 22usize, - end_line: 2145usize, + end_line: 2157usize, end_col: 80usize, compile_fail: false, no_run: false, @@ -21591,7 +21585,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "SELECT eql_v3.{0}(value)::text FROM empty_agg", "min", ), ) @@ -21605,7 +21599,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "empty rowset to eql_v3.{0} on {1} must return NULL, got {2:?}", "min", d, result, @@ -21662,10 +21656,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2170usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2182usize, start_col: 22usize, - end_line: 2170usize, + end_line: 2182usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -21689,7 +21683,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "SELECT eql_v3.{0}(NULL::{1})::text FROM generate_series(1, 3)", "min", d, ), @@ -21704,7 +21698,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "all-NULL input to eql_v3.{0} on {1} must return NULL, got {2:?}; SQL={3}", "min", d, result, @@ -21761,10 +21755,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2196usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2208usize, start_col: 22usize, - end_line: 2196usize, + end_line: 2208usize, end_col: 85usize, compile_fail: false, no_run: false, @@ -21787,7 +21781,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 2) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -21857,7 +21851,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "SELECT eql_v3.{0}(value)::text FROM mixed_null", "min", ), ) @@ -21873,7 +21867,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "eql_v3.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", "min", "min", expected_plaintext, @@ -21939,10 +21933,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2087usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2099usize, start_col: 22usize, - end_line: 2087usize, + end_line: 2099usize, end_col: 73usize, compile_fail: false, no_run: false, @@ -21965,7 +21959,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let extremum: i32 = ::FIXTURE_VALUES + let extremum: i32 = ::fixture_values() .iter() .copied() .max() @@ -21988,7 +21982,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(payload::{1})::text FROM {2}", + "SELECT eql_v3.{0}(payload::{1})::text FROM {2}", "max", d, fixture, @@ -22008,7 +22002,7 @@ pub mod int4 { &*right_val, ::core::option::Option::Some( format_args!( - "eql_v2.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", + "eql_v3.{0}({1}) must return the payload of plaintext={2:?} (the fixture {3})", "max", d, extremum, @@ -22023,7 +22017,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.ord_term(eql_v2.{0}(payload::{1})) = eql_v2.ord_term($1::jsonb::{1}) FROM {2}", + "SELECT eql_v3.ord_term(eql_v3.{0}(payload::{1})) = eql_v3.ord_term($1::jsonb::{1}) FROM {2}", "max", d, fixture, @@ -22040,7 +22034,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.ord_term(eql_v2.{0}({1})) must equal eql_v2.ord_term() for plaintext={2:?}", + "eql_v3.ord_term(eql_v3.{0}({1})) must equal eql_v3.ord_term() for plaintext={2:?}", "max", d, extremum, @@ -22103,10 +22097,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2145usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2157usize, start_col: 22usize, - end_line: 2145usize, + end_line: 2157usize, end_col: 80usize, compile_fail: false, no_run: false, @@ -22144,7 +22138,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM empty_agg", + "SELECT eql_v3.{0}(value)::text FROM empty_agg", "max", ), ) @@ -22158,7 +22152,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "empty rowset to eql_v2.{0} on {1} must return NULL, got {2:?}", + "empty rowset to eql_v3.{0} on {1} must return NULL, got {2:?}", "max", d, result, @@ -22215,10 +22209,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2170usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2182usize, start_col: 22usize, - end_line: 2170usize, + end_line: 2182usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -22242,7 +22236,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(NULL::{1})::text FROM generate_series(1, 3)", + "SELECT eql_v3.{0}(NULL::{1})::text FROM generate_series(1, 3)", "max", d, ), @@ -22257,7 +22251,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "all-NULL input to eql_v2.{0} on {1} must return NULL, got {2:?}; SQL={3}", + "all-NULL input to eql_v3.{0} on {1} must return NULL, got {2:?}; SQL={3}", "max", d, result, @@ -22314,10 +22308,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2196usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2208usize, start_col: 22usize, - end_line: 2196usize, + end_line: 2208usize, end_col: 85usize, compile_fail: false, no_run: false, @@ -22340,7 +22334,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 2) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -22410,7 +22404,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value)::text FROM mixed_null", + "SELECT eql_v3.{0}(value)::text FROM mixed_null", "max", ), ) @@ -22426,7 +22420,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", + "eql_v3.{0} on mixed NULL/non-NULL must return the {1} non-NULL value (plaintext={2:?}); want {3:?}, got {4:?}", "max", "max", expected_plaintext, @@ -22492,10 +22486,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2375usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2387usize, start_col: 22usize, - end_line: 2375usize, + end_line: 2387usize, end_col: 82usize, compile_fail: false, no_run: false, @@ -22518,7 +22512,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 5) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -22624,7 +22618,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "SELECT group_key, eql_v3.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", "min", ), ) @@ -22652,7 +22646,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 1 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "min", d, group1_extremum, @@ -22671,7 +22665,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 2 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "min", d, group2_extremum, @@ -22738,10 +22732,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2375usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2387usize, start_col: 22usize, - end_line: 2375usize, + end_line: 2387usize, end_col: 82usize, compile_fail: false, no_run: false, @@ -22764,7 +22758,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 5) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -22870,7 +22864,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "SELECT group_key, eql_v3.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", "max", ), ) @@ -22898,7 +22892,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 1 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "max", d, group1_extremum, @@ -22917,7 +22911,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 2 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "max", d, group2_extremum, @@ -22984,10 +22978,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2375usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2387usize, start_col: 22usize, - end_line: 2375usize, + end_line: 2387usize, end_col: 82usize, compile_fail: false, no_run: false, @@ -23010,7 +23004,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 5) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -23116,7 +23110,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "SELECT group_key, eql_v3.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", "min", ), ) @@ -23144,7 +23138,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 1 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "min", d, group1_extremum, @@ -23163,7 +23157,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 2 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "min", d, group2_extremum, @@ -23230,10 +23224,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2375usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2387usize, start_col: 22usize, - end_line: 2375usize, + end_line: 2387usize, end_col: 82usize, compile_fail: false, no_run: false, @@ -23256,7 +23250,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let values: &[i32] = ::FIXTURE_VALUES; + let values: &[i32] = ::fixture_values(); if ::anyhow::__private::not(values.len() >= 5) { return ::anyhow::__private::Err( ::anyhow::Error::msg( @@ -23362,7 +23356,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT group_key, eql_v2.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", + "SELECT group_key, eql_v3.{0}(value)::text FROM group_test GROUP BY group_key ORDER BY group_key", "max", ), ) @@ -23390,7 +23384,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 1 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 1 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "max", d, group1_extremum, @@ -23409,7 +23403,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "group 2 eql_v2.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", + "group 2 eql_v3.{0}({1}) must yield payload for plaintext={2:?}; want ({3}, {4:?}), got {5:?}", "max", d, group2_extremum, @@ -23476,10 +23470,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2290usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2302usize, start_col: 22usize, - end_line: 2290usize, + end_line: 2302usize, end_col: 77usize, compile_fail: false, no_run: false, @@ -23506,7 +23500,7 @@ pub mod int4 { FROM pg_proc p \ JOIN pg_aggregate a ON a.aggfnoid = p.oid \ WHERE p.proname = $1 \ - AND p.pronamespace = 'eql_v2'::regnamespace \ + AND p.pronamespace = 'eql_v3'::regnamespace \ AND p.proargtypes[0]::regtype = $2::regtype", ) .bind(agg) @@ -23517,7 +23511,7 @@ pub mod int4 { return ::anyhow::__private::Err({ let error = ::anyhow::__private::format_err( format_args!( - "eql_v2.{0}({1}) must be PARALLEL SAFE (proparallel=\'s\'), got {2:?}", + "eql_v3.{0}({1}) must be PARALLEL SAFE (proparallel=\'s\'), got {2:?}", agg, d, proparallel, @@ -23530,7 +23524,7 @@ pub mod int4 { return ::anyhow::__private::Err({ let error = ::anyhow::__private::format_err( format_args!( - "eql_v2.{0}({1}) must declare a combinefunc for partial aggregation", + "eql_v3.{0}({1}) must declare a combinefunc for partial aggregation", agg, d, ), @@ -23585,10 +23579,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2290usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2302usize, start_col: 22usize, - end_line: 2290usize, + end_line: 2302usize, end_col: 77usize, compile_fail: false, no_run: false, @@ -23615,7 +23609,7 @@ pub mod int4 { FROM pg_proc p \ JOIN pg_aggregate a ON a.aggfnoid = p.oid \ WHERE p.proname = $1 \ - AND p.pronamespace = 'eql_v2'::regnamespace \ + AND p.pronamespace = 'eql_v3'::regnamespace \ AND p.proargtypes[0]::regtype = $2::regtype", ) .bind(agg) @@ -23626,7 +23620,7 @@ pub mod int4 { return ::anyhow::__private::Err({ let error = ::anyhow::__private::format_err( format_args!( - "eql_v2.{0}({1}) must be PARALLEL SAFE (proparallel=\'s\'), got {2:?}", + "eql_v3.{0}({1}) must be PARALLEL SAFE (proparallel=\'s\'), got {2:?}", agg, d, proparallel, @@ -23639,7 +23633,7 @@ pub mod int4 { return ::anyhow::__private::Err({ let error = ::anyhow::__private::format_err( format_args!( - "eql_v2.{0}({1}) must declare a combinefunc for partial aggregation", + "eql_v3.{0}({1}) must declare a combinefunc for partial aggregation", agg, d, ), @@ -23694,10 +23688,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2531usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2543usize, start_col: 22usize, - end_line: 2531usize, + end_line: 2543usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -23749,7 +23743,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value) FROM typecheck_table", + "SELECT eql_v3.{0}(value) FROM typecheck_table", "min", ), ) @@ -23761,7 +23755,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "eql_v3.{0} on non-ord variant {1} must raise but succeeded", "min", d, ), @@ -23780,7 +23774,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v3.{0}({1}), got {2:?} (message: {3})", "min", d, code, @@ -23839,10 +23833,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2531usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2543usize, start_col: 22usize, - end_line: 2531usize, + end_line: 2543usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -23894,7 +23888,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value) FROM typecheck_table", + "SELECT eql_v3.{0}(value) FROM typecheck_table", "max", ), ) @@ -23906,7 +23900,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "eql_v3.{0} on non-ord variant {1} must raise but succeeded", "max", d, ), @@ -23925,7 +23919,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v3.{0}({1}), got {2:?} (message: {3})", "max", d, code, @@ -23984,10 +23978,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2531usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2543usize, start_col: 22usize, - end_line: 2531usize, + end_line: 2543usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -24039,7 +24033,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value) FROM typecheck_table", + "SELECT eql_v3.{0}(value) FROM typecheck_table", "min", ), ) @@ -24051,7 +24045,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "eql_v3.{0} on non-ord variant {1} must raise but succeeded", "min", d, ), @@ -24070,7 +24064,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v3.{0}({1}), got {2:?} (message: {3})", "min", d, code, @@ -24129,10 +24123,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2531usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2543usize, start_col: 22usize, - end_line: 2531usize, + end_line: 2543usize, end_col: 83usize, compile_fail: false, no_run: false, @@ -24184,7 +24178,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT eql_v2.{0}(value) FROM typecheck_table", + "SELECT eql_v3.{0}(value) FROM typecheck_table", "max", ), ) @@ -24196,7 +24190,7 @@ pub mod int4 { &::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "eql_v2.{0} on non-ord variant {1} must raise but succeeded", + "eql_v3.{0} on non-ord variant {1} must raise but succeeded", "max", d, ), @@ -24215,7 +24209,7 @@ pub mod int4 { ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v2.{0}({1}), got {2:?} (message: {3})", + "expected SQLSTATE 42883 (undefined_function) or 42725 (ambiguous_function) for eql_v3.{0}({1}), got {2:?} (message: {3})", "max", d, code, @@ -24274,10 +24268,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2632usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2644usize, start_col: 22usize, - end_line: 2632usize, + end_line: 2644usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -24300,7 +24294,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Storage); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query( &::alloc::__export::must_use({ @@ -24402,10 +24396,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2665usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2677usize, start_col: 22usize, - end_line: 2665usize, + end_line: 2677usize, end_col: 69usize, compile_fail: false, no_run: false, @@ -24428,7 +24422,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Storage); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), @@ -24506,10 +24500,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2632usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2644usize, start_col: 22usize, - end_line: 2632usize, + end_line: 2644usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -24532,7 +24526,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Eq); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query( &::alloc::__export::must_use({ @@ -24632,10 +24626,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_eq_count_path_cast"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2665usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2677usize, start_col: 22usize, - end_line: 2665usize, + end_line: 2677usize, end_col: 69usize, compile_fail: false, no_run: false, @@ -24658,7 +24652,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Eq); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), @@ -24736,10 +24730,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2709usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2721usize, start_col: 22usize, - end_line: 2709usize, + end_line: 2721usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -24768,7 +24762,7 @@ pub mod int4 { ::alloc::fmt::format(format_args!("{0}(value)", extractor_fn)) }); let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query( &::alloc::__export::must_use({ @@ -24876,10 +24870,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2632usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2644usize, start_col: 22usize, - end_line: 2632usize, + end_line: 2644usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -24902,7 +24896,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query( &::alloc::__export::must_use({ @@ -25002,10 +24996,10 @@ pub mod int4 { name: test::StaticTestName("scalars::int4::matrix_int4_ord_count_path_cast"), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2665usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2677usize, start_col: 22usize, - end_line: 2665usize, + end_line: 2677usize, end_col: 69usize, compile_fail: false, no_run: false, @@ -25028,7 +25022,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::Ord); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), @@ -25106,10 +25100,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2709usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2721usize, start_col: 22usize, - end_line: 2709usize, + end_line: 2721usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -25138,7 +25132,7 @@ pub mod int4 { ::alloc::fmt::format(format_args!("{0}(value)", extractor_fn)) }); let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query( &::alloc::__export::must_use({ @@ -25246,10 +25240,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2632usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2644usize, start_col: 22usize, - end_line: 2632usize, + end_line: 2644usize, end_col: 72usize, compile_fail: false, no_run: false, @@ -25272,7 +25266,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query( &::alloc::__export::must_use({ @@ -25374,10 +25368,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2665usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2677usize, start_col: 22usize, - end_line: 2665usize, + end_line: 2677usize, end_col: 69usize, compile_fail: false, no_run: false, @@ -25400,7 +25394,7 @@ pub mod int4 { >(::eql_tests::scalar_domains::Variant::OrdOre); let d = &spec.sql_domain; let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!("SELECT COUNT(payload::{0}) FROM {1}", d, fixture), @@ -25478,10 +25472,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 2709usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2721usize, start_col: 22usize, - end_line: 2709usize, + end_line: 2721usize, end_col: 78usize, compile_fail: false, no_run: false, @@ -25510,7 +25504,7 @@ pub mod int4 { ::alloc::fmt::format(format_args!("{0}(value)", extractor_fn)) }); let fixture = ::fixture_table_name(); - let expected = ::FIXTURE_VALUES.len() as i64; + let expected = ::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query( &::alloc::__export::must_use({ @@ -25618,10 +25612,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -25638,27 +25632,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "all" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - "", &spec.sql_domain, "ASC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if "".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "ASC" == "DESC" { @@ -25739,10 +25747,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -25759,27 +25767,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "all" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - "", &spec.sql_domain, "DESC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if "".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "DESC" == "DESC" { @@ -25860,10 +25882,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -25880,27 +25902,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "gt_zero" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - " WHERE plaintext > 0", &spec.sql_domain, "ASC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if " WHERE plaintext > 0".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "ASC" == "DESC" { @@ -25981,10 +26017,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -26001,27 +26037,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::Ord); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "gt_zero" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - " WHERE plaintext > 0", &spec.sql_domain, "DESC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if " WHERE plaintext > 0".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "DESC" == "DESC" { @@ -26102,10 +26152,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -26122,27 +26172,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "all" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - "", &spec.sql_domain, "ASC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if "".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "ASC" == "DESC" { @@ -26223,10 +26287,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -26243,27 +26307,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "all" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - "", &spec.sql_domain, "DESC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if "".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "DESC" == "DESC" { @@ -26344,10 +26422,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -26364,27 +26442,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "gt_zero" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - " WHERE plaintext > 0", &spec.sql_domain, "ASC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if " WHERE plaintext > 0".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "ASC" == "DESC" { @@ -26465,10 +26557,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1781usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1782usize, start_col: 22usize, - end_line: 1781usize, + end_line: 1782usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -26485,27 +26577,41 @@ pub mod int4 { pool: sqlx::PgPool, ) -> anyhow::Result<()> { { + use ::eql_tests::scalar_domains::ScalarType; let spec = ::eql_tests::scalar_domains::ScalarDomainSpec::new::< i32, >(::eql_tests::scalar_domains::Variant::OrdOre); - let fixture_table = ::fixture_table_name(); + let fixture_table = ::fixture_table_name(); + let zero: i32 = Default::default(); + let gt_zero = "gt_zero" == "gt_zero"; + let where_clause = if gt_zero { + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!( + " WHERE plaintext > {0}", + ::to_sql_literal(zero), + ), + ) + }) + } else { + String::new() + }; let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {0}{1} ORDER BY eql_v2.ord_term(payload::{2}) {3}", + "SELECT plaintext FROM {0}{3} ORDER BY eql_v3.ord_term(payload::{1}) {2}", fixture_table, - " WHERE plaintext > 0", &spec.sql_domain, "DESC", + where_clause, ), ) }); let actual: Vec = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: i32 = Default::default(); - let mut expected: Vec = ::FIXTURE_VALUES + let mut expected: Vec = ::fixture_values() .to_vec(); expected.sort(); - if " WHERE plaintext > 0".contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if "DESC" == "DESC" { @@ -26586,10 +26692,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -26661,7 +26767,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "ASC", "FIRST", table, @@ -26671,7 +26777,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "ASC" == "DESC" { @@ -26762,10 +26868,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -26837,7 +26943,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "ASC", "LAST", table, @@ -26847,7 +26953,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "ASC" == "DESC" { @@ -26938,10 +27044,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -27013,7 +27119,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "DESC", "FIRST", table, @@ -27023,7 +27129,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "DESC" == "DESC" { @@ -27114,10 +27220,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -27189,7 +27295,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "DESC", "LAST", table, @@ -27199,7 +27305,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "DESC" == "DESC" { @@ -27290,10 +27396,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -27365,7 +27471,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "ASC", "FIRST", table, @@ -27375,7 +27481,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "ASC" == "DESC" { @@ -27466,10 +27572,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -27541,7 +27647,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "ASC", "LAST", table, @@ -27551,7 +27657,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "ASC" == "DESC" { @@ -27642,10 +27748,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -27717,7 +27823,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "DESC", "FIRST", table, @@ -27727,7 +27833,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "DESC" == "DESC" { @@ -27818,10 +27924,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1880usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 1892usize, start_col: 22usize, - end_line: 1880usize, + end_line: 1892usize, end_col: 74usize, compile_fail: false, no_run: false, @@ -27893,7 +27999,7 @@ pub mod int4 { let sql = ::alloc::__export::must_use({ ::alloc::fmt::format( format_args!( - "SELECT plaintext FROM {2} ORDER BY eql_v2.ord_term(value) {0} NULLS {1}", + "SELECT plaintext FROM {2} ORDER BY eql_v3.ord_term(value) {0} NULLS {1}", "DESC", "LAST", table, @@ -27903,7 +28009,7 @@ pub mod int4 { let actual: Vec> = sqlx::query_scalar(&sql) .fetch_all(&mut *tx) .await?; - let mut non_null: Vec = ::FIXTURE_VALUES + let mut non_null: Vec = ::fixture_values() .to_vec(); non_null.sort(); if "DESC" == "DESC" { @@ -27994,10 +28100,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, @@ -28097,10 +28203,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, @@ -28200,10 +28306,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, @@ -28303,10 +28409,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, @@ -28406,10 +28512,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, @@ -28509,10 +28615,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, @@ -28612,10 +28718,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, @@ -28715,10 +28821,10 @@ pub mod int4 { ), ignore: false, ignore_message: ::core::option::Option::None, - source_file: "src/matrix.rs", - start_line: 1997usize, + source_file: "tests/sqlx/src/matrix.rs", + start_line: 2009usize, start_col: 22usize, - end_line: 1997usize, + end_line: 2009usize, end_col: 87usize, compile_fail: false, no_run: false, diff --git a/tests/sqlx/src/matrix.rs b/tests/sqlx/src/matrix.rs index 644ea351..2fdb9e5b 100644 --- a/tests/sqlx/src/matrix.rs +++ b/tests/sqlx/src/matrix.rs @@ -149,10 +149,11 @@ fn collect_index_scan_nodes(value: &serde_json::Value, found: &mut Vec<(String, /// type (see `scalar_domains.rs`, `format!("eql_v3.{}…", T::PG_TYPE)`). /// /// Pivots — the comparison anchors swept by the correctness / cross-shape -/// arms — are derived from the scalar type: `MIN`, `MAX`, and zero -/// (`Default::default()`). The fixture must contain those three plaintext -/// rows, since each pivot's ciphertext is fetched at test time via -/// `fetch_fixture_payload`. +/// arms — are derived from the scalar type: `min_pivot()`, `max_pivot()`, and +/// zero (`Default::default()`). Integer scalars resolve `min_pivot`/`max_pivot` +/// to `Self::MIN`/`Self::MAX`; temporal scalars use explicit sentinel dates. The +/// fixture must contain those three plaintext rows, since each pivot's +/// ciphertext is fetched at test time via `fetch_fixture_payload`. #[macro_export] macro_rules! ordered_numeric_matrix { ( @@ -175,8 +176,8 @@ macro_rules! ordered_numeric_matrix { ord_domains = [(ord, Ord), (ord_ore, OrdOre)], ord_ore_domains = [(ord_ore, OrdOre)], pivots = [ - (min, <$scalar>::MIN), - (max, <$scalar>::MAX), + (min, <$scalar as $crate::scalar_domains::ScalarType>::min_pivot()), + (max, <$scalar as $crate::scalar_domains::ScalarType>::max_pivot()), (zero, <$scalar as ::core::default::Default>::default()), ], eq_ops = [(eq, "="), (neq, "<>")], @@ -1179,7 +1180,7 @@ macro_rules! __scalar_matrix_scale_case { "_scale_", $using, "_idx", ); - let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + let values: &[$scalar] = <$scalar as ScalarType>::fixture_values(); anyhow::ensure!(values.len() >= 2, "scale test requires >= 2 fixture rows for distinct filler/pivot"); let filler = values[0]; @@ -1277,7 +1278,7 @@ macro_rules! __scalar_matrix_scale_default_case { "_scaledef_", $using, "_idx", ); - let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + let values: &[$scalar] = <$scalar as ScalarType>::fixture_values(); anyhow::ensure!(values.len() >= 2, "scale test requires >= 2 fixture rows for distinct filler/pivot"); let filler = values[0]; @@ -1324,8 +1325,8 @@ SELECT $1::jsonb::{d} FROM generate_series(1, 5000)", // ============================================================================ // Fixture-shape category — one test per type that pins the fixture's -// structural invariants: row count matches `T::FIXTURE_VALUES.len()`, -// ids are sequential from 1, plaintext column matches FIXTURE_VALUES in +// structural invariants: row count matches `T::fixture_values().len()`, +// ids are sequential from 1, plaintext column matches fixture_values() in // order, every payload carries the variant terms (`hm`, `ob`, `c`), // distinct plaintexts produce distinct hm terms, every payload declares // `v=2`. A single test runs all assertions to keep pool-setup cost @@ -1345,7 +1346,7 @@ macro_rules! __scalar_matrix_fixture_shape { ) -> anyhow::Result<()> { use $crate::scalar_domains::ScalarType; let table = <$scalar as ScalarType>::fixture_table_name(); - let expected: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + let expected: &[$scalar] = <$scalar as ScalarType>::fixture_values(); let n = expected.len() as i64; let count: i64 = sqlx::query_scalar(&format!( @@ -1457,7 +1458,7 @@ macro_rules! __scalar_matrix_ord_routes_case { let fixture_table = <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); let pivot: $scalar = - <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES[0]; + <$scalar as $crate::scalar_domains::ScalarType>::fixture_values()[0]; let pivot_lit = <$scalar as $crate::scalar_domains::ScalarType>::to_sql_literal(pivot); @@ -1508,7 +1509,7 @@ macro_rules! __scalar_matrix_ord_routes_case { // relaxed the two assertions cannot silently compensate for // each other — the derivation stays honest regardless. let expected_neq = - <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES.len() as i64 + <$scalar as $crate::scalar_domains::ScalarType>::fixture_values().len() as i64 - eq_count; let neq_count: i64 = sqlx::query_scalar(&format!( "SELECT count(*) FROM {table} WHERE value <> $1::jsonb::{d}", @@ -1672,7 +1673,7 @@ macro_rules! __scalar_matrix_index_case { .execute(&mut *tx).await?; sqlx::query("SET LOCAL enable_seqscan = off").execute(&mut *tx).await?; - let pivot: $scalar = <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES[0]; + let pivot: $scalar = <$scalar as $crate::scalar_domains::ScalarType>::fixture_values()[0]; let payload = $crate::scalar_domains::fetch_fixture_payload::<$scalar>(&pool, pivot).await?; let lit = $crate::scalar_domains::sql_string_literal(&payload); @@ -1747,24 +1748,22 @@ macro_rules! __scalar_matrix_order_by_domain { $crate::__scalar_matrix_order_by_case! { suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, dom_name = $dom_name, variant = $variant, - mode_name = asc_no_where, direction = "ASC", where_clause = "", + mode_name = asc_no_where, direction = "ASC", filter = all, } $crate::__scalar_matrix_order_by_case! { suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, dom_name = $dom_name, variant = $variant, - mode_name = desc_no_where, direction = "DESC", where_clause = "", + mode_name = desc_no_where, direction = "DESC", filter = all, } $crate::__scalar_matrix_order_by_case! { suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, dom_name = $dom_name, variant = $variant, - mode_name = asc_with_where, direction = "ASC", - where_clause = " WHERE plaintext > 0", + mode_name = asc_with_where, direction = "ASC", filter = gt_zero, } $crate::__scalar_matrix_order_by_case! { suite = $suite, scalar = $scalar, script = $script, script_path = $script_path, dom_name = $dom_name, variant = $variant, - mode_name = desc_with_where, direction = "DESC", - where_clause = " WHERE plaintext > 0", + mode_name = desc_with_where, direction = "DESC", filter = gt_zero, } }; } @@ -1776,29 +1775,40 @@ macro_rules! __scalar_matrix_order_by_case { suite = $suite:ident, scalar = $scalar:ty, script = $script:literal, script_path = $script_path:literal, dom_name = $dom_name:ident, variant = $variant:ident, mode_name = $mode_name:ident, direction = $direction:literal, - where_clause = $where_clause:literal $(,)? + filter = $filter:ident $(,)? ) => { $crate::paste::paste! { #[sqlx::test(fixtures(path = $script_path, scripts($script)))] async fn []( pool: sqlx::PgPool, ) -> anyhow::Result<()> { + use $crate::scalar_domains::ScalarType; let spec = $crate::__scalar_matrix_spec!($scalar, $variant); - let fixture_table = - <$scalar as $crate::scalar_domains::ScalarType>::fixture_table_name(); + let fixture_table = <$scalar as ScalarType>::fixture_table_name(); + + let zero: $scalar = Default::default(); + let gt_zero = stringify!($filter) == "gt_zero"; + // Build the WHERE clause from the zero pivot's SQL literal so it + // is type-agnostic: `plaintext > 0` for integers, `plaintext > + // '1970-01-01'` for dates. A hardcoded `> 0` would not typecheck + // against a non-integer plaintext column. + let where_clause = if gt_zero { + format!(" WHERE plaintext > {}", <$scalar as ScalarType>::to_sql_literal(zero)) + } else { + String::new() + }; let sql = format!( "SELECT plaintext FROM {fixture}{where_clause} \ ORDER BY eql_v3.ord_term(payload::{d}) {dir}", - fixture = fixture_table, where_clause = $where_clause, + fixture = fixture_table, d = &spec.sql_domain, dir = $direction, ); let actual: Vec<$scalar> = sqlx::query_scalar(&sql).fetch_all(&pool).await?; - let zero: $scalar = Default::default(); let mut expected: Vec<$scalar> = - <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES.to_vec(); + <$scalar as ScalarType>::fixture_values().to_vec(); expected.sort(); - if $where_clause.contains("plaintext > 0") { + if gt_zero { expected.retain(|v| *v > zero); } if $direction == "DESC" { expected.reverse(); } @@ -1922,7 +1932,7 @@ ORDER BY eql_v3.ord_term(value) {dir} NULLS {nulls}", // Ground truth: non-NULL plaintexts sorted (reversed for DESC), // with NULL_ROWS Nones at the requested end. let mut non_null: Vec<$scalar> = - <$scalar as $crate::scalar_domains::ScalarType>::FIXTURE_VALUES.to_vec(); + <$scalar as $crate::scalar_domains::ScalarType>::fixture_values().to_vec(); non_null.sort(); if $direction == "DESC" { non_null.reverse(); } let sorted = non_null.into_iter().map(Some); @@ -2093,7 +2103,7 @@ macro_rules! __scalar_matrix_aggregate_case { let spec = $crate::__scalar_matrix_spec!($scalar, $variant); let d = &spec.sql_domain; let fixture = <$scalar as ScalarType>::fixture_table_name(); - let extremum: $scalar = <$scalar as ScalarType>::FIXTURE_VALUES + let extremum: $scalar = <$scalar as ScalarType>::fixture_values() .iter() .copied() .$picker() @@ -2202,7 +2212,7 @@ macro_rules! __scalar_matrix_aggregate_case { let spec = $crate::__scalar_matrix_spec!($scalar, $variant); let d = &spec.sql_domain; let fixture = <$scalar as ScalarType>::fixture_table_name(); - let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + let values: &[$scalar] = <$scalar as ScalarType>::fixture_values(); anyhow::ensure!( values.len() >= 2, "mixed-NULL test needs >= 2 fixture values; got {}", @@ -2381,7 +2391,7 @@ macro_rules! __scalar_matrix_aggregate_group_by_case { let spec = $crate::__scalar_matrix_spec!($scalar, $variant); let d = &spec.sql_domain; let fixture = <$scalar as ScalarType>::fixture_table_name(); - let values: &[$scalar] = <$scalar as ScalarType>::FIXTURE_VALUES; + let values: &[$scalar] = <$scalar as ScalarType>::fixture_values(); anyhow::ensure!( values.len() >= 5, "GROUP BY test needs >= 5 fixture values; got {}", @@ -2638,7 +2648,7 @@ macro_rules! __scalar_matrix_count_case { let spec = $crate::__scalar_matrix_spec!($scalar, $variant); let d = &spec.sql_domain; let fixture = <$scalar as ScalarType>::fixture_table_name(); - let expected = <$scalar as ScalarType>::FIXTURE_VALUES.len() as i64; + let expected = <$scalar as ScalarType>::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query(&format!( @@ -2671,7 +2681,7 @@ macro_rules! __scalar_matrix_count_case { let spec = $crate::__scalar_matrix_spec!($scalar, $variant); let d = &spec.sql_domain; let fixture = <$scalar as ScalarType>::fixture_table_name(); - let expected = <$scalar as ScalarType>::FIXTURE_VALUES.len() as i64; + let expected = <$scalar as ScalarType>::fixture_values().len() as i64; let sql = format!( "SELECT COUNT(payload::{d}) FROM {fixture}", @@ -2718,7 +2728,7 @@ macro_rules! __scalar_matrix_count_distinct_dispatch { .expect("non-Storage variant must expose an extractor"); let extractor = format!("{extractor_fn}(value)"); let fixture = <$scalar as ScalarType>::fixture_table_name(); - let expected = <$scalar as ScalarType>::FIXTURE_VALUES.len() as i64; + let expected = <$scalar as ScalarType>::fixture_values().len() as i64; let mut tx = pool.begin().await?; sqlx::query(&format!( diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index e9f91e3b..ba432c3e 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -7,7 +7,7 @@ //! else — the four `eql_v2_{,_eq,_ord,_ord_ore}` domains, per-domain //! payload shapes, supported operators, index extractor expressions, //! ground-truth result sets — is derived from `T::PG_TYPE`, -//! `T::FIXTURE_VALUES`, and the `Variant` enum. +//! `T::fixture_values()`, and the `Variant` enum. use anyhow::{bail, Context, Result}; use sqlx::PgPool; @@ -34,12 +34,27 @@ pub trait ScalarType: /// Distinct plaintext values present in the fixture. Order doesn't /// matter — `expected_forward` sorts before returning. /// - /// For types driven by `ordered_numeric_matrix!`, the fixture MUST - /// include `MIN`, `MAX`, and zero (`Default::default()`): the matrix - /// uses those three as comparison pivots and fetches each one's - /// ciphertext via `fetch_fixture_payload`, which fails loudly if the - /// row is absent. - const FIXTURE_VALUES: &'static [Self]; + /// A method rather than a `const` so a scalar whose values can't be + /// `const`-constructed can return a borrow of a lazily-built `Vec`; + /// integer scalars return their `eql_scalars::_VALUES` const directly. + /// + /// For types driven by `ordered_numeric_matrix!`, the values MUST + /// include the three pivots (`min_pivot()`, `max_pivot()`, and zero + /// `Default::default()`): the matrix uses those as comparison pivots and + /// fetches each one's ciphertext via `fetch_fixture_payload`, which fails + /// loudly if the row is absent. + fn fixture_values() -> &'static [Self]; + + /// The low comparison pivot swept by the correctness / cross-shape arms. + /// Integer scalars return `Self::MIN`. A trait method (rather than the + /// matrix referencing `Self::MIN` directly) so a scalar without an inherent + /// `::MIN` const can supply an explicit sentinel; the returned value must be + /// present verbatim in `fixture_values()`. + fn min_pivot() -> Self; + + /// The high comparison pivot. Integer scalars return `Self::MAX`. Must be + /// present verbatim in `fixture_values()`. + fn max_pivot() -> Self; /// `fixtures.eql_v2_`. fn fixture_table_name() -> String { @@ -64,7 +79,7 @@ pub trait ScalarType: ">=" => |a, b| a >= b, other => panic!("expected_forward: unsupported operator {other}"), }; - let mut values: Vec = Self::FIXTURE_VALUES + let mut values: Vec = Self::fixture_values() .iter() .copied() .filter(|v| predicate(*v, pivot)) @@ -75,9 +90,10 @@ pub trait ScalarType: } // The per-type `impl ScalarType` blocks (one per scalar, each carrying its -// `PG_TYPE` token string and `FIXTURE_VALUES = eql_scalars::_VALUES`) -// are generated from the single harness list in `scalar_types.rs`. To add a -// type, add a `token => rust_type` line there — not an impl here. +// `PG_TYPE` token string, `fixture_values() = eql_scalars::_VALUES`, and +// `min_pivot()`/`max_pivot()` = `Self::MIN`/`Self::MAX`) are generated from the +// single harness list in `scalar_types.rs`. To add a type, add a +// `token => rust_type` line there — not an impl here. crate::scalar_types!(scalar_type_impls); /// Per-domain capability + payload shape. Storage carries no terms, `Eq` diff --git a/tests/sqlx/tests/encrypted_domain/family/mutations.rs b/tests/sqlx/tests/encrypted_domain/family/mutations.rs index 7be20853..a9cb23ce 100644 --- a/tests/sqlx/tests/encrypted_domain/family/mutations.rs +++ b/tests/sqlx/tests/encrypted_domain/family/mutations.rs @@ -209,7 +209,7 @@ async fn blocking_lt_flips_lt_arm_but_not_order_by(pool: PgPool) -> Result<()> { let order_by_sql = "SELECT plaintext FROM fixtures.eql_v2_int4 \ ORDER BY eql_v3.ord_term(payload::eql_v3.int4_ord) ASC"; - let mut ascending: Vec = ::FIXTURE_VALUES.to_vec(); + let mut ascending: Vec = ::fixture_values().to_vec(); ascending.sort(); // Baseline: `<` works (no raise) and ORDER BY is plaintext-sorted. @@ -336,7 +336,7 @@ async fn collapsing_ord_term_flips_order_by_arm(pool: PgPool) -> Result<()> { let order_by_desc = "SELECT plaintext FROM fixtures.eql_v2_int4 \ ORDER BY eql_v3.ord_term(payload::eql_v3.int4_ord) DESC"; - let mut descending: Vec = ::FIXTURE_VALUES.to_vec(); + let mut descending: Vec = ::fixture_values().to_vec(); descending.sort(); descending.reverse(); From 567c5bba6f2ee642ab7a99a0cb6a30dcffa40fde Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 21:32:59 +1000 Subject: [PATCH 86/93] chore: quote echo var in tasks/build.sh Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tasks/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/build.sh b/tasks/build.sh index 4d10e752..2bf73620 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -177,7 +177,7 @@ cat tasks/uninstall-protect.sql >> release/cipherstash-encrypt-protect-uninstall # ever pins eql_v2 functions), so appending it would both fail a clean v3 # install and break the self-containment grep. find src/v3 -type f -path "*.sql" ! -path "*_test.sql" | while IFS= read -r sql_file; do - echo $sql_file + echo "$sql_file" echo "$sql_file $sql_file" >> src/deps-v3.txt From 09d1de1778fe5f97b0eb3bddc48b9e7e7f8d23ca Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 4 Jun 2026 09:20:46 +1000 Subject: [PATCH 87/93] fix: apply CodeRabbit auto-fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scalar_domains.rs: document fixture_values() stable-order contract (callers compare element-wise and index positionally without sorting) - eql-tests-macros: assert emitted min_pivot/max_pivot bodies instead of loose MIN/MAX substrings that also match the doc comment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- crates/eql-tests-macros/src/lib.rs | 11 +++++++---- tests/sqlx/src/scalar_domains.rs | 9 +++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/eql-tests-macros/src/lib.rs b/crates/eql-tests-macros/src/lib.rs index 408648ae..b88a0677 100644 --- a/crates/eql-tests-macros/src/lib.rs +++ b/crates/eql-tests-macros/src/lib.rs @@ -267,10 +267,13 @@ mod tests { assert!(out.contains(":: eql_scalars :: INT8_VALUES")); // const→fn: fixture values is a method now, plus the integer pivots. assert!(out.contains("fn fixture_values")); - assert!(out.contains("fn min_pivot")); - assert!(out.contains("fn max_pivot")); - assert!(out.contains("MIN")); - assert!(out.contains("MAX")); + // Assert the emitted pivot bodies, not bare `MIN`/`MAX` substrings: + // the latter also appear in the doc comment, so a loose check would + // pass even if the bodies stopped returning the inherent bounds. + assert!(out.contains("fn min_pivot () -> i32 { < i32 > :: MIN }")); + assert!(out.contains("fn max_pivot () -> i32 { < i32 > :: MAX }")); + assert!(out.contains("fn min_pivot () -> i64 { < i64 > :: MIN }")); + assert!(out.contains("fn max_pivot () -> i64 { < i64 > :: MAX }")); } #[test] diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index ba432c3e..6c3d9a0c 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -31,8 +31,13 @@ pub trait ScalarType: /// name and the fixture script name. Examples: `"int4"`, `"int8"`. const PG_TYPE: &'static str; - /// Distinct plaintext values present in the fixture. Order doesn't - /// matter — `expected_forward` sorts before returning. + /// Distinct plaintext values present in the fixture, in a stable + /// order that MUST match fixture insertion order (the SQL script's + /// `id` sequence). Callers rely on this: the fixture-shape test + /// compares this slice element-wise against the `ORDER BY id` + /// plaintext column, and the scale/index arms index positionally + /// (`[0]`, `[len / 2]`) without sorting. A lazily-built `Vec` impl + /// must therefore be built deterministically in that same order. /// /// A method rather than a `const` so a scalar whose values can't be /// `const`-constructed can return a borrow of a lazily-built `Vec`; From 0bcf8215d718a5ab0ba1a585b19817eca89462b5 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 4 Jun 2026 17:41:22 +1000 Subject: [PATCH 88/93] =?UTF-8?q?fix(v3):=20address=20PR=20#255=20review?= =?UTF-8?q?=20feedback=20=E2=80=94=20SQL=20inlining,=20stale=20docs,=20tes?= =?UTF-8?q?t=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collates and resolves code-review feedback on the self-contained eql_v3 PR. Inlinable SEM helpers (coderdan threads): - Convert eql_v3.jsonb_array_to_bytea_array and eql_v3.jsonb_array_to_ore_block_u64_8_256 from LANGUAGE plpgsql to inlinable LANGUAGE sql IMMUTABLE (no SET), using a CASE-scalar-subquery form so JSON-null/empty-array inputs still return NULL rather than raising (a naive FROM-SRF + WHERE rewrite would regress null to error). - These take a bare jsonb (not a domain), so pin_search_path.sql's structural skip does not cover them; opt them in via its documented 'eql-inline-critical' COMMENT marker so they install unpinned and the planner can inline them. Add matching splinter allowlist rows. The eql_v2 copies stay plpgsql by design. Direct SEM tests added (sem.rs). Stale references: - eql-functions.md / sql-support.md: v3 extractors now document eql_v3 SEM return types, not eql_v2. - pin_search_path.sql header, scalar_types.rs and mutations.rs comments. Test/build hardening: - writer.rs validates the AUTO-GENERATED ownership header before writing. - eql-scalars int_values! fails at compile time on narrowed-fixture overflow. - parity.rs asserts the generated file set, not just per-file contents. - mise.toml: shared test:sqlx:prep so test:sqlx:watch gets the same prep. - build.sh v3-only build rejects REQUIRE edges outside src/v3. - fixtures: single-source PAYLOAD_COLUMN const; enforce 63-byte identifier limit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- crates/eql-codegen/src/writer.rs | 35 ++++ crates/eql-codegen/tests/parity.rs | 23 +++ crates/eql-scalars/src/lib.rs | 15 +- docs/reference/eql-functions.md | 10 +- docs/reference/sql-support.md | 2 +- mise.toml | 15 +- src/v3/common.sql | 39 ++-- src/v3/sem/ore_block_u64_8_256/functions.sql | 40 +++-- tasks/build.sh | 25 +++ tasks/pin_search_path.sql | 2 +- tasks/test/splinter.sh | 2 + tests/sqlx/src/fixtures/cipherstash.rs | 7 +- tests/sqlx/src/fixtures/driver.rs | 19 +- tests/sqlx/src/fixtures/validation.rs | 27 ++- tests/sqlx/src/scalar_types.rs | 2 +- .../encrypted_domain/family/inlinability.rs | 81 ++++++++- .../encrypted_domain/family/mutations.rs | 2 +- .../sqlx/tests/encrypted_domain/family/sem.rs | 170 ++++++++++++++++++ 18 files changed, 461 insertions(+), 55 deletions(-) diff --git a/crates/eql-codegen/src/writer.rs b/crates/eql-codegen/src/writer.rs index afbcdce0..4d310fc8 100644 --- a/crates/eql-codegen/src/writer.rs +++ b/crates/eql-codegen/src/writer.rs @@ -90,6 +90,25 @@ pub fn ensure_generated_paths_writable(paths: &[PathBuf]) -> Result<(), WriteErr /// — it does not prepend a header. pub fn write_generated_file(path: &Path, body: &str) -> Result<(), WriteError> { ensure_generated_paths_writable(std::slice::from_ref(&path.to_path_buf()))?; + // The template is trusted to carry the ownership marker as its first line, + // but a renderer bug (or a hand-edited template) could drop it — which would + // then defeat `is_generated`/`clean_generated_files`, leaving an unowned file + // the next run refuses to overwrite. Validate the marker before writing. + let first = body + .lines() + .next() + .unwrap_or("") + .trim_end_matches(['\r', '\n']); + if first != sql_marker() { + return Err(WriteError::Ownership(format!( + "refusing to write generated file without the AUTO-GENERATED marker as its \ + first line: {} (expected first line {:?}, got {:?}). The SQL template must \ + emit the marker.", + path.display(), + sql_marker(), + first + ))); + } if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } @@ -171,6 +190,22 @@ mod tests { assert!(is_generated(&p)); } + #[test] + fn write_rejects_body_without_marker() { + let d = tmp(); + let p = d.path().join("int4_types.sql"); + // A body whose first line is NOT the AUTO-GENERATED marker must be + // rejected — the template is required to emit it. + let body = "-- REQUIRE: src/v3/schema.sql\nDO $$ BEGIN END $$;\n"; + let err = write_generated_file(&p, body).unwrap_err(); + assert!(matches!(err, WriteError::Ownership(_))); + assert!(err.to_string().contains("AUTO-GENERATED marker")); + assert!( + !p.exists(), + "no file should be written when the marker is missing" + ); + } + #[test] fn write_refuses_to_overwrite_handwritten() { let d = tmp(); diff --git a/crates/eql-codegen/tests/parity.rs b/crates/eql-codegen/tests/parity.rs index d5efb193..a0f1e3b1 100644 --- a/crates/eql-codegen/tests/parity.rs +++ b/crates/eql-codegen/tests/parity.rs @@ -37,6 +37,29 @@ fn rust_generator_matches_int4_golden_files() { let ref_dir = root.join("tests/codegen/reference/int4"); let gen_dir = out.join("src/v3/scalars/int4"); + + // Assert the generated .sql file SET matches the reference set first — the + // per-file byte comparison below only iterates reference files, so a missing + // generated file (or an extra one the reference never pins) would otherwise + // pass silently. + let sql_names = |dir: &std::path::Path| -> Vec { + let mut names: Vec = fs::read_dir(dir) + .unwrap() + .filter_map(|e| e.ok().map(|e| e.path())) + .filter(|p| p.extension().and_then(|x| x.to_str()) == Some("sql")) + .map(|p| p.file_name().unwrap().to_str().unwrap().to_string()) + .collect(); + names.sort(); + names + }; + let ref_names = sql_names(&ref_dir); + let gen_names = sql_names(&gen_dir); + assert_eq!( + gen_names, ref_names, + "generated int4 .sql file set differs from golden reference set \ + (reference: {ref_names:?}, generated: {gen_names:?})" + ); + for entry in fs::read_dir(&ref_dir).unwrap() { let path = entry.unwrap().path(); if path.extension().and_then(|e| e.to_str()) != Some("sql") { diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index 1d40a7e1..79017728 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -403,7 +403,20 @@ macro_rules! int_values { let mut i = 0; while i < N { out[i] = match SPEC.fixtures[i].numeric_value(SPEC.kind) { - Some(v) => v as $ty, + Some(v) => { + // Const-eval bounds check: a fixture value that does + // not fit the narrowed target type would otherwise be + // silently truncated/wrapped by `as`. Make it a + // compile-time error instead. + if v < <$ty>::MIN as i128 || v > <$ty>::MAX as i128 { + panic!(concat!( + "integer scalar fixture value out of range for `", + stringify!($ty), + "`" + )); + } + v as $ty + } None => panic!("integer scalar fixture must resolve to a number"), }; i += 1; diff --git a/docs/reference/eql-functions.md b/docs/reference/eql-functions.md index 20c6f958..b610349c 100644 --- a/docs/reference/eql-functions.md +++ b/docs/reference/eql-functions.md @@ -429,14 +429,14 @@ encrypted-domain value. Generated per eq/ord-capable variant of every scalar type — see [Adding a Scalar Encrypted-Domain Type](./adding-a-scalar-encrypted-domain-type.md). The argument type selects the overload, and both are inlinable so a functional index built on the extractor engages. The extractors live in -the `eql_v3` schema; their return types remain the core `eql_v2` -index-term types. +the `eql_v3` schema; their return types are the self-contained `eql_v3` +SEM index-term types. ```sql -- int4 — generated for every scalar type's eq / ord variants. -eql_v3.eq_term(a eql_v3.int4_eq) RETURNS eql_v2.hmac_256 -eql_v3.ord_term(a eql_v3.int4_ord) RETURNS eql_v2.ore_block_u64_8_256 -eql_v3.ord_term(a eql_v3.int4_ord_ore) RETURNS eql_v2.ore_block_u64_8_256 +eql_v3.eq_term(a eql_v3.int4_eq) RETURNS eql_v3.hmac_256 +eql_v3.ord_term(a eql_v3.int4_ord) RETURNS eql_v3.ore_block_u64_8_256 +eql_v3.ord_term(a eql_v3.int4_ord_ore) RETURNS eql_v3.ore_block_u64_8_256 ``` **Example:** diff --git a/docs/reference/sql-support.md b/docs/reference/sql-support.md index c5204850..82770ad9 100644 --- a/docs/reference/sql-support.md +++ b/docs/reference/sql-support.md @@ -61,7 +61,7 @@ Use the equivalent [`jsonb_path_query`](#jsonb-functions-and-selectors-enabled-b ## Encrypted-domain scalar types (`eql_v3.`) -Scalar encrypted-domain types (e.g. `eql_v3.int4`; see [Adding a Scalar Encrypted-Domain Type](./adding-a-scalar-encrypted-domain-type.md)) are a different access model from the matrix above. Instead of configuring a search index on an `eql_v2_encrypted` column, you type the column as a specific domain *variant* whose operator surface is fixed at generation time. The index terms travel in the payload; there is no `add_search_config` step. The domains and their operator surface live in the `eql_v3` schema (dropped by `DROP SCHEMA eql_v3 CASCADE`, and they survive an `eql_v2` uninstall); their extracted index-term types remain the core `eql_v2` types. +Scalar encrypted-domain types (e.g. `eql_v3.int4`; see [Adding a Scalar Encrypted-Domain Type](./adding-a-scalar-encrypted-domain-type.md)) are a different access model from the matrix above. Instead of configuring a search index on an `eql_v2_encrypted` column, you type the column as a specific domain *variant* whose operator surface is fixed at generation time. The index terms travel in the payload; there is no `add_search_config` step. The domains and their operator surface live in the `eql_v3` schema (dropped by `DROP SCHEMA eql_v3 CASCADE`, and they survive an `eql_v2` uninstall); their extracted index-term types are the self-contained `eql_v3` SEM types (`eql_v3.hmac_256`, `eql_v3.ore_block_u64_8_256`). Each scalar type `` generates one storage-only variant plus eq/ord query variants: diff --git a/mise.toml b/mise.toml index ccd458fc..8baafecb 100644 --- a/mise.toml +++ b/mise.toml @@ -42,8 +42,8 @@ run = """ rm -f release/cipherstash-encrypt.sql """ -[tasks."test:sqlx"] -description = "Run SQLx tests with hybrid migration approach" +[tasks."test:sqlx:prep"] +description = "Prepare the SQLx test DB: cp built EQL into migrations, migrate, regenerate fixtures" # `build` produces release/cipherstash-encrypt.sql, which is then cp'd into # tests/sqlx/migrations/001_install_eql.sql below. Without this dep, a stale # release artifact silently ships an old EQL extension into the test DB and @@ -73,14 +73,23 @@ sqlx migrate run echo "Regenerating SQLx fixtures..." cd "{{config_root}}" mise run fixture:generate:all +""" +[tasks."test:sqlx"] +description = "Run SQLx tests with hybrid migration approach" +# Prep (build + cp + migrate + fixtures) is shared with test:sqlx:watch. +depends = ["test:sqlx:prep"] +dir = "{{config_root}}/tests/sqlx" +run = """ echo "Running Rust tests..." -cd tests/sqlx cargo test """ [tasks."test:sqlx:watch"] description = "Run SQLx tests in watch mode (rebuild EQL on changes)" +# Same prep as test:sqlx so watch mode starts from a migrated DB + fresh +# fixtures, not a stale checkout. +depends = ["test:sqlx:prep"] dir = "{{config_root}}/tests/sqlx" run = """ cargo watch -x test diff --git a/src/v3/common.sql b/src/v3/common.sql index b30366fc..698989c7 100644 --- a/src/v3/common.sql +++ b/src/v3/common.sql @@ -18,21 +18,32 @@ --! --! @note Returns NULL if input is JSON null --! @note Each array element is hex-decoded to bytea +--! @note Inlinable `LANGUAGE sql` IMMUTABLE form (no `SET search_path`) so the +--! planner can fold this per-encrypted-value helper into the calling query. +--! This deliberately diverges from the v2 plpgsql equivalent (intentionally +--! left unchanged): the `CASE WHEN jsonb_typeof(val) = 'array'` guard only +--! evaluates the set-returning `jsonb_array_elements_text` for an array, so a +--! non-array JSON scalar returns NULL here instead of raising "cannot extract +--! elements from a scalar". Both callers only ever pass an array or JSON null +--! (`val->'ob'`), so the divergence is unreachable in practice; JSON null and +--! empty array still return NULL exactly as before. CREATE FUNCTION eql_v3.jsonb_array_to_bytea_array(val jsonb) RETURNS bytea[] - SET search_path = pg_catalog, extensions, public + IMMUTABLE AS $$ -DECLARE - terms_arr bytea[]; -BEGIN - IF jsonb_typeof(val) = 'null' THEN - RETURN NULL; - END IF; + SELECT CASE WHEN jsonb_typeof(val) = 'array' + THEN ( + SELECT array_agg(decode(value::text, 'hex')::bytea) + FROM jsonb_array_elements_text(val) AS value + ) + ELSE NULL + END; +$$ LANGUAGE sql; - SELECT array_agg(decode(value::text, 'hex')::bytea) - INTO terms_arr - FROM jsonb_array_elements_text(val) AS value; - - RETURN terms_arr; -END; -$$ LANGUAGE plpgsql; +--! @internal Mark this hand-written helper inline-critical so the post-install +--! pin_search_path pass leaves it unpinned (no `SET search_path`), preserving +--! SQL-function inlining. It takes a bare `jsonb` arg (not a jsonb-backed +--! encrypted DOMAIN), so the structural skip in tasks/pin_search_path.sql does +--! not recognise it; this marker is the documented manual opt-in. +COMMENT ON FUNCTION eql_v3.jsonb_array_to_bytea_array(jsonb) IS + 'eql-inline-critical: per-encrypted-value ORE helper; must stay inlinable (unpinned search_path)'; diff --git a/src/v3/sem/ore_block_u64_8_256/functions.sql b/src/v3/sem/ore_block_u64_8_256/functions.sql index f86ef8a4..ccc6a817 100644 --- a/src/v3/sem/ore_block_u64_8_256/functions.sql +++ b/src/v3/sem/ore_block_u64_8_256/functions.sql @@ -17,24 +17,34 @@ --! @internal --! @param val jsonb Array of hex-encoded ORE block terms --! @return eql_v3.ore_block_u64_8_256 ORE block composite, or NULL if input is null +--! @note Inlinable `LANGUAGE sql` IMMUTABLE form (no `SET search_path`) so the +--! planner can fold this per-encrypted-value helper into the calling query. +--! This deliberately diverges from the v2 plpgsql equivalent (intentionally +--! left unchanged): the `CASE WHEN jsonb_typeof(val) = 'array'` guard only +--! evaluates the array path for an array, so a non-array JSON scalar returns +--! NULL here instead of raising. The sole caller passes `val->'ob'`, always an +--! array or JSON null, so the divergence is unreachable in practice; JSON null +--! and empty array still return NULL exactly as before. CREATE FUNCTION eql_v3.jsonb_array_to_ore_block_u64_8_256(val jsonb) RETURNS eql_v3.ore_block_u64_8_256 - SET search_path = pg_catalog, extensions, public + IMMUTABLE AS $$ -DECLARE - terms eql_v3.ore_block_u64_8_256_term[]; -BEGIN - IF jsonb_typeof(val) = 'null' THEN - RETURN NULL; - END IF; - - SELECT array_agg(ROW(b)::eql_v3.ore_block_u64_8_256_term) - INTO terms - FROM unnest(eql_v3.jsonb_array_to_bytea_array(val)) AS b; - - RETURN ROW(terms)::eql_v3.ore_block_u64_8_256; -END; -$$ LANGUAGE plpgsql; + SELECT CASE WHEN jsonb_typeof(val) = 'array' + THEN ROW(( + SELECT array_agg(ROW(b)::eql_v3.ore_block_u64_8_256_term) + FROM unnest(eql_v3.jsonb_array_to_bytea_array(val)) AS b + ))::eql_v3.ore_block_u64_8_256 + ELSE NULL + END; +$$ LANGUAGE sql; + +--! @internal Mark this hand-written helper inline-critical so the post-install +--! pin_search_path pass leaves it unpinned (no `SET search_path`), preserving +--! SQL-function inlining. It takes a bare `jsonb` arg (not a jsonb-backed +--! encrypted DOMAIN), so the structural skip in tasks/pin_search_path.sql does +--! not recognise it; this marker is the documented manual opt-in. +COMMENT ON FUNCTION eql_v3.jsonb_array_to_ore_block_u64_8_256(jsonb) IS + 'eql-inline-critical: per-encrypted-value ORE helper; must stay inlinable (unpinned search_path)'; --! @brief Extract ORE block index term from JSONB payload diff --git a/tasks/build.sh b/tasks/build.sh index 2bf73620..4d3d94e0 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -48,6 +48,29 @@ verify_deps_exist() { fi } +# Fail loudly if any v3 REQUIRE edge points OUTSIDE src/v3. The v3-only build +# must be self-contained (no eql_v2 coupling); a stray `-- REQUIRE: src/...` +# edge to a non-v3 file would silently pull eql_v2 SQL into the v3 artefact (or +# tsort would drop it), breaking self-containment. Each line in deps-v3.txt is +# " "; self-edges (file == dep) are skipped, every other dep target +# must start with src/v3/. +verify_v3_self_contained() { + local dep_file=$1 + local offending=0 + while IFS=' ' read -r src dep; do + [[ -z "$dep" ]] && continue + [[ "$src" == "$dep" ]] && continue + if [[ "$dep" != src/v3/* ]]; then + echo "ERROR: v3 REQUIRE edge points outside src/v3: $src -- REQUIRE: $dep" >&2 + offending=1 + fi + done < "$dep_file" + if [[ $offending -ne 0 ]]; then + echo "ERROR: v3-only build is not self-contained — a -- REQUIRE: target lives outside src/v3 (see above)." >&2 + exit 1 + fi +} + mkdir -p release rm -f release/cipherstash-encrypt-uninstall.sql @@ -191,6 +214,8 @@ find src/v3 -type f -path "*.sql" ! -path "*_test.sql" | while IFS= read -r sql_ done < "$sql_file" done +verify_v3_self_contained src/deps-v3.txt + cat src/deps-v3.txt | tsort | tac > src/deps-ordered-v3.txt verify_deps_exist src/deps-ordered-v3.txt diff --git a/tasks/pin_search_path.sql b/tasks/pin_search_path.sql index 1abf9c7d..75eed567 100644 --- a/tasks/pin_search_path.sql +++ b/tasks/pin_search_path.sql @@ -1,5 +1,5 @@ --! @file pin_search_path.sql ---! @brief Post-install: pin search_path on every eql_v2.* function +--! @brief Post-install: pin search_path on every eql_v2.* and eql_v3.* function --! --! This file is appended verbatim by `tasks/build.sh` to the end of every --! release variant (main, supabase, protect/stack), AFTER all `src/**/*.sql` diff --git a/tasks/test/splinter.sh b/tasks/test/splinter.sh index 9d8c3cb1..282b485b 100755 --- a/tasks/test/splinter.sh +++ b/tasks/test/splinter.sh @@ -124,6 +124,8 @@ function_search_path_mutable eql_v3 ore_block_u64_8_256_lte function Inner compa function_search_path_mutable eql_v3 ore_block_u64_8_256_gt function Inner comparator for the eql_v3 ore_block_u64_8_256 `>` operator. Same rationale as eql_v3.ore_block_u64_8_256_eq. function_search_path_mutable eql_v3 ore_block_u64_8_256_gte function Inner comparator for the eql_v3 ore_block_u64_8_256 `>=` operator. Same rationale as eql_v3.ore_block_u64_8_256_eq. function_search_path_mutable eql_v3 hmac_256 function HMAC equality extractor for the eql_v3 SEM fork: inlinable SQL (jsonb) constructor used inside eql_v3.eq_term. Must inline so the functional hash/btree index on eql_v3.eq_term(col) engages. Mirrors eql_v2.hmac_256. +function_search_path_mutable eql_v3 jsonb_array_to_bytea_array function Hand-written jsonb→bytea[] helper for the eql_v3 SEM fork: inlinable SQL (no SET, IMMUTABLE). Reached per-encrypted-value through eql_v3.ore_block_u64_8_256; must inline so the planner can fold it into the calling query. Pinned by neither the structural skip (it takes bare jsonb, not a jsonb-backed domain) nor an inline-critical OID clause — it carries the documented `eql-inline-critical` COMMENT marker that tasks/pin_search_path.sql honours. The eql_v2 copy stays plpgsql (pinned) by design. +function_search_path_mutable eql_v3 jsonb_array_to_ore_block_u64_8_256 function Hand-written jsonb→ore_block composite helper for the eql_v3 SEM fork: inlinable SQL (no SET, IMMUTABLE). Same rationale as eql_v3.jsonb_array_to_bytea_array — reached per-encrypted-value through eql_v3.ore_block_u64_8_256, carries the `eql-inline-critical` COMMENT marker. The eql_v2 copy stays plpgsql (pinned) by design. ALLOW # Wrap splinter (a single bare SELECT expression) into a subquery we can diff --git a/tests/sqlx/src/fixtures/cipherstash.rs b/tests/sqlx/src/fixtures/cipherstash.rs index adb12173..22e552fa 100644 --- a/tests/sqlx/src/fixtures/cipherstash.rs +++ b/tests/sqlx/src/fixtures/cipherstash.rs @@ -59,9 +59,14 @@ async fn build_cipher() -> Result>> { /// fail on an unknown index name. Extending fixture coverage to a new /// index is one variant on `IndexKind` plus one arm here, both compile- /// time checked. +/// The single encrypted-payload column name. Single-sourced here so the +/// `ColumnConfig` built for encryption and the `INSERT` target column in the +/// driver cannot drift apart. +pub const PAYLOAD_COLUMN: &str = "payload"; + pub fn column_config_for(spec_indexes: &[IndexKind], cast: Cast) -> Result { let column_type = cast_to_column_type(cast)?; - let mut config = ColumnConfig::build("payload").casts_as(column_type); + let mut config = ColumnConfig::build(PAYLOAD_COLUMN).casts_as(column_type); for ix in spec_indexes { config = config.add_index(Index::new(index_type_for(*ix))); diff --git a/tests/sqlx/src/fixtures/driver.rs b/tests/sqlx/src/fixtures/driver.rs index 1d6c3f45..127e20a1 100644 --- a/tests/sqlx/src/fixtures/driver.rs +++ b/tests/sqlx/src/fixtures/driver.rs @@ -196,12 +196,19 @@ where .context("building ColumnConfig from FixtureSpec indexes")?; let working = self.working_table(); - let payloads = cipherstash::encrypt_store(&working, "payload", self.values(), &config) - .await - .context("encrypting fixture values")?; - - let insert = - format!("INSERT INTO public.{working} (id, plaintext, payload) VALUES ($1, $2, $3)"); + let payloads = cipherstash::encrypt_store( + &working, + cipherstash::PAYLOAD_COLUMN, + self.values(), + &config, + ) + .await + .context("encrypting fixture values")?; + + let insert = format!( + "INSERT INTO public.{working} (id, plaintext, {col}) VALUES ($1, $2, $3)", + col = cipherstash::PAYLOAD_COLUMN + ); for (i, (value, payload)) in self.values().iter().zip(payloads).enumerate() { let id = (i as i64) + 1; sqlx::query(&insert) diff --git a/tests/sqlx/src/fixtures/validation.rs b/tests/sqlx/src/fixtures/validation.rs index e641a726..61746e60 100644 --- a/tests/sqlx/src/fixtures/validation.rs +++ b/tests/sqlx/src/fixtures/validation.rs @@ -5,8 +5,17 @@ use std::fmt; -/// Lowercase snake-case identifier, must start with a letter: `^[a-z][a-z0-9_]*$`. +/// Maximum unquoted identifier length PostgreSQL preserves; longer identifiers +/// are silently truncated (`NAMEDATALEN - 1`). +const MAX_IDENTIFIER_LEN: usize = 63; + +/// Lowercase snake-case identifier, must start with a letter and be at most +/// 63 bytes (PostgreSQL truncates beyond that): `^[a-z][a-z0-9_]{0,62}$`. fn is_valid_identifier(s: &str) -> bool { + // All accepted chars are single-byte ASCII, so byte length == char count. + if s.len() > MAX_IDENTIFIER_LEN { + return false; + } let mut chars = s.chars(); match chars.next() { Some(c) if c.is_ascii_lowercase() => {} @@ -107,6 +116,22 @@ mod tests { assert!(FixtureIdentifier::try_from("a;DROP").is_err()); // injection attempt } + #[test] + fn accepts_63_char_identifier() { + // 63 bytes is the longest PostgreSQL preserves unquoted. + let id = format!("a{}", "b".repeat(62)); + assert_eq!(id.len(), 63); + assert!(FixtureIdentifier::try_from(id.as_str()).is_ok()); + } + + #[test] + fn rejects_64_char_identifier() { + // 64 bytes would be silently truncated by PostgreSQL. + let id = format!("a{}", "b".repeat(63)); + assert_eq!(id.len(), 64); + assert!(FixtureIdentifier::try_from(id.as_str()).is_err()); + } + #[test] fn identifier_renders_via_display() { let id = FixtureIdentifier::try_from("eql_v2_int4").unwrap(); diff --git a/tests/sqlx/src/scalar_types.rs b/tests/sqlx/src/scalar_types.rs index 0d6a70a8..676cc0b1 100644 --- a/tests/sqlx/src/scalar_types.rs +++ b/tests/sqlx/src/scalar_types.rs @@ -14,7 +14,7 @@ //! to forward it to the matching `eql_tests_macros` proc-macro: //! //! - `scalar_type_impls` — `scalar_domains.rs` (lib): the `impl ScalarType` block. -//! - `fixture_modules` — `fixtures/mod.rs` (lib): the `pub mod eql_v2_` modules. +//! - `fixture_modules` — `fixtures/mod.rs` (lib): the `pub mod eql_v3_` modules. //! - `matrix_suites` — `tests/encrypted_domain/scalars/mod.rs` (test binary): //! the `ordered_numeric_matrix!` suites. //! - `fixture_dispatch` — `tests/generate_all_fixtures.rs` (test binary): the diff --git a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs index 1a250ef9..8caed2bf 100644 --- a/tests/sqlx/tests/encrypted_domain/family/inlinability.rs +++ b/tests/sqlx/tests/encrypted_domain/family/inlinability.rs @@ -89,10 +89,22 @@ async fn no_encrypted_domain_inline_critical_function_is_pinned(pool: PgPool) -> /// Direct guard for the self-contained eql_v3 SEM index-term functions. Unlike /// the structural guard above (which covers jsonb-domain-arg functions), these -/// take a composite (ore_block_u64_8_256) or raw jsonb (hmac_256) arg, so they -/// are NOT caught by the structural pin-skip and need explicit inline_critical -/// allowlisting. If pin_search_path.sql pins any of them, v3 functional-index -/// inlining silently regresses to Seq Scan — this test fails instead. +/// take a composite (ore_block_u64_8_256) or raw jsonb (hmac_256/the two +/// per-encrypted-value `jsonb_array_to_*` helpers) arg, so they are NOT caught +/// by the structural pin-skip and need explicit inline_critical allowlisting. +/// If pin_search_path.sql pins any of them, v3 functional-index inlining +/// silently regresses to Seq Scan — this test fails instead. +/// +/// `jsonb_array_to_bytea_array(jsonb)` and +/// `jsonb_array_to_ore_block_u64_8_256(jsonb)` are included here: both take a +/// bare `jsonb` arg (not a jsonb-backed encrypted DOMAIN), so the structural +/// skip in tasks/pin_search_path.sql does not recognise them — they are kept +/// unpinned by the `eql-inline-critical` COMMENT marker instead. This test +/// asserts the unpinned + inlinable-SQL state directly; the companion +/// `eql_v3_sem_inline_critical_functions_carry_marker` test below asserts the +/// marker itself, so an edit that drops the marker (or a pin_search_path.sql +/// refactor that stops honouring it) fails CI even though both checks live in +/// separate tests. #[sqlx::test] async fn eql_v3_sem_inline_critical_functions_are_unpinned(pool: PgPool) -> Result<()> { let rows: Vec<(String,)> = sqlx::query_as( @@ -106,7 +118,10 @@ async fn eql_v3_sem_inline_critical_functions_are_unpinned(pool: PgPool) -> Resu 'ore_block_u64_8_256_eq','ore_block_u64_8_256_neq', 'ore_block_u64_8_256_lt','ore_block_u64_8_256_lte', 'ore_block_u64_8_256_gt','ore_block_u64_8_256_gte')) - OR (p.pronargs = 1 AND p.proname = 'hmac_256' + OR (p.pronargs = 1 AND p.proname IN ( + 'hmac_256', + 'jsonb_array_to_bytea_array', + 'jsonb_array_to_ore_block_u64_8_256') AND p.proargtypes[0] = 'jsonb'::regtype) ) AND ( @@ -129,6 +144,62 @@ async fn eql_v3_sem_inline_critical_functions_are_unpinned(pool: PgPool) -> Resu Ok(()) } +/// Companion guard for the two bare-`jsonb` per-encrypted-value helpers +/// (`jsonb_array_to_bytea_array`, `jsonb_array_to_ore_block_u64_8_256`). The +/// unpinned state asserted above is only DURABLE because each helper carries an +/// `eql-inline-critical` COMMENT marker that `tasks/pin_search_path.sql` honours +/// (it skips pinning functions whose `pg_description` matches +/// `'eql-inline-critical%'`). Neither helper is caught by the structural +/// jsonb-domain skip, so the marker is the ONLY thing keeping them unpinned — +/// an edit that removes the marker, or a pin_search_path.sql refactor that drops +/// the marker handling, would silently re-pin them and break inlining. This test +/// asserts the marker is present (and the helpers are SQL/IMMUTABLE) so that +/// failure surfaces here. +#[sqlx::test] +async fn eql_v3_sem_inline_critical_helpers_carry_marker(pool: PgPool) -> Result<()> { + // Each expected helper must appear with a present inline-critical marker + // and be inlinable SQL/IMMUTABLE. Any helper that is missing, unmarked, or + // not inlinable SQL/IMMUTABLE is an offender. + let offenders: Vec<(String, Option, String, String)> = sqlx::query_as( + r#" + WITH expected(proname) AS ( + VALUES ('jsonb_array_to_bytea_array'), + ('jsonb_array_to_ore_block_u64_8_256') + ) + SELECT e.proname AS proname, + d.description AS marker, + l.lanname AS prolang, + p.provolatile::text AS provolatile + FROM expected e + LEFT JOIN pg_catalog.pg_proc p + ON p.proname = e.proname + AND p.pronamespace = 'eql_v3'::regnamespace + AND p.pronargs = 1 + AND p.proargtypes[0] = 'jsonb'::regtype + LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang + LEFT JOIN pg_catalog.pg_description d + ON d.objoid = p.oid AND d.classoid = 'pg_proc'::regclass + WHERE p.oid IS NULL + OR d.description IS NULL + OR d.description NOT LIKE 'eql-inline-critical%' + OR l.lanname IS DISTINCT FROM 'sql' + OR p.provolatile IS DISTINCT FROM 'i' + ORDER BY e.proname + "#, + ) + .fetch_all(&pool) + .await?; + + assert!( + offenders.is_empty(), + "eql_v3 SEM bare-jsonb helpers must carry an `eql-inline-critical` COMMENT \ + marker and be inlinable SQL/IMMUTABLE — the marker is what keeps \ + pin_search_path.sql from pinning them. Offenders \ + (proname, marker, prolang, provolatile): {offenders:#?}" + ); + Ok(()) +} + #[sqlx::test] async fn every_inline_critical_eligible_domain_has_inline_critical_functions( pool: PgPool, diff --git a/tests/sqlx/tests/encrypted_domain/family/mutations.rs b/tests/sqlx/tests/encrypted_domain/family/mutations.rs index 36d394d8..25389842 100644 --- a/tests/sqlx/tests/encrypted_domain/family/mutations.rs +++ b/tests/sqlx/tests/encrypted_domain/family/mutations.rs @@ -198,7 +198,7 @@ async fn dropping_strict_on_eq_flips_supported_null_arm(pool: PgPool) -> Result< Ok(()) } -// 5. Ord `<` correctness routes through `eql_v2.lt`. Turning `lt` into a +// 5. Ord `<` correctness routes through `eql_v3.lt`. Turning `lt` into a // blocker makes `<` raise — proving the ord `<` correctness arm has teeth. // Crucially, ORDER BY routes through `ord_term`, NOT `<`, so it must stay // green here. This is the #5-vs-#7 split: #5 attacks `<`, #7 attacks the diff --git a/tests/sqlx/tests/encrypted_domain/family/sem.rs b/tests/sqlx/tests/encrypted_domain/family/sem.rs index 3d12bdd8..b7d2543c 100644 --- a/tests/sqlx/tests/encrypted_domain/family/sem.rs +++ b/tests/sqlx/tests/encrypted_domain/family/sem.rs @@ -223,3 +223,173 @@ async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<() ); Ok(()) } + +/// T6 — Characterization of `eql_v3.jsonb_array_to_bytea_array(jsonb)` across its +/// three real-world input shapes. This is the safety net for the plpgsql→sql +/// inlining refactor (the function is reached per-encrypted-value, so it must be +/// inlinable). Behaviour pinned: +/// - JSON null (`'null'`) → NULL (the load-bearing null guard) +/// - empty array (`'[]'`) → NULL (array_agg over zero rows is NULL) +/// - populated array → decoded bytea[] +/// +/// Note the deliberate divergence the inlinable CASE form introduces vs. the +/// v2 plpgsql equivalent: a non-array JSON *scalar* (e.g. a number) returns NULL +/// rather than raising `cannot extract elements from a scalar`. Both callers only +/// ever pass an array or json-null (`val->'ob'`), so this is unreachable in +/// practice; we pin it here so the divergence is intentional and visible. +#[sqlx::test] +async fn jsonb_array_to_bytea_array_input_shapes(pool: PgPool) -> Result<()> { + // SQL NULL (distinct from JSON null `'null'`). The function is NOT STRICT, + // so the body runs: `jsonb_typeof(NULL)` is NULL → the CASE guard + // `WHEN jsonb_typeof(val) = 'array'` is not-true → ELSE NULL. + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_bytea_array(NULL::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!( + is_null, + "SQL NULL must yield NULL bytea[] (function is not STRICT)" + ); + + // JSON null → NULL. + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_bytea_array('null'::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!(is_null, "JSON null must yield NULL bytea[]"); + + // Empty array → NULL (array_agg over zero rows). + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_bytea_array('[]'::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!(is_null, "empty JSON array must yield NULL bytea[]"); + + // Single-element array → one decoded bytea element. + let decoded: Vec> = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_bytea_array('[\"aabb\"]'::jsonb)") + .fetch_one(&pool) + .await?; + assert_eq!( + decoded, + vec![vec![0xaau8, 0xbb]], + "single-element array must hex-decode to a 1-element bytea[]" + ); + + // Populated array → hex-decoded bytea[] round-trip. + let decoded: Vec> = sqlx::query_scalar( + "SELECT eql_v3.jsonb_array_to_bytea_array('[\"aabb\",\"ccdd\"]'::jsonb)", + ) + .fetch_one(&pool) + .await?; + assert_eq!( + decoded, + vec![vec![0xaau8, 0xbb], vec![0xccu8, 0xdd]], + "populated array must hex-decode to bytea[]" + ); + + // Deliberate delta: a non-array JSON scalar returns NULL (not a raise). + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_bytea_array('5'::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!( + is_null, + "non-array JSON scalar must yield NULL (documented delta)" + ); + + // Same delta for a non-array JSON object — `jsonb_typeof` is 'object', so + // the CASE guard is not-true → ELSE NULL (not a raise). + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_bytea_array('{}'::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!( + is_null, + "non-array JSON object must yield NULL (documented delta)" + ); + + Ok(()) +} + +/// T7 — Characterization of `eql_v3.jsonb_array_to_ore_block_u64_8_256(jsonb)` +/// across the same three input shapes. Safety net for the same plpgsql→sql +/// inlining refactor. Behaviour pinned: +/// - JSON null (`'null'`) → NULL composite +/// - empty array (`'[]'`) → NULL composite (inner array_agg is NULL) +/// - populated array → non-NULL composite with one term per element +/// +/// Same documented delta as T6 for a non-array JSON scalar. +#[sqlx::test] +async fn jsonb_array_to_ore_block_input_shapes(pool: PgPool) -> Result<()> { + // SQL NULL (distinct from JSON null `'null'`). Not STRICT, so the body + // runs: `jsonb_typeof(NULL)` is NULL → CASE guard not-true → ELSE NULL. + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_ore_block_u64_8_256(NULL::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!( + is_null, + "SQL NULL must yield NULL composite (function is not STRICT)" + ); + + // JSON null → NULL composite. + let is_null: bool = sqlx::query_scalar( + "SELECT eql_v3.jsonb_array_to_ore_block_u64_8_256('null'::jsonb) IS NULL", + ) + .fetch_one(&pool) + .await?; + assert!(is_null, "JSON null must yield NULL composite"); + + // Empty array → NULL composite. + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_ore_block_u64_8_256('[]'::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!(is_null, "empty JSON array must yield NULL composite"); + + // Single-element array → non-NULL composite with exactly 1 term. + let term_count: i32 = sqlx::query_scalar( + "SELECT cardinality((eql_v3.jsonb_array_to_ore_block_u64_8_256('[\"aabb\"]'::jsonb)).terms)", + ) + .fetch_one(&pool) + .await?; + assert_eq!( + term_count, 1, + "single-element array must yield exactly one term" + ); + + // Populated array → non-NULL composite with one term per element. + let term_count: i32 = sqlx::query_scalar( + "SELECT cardinality((eql_v3.jsonb_array_to_ore_block_u64_8_256('[\"aabb\",\"ccdd\",\"eeff\"]'::jsonb)).terms)", + ) + .fetch_one(&pool) + .await?; + assert_eq!( + term_count, 3, + "populated array must yield one term per element" + ); + + // Deliberate delta: a non-array JSON scalar returns NULL (not a raise). + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_ore_block_u64_8_256('5'::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!( + is_null, + "non-array JSON scalar must yield NULL (documented delta)" + ); + + // Same delta for a non-array JSON object — `jsonb_typeof` is 'object', so + // the CASE guard is not-true → ELSE NULL (not a raise). + let is_null: bool = + sqlx::query_scalar("SELECT eql_v3.jsonb_array_to_ore_block_u64_8_256('{}'::jsonb) IS NULL") + .fetch_one(&pool) + .await?; + assert!( + is_null, + "non-array JSON object must yield NULL (documented delta)" + ); + + Ok(()) +} From fd0bffc8e39e5b76e834a92fa93c1cd584ef5261 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 3 Jun 2026 20:58:17 +1000 Subject: [PATCH 89/93] feat(scalars): add eql_v3.date encrypted-domain type + temporal harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the `eql_v3.date` encrypted-domain scalar — the first non-integer ordered type — on top of the integer-agnostic harness refactor (parent PR). The SQL codegen needs no change (domains are jsonb-backed and token-driven); the work is one catalog row plus the temporal wiring the refactor enabled. Catalog (`eql-scalars`): `ScalarKind::Date` (`chrono::NaiveDate`), `Fixture::Date(&str)` (zero-dep ISO strings), `DATE_FIXTURES` (16 dates incl. the three matrix pivots), and `pub const DATE` appended to `CATALOG`, with mirrored panic / pivot-presence / token-order tests. Harness: an explicit `[temporal]` marker in the `scalar_types!` dispatch list drives the divergences from the integer path — the `impl ScalarType` for a temporal scalar is hand-written (chrono values can't be a `const` slice; pivots are explicit sentinels), and `scalar_fixture!` stamps a pivot-presence assert instead of the integer signed-extreme asserts. Adds `impl ScalarType for NaiveDate` (LazyLock-parsed values, `min_pivot`/`max_pivot` sentinels, quoted `to_sql_literal`), `EqlPlaintext for NaiveDate` (Cast::DATE), the sqlx `chrono` feature + direct `chrono` dep, the CHANGELOG entry, and a temporal-kinds note in the adding-a-scalar reference. --- CHANGELOG.md | 1 + Cargo.lock | 5 + crates/eql-scalars/src/lib.rs | 144 +++++++++++- crates/eql-tests-macros/src/lib.rs | 222 ++++++++++++++++-- .../adding-a-scalar-encrypted-domain-type.md | 34 ++- tasks/build.sh | 2 + tests/sqlx/Cargo.toml | 6 +- tests/sqlx/src/fixtures/eql_plaintext.rs | 50 +++- tests/sqlx/src/fixtures/scalar_fixture.rs | 93 ++++++-- tests/sqlx/src/scalar_domains.rs | 139 ++++++++++- tests/sqlx/src/scalar_types.rs | 7 +- 11 files changed, 627 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d15fa325..0c128bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Each entry that ships in a published release links to the PR that introduced it. - **`eql_v3` encrypted-domain schema, with the `int4` family as its first member.** Encrypted-domain type families now live in a new, additional `eql_v3` schema (the existing `eql_v2` schema is unchanged — it keeps the core types/operators and stays the documented public API). Four jsonb-backed domains for encrypted `int4` columns: `eql_v3.int4` (storage-only), `eql_v3.int4_eq` (`=` / `<>` via HMAC), and `eql_v3.int4_ord` / `eql_v3.int4_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms). Supported comparisons resolve to inlinable wrappers; the native `jsonb` operator surface reachable through domain fallback is blocked (raises rather than silently mis-resolving). Each domain's `CHECK` requires the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), and pins the payload version (`VALUE->>'v' = '2'`, matching `eql_v2._encrypted_check_v`) — so a missing key or wrong-version payload is rejected on insert or cast rather than surfacing later at query time. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. The extractors return the searchable-encrypted-metadata index-term types `eql_v3.hmac_256` / `eql_v3.ore_block_u64_8_256`, which `eql_v3` owns directly (see the self-contained `eql_v3` schema entry below). Why: a type-safe, per-capability encrypted integer column instead of the untyped `eql_v2_encrypted`, namespaced under its own schema. This is the reference scalar implementation for the generated domain family. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239), supersedes [#225](https://github.com/cipherstash/encrypt-query-language/pull/225)) - **`eql_v3.int2` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int2` columns — `eql_v3.int2` (storage-only), `eql_v3.int2_eq` (`=` / `<>` via HMAC), and `eql_v3.int2_ord` / `eql_v3.int2_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from the `int2` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `smallint` column, proving the scalar generator generalizes beyond the `int4` reference. ([#243](https://github.com/cipherstash/encrypt-query-language/pull/243)) - **`eql_v3.int8` encrypted-domain type family.** Four jsonb-backed domains for encrypted `int8` columns — `eql_v3.int8` (storage-only), `eql_v3.int8_eq` (`=` / `<>` via HMAC), and `eql_v3.int8_ord` / `eql_v3.int8_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from the `int8` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: a type-safe, per-capability encrypted `bigint` column, extending the scalar generator across the full 64-bit integer width. ([#253](https://github.com/cipherstash/encrypt-query-language/pull/253)) +- **`eql_v3.date` encrypted-domain type family.** Four jsonb-backed domains for encrypted `date` columns — `eql_v3.date` (storage-only), `eql_v3.date_eq` (`=` / `<>` via HMAC), and `eql_v3.date_ord` / `eql_v3.date_ord_ore` (also `<` `<=` `>` `>=` via ORE block terms, with `MIN` / `MAX` aggregates) — generated from the `date` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. Plaintexts encrypt under the `date` cast and compare via the same ORE block terms as the integer scalars (ORE is plaintext-agnostic — dates order like integers). Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. Why: the first **non-integer ordered** scalar encrypted-domain type — a type-safe, per-capability encrypted `date` column — proving the generator and SQLx test matrix generalize beyond fixed-width integers. ([#256](https://github.com/cipherstash/encrypt-query-language/pull/256)) - **Per-domain `MIN` / `MAX` aggregates for the encrypted-domain family.** `eql_v3.min(eql_v3._ord)` / `eql_v3.max(eql_v3._ord)` (and the `_ord_ore` twin) are generated for every ord-capable scalar variant, giving type-safe extrema on domain-typed columns — comparison routes through the variant's `<` / `>` operator (ORE block term, no decryption). The aggregates are declared `PARALLEL = SAFE` with a combine function (the state function itself — min/max are associative), so PostgreSQL can use partial/parallel aggregation on large `GROUP BY` workloads. Why: the new domain types previously had no equivalent of the composite-type aggregates. The existing `eql_v2.min(eql_v2_encrypted)` / `eql_v2.max(eql_v2_encrypted)` aggregates are **retained** and continue to work on `eql_v2_encrypted` columns; the per-domain aggregates are additive and coexist with them. ([#239](https://github.com/cipherstash/encrypt-query-language/pull/239)) - **Self-contained `eql_v3` schema + standalone `release/cipherstash-encrypt-v3.sql` installer.** The `eql_v3` encrypted-domain surface no longer depends on `eql_v2` at runtime: it now owns its own copies of the searchable-encrypted-metadata (SEM) index-term types — `eql_v3.hmac_256` and `eql_v3.ore_block_u64_8_256` (with its btree operator class) — so the `eql_v3.eq_term` / `eql_v3.ord_term` extractors return `eql_v3` types and no `eql_v2.` appears anywhere in the v3 SQL. The whole v3 surface relocated under a single `src/v3/` tree (`src/v3/sem/` for the hand-written SEM types, `src/v3/scalars/` for the generated domain families). A new build variant ships the `eql_v3` schema on its own as `release/cipherstash-encrypt-v3.sql`, installable into a database with no `eql_v2` present; a CI gate greps that artifact and its dependency closure to keep it `eql_v2`-free. Why: a clean foundation for the per-scalar encrypted-domain model to stand alone, ahead of it replacing the `eql_v2_encrypted` composite column type. This is additive — a new schema and a new artifact — and leaves `eql_v2` byte-for-byte unchanged. ([#255](https://github.com/cipherstash/encrypt-query-language/pull/255)) diff --git a/Cargo.lock b/Cargo.lock index 3f44eee0..bfecae6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1182,6 +1182,7 @@ name = "eql_tests" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "cipherstash-client", "eql-scalars", "eql-tests-macros", @@ -3651,6 +3652,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes", + "chrono", "crc", "crossbeam-queue", "either", @@ -3726,6 +3728,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", + "chrono", "crc", "digest 0.10.7", "dotenvy", @@ -3767,6 +3770,7 @@ dependencies = [ "base64", "bitflags", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -3801,6 +3805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index be910d2e..d1bab768 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -31,6 +31,11 @@ pub enum ScalarKind { Numeric, Text, Jsonb, + /// Calendar date (`chrono::NaiveDate`). Ordered like the integer kinds via + /// ORE, but string-backed (ISO-8601) at the catalog layer and with no i128 + /// range — so it is *not* `is_int()` and the bounded-numeric accessors + /// panic for it, exactly like the other non-integer kinds. + Date, } impl ScalarKind { @@ -50,6 +55,7 @@ impl ScalarKind { ScalarKind::Numeric => "numeric", ScalarKind::Text => "text", ScalarKind::Jsonb => "jsonb", + ScalarKind::Date => "chrono::NaiveDate", } } @@ -61,7 +67,7 @@ impl ScalarKind { ScalarKind::I64 => "i64::MIN", // Explicit (not `_`) so a future integer variant is a compile // error here rather than silently hitting the panic. - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { panic!("min_symbol is only defined for integer kinds") } } @@ -73,7 +79,7 @@ impl ScalarKind { ScalarKind::I16 => "i16::MAX", ScalarKind::I32 => "i32::MAX", ScalarKind::I64 => "i64::MAX", - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { panic!("max_symbol is only defined for integer kinds") } } @@ -83,7 +89,7 @@ impl ScalarKind { pub const fn zero_symbol(self) -> &'static str { match self { ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 => "0", - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { panic!("zero_symbol is only defined for integer kinds") } } @@ -96,7 +102,7 @@ impl ScalarKind { ScalarKind::I16 => i16::MIN as i128, ScalarKind::I32 => i32::MIN as i128, ScalarKind::I64 => i64::MIN as i128, - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { panic!("min_value is only defined for integer kinds") } } @@ -109,7 +115,7 @@ impl ScalarKind { ScalarKind::I16 => i16::MAX as i128, ScalarKind::I32 => i32::MAX as i128, ScalarKind::I64 => i64::MAX as i128, - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { panic!("max_value is only defined for integer kinds") } } @@ -249,6 +255,10 @@ pub enum Fixture { Numeric(&'static str), Text(&'static str), Jsonb(&'static str), + /// An ISO-8601 date string (`"1970-01-01"`). The catalog stays zero-dep, so + /// the string is parsed into a `chrono::NaiveDate` in the SQLx harness, not + /// here. Distinct by literal, like the other string-backed fixtures. + Date(&'static str), } impl Fixture { @@ -264,7 +274,7 @@ impl Fixture { Fixture::Max => Some(kind.max_value()), Fixture::Zero => Some(0), Fixture::Int(n) => Some(n), - Fixture::Numeric(_) | Fixture::Text(_) | Fixture::Jsonb(_) => None, + Fixture::Numeric(_) | Fixture::Text(_) | Fixture::Jsonb(_) | Fixture::Date(_) => None, } } @@ -276,7 +286,7 @@ impl Fixture { Fixture::Max => kind.max_symbol().to_string(), Fixture::Zero => kind.zero_symbol().to_string(), Fixture::Int(n) => n.to_string(), - Fixture::Numeric(s) | Fixture::Text(s) | Fixture::Jsonb(s) => { + Fixture::Numeric(s) | Fixture::Text(s) | Fixture::Jsonb(s) | Fixture::Date(s) => { format!("{s:?}") } } @@ -349,6 +359,7 @@ macro_rules! fixtures { (text; $($s:literal),* $(,)?) => { &[$(Fixture::Text($s)),*] }; (numeric; $($s:literal),* $(,)?) => { &[$(Fixture::Numeric($s)),*] }; (jsonb; $($s:literal),* $(,)?) => { &[$(Fixture::Jsonb($s)),*] }; + (date; $($s:literal),* $(,)?) => { &[$(Fixture::Date($s)),*] }; } /// int4 fixture plaintexts — verbatim from `tasks/codegen/types/int4.toml`. @@ -370,6 +381,20 @@ const INT8_FIXTURES: &[Fixture] = fixtures!(int i64; Min, N(-5000000000), N(-100), N(-1), Zero, N(1), N(2), N(5), N(10), N(17), N(25), N(42), N(50), N(100), N(250), N(1000), N(9999), N(5000000000), Max); +/// date fixture plaintexts — ISO-8601 (`YYYY-MM-DD`) strings, parsed into +/// `chrono::NaiveDate` in the SQLx harness (the catalog stays zero-dep). The +/// three temporal pivots MUST be present verbatim: `"1900-01-01"` (min_pivot), +/// `"1970-01-01"` (zero = `NaiveDate::default()`), and `"2099-12-31"` +/// (max_pivot) — the matrix fetches each one's ciphertext via +/// `fetch_fixture_payload`, which fails loudly if a row is absent. The interior +/// dates span varied years/months so range operators yield distinguishable +/// counts. All distinct. +const DATE_FIXTURES: &[Fixture] = fixtures!(date; + "1900-01-01", "1950-07-15", "1969-12-31", "1970-01-01", "1970-01-02", + "1980-02-29", "1991-11-09", "1999-12-31", "2000-01-01", "2004-02-29", + "2012-06-30", "2016-03-15", "2020-10-21", "2024-02-29", "2038-01-19", + "2099-12-31"); + const INT4: ScalarSpec = ScalarSpec { token: "int4", kind: ScalarKind::I32, @@ -391,13 +416,28 @@ const INT8: ScalarSpec = ScalarSpec { fixtures: INT8_FIXTURES, }; +/// `date` — an ordered, non-integer scalar. Reuses `ORDERED_INT_DOMAINS` (the +/// four-domain ordered shape is identical to the integer scalars); only the +/// kind and fixtures differ. +/// +/// Public (unlike the integer specs) because the SQLx harness reads +/// `DATE.fixtures` directly to parse the ISO strings into `chrono::NaiveDate` +/// at runtime — there is no `DATE_VALUES` const (chrono is not `const`-friendly +/// and `eql-scalars` stays zero-dep, so no typed slice is materialised here). +pub const DATE: ScalarSpec = ScalarSpec { + token: "date", + kind: ScalarKind::Date, + domains: ORDERED_INT_DOMAINS, + fixtures: DATE_FIXTURES, +}; + /// The scalar catalog — the single source of truth. Order is significant (it /// drives generation order). New types are appended as their SQL surface lands. -pub const CATALOG: &[ScalarSpec] = &[INT4, INT2, INT8]; +pub const CATALOG: &[ScalarSpec] = &[INT4, INT2, INT8, DATE]; /// Materialise an integer scalar's fixtures into a typed `&'static` slice at /// compile time. This is the **single-sourced** plaintext list the SQLx test -/// matrix reads as `ScalarType::FIXTURE_VALUES` and the fixture generator +/// matrix reads via `ScalarType::fixture_values()` and the fixture generator /// encrypts — derived from the same `CATALOG` row that drives SQL generation, /// so the oracle cannot drift from the fixture. (It replaces the old generated, /// committed `tests/sqlx/src/fixtures/_values.rs` — a Rust source of truth no @@ -478,6 +518,7 @@ mod rust_tests { assert!(!ScalarKind::Numeric.is_int()); assert!(!ScalarKind::Text.is_int()); assert!(!ScalarKind::Jsonb.is_int()); + assert!(!ScalarKind::Date.is_int()); } // Pin that the bounded-numeric accessors panic (with message) on non-int kinds. @@ -522,6 +563,46 @@ mod rust_tests { assert_eq!(ScalarKind::I64.min_value(), -9_223_372_036_854_775_808_i128); assert_eq!(ScalarKind::I64.max_value(), 9_223_372_036_854_775_807_i128); } + + #[test] + fn date_maps_to_naive_date() { + // Ordered, non-integer kind: it carries a rust type but no i128 range, + // so it is not `is_int()` and the bounded accessors panic (below). + assert_eq!(ScalarKind::Date.rust_type(), "chrono::NaiveDate"); + assert!(!ScalarKind::Date.is_int()); + } + + // The bounded-numeric accessors panic on Date exactly as on the other + // non-integer kinds — Date is not an integer kind. + #[test] + #[should_panic(expected = "min_symbol is only defined for integer kinds")] + fn min_symbol_panics_on_date() { + ScalarKind::Date.min_symbol(); + } + + #[test] + #[should_panic(expected = "max_symbol is only defined for integer kinds")] + fn max_symbol_panics_on_date() { + ScalarKind::Date.max_symbol(); + } + + #[test] + #[should_panic(expected = "zero_symbol is only defined for integer kinds")] + fn zero_symbol_panics_on_date() { + ScalarKind::Date.zero_symbol(); + } + + #[test] + #[should_panic(expected = "min_value is only defined for integer kinds")] + fn min_value_panics_on_date() { + ScalarKind::Date.min_value(); + } + + #[test] + #[should_panic(expected = "max_value is only defined for integer kinds")] + fn max_value_panics_on_date() { + ScalarKind::Date.max_value(); + } } #[cfg(test)] @@ -682,6 +763,10 @@ mod fixture_tests { Fixture::Jsonb(r#"{"a":1}"#).numeric_value(ScalarKind::Jsonb), None ); + assert_eq!( + Fixture::Date("1970-01-01").numeric_value(ScalarKind::Date), + None + ); } #[test] @@ -718,6 +803,10 @@ mod fixture_tests { Fixture::Jsonb(r#"{"a":1}"#).render_literal(ScalarKind::Jsonb), r#""{\"a\":1}""# ); + assert_eq!( + Fixture::Date("1970-01-01").render_literal(ScalarKind::Date), + "\"1970-01-01\"" + ); } #[test] @@ -741,6 +830,11 @@ mod fixture_tests { assert_eq!(NUMS, &[Fixture::Numeric("0.1"), Fixture::Numeric("-2.5")]); const JSONS: &[Fixture] = fixtures!(jsonb; r#"{"a":1}"#); assert_eq!(JSONS, &[Fixture::Jsonb(r#"{"a":1}"#)]); + const DATES: &[Fixture] = fixtures!(date; "1970-01-01", "2099-12-31"); + assert_eq!( + DATES, + &[Fixture::Date("1970-01-01"), Fixture::Date("2099-12-31")] + ); } #[test] @@ -773,9 +867,33 @@ mod catalog_tests { } #[test] - fn catalog_has_int4_int2_int8_in_order() { + fn catalog_has_int4_int2_int8_date_in_order() { let tokens: Vec<&str> = CATALOG.iter().map(|s| s.token).collect(); - assert_eq!(tokens, vec!["int4", "int2", "int8"]); + assert_eq!(tokens, vec!["int4", "int2", "int8", "date"]); + } + + /// The three temporal matrix pivots must be present verbatim in DATE's + /// fixture strings — `fetch_fixture_payload` fetches each one's ciphertext, + /// failing loudly if absent. The integer `fixtures_include_min_max_and_zero` + /// invariant filters `is_int()` and skips date, so this is its temporal + /// analogue. + #[test] + fn temporal_fixtures_include_pivot_plaintexts() { + let date = scalar("date"); + let strings: Vec<&str> = date + .fixtures + .iter() + .filter_map(|f| match f { + Fixture::Date(s) => Some(*s), + _ => None, + }) + .collect(); + for pivot in ["1900-01-01", "1970-01-01", "2099-12-31"] { + assert!( + strings.contains(&pivot), + "date fixtures missing temporal pivot {pivot}" + ); + } } #[test] @@ -901,7 +1019,9 @@ mod invariant_tests { fn distinct_key(f: Fixture, kind: ScalarKind) -> DistinctKey { match f { - Fixture::Numeric(s) | Fixture::Text(s) | Fixture::Jsonb(s) => DistinctKey::Str(s), + Fixture::Numeric(s) | Fixture::Text(s) | Fixture::Jsonb(s) | Fixture::Date(s) => { + DistinctKey::Str(s) + } _ => DistinctKey::Num( f.numeric_value(kind) .expect("sentinel/Int fixtures resolve to a number"), diff --git a/crates/eql-tests-macros/src/lib.rs b/crates/eql-tests-macros/src/lib.rs index b88a0677..e252bca0 100644 --- a/crates/eql-tests-macros/src/lib.rs +++ b/crates/eql-tests-macros/src/lib.rs @@ -32,15 +32,74 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{Ident, Token, Type}; +use syn::{bracketed, Ident, Token, Type}; -/// One `token => rust_type` entry. +/// One `token => rust_type` entry, with an optional trailing `[temporal]` flag. struct ScalarEntry { /// Postgres type token (`int4`); also the fixture/domain suffix and the /// matrix `suite` ident. token: Ident, /// Rust plaintext type (`i32`). rust_type: Type, + /// Whether this entry is a **temporal** (chrono-backed) scalar rather than a + /// fixed-width integer. Declared explicitly via a trailing `[temporal]` + /// marker in the dispatch list (`date => chrono::NaiveDate [temporal]`) + /// rather than sniffed from the Rust type path — so a temporal type that + /// isn't chrono-spelled, or a non-temporal type whose path happens to + /// contain `DateTime`, cannot be misclassified. + temporal: bool, +} + +/// The recognised optional entry markers, written in `[brackets]` after the +/// rust type (`date => chrono::NaiveDate [temporal]`). `temporal` is the only +/// one today; a new marker is added here so the accepted set stays a single +/// source of truth — `parse_optional_marker` validates against this slice and +/// the rejection message lists it verbatim. +const SUPPORTED_MARKERS: &[&str] = &["temporal"]; + +/// Parse the optional trailing `[marker]` on a scalar entry, returning whether +/// the `temporal` marker was present. +/// +/// Absent brackets → `false` (an ordinary integer scalar). When brackets are +/// present they must hold exactly one recognised identifier: an unknown marker +/// (`[temporial]`), empty brackets (`[]`), or trailing junk (`[temporal foo]`) +/// are all hard parse errors. The whole point of the explicit marker is that a +/// typo fails loudly rather than silently defaulting an entry to integer, so +/// the parse is strict on every malformed shape, not just unknown names. +fn parse_optional_marker(input: ParseStream) -> syn::Result { + if !input.peek(syn::token::Bracket) { + return Ok(false); + } + let content; + bracketed!(content in input); + + let marker: Ident = content.parse()?; + // Reject anything after the single marker ident (`[temporal foo]`, + // `[temporal, bar]`) — otherwise `bracketed!` would silently drop it. + if !content.is_empty() { + let rest: TokenStream2 = content.parse()?; + return Err(syn::Error::new_spanned( + rest, + "expected a single marker identifier, e.g. `[temporal]`", + )); + } + + let name = marker.to_string(); + if !SUPPORTED_MARKERS.contains(&name.as_str()) { + let supported = SUPPORTED_MARKERS + .iter() + .map(|m| format!("`{m}`")) + .collect::>() + .join(", "); + return Err(syn::Error::new( + marker.span(), + format!("unknown scalar marker `{name}`; supported markers: {supported}"), + )); + } + + // Only `temporal` flips the temporal flag; a future non-temporal marker + // would pass validation above but leave this `false`. + Ok(name == "temporal") } impl Parse for ScalarEntry { @@ -48,7 +107,28 @@ impl Parse for ScalarEntry { let token: Ident = input.parse()?; input.parse::]>()?; let rust_type: Type = input.parse()?; - Ok(ScalarEntry { token, rust_type }) + let temporal = parse_optional_marker(input)?; + Ok(ScalarEntry { + token, + rust_type, + temporal, + }) + } +} + +impl ScalarEntry { + /// Whether this entry is a **temporal** (chrono-backed) scalar rather than a + /// fixed-width integer, as declared by the `[temporal]` marker. It drives + /// two divergences: + /// + /// 1. The `impl ScalarType` for a temporal scalar is **hand-written** in + /// `scalar_domains.rs` (chrono values can't be a `const` slice and the + /// pivots are explicit sentinels), so `emit_scalar_type_impls` skips it. + /// 2. The integer-only fixture asserts (`::MIN`, `contains(&0)`, + /// `any(|v| v < 0)`) don't typecheck for a date, so `scalar_fixture!` + /// stamps a temporal (pivot-presence) variant instead. + fn is_temporal(&self) -> bool { + self.temporal } } @@ -78,7 +158,9 @@ fn values_const_ident(token: &Ident) -> Ident { /// Emit one `impl ScalarType for ` per entry. See /// [`emit_scalar_type_impls`]. fn scalar_type_impls_tokens(list: &ScalarList) -> TokenStream2 { - let impls = list.entries.iter().map(|e| { + // Temporal scalars hand-write their `impl ScalarType` (see `is_temporal`); + // only integer scalars get a macro-generated impl. + let impls = list.entries.iter().filter(|e| !e.is_temporal()).map(|e| { let token_str = e.token.to_string(); let rust_type = &e.rust_type; let values = values_const_ident(&e.token); @@ -88,10 +170,7 @@ fn scalar_type_impls_tokens(list: &ScalarList) -> TokenStream2 { /// The catalog `eql_scalars::*_VALUES` list — the same values /// the fixture generator encrypts, so the oracle can't drift - /// from the fixture. A method (not a `const`) so non-integer - /// scalars whose values can't be `const`-constructed can return - /// a borrow of a lazily-built `Vec`; integer scalars hand back - /// their catalog const directly. + /// from the fixture. fn fixture_values() -> &'static [#rust_type] { ::eql_scalars::#values } @@ -117,16 +196,33 @@ fn scalar_fixture_modules_tokens(list: &ScalarList) -> TokenStream2 { let mods = list.entries.iter().map(|e| { let token_str = e.token.to_string(); let rust_type = &e.rust_type; - let values = values_const_ident(&e.token); let mod_ident = format_ident!("eql_v2_{}", e.token); let fixture_name = format!("eql_v2_{}", token_str); - quote! { - #[doc = concat!("`eql_v2_", #token_str, "` scalar fixture — generated by `scalar_types!`.")] - pub mod #mod_ident { - use ::eql_scalars::#values as VALUES; - // `scalar_fixture!` is `#[macro_export]`ed by `eql-tests`; - // these modules expand into that lib, so `crate::` resolves it. - crate::scalar_fixture!(#fixture_name, #rust_type, VALUES); + if e.is_temporal() { + // Temporal scalars have no `eql_scalars::_VALUES` const (chrono + // is not `const`-friendly). The values come from the harness + // accessor (`_values()`), and the fixture stamps the + // `temporal` kind so the integer-only signed-extreme asserts are + // replaced by a pivot-presence assert. The accessor name mirrors + // the token (`date` -> `date_values`). + let values_fn = format_ident!("{}_values", e.token); + quote! { + #[doc = concat!("`eql_v2_", #token_str, "` temporal scalar fixture — generated by `scalar_types!`.")] + pub mod #mod_ident { + use crate::scalar_domains::#values_fn as values; + crate::scalar_fixture!(temporal, #fixture_name, #rust_type, values()); + } + } + } else { + let values = values_const_ident(&e.token); + quote! { + #[doc = concat!("`eql_v2_", #token_str, "` scalar fixture — generated by `scalar_types!`.")] + pub mod #mod_ident { + use ::eql_scalars::#values as VALUES; + // `scalar_fixture!` is `#[macro_export]`ed by `eql-tests`; + // these modules expand into that lib, so `crate::` resolves it. + crate::scalar_fixture!(int, #fixture_name, #rust_type, VALUES); + } } } }); @@ -284,6 +380,100 @@ mod tests { assert!(out.contains("crate :: scalar_fixture !")); assert!(out.contains(r#""eql_v2_int4""#)); assert!(out.contains(":: eql_scalars :: INT4_VALUES as VALUES")); + // Integer entries stamp the `int` kind discriminator. + assert!(out.contains("int ,")); + } + + #[test] + fn temporal_entry_skips_impl_and_stamps_temporal_fixture() { + let list = + syn::parse_str::("int4 => i32, date => chrono::NaiveDate [temporal],") + .unwrap(); + // Impl emitter skips the temporal entry (hand-written impl). + let impls = norm(&scalar_type_impls_tokens(&list)); + assert!(impls.contains("impl ScalarType for i32")); + assert!(!impls.contains("NaiveDate")); + // Fixture-module emitter stamps the temporal kind + harness accessor. + let mods = norm(&scalar_fixture_modules_tokens(&list)); + assert!(mods.contains("pub mod eql_v2_date")); + assert!(mods.contains("temporal ,")); + assert!(mods.contains("date_values")); + // Matrix + dispatch emitters include the temporal entry like any other. + let suites = norm(&scalar_matrix_suites_tokens(&list)); + assert!(suites.contains("pub mod date")); + assert!(suites.contains("scalar = chrono :: NaiveDate")); + let dispatch = norm(&fixture_dispatch_tokens(&list)); + assert!(dispatch.contains(r#""date" =>"#)); + } + + /// Parse a single entry, asserting it parses, and return whether it is + /// temporal. Keeps the per-shape assertions below to one line each. + fn parse_entry_is_temporal(src: &str) -> bool { + syn::parse_str::(src) + .unwrap_or_else(|e| panic!("`{src}` should parse: {e}")) + .is_temporal() + } + + /// Parse a single entry expecting a parse error, returning the message. + fn parse_entry_err(src: &str) -> String { + match syn::parse_str::(src) { + Ok(_) => panic!("`{src}` should have failed to parse"), + Err(e) => e.to_string(), + } + } + + #[test] + fn no_marker_is_integer() { + // No brackets → integer, even when the type path mentions chrono: + // temporal-ness is declared, never inferred from the rust type. + assert!(!parse_entry_is_temporal("int4 => i32")); + assert!(!parse_entry_is_temporal("date => chrono::NaiveDate")); + } + + #[test] + fn temporal_marker_sets_the_flag() { + assert!(parse_entry_is_temporal( + "date => chrono::NaiveDate [temporal]" + )); + // Marker binds to its own entry, not the next one, across a list. + let list = + syn::parse_str::("date => chrono::NaiveDate [temporal], int4 => i32,") + .unwrap(); + assert!(list.entries[0].is_temporal()); + assert!(!list.entries[1].is_temporal()); + } + + #[test] + fn unknown_marker_errors_and_lists_the_supported_set() { + let msg = parse_entry_err("date => chrono::NaiveDate [temporial]"); + // Names the offending marker and the supported set, so the message is + // actionable rather than just "parse error". + assert!(msg.contains("unknown scalar marker"), "got: {msg}"); + assert!( + msg.contains("temporial"), + "should name the bad marker: {msg}" + ); + assert!( + msg.contains("`temporal`"), + "should list supported markers: {msg}" + ); + } + + #[test] + fn empty_marker_brackets_error() { + // `[]` has no marker ident to parse — a malformed entry, not a no-op. + let msg = parse_entry_err("date => chrono::NaiveDate []"); + assert!(!msg.is_empty()); + } + + #[test] + fn trailing_junk_in_marker_brackets_errors() { + // Regression guard: `[temporal foo]` / `[temporal, bar]` must NOT be + // silently accepted as `temporal` with the extra tokens dropped. + let msg = parse_entry_err("date => chrono::NaiveDate [temporal foo]"); + assert!(msg.contains("single marker identifier"), "got: {msg}"); + let msg = parse_entry_err("date => chrono::NaiveDate [temporal, bar]"); + assert!(msg.contains("single marker identifier"), "got: {msg}"); } #[test] diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index e4ae242b..95d73a98 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -175,11 +175,41 @@ int_values!(INT4_VALUES, i32, INT4); ``` Both consumers reference that single symbol — the fixture generator -(`fixtures::eql_v2_::spec`) and the matrix oracle's `FIXTURE_VALUES` — so the -oracle cannot drift from the values the generator encrypts. There is no +(`fixtures::eql_v2_::spec`) and the matrix oracle's `fixture_values()` — so +the oracle cannot drift from the values the generator encrypts. There is no committed `_values.rs`: a Rust source of truth does not round-trip through generated Rust. Pin the exact materialised list with a `values_tests` assertion. +### Temporal kinds — string-backed fixtures and the pivot trait + +A **temporal** scalar (the `date` reference; `timestamptz` follows the same +shape) is *ordered but non-integer*, so it diverges from the integer path in +three places — all in the catalog/harness, never the SQL codegen (domains stay +jsonb-backed and token-driven): + +- **String-backed fixtures.** `eql-scalars` stays zero-dependency, so the + catalog stores ISO strings (`Fixture::Date("1970-01-01")`), not `chrono` + values. There is **no** `int_values!` / `_VALUES` const for a temporal kind + (chrono constructors are not `const`). The SQLx harness parses the catalog + strings into a `LazyLock>` and exposes them via a + `date_values()` accessor; `ScalarType::fixture_values()` returns a borrow of + that. The fixtures must include the three pivot plaintexts verbatim — for + `date`: `"1900-01-01"` (min), `"1970-01-01"` (zero = `NaiveDate::default()`), + `"2099-12-31"` (max) — guarded by `temporal_fixtures_include_pivot_plaintexts`. +- **The pivot trait, not `Self::MIN`/`MAX`.** `ScalarType::fixture_values()` is a + method (not a `const`), and the comparison pivots come from + `ScalarType::min_pivot()` / `max_pivot()` (zero stays `Default::default()`). + Integer impls return `Self::MIN`/`Self::MAX` (emitted by the proc-macro); + temporal impls return explicit sentinel dates and are **hand-written** in + `scalar_domains.rs` (the macro emits only integer impls). `to_sql_literal` is + overridden to single-quote the value (`'1970-01-01'`), since a bare `Display` + date is not a valid SQL literal. +- **The sqlx `chrono` feature.** The test crate enables sqlx's `chrono` feature + (and depends on `chrono` directly) so `Encode`/`Decode`/`Type` resolve for + `NaiveDate`. The integer-only fixture asserts (`::MIN`, `contains(&0)`, + `v < 0`) are stamped only for `int` entries; temporal entries stamp a + pivot-presence assert instead (the `kind` discriminator on `scalar_fixture!`). + --- ## 3. Wire the SQLx matrix oracle diff --git a/tasks/build.sh b/tasks/build.sh index 4d3d94e0..98dee8db 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash #MISE description="Build SQL into single release file" #MISE alias="b" +#MISE sources=["src/**/*.sql", "tasks/pin_search_path.sql", "tasks/uninstall.sql", "tasks/uninstall-protect.sql", "crates/eql-scalars/src/**/*.rs", "crates/eql-codegen/src/**/*.rs"] +#MISE outputs=["release/cipherstash-encrypt.sql","release/cipherstash-encrypt-uninstall.sql","release/cipherstash-encrypt-protect.sql","release/cipherstash-encrypt-protect-uninstall.sql"] #USAGE flag "--version " help="Specify release version of EQL" default="DEV" #!/bin/bash diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index 208f6d86..e67941c1 100644 --- a/tests/sqlx/Cargo.toml +++ b/tests/sqlx/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros"] } +sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros", "chrono"] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -12,6 +12,10 @@ anyhow = "1" hex = "0.4" jsonschema = { version = "0.46.4", default-features = false } cipherstash-client = { version = "0.35", features = ["tokio"] } +# chrono is already in the tree transitively (cipherstash-client / ore-rs); pin +# it as a direct dependency so the harness can name `chrono::NaiveDate` for the +# `date` scalar (Encode/Decode/Type come from the sqlx `chrono` feature above). +chrono = { version = "0.4", default-features = false } paste = "1" eql-scalars = { path = "../../crates/eql-scalars" } eql-tests-macros = { path = "../../crates/eql-tests-macros" } diff --git a/tests/sqlx/src/fixtures/eql_plaintext.rs b/tests/sqlx/src/fixtures/eql_plaintext.rs index 72c5b2a0..65c9f43e 100644 --- a/tests/sqlx/src/fixtures/eql_plaintext.rs +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -56,6 +56,7 @@ impl PlaintextSqlType { pub const INTEGER: PlaintextSqlType = PlaintextSqlType("integer"); pub const SMALLINT: PlaintextSqlType = PlaintextSqlType("smallint"); pub const BIGINT: PlaintextSqlType = PlaintextSqlType("bigint"); + pub const DATE: PlaintextSqlType = PlaintextSqlType("date"); pub fn as_str(&self) -> &'static str { self.0 @@ -70,30 +71,32 @@ impl fmt::Display for PlaintextSqlType { /// The EQL `cast_as` for a scalar kind, drawn from the `Cast` allowlist. /// -/// Only the integer kinds have `EqlPlaintext` impls, so only those resolve; -/// the non-integer kinds mirror the `eql_scalars` accessor convention and -/// `panic!`, since no impl can ever reach them. +/// Only the wired kinds (the integer kinds plus `Date`) have `EqlPlaintext` +/// impls, so only those resolve; the remaining kinds mirror the `eql_scalars` +/// accessor convention and `panic!`, since no impl can ever reach them. const fn cast_for_kind(kind: ScalarKind) -> Cast { match kind { ScalarKind::I32 => Cast::INT, ScalarKind::I16 => Cast::SMALL_INT, ScalarKind::I64 => Cast::BIG_INT, + ScalarKind::Date => Cast::DATE, ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { - panic!("EqlPlaintext is only implemented for integer scalar kinds") + panic!("EqlPlaintext is only implemented for the wired scalar kinds") } } } /// The `plaintext` oracle column SQL type for a scalar kind, drawn from the -/// `PlaintextSqlType` allowlist. As with `cast_for_kind`, only integer kinds -/// resolve. +/// `PlaintextSqlType` allowlist. As with `cast_for_kind`, only the wired kinds +/// (integers plus `Date`) resolve. const fn plaintext_sql_type_for_kind(kind: ScalarKind) -> PlaintextSqlType { match kind { ScalarKind::I32 => PlaintextSqlType::INTEGER, ScalarKind::I16 => PlaintextSqlType::SMALLINT, ScalarKind::I64 => PlaintextSqlType::BIGINT, + ScalarKind::Date => PlaintextSqlType::DATE, ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb => { - panic!("EqlPlaintext is only implemented for integer scalar kinds") + panic!("EqlPlaintext is only implemented for the wired scalar kinds") } } } @@ -103,6 +106,7 @@ mod sealed { impl Sealed for i32 {} impl Sealed for i16 {} impl Sealed for i64 {} + impl Sealed for chrono::NaiveDate {} } /// A Rust type usable as a fixture `plaintext` value, carrying its EQL cast @@ -154,6 +158,14 @@ impl EqlPlaintext for i64 { } } +impl EqlPlaintext for chrono::NaiveDate { + const KIND: ScalarKind = ScalarKind::Date; + + fn to_plaintext(&self) -> Plaintext { + Plaintext::NaiveDate(Some(*self)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -223,4 +235,28 @@ mod tests { other => panic!("expected Plaintext::BigInt(Some(42)), got {other:?}"), } } + + #[test] + fn naive_date_casts_to_date() { + assert_eq!(::CAST.as_str(), "date"); + } + + #[test] + fn naive_date_plaintext_sql_type_is_date() { + assert_eq!( + ::PLAINTEXT_SQL_TYPE.as_str(), + "date" + ); + } + + #[test] + fn naive_date_to_plaintext_wraps_in_naive_date_variant() { + // A NaiveDate must lift into the NaiveDate variant so the fixture + // driver encrypts it under the `date` cast. + let d = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(); + match d.to_plaintext() { + Plaintext::NaiveDate(Some(value)) => assert_eq!(value, d), + other => panic!("expected Plaintext::NaiveDate(Some(1970-01-01)), got {other:?}"), + } + } } diff --git a/tests/sqlx/src/fixtures/scalar_fixture.rs b/tests/sqlx/src/fixtures/scalar_fixture.rs index 1232b8b2..d6a955ab 100644 --- a/tests/sqlx/src/fixtures/scalar_fixture.rs +++ b/tests/sqlx/src/fixtures/scalar_fixture.rs @@ -13,37 +13,28 @@ /// Stamp out the `spec()` builder, the `fixture-gen` generator test, and the /// property-test module for a scalar fixture. /// +/// The leading **kind** discriminator (`int` / `temporal`) selects which +/// property asserts are stamped — the rest of the expansion is identical: +/// +/// - `int` — signed-extreme asserts (`<$ty>::MIN`/`MAX`, `contains(&0)`, +/// `any(|v| v < 0)`). These typecheck only for integer plaintexts. +/// - `temporal` — a pivot-presence assert (`min_pivot`/`max_pivot`/zero from the +/// `ScalarType` impl all appear in the values). `<$ty>::MIN` / `< 0` don't +/// exist for a `chrono::NaiveDate`, so the integer asserts can't be reused. +/// /// - `$name` — the fixture name (`"eql_v2_int2"`), drives every derived path. -/// - `$ty` — the Rust plaintext type (`i16`); `<$ty>::MIN`/`MAX` supply the -/// signed-extreme assertions. -/// - `$values` — the catalog-materialised value const (`eql_scalars::INT2_VALUES`). +/// - `$ty` — the Rust plaintext type (`i16` / `chrono::NaiveDate`). +/// - `$values` — the value source: the catalog const (`eql_scalars::INT2_VALUES`) +/// for integers, or the harness accessor (`date_values()`) for temporal. /// /// Indexes are fixed to `Unique` (HMAC, drives `=` / `<>`) and `Ore` (ORE /// block terms, drives `<` `<=` `>` `>=`) with a committed `jsonb` payload — /// the shape shared by every ordered scalar domain. #[macro_export] macro_rules! scalar_fixture { - ($name:literal, $ty:ty, $values:expr $(,)?) => { - /// The complete fixture definition. `IndexKind::Unique` drives `=` / - /// `<>` (HMAC); `IndexKind::Ore` drives `<` `<=` `>` `>=` (ORE block - /// terms). - pub fn spec() -> $crate::fixtures::FixtureSpec<'static, $ty> { - $crate::fixtures::FixtureSpec::new($name) - .with_index($crate::fixtures::IndexKind::Unique) - .with_index($crate::fixtures::IndexKind::Ore) - .with_column_type("jsonb") - .with_values($values) - } - - /// The generator. Gated by `fixture-gen` so `cargo test` never compiles - /// it; `#[ignore]` is a second guard. Run via - /// `mise run fixture:generate`. - #[cfg(feature = "fixture-gen")] - #[tokio::test] - #[ignore = "generator — run via `mise run fixture:generate`"] - async fn generate() -> anyhow::Result<()> { - spec().run().await - } + // Integer scalars: signed-extreme property asserts. + (int, $name:literal, $ty:ty, $values:expr $(,)?) => { + $crate::scalar_fixture!(@common $name, $ty, $values); #[cfg(test)] mod tests { @@ -79,4 +70,58 @@ macro_rules! scalar_fixture { } } }; + + // Temporal scalars: pivot-presence property assert (no signed extremes). + (temporal, $name:literal, $ty:ty, $values:expr $(,)?) => { + $crate::scalar_fixture!(@common $name, $ty, $values); + + #[cfg(test)] + mod tests { + use super::*; + use $crate::scalar_domains::ScalarType; + + #[test] + fn spec_is_complete() { + assert!(spec().check_complete().is_ok()); + } + + #[test] + fn spec_includes_pivots() { + // The three matrix pivots (min/max/zero) must be present in the + // fixture — `fetch_fixture_payload` fetches each at test time. + let spec = spec(); + let values = spec.values(); + let min = <$ty as ScalarType>::min_pivot(); + let max = <$ty as ScalarType>::max_pivot(); + let zero: $ty = ::core::default::Default::default(); + assert!(values.contains(&min), "spec must include min_pivot {min:?}"); + assert!(values.contains(&max), "spec must include max_pivot {max:?}"); + assert!(values.contains(&zero), "spec must include zero pivot {zero:?}"); + } + } + }; + + // Shared expansion: the `spec()` builder + the gated generator test. + (@common $name:literal, $ty:ty, $values:expr) => { + /// The complete fixture definition. `IndexKind::Unique` drives `=` / + /// `<>` (HMAC); `IndexKind::Ore` drives `<` `<=` `>` `>=` (ORE block + /// terms). + pub fn spec() -> $crate::fixtures::FixtureSpec<'static, $ty> { + $crate::fixtures::FixtureSpec::new($name) + .with_index($crate::fixtures::IndexKind::Unique) + .with_index($crate::fixtures::IndexKind::Ore) + .with_column_type("jsonb") + .with_values($values) + } + + /// The generator. Gated by `fixture-gen` so `cargo test` never compiles + /// it; `#[ignore]` is a second guard. Run via + /// `mise run fixture:generate`. + #[cfg(feature = "fixture-gen")] + #[tokio::test] + #[ignore = "generator — run via `mise run fixture:generate`"] + async fn generate() -> anyhow::Result<()> { + spec().run().await + } + }; } diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index 6c3d9a0c..69453f59 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -12,6 +12,7 @@ use anyhow::{bail, Context, Result}; use sqlx::PgPool; use std::fmt::{Debug, Display}; +use std::sync::LazyLock; /// One impl per scalar type. Two `const`s and the rest defaults. pub trait ScalarType: @@ -39,9 +40,11 @@ pub trait ScalarType: /// (`[0]`, `[len / 2]`) without sorting. A lazily-built `Vec` impl /// must therefore be built deterministically in that same order. /// - /// A method rather than a `const` so a scalar whose values can't be - /// `const`-constructed can return a borrow of a lazily-built `Vec`; - /// integer scalars return their `eql_scalars::_VALUES` const directly. + /// A method rather than a `const` because non-integer scalars (e.g. + /// `chrono::NaiveDate`, whose `from_ymd_opt` is not `const`) cannot be + /// materialised into a const slice; the harness builds those into a + /// `LazyLock>` and returns a borrow of it (see `date_values`). + /// Integer scalars return their `eql_scalars::_VALUES` const directly. /// /// For types driven by `ordered_numeric_matrix!`, the values MUST /// include the three pivots (`min_pivot()`, `max_pivot()`, and zero @@ -51,14 +54,15 @@ pub trait ScalarType: fn fixture_values() -> &'static [Self]; /// The low comparison pivot swept by the correctness / cross-shape arms. - /// Integer scalars return `Self::MIN`. A trait method (rather than the - /// matrix referencing `Self::MIN` directly) so a scalar without an inherent - /// `::MIN` const can supply an explicit sentinel; the returned value must be - /// present verbatim in `fixture_values()`. + /// Integer scalars return `Self::MIN`; temporal scalars return an explicit + /// sentinel (e.g. `1900-01-01`). A trait method rather than `Self::MIN` + /// because `chrono::DateTime` exposes `MAX_UTC`, not an inherent + /// `::MAX` const. The pivot must be present verbatim in `fixture_values()`. fn min_pivot() -> Self; - /// The high comparison pivot. Integer scalars return `Self::MAX`. Must be - /// present verbatim in `fixture_values()`. + /// The high comparison pivot. Integer scalars return `Self::MAX`; temporal + /// scalars return an explicit sentinel (e.g. `2099-12-31`). Must be present + /// verbatim in `fixture_values()`. fn max_pivot() -> Self; /// `fixtures.eql_v2_`. @@ -94,13 +98,122 @@ pub trait ScalarType: } } -// The per-type `impl ScalarType` blocks (one per scalar, each carrying its -// `PG_TYPE` token string, `fixture_values() = eql_scalars::_VALUES`, and -// `min_pivot()`/`max_pivot()` = `Self::MIN`/`Self::MAX`) are generated from the -// single harness list in `scalar_types.rs`. To add a type, add a +// The per-type `impl ScalarType` blocks for the **integer** scalars (each +// carrying its `PG_TYPE` token, `fixture_values() = eql_scalars::_VALUES`, +// and `min_pivot()`/`max_pivot()` = `Self::MIN`/`Self::MAX`) are generated from +// the single harness list in `scalar_types.rs`. To add an integer type, add a // `token => rust_type` line there — not an impl here. +// +// Temporal scalars (`chrono::NaiveDate`, and `DateTime` in the stacked +// timestamptz PR) are hand-written below instead: their fixture values cannot be +// a `const` slice (chrono constructors are not `const`), and their pivots are +// explicit sentinels rather than `Self::MIN`/`Self::MAX`. The macro emits only +// integer impls. crate::scalar_types!(scalar_type_impls); +/// Typed `chrono::NaiveDate` fixture values, parsed once from `date`'s catalog +/// row. The catalog stores ISO strings (zero-dep); parsing into `NaiveDate` +/// lives here. `from_ymd_opt` is not `const`, so this cannot be a const slice — +/// hence the `LazyLock>` + `fixture_values()`-returns-a-borrow shape that +/// the const→fn trait change exists to allow. +static DATE_VALUES_CELL: LazyLock> = LazyLock::new(|| { + eql_scalars::DATE + .fixtures + .iter() + .map(|f| match f { + eql_scalars::Fixture::Date(s) => chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") + .unwrap_or_else(|e| panic!("invalid date fixture {s:?}: {e}")), + other => panic!("date catalog fixture must be Fixture::Date, got {other:?}"), + }) + .collect() +}); + +/// The parsed `chrono::NaiveDate` fixture values, in catalog order. Mirrors the +/// `eql_scalars::_VALUES` accessor pattern for the integer scalars; the +/// stacked timestamptz PR adds a sibling `timestamptz_values()`. Public so the +/// `eql_v2_date` fixture module (emitted by `scalar_types!(fixture_modules)`) +/// can hand the slice to `scalar_fixture!` — temporal scalars have no +/// `eql_scalars::_VALUES` const to point at. +pub fn date_values() -> &'static [chrono::NaiveDate] { + &DATE_VALUES_CELL +} + +impl ScalarType for chrono::NaiveDate { + const PG_TYPE: &'static str = "date"; + + fn fixture_values() -> &'static [Self] { + date_values() + } + + /// Temporal min pivot — `1900-01-01`, present verbatim in the catalog + /// fixtures (not `Self::MIN`, which would be far outside the fixture set). + fn min_pivot() -> Self { + chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("1900-01-01 is a valid date") + } + + /// Temporal max pivot — `2099-12-31`, present verbatim in the catalog + /// fixtures. + fn max_pivot() -> Self { + chrono::NaiveDate::from_ymd_opt(2099, 12, 31).expect("2099-12-31 is a valid date") + } + + /// `Display` renders a `NaiveDate` as `2099-12-31` (unquoted), which is not + /// a valid SQL literal on its own — wrap it in single quotes. + fn to_sql_literal(value: Self) -> String { + format!("'{value}'") + } +} + +#[cfg(test)] +mod date_value_tests { + use super::*; + + /// The parsed `NaiveDate` values match the catalog fixture strings in + /// order and count — the harness oracle cannot drift from the catalog the + /// fixture generator encrypts. + #[test] + fn date_values_match_catalog_fixtures() { + let catalog: Vec<&str> = eql_scalars::DATE + .fixtures + .iter() + .map(|f| match f { + eql_scalars::Fixture::Date(s) => *s, + other => panic!("unexpected non-date fixture {other:?}"), + }) + .collect(); + let parsed = ::fixture_values(); + assert_eq!( + parsed.len(), + catalog.len(), + "parsed date count must match catalog fixture count" + ); + for (date, iso) in parsed.iter().zip(&catalog) { + assert_eq!(&date.format("%Y-%m-%d").to_string(), iso); + } + } + + /// The three temporal pivots resolve to fixture rows present verbatim. + #[test] + fn date_pivots_are_in_fixture_values() { + let values = ::fixture_values(); + let min = ::min_pivot(); + let max = ::max_pivot(); + let zero = chrono::NaiveDate::default(); + assert!(values.contains(&min), "min_pivot {min} must be a fixture"); + assert!(values.contains(&max), "max_pivot {max} must be a fixture"); + assert!( + values.contains(&zero), + "zero pivot {zero} must be a fixture" + ); + // Default is 1970-01-01, the documented zero pivot. + assert_eq!( + zero, + chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(), + "NaiveDate::default() must be 1970-01-01" + ); + } +} + /// Per-domain capability + payload shape. Storage carries no terms, `Eq` /// adds `hm`, `Ord`/`OrdOre` add `ob`. `Ord` and `OrdOre` are deliberate /// twins — same operator surface, different SQL domain names — for the diff --git a/tests/sqlx/src/scalar_types.rs b/tests/sqlx/src/scalar_types.rs index b9b9f112..6747900a 100644 --- a/tests/sqlx/src/scalar_types.rs +++ b/tests/sqlx/src/scalar_types.rs @@ -4,7 +4,11 @@ //! To add a scalar encrypted-domain type to the SQLx matrix, add one //! `token => rust_type` line below (plus the catalog row in `eql-scalars` and //! the `EqlPlaintext` impl, owned separately — see -//! `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3). +//! `docs/reference/adding-a-scalar-encrypted-domain-type.md` §3). A temporal +//! (chrono-backed) scalar adds a trailing `[temporal]` marker +//! (`date => chrono::NaiveDate [temporal]`): it hand-writes its `impl +//! ScalarType` in `scalar_domains.rs` and gets pivot-presence fixture asserts +//! instead of the integer signed-extreme ones. //! //! The harness pieces live in three separate compilation contexts (the //! `eql-tests` lib, the `encrypted_domain` integration-test binary, and the @@ -48,6 +52,7 @@ macro_rules! scalar_types { int4 => i32, int2 => i16, int8 => i64, + date => chrono::NaiveDate [temporal], } }; } From 3b908d9ba959e60a698fc5ce59668ea82a83907a Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 4 Jun 2026 23:44:00 +1000 Subject: [PATCH 90/93] docs: correct stale 'out of scope' scalar coverage in adding-a-scalar reference CATALOG now includes the non-integer ordered scalar (date) alongside the integers, so the 'only the integer scalars today' wording was stale. Addresses CodeRabbit feedback on PR #256. --- docs/reference/adding-a-scalar-encrypted-domain-type.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index 95d73a98..4438e88a 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -653,7 +653,8 @@ golden reference under `tests/codegen/reference/int4/`. `text` and `jsonb` are **not** materialised through this generator. The `ScalarKind` enum carries `Text` / `Numeric` / `Jsonb` variants and the `Fixture` enum carries their string-backed shapes at the capability layer, but -`CATALOG` declares only the integer scalars today, so no `text` / `jsonb` SQL +`CATALOG` declares only the ordered scalars today — the fixed-width integers +(`int2` / `int4` / `int8`) and the temporal `date` — so no `text` / `jsonb` SQL surface is generated. Text and JSONB encrypted behaviour lives on the composite `eql_v2_encrypted` type and its hand-written operator surface in `src/encrypted/` and `src/operators/`, not the scalar materializer. `jsonb` in particular needs a From 955a894336b115b4124998660ead112aaf06c4d6 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 4 Jun 2026 23:46:00 +1000 Subject: [PATCH 91/93] feat(eql-scalars): add total BoundedIntKind sub-enum --- crates/eql-scalars/src/lib.rs | 90 +++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index d1bab768..88b7259e 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -15,14 +15,80 @@ //! //! Public names are consumed verbatim by the later codegen plans — do not rename. +/// The fixed-width integer kinds — exactly those scalar kinds with an `i128` +/// range and `MIN`/`MAX`/`Zero` sentinels. These accessors are **total**: every +/// variant answers every method. Non-integer kinds (`Numeric`/`Text`/`Jsonb`/ +/// `Date`) are simply not representable here, so there is no partial function to +/// panic — `ScalarKind::Date` cannot call `min_symbol()` because `Date` is not a +/// `BoundedIntKind`. Reach this type from a `ScalarKind` via +/// [`ScalarKind::as_bounded_int`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BoundedIntKind { + I16, + I32, + I64, +} + +impl BoundedIntKind { + /// The Rust type name as it appears in generated source (e.g. `"i32"`). + pub const fn rust_type(self) -> &'static str { + match self { + BoundedIntKind::I16 => "i16", + BoundedIntKind::I32 => "i32", + BoundedIntKind::I64 => "i64", + } + } + + /// The `MIN` named-constant symbol (e.g. `"i32::MIN"`). + pub const fn min_symbol(self) -> &'static str { + match self { + BoundedIntKind::I16 => "i16::MIN", + BoundedIntKind::I32 => "i32::MIN", + BoundedIntKind::I64 => "i64::MIN", + } + } + + /// The `MAX` named-constant symbol (e.g. `"i32::MAX"`). + pub const fn max_symbol(self) -> &'static str { + match self { + BoundedIntKind::I16 => "i16::MAX", + BoundedIntKind::I32 => "i32::MAX", + BoundedIntKind::I64 => "i64::MAX", + } + } + + /// The zero literal symbol (always `"0"`). + pub const fn zero_symbol(self) -> &'static str { + "0" + } + + /// Inclusive lower bound of the representable range, widened to `i128`. + pub const fn min_value(self) -> i128 { + match self { + BoundedIntKind::I16 => i16::MIN as i128, + BoundedIntKind::I32 => i32::MIN as i128, + BoundedIntKind::I64 => i64::MIN as i128, + } + } + + /// Inclusive upper bound of the representable range, widened to `i128`. + pub const fn max_value(self) -> i128 { + match self { + BoundedIntKind::I16 => i16::MAX as i128, + BoundedIntKind::I32 => i32::MAX as i128, + BoundedIntKind::I64 => i64::MAX as i128, + } + } +} + /// The native scalar a domain type maps onto. Integer kinds carry i128 bounds; /// the others (`Numeric`/`Text`/`Jsonb`) have string fixtures and no numeric /// range — though `Numeric`/`Text` are still ORE-orderable, only `Jsonb` is not. /// Capability layer only: `CATALOG` declares which kinds actually exist. /// -/// The bounded-numeric accessors below `panic!` on non-integer kinds; callers -/// gate with `is_int()`, so the panic guards against misuse rather than being a -/// reachable path (kept over `Option` to spare every integer caller an unwrap). +/// The bounded-numeric accessors live on the total [`BoundedIntKind`], reached +/// via [`ScalarKind::as_bounded_int`]; non-integer kinds have no such accessor, +/// so misuse is a compile error rather than a runtime panic. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ScalarKind { I16, @@ -490,6 +556,24 @@ int_values!(INT8_VALUES, i64, INT8); mod rust_tests { use super::*; + #[test] + fn bounded_int_kind_accessors_are_total() { + assert_eq!(BoundedIntKind::I16.rust_type(), "i16"); + assert_eq!(BoundedIntKind::I16.min_symbol(), "i16::MIN"); + assert_eq!(BoundedIntKind::I16.max_symbol(), "i16::MAX"); + assert_eq!(BoundedIntKind::I16.zero_symbol(), "0"); + assert_eq!(BoundedIntKind::I16.min_value(), -32_768_i128); + assert_eq!(BoundedIntKind::I16.max_value(), 32_767_i128); + + assert_eq!(BoundedIntKind::I32.min_symbol(), "i32::MIN"); + assert_eq!(BoundedIntKind::I32.min_value(), -2_147_483_648_i128); + assert_eq!(BoundedIntKind::I32.max_value(), 2_147_483_647_i128); + + assert_eq!(BoundedIntKind::I64.max_symbol(), "i64::MAX"); + assert_eq!(BoundedIntKind::I64.min_value(), -9_223_372_036_854_775_808_i128); + assert_eq!(BoundedIntKind::I64.max_value(), 9_223_372_036_854_775_807_i128); + } + #[test] fn i32_facts_match_int4() { assert_eq!(ScalarKind::I32.rust_type(), "i32"); From 325867d4840e9d8b4df421451b65c8e059245d44 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 4 Jun 2026 23:51:12 +1000 Subject: [PATCH 92/93] refactor(eql-scalars): move bounded accessors to BoundedIntKind ScalarKind loses the five partial accessors that panicked on non-integer kinds; bounds now live on the total BoundedIntKind, reached via ScalarKind::as_bounded_int(). Date::min_symbol() is now a compile error. A CATALOG invariant test replaces the deleted #[should_panic] tests. --- crates/eql-scalars/src/lib.rs | 279 +++++++++++++++------------------- 1 file changed, 125 insertions(+), 154 deletions(-) diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index 88b7259e..b5a362c7 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -99,17 +99,33 @@ pub enum ScalarKind { Jsonb, /// Calendar date (`chrono::NaiveDate`). Ordered like the integer kinds via /// ORE, but string-backed (ISO-8601) at the catalog layer and with no i128 - /// range — so it is *not* `is_int()` and the bounded-numeric accessors - /// panic for it, exactly like the other non-integer kinds. + /// range — so it is *not* `is_int()` and `as_bounded_int()` returns `None` + /// for it, like the other non-integer kinds. The bounded-numeric accessors + /// live on `BoundedIntKind`, which `Date` cannot be, so they are + /// unreachable for it by construction rather than by a runtime panic. Date, } impl ScalarKind { - /// Fixed-width integer kinds — those with i128 bounds and `Min`/`Max`/`Zero` - /// sentinels. Gates the bounded-numeric accessors and invariants. NOT an - /// orderability test: `Numeric`/`Text` are ORE-orderable yet not integers. + /// The fixed-width integer kinds — those with `i128` bounds and + /// `Min`/`Max`/`Zero` sentinels — projected onto [`BoundedIntKind`], or + /// `None` for the non-integer kinds. The single boundary where "this kind has + /// bounds" is decided; the bounded accessors live on `BoundedIntKind` and are + /// total there. NOT an orderability test: `Numeric`/`Text`/`Date` are + /// ORE-orderable yet not integers. + pub const fn as_bounded_int(self) -> Option { + match self { + ScalarKind::I16 => Some(BoundedIntKind::I16), + ScalarKind::I32 => Some(BoundedIntKind::I32), + ScalarKind::I64 => Some(BoundedIntKind::I64), + ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => None, + } + } + + /// True for the fixed-width integer kinds. Gates the bounded-numeric + /// invariants. Equivalent to `self.as_bounded_int().is_some()`. pub const fn is_int(self) -> bool { - matches!(self, ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64) + self.as_bounded_int().is_some() } /// The Rust type name as it appears in generated source (e.g. `"i32"`). @@ -124,68 +140,6 @@ impl ScalarKind { ScalarKind::Date => "chrono::NaiveDate", } } - - /// The `MIN` named-constant symbol (e.g. `"i32::MIN"`). Integer kinds only. - pub const fn min_symbol(self) -> &'static str { - match self { - ScalarKind::I16 => "i16::MIN", - ScalarKind::I32 => "i32::MIN", - ScalarKind::I64 => "i64::MIN", - // Explicit (not `_`) so a future integer variant is a compile - // error here rather than silently hitting the panic. - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { - panic!("min_symbol is only defined for integer kinds") - } - } - } - - /// The `MAX` named-constant symbol (e.g. `"i32::MAX"`). Integer kinds only. - pub const fn max_symbol(self) -> &'static str { - match self { - ScalarKind::I16 => "i16::MAX", - ScalarKind::I32 => "i32::MAX", - ScalarKind::I64 => "i64::MAX", - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { - panic!("max_symbol is only defined for integer kinds") - } - } - } - - /// The zero literal symbol (always `"0"`). Integer kinds only. - pub const fn zero_symbol(self) -> &'static str { - match self { - ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 => "0", - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { - panic!("zero_symbol is only defined for integer kinds") - } - } - } - - /// Inclusive lower bound of the representable range, widened to `i128`. - /// Integer kinds only. - pub const fn min_value(self) -> i128 { - match self { - ScalarKind::I16 => i16::MIN as i128, - ScalarKind::I32 => i32::MIN as i128, - ScalarKind::I64 => i64::MIN as i128, - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { - panic!("min_value is only defined for integer kinds") - } - } - } - - /// Inclusive upper bound of the representable range, widened to `i128`. - /// Integer kinds only. - pub const fn max_value(self) -> i128 { - match self { - ScalarKind::I16 => i16::MAX as i128, - ScalarKind::I32 => i32::MAX as i128, - ScalarKind::I64 => i64::MAX as i128, - ScalarKind::Numeric | ScalarKind::Text | ScalarKind::Jsonb | ScalarKind::Date => { - panic!("max_value is only defined for integer kinds") - } - } - } } /// A fixed index term known to the scalar materializer. @@ -336,8 +290,18 @@ impl Fixture { /// list into a typed `&'static` array at compile time. pub const fn numeric_value(self, kind: ScalarKind) -> Option { match self { - Fixture::Min => Some(kind.min_value()), - Fixture::Max => Some(kind.max_value()), + // `?` is not allowed in `const fn`, so match `as_bounded_int()` + // explicitly. A pivot on a non-integer kind resolves to `None`; the + // `pivot_sentinels_only_appear_with_integer_kinds` catalog test + // guarantees that combination never reaches a real `CATALOG` row. + Fixture::Min => match kind.as_bounded_int() { + Some(k) => Some(k.min_value()), + None => None, + }, + Fixture::Max => match kind.as_bounded_int() { + Some(k) => Some(k.max_value()), + None => None, + }, Fixture::Zero => Some(0), Fixture::Int(n) => Some(n), Fixture::Numeric(_) | Fixture::Text(_) | Fixture::Jsonb(_) | Fixture::Date(_) => None, @@ -347,10 +311,23 @@ impl Fixture { /// Render as a Rust source literal: sentinels -> named constant, `Int` -> the /// number, string kinds -> a `Debug`-quoted (Rust-escaped, not SQL) literal. pub fn render_literal(self, kind: ScalarKind) -> String { + const PIVOT_MSG: &str = "Min/Max/Zero fixtures require an integer kind"; match self { - Fixture::Min => kind.min_symbol().to_string(), - Fixture::Max => kind.max_symbol().to_string(), - Fixture::Zero => kind.zero_symbol().to_string(), + Fixture::Min => kind + .as_bounded_int() + .expect(PIVOT_MSG) + .min_symbol() + .to_string(), + Fixture::Max => kind + .as_bounded_int() + .expect(PIVOT_MSG) + .max_symbol() + .to_string(), + Fixture::Zero => kind + .as_bounded_int() + .expect(PIVOT_MSG) + .zero_symbol() + .to_string(), Fixture::Int(n) => n.to_string(), Fixture::Numeric(s) | Fixture::Text(s) | Fixture::Jsonb(s) | Fixture::Date(s) => { format!("{s:?}") @@ -570,28 +547,51 @@ mod rust_tests { assert_eq!(BoundedIntKind::I32.max_value(), 2_147_483_647_i128); assert_eq!(BoundedIntKind::I64.max_symbol(), "i64::MAX"); - assert_eq!(BoundedIntKind::I64.min_value(), -9_223_372_036_854_775_808_i128); - assert_eq!(BoundedIntKind::I64.max_value(), 9_223_372_036_854_775_807_i128); + assert_eq!( + BoundedIntKind::I64.min_value(), + -9_223_372_036_854_775_808_i128 + ); + assert_eq!( + BoundedIntKind::I64.max_value(), + 9_223_372_036_854_775_807_i128 + ); + } + + #[test] + fn as_bounded_int_maps_integer_kinds_only() { + assert_eq!(ScalarKind::I16.as_bounded_int(), Some(BoundedIntKind::I16)); + assert_eq!(ScalarKind::I32.as_bounded_int(), Some(BoundedIntKind::I32)); + assert_eq!(ScalarKind::I64.as_bounded_int(), Some(BoundedIntKind::I64)); + assert_eq!(ScalarKind::Numeric.as_bounded_int(), None); + assert_eq!(ScalarKind::Text.as_bounded_int(), None); + assert_eq!(ScalarKind::Jsonb.as_bounded_int(), None); + assert_eq!(ScalarKind::Date.as_bounded_int(), None); } #[test] fn i32_facts_match_int4() { assert_eq!(ScalarKind::I32.rust_type(), "i32"); - assert_eq!(ScalarKind::I32.min_symbol(), "i32::MIN"); - assert_eq!(ScalarKind::I32.max_symbol(), "i32::MAX"); - assert_eq!(ScalarKind::I32.zero_symbol(), "0"); - assert_eq!(ScalarKind::I32.min_value(), -2_147_483_648_i128); - assert_eq!(ScalarKind::I32.max_value(), 2_147_483_647_i128); + let k = ScalarKind::I32 + .as_bounded_int() + .expect("I32 is an integer kind"); + assert_eq!(k.min_symbol(), "i32::MIN"); + assert_eq!(k.max_symbol(), "i32::MAX"); + assert_eq!(k.zero_symbol(), "0"); + assert_eq!(k.min_value(), -2_147_483_648_i128); + assert_eq!(k.max_value(), 2_147_483_647_i128); } #[test] fn i16_facts_match_int2() { assert_eq!(ScalarKind::I16.rust_type(), "i16"); - assert_eq!(ScalarKind::I16.min_symbol(), "i16::MIN"); - assert_eq!(ScalarKind::I16.max_symbol(), "i16::MAX"); - assert_eq!(ScalarKind::I16.zero_symbol(), "0"); - assert_eq!(ScalarKind::I16.min_value(), -32_768_i128); - assert_eq!(ScalarKind::I16.max_value(), 32_767_i128); + let k = ScalarKind::I16 + .as_bounded_int() + .expect("I16 is an integer kind"); + assert_eq!(k.min_symbol(), "i16::MIN"); + assert_eq!(k.max_symbol(), "i16::MAX"); + assert_eq!(k.zero_symbol(), "0"); + assert_eq!(k.min_value(), -32_768_i128); + assert_eq!(k.max_value(), 32_767_i128); } #[test] @@ -605,87 +605,50 @@ mod rust_tests { assert!(!ScalarKind::Date.is_int()); } - // Pin that the bounded-numeric accessors panic (with message) on non-int kinds. - #[test] - #[should_panic(expected = "min_symbol is only defined for integer kinds")] - fn min_symbol_panics_on_non_int_kind() { - ScalarKind::Text.min_symbol(); - } - - #[test] - #[should_panic(expected = "max_symbol is only defined for integer kinds")] - fn max_symbol_panics_on_non_int_kind() { - ScalarKind::Numeric.max_symbol(); - } - - #[test] - #[should_panic(expected = "zero_symbol is only defined for integer kinds")] - fn zero_symbol_panics_on_non_int_kind() { - ScalarKind::Jsonb.zero_symbol(); - } - - #[test] - #[should_panic(expected = "min_value is only defined for integer kinds")] - fn min_value_panics_on_non_int_kind() { - ScalarKind::Text.min_value(); - } - - #[test] - #[should_panic(expected = "max_value is only defined for integer kinds")] - fn max_value_panics_on_non_int_kind() { - ScalarKind::Jsonb.max_value(); - } - #[test] fn i64_facts() { // Capability-layer fact: i64 is the Rust kind a future int8 maps onto. // Present here so adding int8 later is a pure `CATALOG` append. assert_eq!(ScalarKind::I64.rust_type(), "i64"); - assert_eq!(ScalarKind::I64.min_symbol(), "i64::MIN"); - assert_eq!(ScalarKind::I64.max_symbol(), "i64::MAX"); - assert_eq!(ScalarKind::I64.zero_symbol(), "0"); - assert_eq!(ScalarKind::I64.min_value(), -9_223_372_036_854_775_808_i128); - assert_eq!(ScalarKind::I64.max_value(), 9_223_372_036_854_775_807_i128); + let k = ScalarKind::I64 + .as_bounded_int() + .expect("I64 is an integer kind"); + assert_eq!(k.min_symbol(), "i64::MIN"); + assert_eq!(k.max_symbol(), "i64::MAX"); + assert_eq!(k.zero_symbol(), "0"); + assert_eq!(k.min_value(), -9_223_372_036_854_775_808_i128); + assert_eq!(k.max_value(), 9_223_372_036_854_775_807_i128); } #[test] fn date_maps_to_naive_date() { // Ordered, non-integer kind: it carries a rust type but no i128 range, - // so it is not `is_int()` and the bounded accessors panic (below). + // so it is not `is_int()` and `as_bounded_int()` returns `None` — the + // bounded accessors are simply not reachable for it. assert_eq!(ScalarKind::Date.rust_type(), "chrono::NaiveDate"); assert!(!ScalarKind::Date.is_int()); + assert_eq!(ScalarKind::Date.as_bounded_int(), None); } - // The bounded-numeric accessors panic on Date exactly as on the other - // non-integer kinds — Date is not an integer kind. - #[test] - #[should_panic(expected = "min_symbol is only defined for integer kinds")] - fn min_symbol_panics_on_date() { - ScalarKind::Date.min_symbol(); - } - - #[test] - #[should_panic(expected = "max_symbol is only defined for integer kinds")] - fn max_symbol_panics_on_date() { - ScalarKind::Date.max_symbol(); - } - + /// The structural guarantee that replaces the old runtime panics: a + /// `Min`/`Max`/`Zero` pivot sentinel may only appear in a `CATALOG` row whose + /// kind is an integer kind. `render_literal` would `expect`-panic and + /// `numeric_value` would resolve to `None` for a pivot on a non-integer kind; + /// this test makes such a row a test failure at the source of truth. #[test] - #[should_panic(expected = "zero_symbol is only defined for integer kinds")] - fn zero_symbol_panics_on_date() { - ScalarKind::Date.zero_symbol(); - } - - #[test] - #[should_panic(expected = "min_value is only defined for integer kinds")] - fn min_value_panics_on_date() { - ScalarKind::Date.min_value(); - } - - #[test] - #[should_panic(expected = "max_value is only defined for integer kinds")] - fn max_value_panics_on_date() { - ScalarKind::Date.max_value(); + fn pivot_sentinels_only_appear_with_integer_kinds() { + for spec in CATALOG { + for fixture in spec.fixtures { + if matches!(fixture, Fixture::Min | Fixture::Max | Fixture::Zero) { + assert!( + spec.kind.is_int(), + "pivot sentinel {fixture:?} on non-integer kind {:?} (token `{}`)", + spec.kind, + spec.token, + ); + } + } + } } } @@ -1118,18 +1081,22 @@ mod invariant_tests { // The MIN/MAX/ZERO pivots are an integer-kind invariant; non-integer // kinds (text/numeric/jsonb) have no such pivots. for s in CATALOG.iter().filter(|s| s.kind.is_int()) { + let bk = s + .kind + .as_bounded_int() + .expect("loop is filtered to integer kinds"); let resolved: Vec = s .fixtures .iter() .filter_map(|f| f.numeric_value(s.kind)) .collect(); assert!( - resolved.contains(&s.kind.min_value()), + resolved.contains(&bk.min_value()), "{} fixtures missing MIN", s.token ); assert!( - resolved.contains(&s.kind.max_value()), + resolved.contains(&bk.max_value()), "{} fixtures missing MAX", s.token ); @@ -1175,7 +1142,11 @@ mod invariant_tests { fn every_fixture_value_is_within_kind_bounds() { // Asserts the resolved sentinels stay within bounds (integer kinds only). for s in CATALOG.iter().filter(|s| s.kind.is_int()) { - let (lo, hi) = (s.kind.min_value(), s.kind.max_value()); + let bk = s + .kind + .as_bounded_int() + .expect("loop is filtered to integer kinds"); + let (lo, hi) = (bk.min_value(), bk.max_value()); for f in s.fixtures { let Some(n) = f.numeric_value(s.kind) else { continue; From 163ec579f32b45b47d4daa5f964ba7a8cc2ac6a0 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 4 Jun 2026 23:54:55 +1000 Subject: [PATCH 93/93] docs: describe BoundedIntKind instead of panicking accessors Update the adding-a-scalar reference's `kind` bullet to reflect that the bounded-numeric accessors moved to the total BoundedIntKind sub-enum, and add the implementation plan under docs/superpowers/plans/. --- .../adding-a-scalar-encrypted-domain-type.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index 4438e88a..2f95c20c 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -93,12 +93,16 @@ than a runtime validator: domain's full name is `token` + `suffix` (`ScalarSpec::domain_name`), pinned by `every_domain_name_starts_with_its_token`. - **`kind`** — a `ScalarKind` (`I16` / `I32` / `I64` / `Numeric` / `Text` / - `Jsonb`), carrying the Rust type name, the `MIN`/`MAX`/zero symbols, and the - numeric bounds. Only the integer kinds have an i128 range with `Min`/`Max`/`Zero` - sentinels; the bounded accessors `panic!` on the others (a misuse guard gated - by `is_int()`). **If `` needs a new scalar width, add a `ScalarKind` - variant** (rust-type name, `MIN`/`MAX`/zero symbols, bounds) with unit tests - over its `impl` methods. + `Jsonb` / `Date`), carrying the Rust type name. Only the integer kinds have an + i128 range with `Min`/`Max`/`Zero` sentinels: those bounded accessors + (`min_symbol`/`max_symbol`/`zero_symbol`/`min_value`/`max_value`) live on the + total `BoundedIntKind` sub-enum, reached via `ScalarKind::as_bounded_int() -> + Option`. Non-integer kinds (`Numeric`/`Text`/`Jsonb`/`Date`) + return `None` and simply have no bounded accessor — misuse is a compile error, + not a runtime panic. **If `` needs a new fixed-width integer, add a + `BoundedIntKind` variant** (rust-type name, `MIN`/`MAX`/zero symbols, bounds) + plus its `ScalarKind` variant and `as_bounded_int` arm, with unit tests over + the `impl` methods. - **`domains`** — a non-empty `&[DomainSpec]` (pinned by `every_type_has_at_least_one_domain`), each a `suffix` + the fixed `&[Term]` it carries. The storage domain is `suffix: ""` with no terms; `_eq => [Term::Hm]`;