From aaec244657392ee073fa41bb0a95a4f5b06142ed Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 11 Jun 2026 18:31:29 +1000 Subject: [PATCH 1/9] feat(v3): N-block ORE comparator + ordered numeric & timestamptz Derive the ORE block count N from term length instead of hardcoding 8, and wire the ordered numeric scalar while promoting timestamptz to ordered. --- Cargo.lock | 4 + crates/eql-scalars/src/lib.rs | 70 +++++++++++----- crates/eql-scalars/src/tests.rs | 46 ++++++++--- crates/eql-tests-macros/src/lib.rs | 14 +++- src/v3/sem/ore_block_256/functions.sql | 32 +++++++- tests/sqlx/Cargo.toml | 7 +- tests/sqlx/src/fixtures/eql_plaintext.rs | 16 +++- tests/sqlx/src/fixtures/scalar_fixture.rs | 31 +++++++ tests/sqlx/src/scalar_domains.rs | 82 +++++++++++++++++++ tests/sqlx/src/scalar_types.rs | 1 + .../encrypted_domain/family/mutations.rs | 24 ++++-- .../sqlx/tests/ore_block_comparator_tests.rs | 52 ++++++++++++ 12 files changed, 329 insertions(+), 50 deletions(-) create mode 100644 tests/sqlx/tests/ore_block_comparator_tests.rs diff --git a/Cargo.lock b/Cargo.lock index dd9a5f47..2fb317e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1200,6 +1200,7 @@ dependencies = [ "hex", "jsonschema", "paste", + "rust_decimal", "serde", "serde_json", "sqlx", @@ -3679,6 +3680,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", + "rust_decimal", "serde", "serde_json", "sha2", @@ -3760,6 +3762,7 @@ dependencies = [ "percent-encoding", "rand 0.8.6", "rsa", + "rust_decimal", "serde", "sha1", "sha2", @@ -3798,6 +3801,7 @@ dependencies = [ "memchr", "once_cell", "rand 0.8.6", + "rust_decimal", "serde", "serde_json", "sha2", diff --git a/crates/eql-scalars/src/lib.rs b/crates/eql-scalars/src/lib.rs index b9f87c70..7607609c 100644 --- a/crates/eql-scalars/src/lib.rs +++ b/crates/eql-scalars/src/lib.rs @@ -230,13 +230,15 @@ const ORDERED_INT_DOMAINS: &[DomainSpec] = &[ }, ]; -/// Equality-only domains: storage (no terms) + `_eq` (hm). Used by scalar types -/// that can hash for equality but cannot (yet) be ordered. `timestamptz` is the -/// first such type: cipherstash encrypts `Plaintext::Timestamp` at native -/// 12-block ORE width, but EQL's only ORE comparator -/// (`eql_v2.compare_ore_block_u64_8_256_term`) is hardcoded to 8 blocks, so an -/// ordered domain would silently mis-order. Ordering is deferred until a -/// wide-ORE (12-block) term exists. +/// Equality-only domains: storage (no terms) + `_eq` (hm). The canonical shape +/// for a scalar type that can hash for equality but is not ORE-orderable. +/// **Currently unused:** `timestamptz` (the previous sole user) was promoted to +/// the ordered shape once `eql_v3.compare_ore_block_256_term` generalized to N +/// blocks and could order its native 12-block ORE width. Retained — and still +/// validated as a known-valid shape by `every_type_uses_a_known_domain_shape` — +/// so a future non-orderable scalar (e.g. a hash-only type) can reuse it without +/// reconstructing the shape. +#[allow(dead_code)] const EQ_ONLY_DOMAINS: &[DomainSpec] = &[ DomainSpec { suffix: "", @@ -296,6 +298,17 @@ const TIMESTAMPTZ_FIXTURES: &[Fixture] = fixtures!(timestamptz; "2012-06-30T11:59:59Z", "2016-03-15T08:15:30Z", "2020-10-21T14:45:00Z", "2024-02-29T17:30:45Z", "2038-01-19T03:14:07Z", "2099-12-31T23:59:59Z"); +/// `numeric` fixture plaintexts — distinct by `Decimal` value, spanning sign, +/// magnitude, and scale, and including `0` plus the min/max pivots +/// (`-1000000000000` / `1000000000000`). They mirror `ore-rs`'s own +/// order-pinning vectors so the 14-block ORE edges (sign + high/low blocks) are +/// exercised. Each literal is distinct by parsed value (no `"1"`/`"1.0"` +/// aliasing) — the harness `numeric_fixtures_distinct_by_value` guard enforces +/// this, since the zero-dep catalog only dedupes by literal string. +const NUMERIC_FIXTURES: &[Fixture] = fixtures!(numeric; + "-1000000000000", "-1000000", "-1.001", "-1", "-0.5", "-0.001", + "0", "0.001", "0.5", "0.999999999", "1", "1.001", "1000000", "1000000000000"); + const INT4: ScalarSpec = ScalarSpec { token: "int4", kind: ScalarKind::I32, @@ -332,27 +345,42 @@ pub const DATE: ScalarSpec = ScalarSpec { fixtures: DATE_FIXTURES, }; -/// `timestamptz` — an **equality-only** (UTC-normalized) non-integer scalar. -/// Uses `EQ_ONLY_DOMAINS` (storage + `_eq`) rather than the four-domain ordered -/// shape: cipherstash encrypts `Plaintext::Timestamp` at native 12-block ORE -/// width, but EQL's only ORE comparator -/// (`eql_v2.compare_ore_block_u64_8_256_term`) is hardcoded to 8 blocks, so an -/// ordered timestamptz domain would silently mis-order. Ordering is deferred to -/// a future PR that adds a wide-ORE (12-block) term. The three "pivot" fixture -/// values are retained as equality pivots; the kind stays ordered-shaped -/// (carries a rust type, no i128 range) so the harness can parse them. +/// `timestamptz` — an **ordered**, UTC-normalized non-integer scalar. Uses the +/// four-domain ordered shape (storage, `_eq`, `_ord`, `_ord_ore`): cipherstash +/// encrypts `Plaintext::Timestamp` at native 12-block ORE width, which the +/// generalized `eql_v3.compare_ore_block_256_term` comparator orders correctly. +/// Values are UTC-normalized (cipherstash has no tz-preserving type) and encrypt +/// under the `timestamp` cast. /// /// Public (like `DATE`) because the SQLx harness reads `TIMESTAMPTZ.fixtures` -/// directly to parse the RFC3339 strings into `chrono::DateTime` at -/// runtime — there is no `TIMESTAMPTZ_VALUES` const (chrono is not -/// `const`-friendly and `eql-scalars` stays zero-dep). +/// directly to parse the RFC3339 strings into `chrono::DateTime` at runtime +/// (no `TIMESTAMPTZ_VALUES` const; `eql-scalars` stays zero-dep). pub const TIMESTAMPTZ: ScalarSpec = ScalarSpec { token: "timestamptz", kind: ScalarKind::Timestamptz, - domains: EQ_ONLY_DOMAINS, + domains: ORDERED_INT_DOMAINS, fixtures: TIMESTAMPTZ_FIXTURES, }; +/// `numeric` — an **ordered** non-integer scalar backed by +/// `rust_decimal::Decimal`. Uses the four-domain ordered shape: cipherstash +/// encrypts `Plaintext::Decimal` at native 14-block ORE width, which the +/// generalized `eql_v3.compare_ore_block_256_term` comparator orders correctly. +/// `numeric_value` returns `None` (no i128 range); ordering is supplied by the +/// harness `Decimal: Ord`, which `ore-rs` guarantees agrees with the ciphertext +/// order (equivalent scales collide, like `Decimal`'s own `Ord`). +/// +/// Public (like `DATE` / `TIMESTAMPTZ`) so the SQLx harness reads +/// `NUMERIC.fixtures` directly to parse the decimal strings into +/// `rust_decimal::Decimal` at runtime (the catalog stays zero-dep: no +/// `rust_decimal`). +pub const NUMERIC: ScalarSpec = ScalarSpec { + token: "numeric", + kind: ScalarKind::Numeric, + domains: ORDERED_INT_DOMAINS, + fixtures: NUMERIC_FIXTURES, +}; + /// Domains for `text`: the ordered shape (with exact `hm` equality on the /// ordered domains), a `_match` domain (`Bloom` containment), and a combined /// `_search` domain carrying equality + ordering + match in one type. @@ -417,7 +445,7 @@ pub const TEXT: ScalarSpec = ScalarSpec { /// 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, DATE, TIMESTAMPTZ, TEXT]; +pub const CATALOG: &[ScalarSpec] = &[INT4, INT2, INT8, DATE, TIMESTAMPTZ, NUMERIC, TEXT]; /// Materialise an integer scalar's fixtures into a typed `&'static` slice at /// compile time. This is the **single-sourced** plaintext list the SQLx test diff --git a/crates/eql-scalars/src/tests.rs b/crates/eql-scalars/src/tests.rs index 85341337..1be3a65f 100644 --- a/crates/eql-scalars/src/tests.rs +++ b/crates/eql-scalars/src/tests.rs @@ -170,7 +170,24 @@ mod rust_tests { let date = CATALOG.iter().find(|s| s.token == "date").unwrap(); assert!(!date.is_eq_only(), "date is ordered"); let ts = CATALOG.iter().find(|s| s.token == "timestamptz").unwrap(); - assert!(ts.is_eq_only(), "timestamptz is equality-only"); + assert!( + !ts.is_eq_only(), + "timestamptz is now ordered (native 12-block ORE, comparator generalized to N blocks)" + ); + + // No catalog type is currently eq-only, so exercise `is_eq_only()`'s + // positive path with a synthetic spec built on the retained + // `EQ_ONLY_DOMAINS` shape (storage + `_eq`, no `_ord`). + let eq_only = ScalarSpec { + token: "synthetic_eq_only", + kind: ScalarKind::Timestamptz, + domains: EQ_ONLY_DOMAINS, + fixtures: &[], + }; + assert!( + eq_only.is_eq_only(), + "a storage+_eq spec (no _ord) must be detected as eq-only" + ); } } @@ -496,11 +513,19 @@ mod catalog_tests { } #[test] - fn catalog_has_int4_int2_int8_date_timestamptz_text_in_order() { + fn catalog_has_int4_int2_int8_date_timestamptz_numeric_text_in_order() { let tokens: Vec<&str> = CATALOG.iter().map(|s| s.token).collect(); assert_eq!( tokens, - vec!["int4", "int2", "int8", "date", "timestamptz", "text"] + vec![ + "int4", + "int2", + "int8", + "date", + "timestamptz", + "numeric", + "text" + ] ); } @@ -710,17 +735,14 @@ mod catalog_tests { #[test] fn ordered_and_eq_only_shapes_are_used_as_declared() { - // Pin which catalog tokens carry which shape, so a row silently flipping - // ORDERED_INT_DOMAINS <-> EQ_ONLY_DOMAINS is caught. timestamptz is - // equality-only (12-block ORE vs 8-block comparator); the rest ordered - // (text adds `_match` and `_search` domains on top, so it is not - // eq_only either). + // All current catalog types use the four-domain ordered shape; none is + // equality-only. (timestamptz was promoted to ordered once the ORE + // comparator generalized to N blocks — see the numeric/ORE work.) for s in CATALOG { let is_eq_only = s.domains.len() == 2; - let expect_eq_only = s.token == "timestamptz"; - assert_eq!( - is_eq_only, expect_eq_only, - "{} domain shape (eq_only={is_eq_only}) does not match expectation", + assert!( + !is_eq_only, + "{} is unexpectedly eq-only; no catalog type is eq-only currently", s.token ); } diff --git a/crates/eql-tests-macros/src/lib.rs b/crates/eql-tests-macros/src/lib.rs index aba3ebaa..8ad49520 100644 --- a/crates/eql-tests-macros/src/lib.rs +++ b/crates/eql-tests-macros/src/lib.rs @@ -93,6 +93,14 @@ fn is_text_token(token: &str) -> bool { spec_for_token(token).kind.is_text() } +/// True when `token`'s catalog row is the `numeric` kind (owned +/// `rust_decimal::Decimal`). Like `text` it is ordered but non-integer and +/// non-chrono, so it stamps the `numeric` fixture discriminator and draws its +/// values from the harness accessor (`numeric_values()`). +fn is_numeric_token(token: &str) -> bool { + matches!(spec_for_token(token).kind, eql_scalars::ScalarKind::Numeric) +} + /// True when `token`'s catalog row declares no ordered domain — equality-only. /// Replaces the `[eq_only]` marker. Consumed by [`matrix_suite_for_entry`] to /// keep an eq-only type out of the ordered matrix (which exercises ordering @@ -224,10 +232,12 @@ fn scalar_fixture_modules_tokens(list: &ScalarList) -> TokenStream2 { format_ident!("temporal") } else if is_text_token(&token_str) { format_ident!("text") + } else if is_numeric_token(&token_str) { + format_ident!("numeric") } else { panic!( - "scalar token `{token_str}` is neither integer, temporal, nor \ - text — no fixture discriminator is wired for its kind" + "scalar token `{token_str}` is neither integer, temporal, text, \ + nor numeric — no fixture discriminator is wired for its kind" ) }; quote! { diff --git a/src/v3/sem/ore_block_256/functions.sql b/src/v3/sem/ore_block_256/functions.sql index 7d5bce88..83bd42e1 100644 --- a/src/v3/sem/ore_block_256/functions.sql +++ b/src/v3/sem/ore_block_256/functions.sql @@ -109,7 +109,16 @@ AS $$ left_block_size CONSTANT smallint := 16; right_block_size CONSTANT smallint := 32; - right_offset CONSTANT smallint := 136; -- 8 * 17 + + -- Block count N is DERIVED from the ciphertext length, not hardcoded to 8. + -- Wire format per term: + -- [ N PRP bytes ][ N*16B left blocks ][ 16B hash key ][ N*32B right blocks ] + -- octet_length = 17*N + 16 + 32*N = 49*N + 16 => N = (octet_length - 16) / 49 + -- This serves int4 (N=8, 408B), timestamp (N=12, 604B), and numeric + -- (N=14, 702B) with one comparator. + n integer; + left_offset integer; -- ordinal offset of the first left block (1 + N PRP bytes) + right_offset integer; -- ordinal start of the right CT (= total left CT length = 17*N) indicator smallint := 0; BEGIN @@ -129,10 +138,23 @@ AS $$ RAISE EXCEPTION 'Ciphertexts are different lengths'; END IF; - FOR block IN 0..7 LOOP + -- Well-formedness: length must be exactly 49*N + 16 for some N >= 1. The + -- modulo alone is insufficient -- a 16-byte term passes (16 - 16) % 49 = 0 + -- and derives N = 0, which would fall through to the all-blocks-equal path + -- and return 0 instead of raising. The `<= 16` clause is load-bearing. + IF octet_length(a.bytes) <= 16 OR (octet_length(a.bytes) - 16) % 49 != 0 THEN + RAISE EXCEPTION 'Malformed ORE term: % bytes', octet_length(a.bytes); + END IF; + + n := (octet_length(a.bytes) - 16) / 49; + left_offset := 1 + n; -- left blocks begin right after the N PRP bytes + right_offset := 17 * n; -- right CT begins right after the 17*N-byte left CT + + FOR block IN 0..n-1 LOOP + -- Compare each PRP byte (the first N bytes) and its 16-byte left block. 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) + OR substr(a.bytes, left_offset + left_block_size * block, left_block_size) != substr(b.bytes, left_offset + left_block_size * block, left_block_size) THEN IF eq THEN unequal_block := block; @@ -145,11 +167,13 @@ AS $$ RETURN 0::integer; END IF; + -- Hash key is the IV from the right CT of b. hash_key := substr(b.bytes, right_offset + 1, 16); + -- First right block is at right_offset + nonce_size (ordinally indexed). 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); + data_block := substr(a.bytes, left_offset + (left_block_size * unequal_block), left_block_size); encrypt_block := encrypt(data_block::bytea, hash_key::bytea, 'aes-ecb'); diff --git a/tests/sqlx/Cargo.toml b/tests/sqlx/Cargo.toml index fbdd9506..a142233c 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", "chrono"] } +sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros", "chrono", "rust_decimal"] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -16,6 +16,11 @@ cipherstash-client = { version = "0.35", features = ["tokio"] } # 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 } +# rust_decimal backs the `numeric` scalar. The sqlx `rust_decimal` feature above +# provides Encode/Decode/Type; this names the type directly for the +# harness impls (scalar_domains.rs, eql_plaintext.rs). Already in the tree +# transitively (cipherstash-client / ore-rs). +rust_decimal = "1" 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 9e85aac5..f447429b 100644 --- a/tests/sqlx/src/fixtures/eql_plaintext.rs +++ b/tests/sqlx/src/fixtures/eql_plaintext.rs @@ -60,6 +60,7 @@ impl PlaintextSqlType { pub const TIMESTAMPTZ: PlaintextSqlType = PlaintextSqlType("timestamp with time zone"); pub const TEXT: PlaintextSqlType = PlaintextSqlType("text"); pub const JSONB: PlaintextSqlType = PlaintextSqlType("jsonb"); + pub const NUMERIC: PlaintextSqlType = PlaintextSqlType("numeric"); pub fn as_str(&self) -> &'static str { self.0 @@ -86,7 +87,8 @@ const fn cast_for_kind(kind: ScalarKind) -> Cast { ScalarKind::Date => Cast::DATE, ScalarKind::Timestamptz => Cast::TIMESTAMP, ScalarKind::Text => Cast::TEXT, - ScalarKind::Numeric | ScalarKind::Jsonb => { + ScalarKind::Numeric => Cast::DECIMAL, + ScalarKind::Jsonb => { panic!("EqlPlaintext is only implemented for the wired scalar kinds") } } @@ -103,7 +105,8 @@ const fn plaintext_sql_type_for_kind(kind: ScalarKind) -> PlaintextSqlType { ScalarKind::Date => PlaintextSqlType::DATE, ScalarKind::Timestamptz => PlaintextSqlType::TIMESTAMPTZ, ScalarKind::Text => PlaintextSqlType::TEXT, - ScalarKind::Numeric | ScalarKind::Jsonb => { + ScalarKind::Numeric => PlaintextSqlType::NUMERIC, + ScalarKind::Jsonb => { panic!("EqlPlaintext is only implemented for the wired scalar kinds") } } @@ -118,6 +121,7 @@ mod sealed { impl Sealed for chrono::DateTime {} impl Sealed for String {} impl Sealed for serde_json::Value {} + impl Sealed for rust_decimal::Decimal {} } /// A Rust type usable as a fixture `plaintext` value, carrying its EQL cast @@ -213,6 +217,14 @@ impl EqlPlaintext for serde_json::Value { } } +impl EqlPlaintext for rust_decimal::Decimal { + const KIND: ScalarKind = ScalarKind::Numeric; + + fn to_plaintext(&self) -> Plaintext { + Plaintext::Decimal(Some(*self)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/tests/sqlx/src/fixtures/scalar_fixture.rs b/tests/sqlx/src/fixtures/scalar_fixture.rs index 6efcfa3d..b84f0a0d 100644 --- a/tests/sqlx/src/fixtures/scalar_fixture.rs +++ b/tests/sqlx/src/fixtures/scalar_fixture.rs @@ -138,6 +138,37 @@ macro_rules! scalar_fixture { } }; + // Numeric scalars (`rust_decimal::Decimal`): ordered, non-chrono. Same + // shape as `temporal` — `[Unique, Ore]` indexes, pivot-presence asserts via + // `OrderedScalar` — but materialised from owned `Decimal` values (no `Match` + // index, no chrono). + (numeric, $name:literal, $ty:ty, $values:expr $(,)?) => { + $crate::scalar_fixture!(@common $name, $ty, $values, [Unique, Ore]); + + #[cfg(test)] + mod tests { + use super::*; + use $crate::scalar_domains::OrderedScalar; + + #[test] + fn spec_is_complete() { + assert!(spec().check_complete().is_ok()); + } + + #[test] + fn spec_includes_pivots() { + let spec = spec(); + let values = spec.values(); + let min = <$ty as OrderedScalar>::min_pivot(); + let mid = <$ty as OrderedScalar>::mid_pivot(); + let max = <$ty as OrderedScalar>::max_pivot(); + assert!(values.contains(&min), "spec must include min_pivot {min:?}"); + assert!(values.contains(&mid), "spec must include mid_pivot {mid:?}"); + assert!(values.contains(&max), "spec must include max_pivot {max:?}"); + } + } + }; + // Shared expansion: the `spec()` builder + the gated generator test. The // trailing `[Unique, Ore, ...]` token list parametrizes the index set. (@common $name:literal, $ty:ty, $values:expr, [$($ix:ident),+ $(,)?]) => { diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index 16e5a088..84bf9da2 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -485,6 +485,88 @@ impl MatchScalar for String { // numeric origin / sign boundary. The signed-only sign-boundary test bounds on // `SignedScalar`, so a `String` instantiation of it would not compile. +// `numeric` is hand-written (like `text`): an owned `rust_decimal::Decimal`, +// not chrono-backed, so it parses the catalog's `Fixture::Numeric` strings into +// a `LazyLock>` rather than going through `temporal_values!`. The +// catalog stays zero-dep, so the parse happens here, not in `eql-scalars`. + +/// Typed `Decimal` fixture values, parsed once from `numeric`'s catalog row. +static NUMERIC_VALUES_CELL: std::sync::LazyLock> = + std::sync::LazyLock::new(|| { + use std::str::FromStr; + eql_scalars::NUMERIC + .fixtures + .iter() + .map(|f| match f { + eql_scalars::Fixture::Numeric(s) => rust_decimal::Decimal::from_str(s) + .unwrap_or_else(|e| panic!("invalid numeric catalog fixture {s:?}: {e}")), + other => panic!("non-numeric fixture in numeric catalog row: {other:?}"), + }) + .collect() + }); + +/// The `Decimal` fixture values, in catalog order. Public so the `eql_v2_numeric` +/// fixture module (emitted by `scalar_types!(fixture_modules)`) can hand the +/// slice to `scalar_fixture!`. +pub fn numeric_values() -> &'static [rust_decimal::Decimal] { + &NUMERIC_VALUES_CELL +} + +impl ScalarType for rust_decimal::Decimal { + const PG_TYPE: &'static str = "numeric"; + + fn fixture_values() -> &'static [Self] { + numeric_values() + } + // `to_sql_literal` inherits the default (`value.to_string()`): a `Decimal`'s + // `Display` form (e.g. `-1000000000000`, `0.001`) is a valid SQL numeric + // literal, so no quoting/override is needed (unlike `text` / `date`). +} + +impl OrderedScalar for rust_decimal::Decimal { + /// The smallest fixture decimal. Present verbatim in `fixture_values()`. + fn min_pivot() -> Self { + use std::str::FromStr; + rust_decimal::Decimal::from_str("-1000000000000").unwrap() + } + + /// The largest fixture decimal. Present verbatim in `fixture_values()`. + fn max_pivot() -> Self { + use std::str::FromStr; + rust_decimal::Decimal::from_str("1000000000000").unwrap() + } + // `mid_pivot` inherits the default `Self::default()` = `Decimal::ZERO` = 0, + // which is a real fixture and the numeric origin. +} + +// `Decimal` is deliberately NOT `SignedScalar`: like `text`, it is an +// ordered non-integer kind. The signed-only sign-boundary test bounds on +// `SignedScalar`, so it is not instantiated for numeric. + +/// `eql-scalars`' distinctness invariant keys `Fixture::Numeric` by its literal +/// string, so `"1"` and `"1.0"` would pass there as "distinct". But they denote +/// the same `Decimal` value (and collide in the ORE ciphertext, per ore-rs's +/// `equivalent_forms_collide_in_ciphertext`), so an aliasing pair would insert +/// duplicate `plaintext` rows and break `fetch_fixture_payload`'s `fetch_one`. +/// This guards distinctness by parsed value, which is the property the fixture +/// table relies on. +#[cfg(test)] +mod numeric_value_guards { + use super::*; + + #[test] + fn fixtures_are_distinct_by_value() { + use std::collections::HashSet; + let vals = numeric_values(); // &[Decimal], parsed from the catalog + let unique: HashSet<_> = vals.iter().collect(); + assert_eq!( + unique.len(), + vals.len(), + "two numeric fixtures alias to the same Decimal value", + ); + } +} + #[cfg(test)] mod text_value_tests { use super::*; diff --git a/tests/sqlx/src/scalar_types.rs b/tests/sqlx/src/scalar_types.rs index a51b288d..926d363d 100644 --- a/tests/sqlx/src/scalar_types.rs +++ b/tests/sqlx/src/scalar_types.rs @@ -56,6 +56,7 @@ macro_rules! scalar_types { int8 => i64, date => chrono::NaiveDate, timestamptz => chrono::DateTime, + numeric => rust_decimal::Decimal, text => String, } }; diff --git a/tests/sqlx/tests/encrypted_domain/family/mutations.rs b/tests/sqlx/tests/encrypted_domain/family/mutations.rs index f2399a2b..889293a5 100644 --- a/tests/sqlx/tests/encrypted_domain/family/mutations.rs +++ b/tests/sqlx/tests/encrypted_domain/family/mutations.rs @@ -212,15 +212,23 @@ async fn blocking_lt_flips_lt_arm_but_not_order_by(pool: PgPool) -> Result<()> { 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?; + // Baseline: `<` works (no raise) and ORDER BY is plaintext-sorted. Uses two + // REAL fixture ORE payloads (smallest two plaintexts), not PLACEHOLDER_PAYLOAD + // — the latter carries a 1-byte `ob` stub that the N-block comparator's + // well-formedness guard now (correctly) rejects. PLACEHOLDER stays in the + // post-mutation `assert_raises` below, where the `lt` blocker raises before + // the comparator ever inspects the term. + let lt_baseline: Option = sqlx::query_scalar( + "SELECT (SELECT payload FROM fixtures.eql_v2_int4 WHERE plaintext = $1)::eql_v3.int4_ord \ + < (SELECT payload FROM fixtures.eql_v2_int4 WHERE plaintext = $2)::eql_v3.int4_ord", + ) + .bind(ascending[0]) + .bind(ascending[1]) + .fetch_one(&pool) + .await?; ensure!( - lt_baseline.is_some(), - "baseline: `_ord` `<` must return a boolean (got {lt_baseline:?})" + lt_baseline == Some(true), + "baseline: smaller `_ord` `<` larger must be true (got {lt_baseline:?})" ); let order_baseline: Vec = sqlx::query_scalar(order_by_sql).fetch_all(&pool).await?; ensure!( diff --git a/tests/sqlx/tests/ore_block_comparator_tests.rs b/tests/sqlx/tests/ore_block_comparator_tests.rs new file mode 100644 index 00000000..cec07bf4 --- /dev/null +++ b/tests/sqlx/tests/ore_block_comparator_tests.rs @@ -0,0 +1,52 @@ +//! Direct unit tests for the generalized N-block ORE comparator +//! `eql_v3.compare_ore_block_256_term`. +//! +//! The malformed-length guards here are creds-free: they construct ORE terms +//! by hand from short byte strings, so they exercise the length validation +//! without needing real (ZeroKMS-generated) ciphertexts. The wide-term ordering +//! test (added in Phase 4) uses generated numeric fixtures. + +use anyhow::Result; +use sqlx::PgPool; + +/// A `bytea` whose length is NOT a valid `49*N + 16` must raise, not silently +/// return 0. Uses a 4-byte term (equal lengths so the equal-length guard does +/// not fire first). +#[sqlx::test] +async fn comparator_rejects_non_conforming_length(pool: PgPool) -> Result<()> { + let sql = "SELECT eql_v3.compare_ore_block_256_term( \ + ROW('\\x00010203'::bytea)::eql_v3.ore_block_256_term, \ + ROW('\\x04050607'::bytea)::eql_v3.ore_block_256_term)"; + let err = sqlx::query_scalar::<_, i32>(sql) + .fetch_one(&pool) + .await + .expect_err("a 4-byte ORE term must raise, not return a comparison"); + assert!( + err.to_string() + .to_lowercase() + .contains("malformed ore term"), + "expected malformed-term error, got: {err}" + ); + Ok(()) +} + +/// A 16-byte term satisfies `(16 - 16) % 49 == 0` and derives N = 0; the +/// `<= 16` clause must still reject it (otherwise it falls through to the +/// all-blocks-equal path and wrongly returns 0). +#[sqlx::test] +async fn comparator_rejects_sixteen_byte_term(pool: PgPool) -> Result<()> { + let sql = "SELECT eql_v3.compare_ore_block_256_term( \ + ROW(repeat('a', 16)::bytea)::eql_v3.ore_block_256_term, \ + ROW(repeat('b', 16)::bytea)::eql_v3.ore_block_256_term)"; + let err = sqlx::query_scalar::<_, i32>(sql) + .fetch_one(&pool) + .await + .expect_err("a 16-byte ORE term (N=0) must raise"); + assert!( + err.to_string() + .to_lowercase() + .contains("malformed ore term"), + "expected malformed-term error, got: {err}" + ); + Ok(()) +} From 9d861c6cd25b34ea4b6691ee7a8e0dd3522bd0d9 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 11 Jun 2026 19:15:00 +1000 Subject: [PATCH 2/9] test(v3): cover 14-block numeric + 12-block timestamptz ORE ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direct comparator tests over generated fixtures: numeric terms are 702 bytes (N=14) and order across a full 14-value ascending chain spanning sign, magnitude, and fractional scale (so the left blocks decide ordering — the regression the missed 9 -> 1+n offset would fail); timestamptz terms are 604 bytes (N=12) and order 1900 < 2099. Verified end-to-end: numeric + timestamptz ordered matrix suites (211 each, incl. < <= > >= / ORDER BY / MIN / MAX) green; full SQLx suite 2026 passed; test:matrix:inventory reconciles both new ordered types; self-contained v3 install green. --- .../sqlx/tests/ore_block_comparator_tests.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/sqlx/tests/ore_block_comparator_tests.rs b/tests/sqlx/tests/ore_block_comparator_tests.rs index cec07bf4..6003d6d5 100644 --- a/tests/sqlx/tests/ore_block_comparator_tests.rs +++ b/tests/sqlx/tests/ore_block_comparator_tests.rs @@ -50,3 +50,81 @@ async fn comparator_rejects_sixteen_byte_term(pool: PgPool) -> Result<()> { ); Ok(()) } + +/// Width: a numeric ORE term must be 14 blocks => 49*14 + 16 = 702 bytes. +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_numeric")))] +async fn numeric_term_is_14_blocks(pool: PgPool) -> Result<()> { + let width: i32 = sqlx::query_scalar( + "SELECT octet_length((((eql_v3.ord_term( \ + (SELECT payload FROM fixtures.eql_v2_numeric WHERE plaintext = (-1000000)::numeric) \ + ::eql_v3.numeric_ord)).terms)[1]).bytes)", + ) + .fetch_one(&pool) + .await?; + assert_eq!(width, 702, "numeric ORE term must be 14 blocks (702 bytes)"); + Ok(()) +} + +/// Full ascending chain of 14-block numeric terms: every adjacent pair must +/// order `-1`. Spans sign, magnitude, and fractional (low-block) scale, so the +/// left blocks — not just the right blocks — decide ordering. This is the +/// regression the missed `9 -> 1+n` left-offset would fail; a single pair could +/// pass against that bug. +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_numeric")))] +async fn numeric_terms_order_in_ascending_chain(pool: PgPool) -> Result<()> { + let ascending = [ + "-1000000000000", + "-1000000", + "-1.001", + "-1", + "-0.5", + "-0.001", + "0", + "0.001", + "0.5", + "0.999999999", + "1", + "1.001", + "1000000", + "1000000000000", + ]; + for pair in ascending.windows(2) { + let (lo, hi) = (pair[0], pair[1]); + let cmp: i32 = sqlx::query_scalar(&format!( + "SELECT eql_v3.compare_ore_block_256_terms( \ + eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_numeric WHERE plaintext = ({lo})::numeric)::eql_v3.numeric_ord), \ + eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_numeric WHERE plaintext = ({hi})::numeric)::eql_v3.numeric_ord))" + )) + .fetch_one(&pool) + .await?; + assert_eq!(cmp, -1, "{lo} must order before {hi}"); + } + Ok(()) +} + +/// Symmetric 12-block (timestamptz, N=12 => 604 bytes) width + ordering check. +/// 12 is the only N strictly between the working 8 and the headline 14. +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_timestamptz")))] +async fn timestamptz_term_is_12_blocks_and_orders(pool: PgPool) -> Result<()> { + let width: i32 = sqlx::query_scalar( + "SELECT octet_length((((eql_v3.ord_term( \ + (SELECT payload FROM fixtures.eql_v2_timestamptz WHERE plaintext = '1970-01-01T00:00:00Z'::timestamptz) \ + ::eql_v3.timestamptz_ord)).terms)[1]).bytes)", + ) + .fetch_one(&pool) + .await?; + assert_eq!( + width, 604, + "timestamptz ORE term must be 12 blocks (604 bytes)" + ); + + let cmp: i32 = sqlx::query_scalar( + "SELECT eql_v3.compare_ore_block_256_terms( \ + eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_timestamptz WHERE plaintext = '1900-01-01T00:00:00Z'::timestamptz)::eql_v3.timestamptz_ord), \ + eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_timestamptz WHERE plaintext = '2099-12-31T23:59:59Z'::timestamptz)::eql_v3.timestamptz_ord))", + ) + .fetch_one(&pool) + .await?; + assert_eq!(cmp, -1, "1900 must order before 2099"); + Ok(()) +} From 3d1b87cac037398a18ba9488b32b85fa3c97ee42 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Thu, 11 Jun 2026 19:16:40 +1000 Subject: [PATCH 3/9] docs(v3): changelog + reference for N-block ORE, ordered numeric/timestamptz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CHANGELOG: rewrite the timestamptz entry (ordering now ships), add the numeric ordered-domain entry, and add a Fixed entry for the N-block ORE comparator generalization + the eql_v3 ore_block_u64_8_256 -> ore_block_256 rename. Reference guide: document the fourth (numeric/Decimal) fixture discriminator — proc-macro routing, scalar_fixture arm, numeric_values accessor + distinctness guard, rust_decimal dep. --- CHANGELOG.md | 7 +++- .../adding-a-scalar-encrypted-domain-type.md | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f60f4903..c5e6997a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ Each entry that ships in a published release links to the PR that introduced it. - **`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)) -- **`eql_v3.timestamptz` encrypted-domain type family (equality-only).** Two jsonb-backed domains for encrypted `timestamptz` columns — `eql_v3.timestamptz` (storage-only) and `eql_v3.timestamptz_eq` (`=` / `<>` via HMAC) — generated from the `timestamptz` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.date` family. Values are **UTC-normalized** (cipherstash has no timezone-preserving type): plaintexts encrypt under the `timestamp` cast. Index via a functional index on the `eql_v3.eq_term` extractor, not an operator class on the domain. **Ordering (`<` `<=` `>` `>=`, `MIN` / `MAX`) is deferred:** cipherstash encrypts `Plaintext::Timestamp` at native 12-block ORE width, but EQL's only ORE comparator (`eql_v2.compare_ore_block_u64_8_256_term`) is hardcoded to 8 blocks, so ordered timestamptz domains would silently mis-order. There are no `eql_v3.timestamptz_ord` / `_ord_ore` domains and no timestamptz `MIN` / `MAX` aggregates until a wide-ORE (12-block) term lands — tracked in [#241](https://github.com/cipherstash/encrypt-query-language/issues/241). Why: a type-safe, equality-searchable encrypted UTC-timestamp column, stacking on the `date` temporal-scalar foundation; ordering follows once the comparator supports the native ciphertext width. ([#257](https://github.com/cipherstash/encrypt-query-language/pull/257)) +- **`eql_v3.timestamptz` encrypted-domain type family (ordered).** Four jsonb-backed domains for encrypted `timestamptz` columns — `eql_v3.timestamptz` (storage-only), `eql_v3.timestamptz_eq` (`=` / `<>` via HMAC), and `eql_v3.timestamptz_ord` / `eql_v3.timestamptz_ord_ore` (also `<` `<=` `>` `>=`, `MIN` / `MAX` via 12-block ORE) — generated from the `timestamptz` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.date` family. Values are **UTC-normalized** (cipherstash has no timezone-preserving type): plaintexts encrypt under the `timestamp` cast. Ordering works because the `eql_v3` ORE block comparator now derives its block count from the ciphertext width (see the comparator entry below) instead of assuming 8. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. ([#257](https://github.com/cipherstash/encrypt-query-language/pull/257)) +- **`eql_v3.numeric` encrypted-domain type family (ordered).** Four jsonb-backed domains for encrypted `numeric` / `decimal` columns — `eql_v3.numeric` (storage-only), `eql_v3.numeric_eq` (`=` / `<>` via HMAC), and `eql_v3.numeric_ord` / `eql_v3.numeric_ord_ore` (also `<` `<=` `>` `>=`, `MIN` / `MAX` via 14-block ORE) — generated from the `numeric` row in `eql-scalars::CATALOG`. cipherstash encrypts `Plaintext::Decimal` at native 14-block ORE width; ordering matches `rust_decimal::Decimal` ordering exactly (equivalent scales such as `1` and `1.0` collide, like Postgres `numeric`). Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors. Why: a type-safe, ordered encrypted decimal column, the first scalar to exercise an ORE term wider than 8 blocks. ([#241](https://github.com/cipherstash/encrypt-query-language/issues/241)) - **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)) - **`eql_v3.text` encrypted-domain family (`text`, `text_eq`, `text_match`, `text_ord`, `text_ord_ore`, `text_search`).** Adds equality (`=` / `<>` via HMAC), match (`@>` / `<@` via a new self-contained `eql_v3.bloom_filter` SEM index term), and ORE ordering (`<` `<=` `>` `>=`, `min` / `max`) for encrypted text, at parity with EQL v2 text — generated from the `text` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. `text` is the first scalar to add a new index `Term` (`Bloom`) and the first non-integer, unbounded ordered kind (lexicographic pivots, hand-written `impl ScalarType`). The combined **`text_search`** domain carries all three capabilities in one type — `=` / `<>` via HMAC, `<` `<=` `>` `>=` / `min` / `max` via ORE, and `@>` / `<@` via bloom filter. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` / `eql_v3.match_term` extractors, not an operator class on the domain. Why: brings searchable encrypted text to the namespaced, `eql_v2`-free `eql_v3` surface. Match is exposed as bloom-filter containment on the `text_match` / `text_search` domains — deliberately *not* SQL `LIKE` (no wildcard/anchoring; probabilistic ngram containment) — and never backs equality. **Equality on the ordered text domains (`text_ord`, `text_ord_ore`) and on `text_search` always routes `=` / `<>` through `hm` (exact HMAC), never the ORE term — ORE is not exact-equality for text** (integer ordered domains keep exact ORE equality, which is lossless for them). ([#260](https://github.com/cipherstash/encrypt-query-language/pull/260)) - **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_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)) @@ -37,6 +38,10 @@ Each entry that ships in a published release links to the PR that introduced it. - **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)) +### Fixed + +- **The `eql_v3` ORE block comparator now orders ciphertexts of any block count, not just 8.** `eql_v3.compare_ore_block_256_term` derives the block count `N` from the term length (`octet_length = 49·N + 16`) instead of hardcoding 8, so encrypted types whose native ORE width exceeds 8 blocks — `numeric` (14) and `timestamptz` (12) — order, range-query, `ORDER BY`, and `MIN`/`MAX` correctly instead of silently mis-ordering. Malformed terms (length not `49·N + 16` for `N ≥ 1`) now raise instead of returning a bogus comparison. The self-contained `eql_v3` SEM type was renamed `eql_v3.ore_block_u64_8_256 → eql_v3.ore_block_256` to reflect that it is width-agnostic (the `eql_v2` type is unchanged). No effect on existing 8-block types (a no-op for `N = 8`). ([#241](https://github.com/cipherstash/encrypt-query-language/issues/241)) + ## [2.3.1] — 2026-05-21 ### Fixed diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index 76d987dc..9c6c107a 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -326,6 +326,42 @@ first added) also needs, in `tests/sqlx/src/fixtures/eql_plaintext.rs`: `Plaintext::*` variant (`Plaintext::NaiveDate` / `Plaintext::Timestamp` / `Plaintext::Text`), plus the three mirrored `#[test]`s. +#### A fourth fixture shape: non-integer, non-chrono, non-text (`numeric` / `Decimal`) + +`numeric` (backed by `rust_decimal::Decimal`, 14-block ORE — the first scalar +whose ORE term is wider than 8 blocks) is ordered but is **neither** the integer +materialiser, **nor** chrono (`temporal`), **nor** `text` (it owns a `Decimal`, +not a `String`, and has no `Match` index). It therefore introduces a **fourth +fixture discriminator**, which means touching the proc-macro routing, not just +the type list. Beyond the §3.1 `eql_plaintext.rs` wiring above (`Cast::DECIMAL`, +`PlaintextSqlType::NUMERIC`, the `cast_for_kind` / `plaintext_sql_type_for_kind` +arms, `Sealed for Decimal`, `EqlPlaintext for Decimal` → `Plaintext::Decimal`), +it also needs: + +- an **`is_numeric_token`** arm in `crates/eql-tests-macros/src/lib.rs`'s + fixture-module router — without it `scalar_types!(fixture_modules)` panics at + compile time on the unrecognised kind (the router handled only `temporal` / + `text` before); +- a **`numeric` arm** in the `scalar_fixture!` macro + (`tests/sqlx/src/fixtures/scalar_fixture.rs`) — the temporal arm's twin + (`[Unique, Ore]`, pivot-presence asserts via `OrderedScalar`), but no `Match` + and no chrono; +- a hand-written **`numeric_values()`** accessor plus `impl ScalarType` / + `OrderedScalar for Decimal` in `tests/sqlx/src/scalar_domains.rs` — parsing the + catalog's `Fixture::Numeric` strings into a `LazyLock>` (the + catalog stays zero-dep; the parse lives in the harness). `Decimal: Ord` supplies + the expected sort order — `ore-rs` guarantees the ciphertext order agrees, and + equivalent scales (`1` ≡ `1.0`) collide like `Decimal`'s own `Ord`. Add a + **`fixtures_are_distinct_by_value`** guard (parse → `HashSet`): the zero-dep + catalog only dedupes by literal string, so `"1"` / `"1.0"` would slip past it + but collide in both the ORE ciphertext and the fixture table; +- the **`rust_decimal` dependency** + the sqlx **`rust_decimal` feature** in + `tests/sqlx/Cargo.toml` (in `[dependencies]`, not `[dev-dependencies]` — the + `Decimal` impls live in the crate's library code). + +See `docs/plans/2026-06-11-ore-block-comparator-n-blocks-design.md` for the full +worked example (and the N-block ORE comparator change the wide term relies on). + ### New-capability domains (e.g. `_match` / `Bloom`) A domain carrying a capability the matrix does not model — `text`'s `_match` From 2111c2afa6d02a2497e4a801f2db2a37955d1a14 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 15 Jun 2026 11:06:19 +1000 Subject: [PATCH 4/9] refactor(v3): finish ore_block_256 rename + sync codegen goldens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the eql_v3.ore_block_u64_8_256 -> ore_block_256 rename across the docs (CLAUDE.md, the scalar guide, eql-functions, sql-support; eql_v2 names left unchanged) and the v3 SEM file header — the @file/subset comment had over-applied the rename to the v2-origin path (src/ore_block_u64_8_256). Regenerate the codegen reference goldens for every catalog type after rebasing onto eql_v3: add the numeric reference dir and expand timestamptz to its now-ordered shape so the parity gate passes. Also address review feedback: - ScalarKind::rust_type returns the now-real numeric type (rust_decimal::Decimal); only jsonb remains surfaceless. De-stale its doc and the 'timestamptz is equality-only' test comment; add numeric_maps_to_decimal. - Correct the guide's stale 'timestamptz is equality-only' prose and a dangling link to the removed design plan doc. - Add comparator_rejects_mismatched_block_widths (8-block vs 14-block terms must raise via the different-lengths guard). - Add the PR link (#276) to the numeric and N-block changelog entries. --- CHANGELOG.md | 4 +- crates/eql-scalars/src/kind.rs | 15 +- crates/eql-scalars/src/tests.rs | 14 +- .../adding-a-scalar-encrypted-domain-type.md | 22 +- src/v3/sem/ore_block_256/functions.sql | 2 +- .../numeric/numeric_eq_functions.sql | 407 ++++++++++++++++++ .../numeric/numeric_eq_operators.sql | 234 ++++++++++ .../reference/numeric/numeric_functions.sql | 404 +++++++++++++++++ .../reference/numeric/numeric_operators.sql | 228 ++++++++++ .../numeric/numeric_ord_aggregates.sql | 63 +++ .../numeric/numeric_ord_functions.sql | 396 +++++++++++++++++ .../numeric/numeric_ord_operators.sql | 246 +++++++++++ .../numeric/numeric_ord_ore_aggregates.sql | 63 +++ .../numeric/numeric_ord_ore_functions.sql | 396 +++++++++++++++++ .../numeric/numeric_ord_ore_operators.sql | 246 +++++++++++ .../reference/numeric/numeric_types.sql | 73 ++++ .../timestamptz_ord_aggregates.sql | 63 +++ .../timestamptz/timestamptz_ord_functions.sql | 396 +++++++++++++++++ .../timestamptz/timestamptz_ord_operators.sql | 246 +++++++++++ .../timestamptz_ord_ore_aggregates.sql | 63 +++ .../timestamptz_ord_ore_functions.sql | 396 +++++++++++++++++ .../timestamptz_ord_ore_operators.sql | 246 +++++++++++ .../timestamptz/timestamptz_types.sql | 32 ++ .../sqlx/tests/ore_block_comparator_tests.rs | 22 + 24 files changed, 4255 insertions(+), 22 deletions(-) create mode 100644 tests/codegen/reference/numeric/numeric_eq_functions.sql create mode 100644 tests/codegen/reference/numeric/numeric_eq_operators.sql create mode 100644 tests/codegen/reference/numeric/numeric_functions.sql create mode 100644 tests/codegen/reference/numeric/numeric_operators.sql create mode 100644 tests/codegen/reference/numeric/numeric_ord_aggregates.sql create mode 100644 tests/codegen/reference/numeric/numeric_ord_functions.sql create mode 100644 tests/codegen/reference/numeric/numeric_ord_operators.sql create mode 100644 tests/codegen/reference/numeric/numeric_ord_ore_aggregates.sql create mode 100644 tests/codegen/reference/numeric/numeric_ord_ore_functions.sql create mode 100644 tests/codegen/reference/numeric/numeric_ord_ore_operators.sql create mode 100644 tests/codegen/reference/numeric/numeric_types.sql create mode 100644 tests/codegen/reference/timestamptz/timestamptz_ord_aggregates.sql create mode 100644 tests/codegen/reference/timestamptz/timestamptz_ord_functions.sql create mode 100644 tests/codegen/reference/timestamptz/timestamptz_ord_operators.sql create mode 100644 tests/codegen/reference/timestamptz/timestamptz_ord_ore_aggregates.sql create mode 100644 tests/codegen/reference/timestamptz/timestamptz_ord_ore_functions.sql create mode 100644 tests/codegen/reference/timestamptz/timestamptz_ord_ore_operators.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e6997a..017b05d9 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. - **`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)) - **`eql_v3.timestamptz` encrypted-domain type family (ordered).** Four jsonb-backed domains for encrypted `timestamptz` columns — `eql_v3.timestamptz` (storage-only), `eql_v3.timestamptz_eq` (`=` / `<>` via HMAC), and `eql_v3.timestamptz_ord` / `eql_v3.timestamptz_ord_ore` (also `<` `<=` `>` `>=`, `MIN` / `MAX` via 12-block ORE) — generated from the `timestamptz` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.date` family. Values are **UTC-normalized** (cipherstash has no timezone-preserving type): plaintexts encrypt under the `timestamp` cast. Ordering works because the `eql_v3` ORE block comparator now derives its block count from the ciphertext width (see the comparator entry below) instead of assuming 8. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors, not an operator class on the domain. ([#257](https://github.com/cipherstash/encrypt-query-language/pull/257)) -- **`eql_v3.numeric` encrypted-domain type family (ordered).** Four jsonb-backed domains for encrypted `numeric` / `decimal` columns — `eql_v3.numeric` (storage-only), `eql_v3.numeric_eq` (`=` / `<>` via HMAC), and `eql_v3.numeric_ord` / `eql_v3.numeric_ord_ore` (also `<` `<=` `>` `>=`, `MIN` / `MAX` via 14-block ORE) — generated from the `numeric` row in `eql-scalars::CATALOG`. cipherstash encrypts `Plaintext::Decimal` at native 14-block ORE width; ordering matches `rust_decimal::Decimal` ordering exactly (equivalent scales such as `1` and `1.0` collide, like Postgres `numeric`). Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors. Why: a type-safe, ordered encrypted decimal column, the first scalar to exercise an ORE term wider than 8 blocks. ([#241](https://github.com/cipherstash/encrypt-query-language/issues/241)) +- **`eql_v3.numeric` encrypted-domain type family (ordered).** Four jsonb-backed domains for encrypted `numeric` / `decimal` columns — `eql_v3.numeric` (storage-only), `eql_v3.numeric_eq` (`=` / `<>` via HMAC), and `eql_v3.numeric_ord` / `eql_v3.numeric_ord_ore` (also `<` `<=` `>` `>=`, `MIN` / `MAX` via 14-block ORE) — generated from the `numeric` row in `eql-scalars::CATALOG`. cipherstash encrypts `Plaintext::Decimal` at native 14-block ORE width; ordering matches `rust_decimal::Decimal` ordering exactly (equivalent scales such as `1` and `1.0` collide, like Postgres `numeric`). Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` extractors. Why: a type-safe, ordered encrypted decimal column, the first scalar to exercise an ORE term wider than 8 blocks. ([#241](https://github.com/cipherstash/encrypt-query-language/issues/241), [#276](https://github.com/cipherstash/encrypt-query-language/pull/276)) - **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)) - **`eql_v3.text` encrypted-domain family (`text`, `text_eq`, `text_match`, `text_ord`, `text_ord_ore`, `text_search`).** Adds equality (`=` / `<>` via HMAC), match (`@>` / `<@` via a new self-contained `eql_v3.bloom_filter` SEM index term), and ORE ordering (`<` `<=` `>` `>=`, `min` / `max`) for encrypted text, at parity with EQL v2 text — generated from the `text` row in `eql-scalars::CATALOG` by the same materializer as the `eql_v3.int4` reference. `text` is the first scalar to add a new index `Term` (`Bloom`) and the first non-integer, unbounded ordered kind (lexicographic pivots, hand-written `impl ScalarType`). The combined **`text_search`** domain carries all three capabilities in one type — `=` / `<>` via HMAC, `<` `<=` `>` `>=` / `min` / `max` via ORE, and `@>` / `<@` via bloom filter. Index via a functional index on the `eql_v3.eq_term` / `eql_v3.ord_term` / `eql_v3.match_term` extractors, not an operator class on the domain. Why: brings searchable encrypted text to the namespaced, `eql_v2`-free `eql_v3` surface. Match is exposed as bloom-filter containment on the `text_match` / `text_search` domains — deliberately *not* SQL `LIKE` (no wildcard/anchoring; probabilistic ngram containment) — and never backs equality. **Equality on the ordered text domains (`text_ord`, `text_ord_ore`) and on `text_search` always routes `=` / `<>` through `hm` (exact HMAC), never the ORE term — ORE is not exact-equality for text** (integer ordered domains keep exact ORE equality, which is lossless for them). ([#260](https://github.com/cipherstash/encrypt-query-language/pull/260)) - **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_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)) @@ -40,7 +40,7 @@ Each entry that ships in a published release links to the PR that introduced it. ### Fixed -- **The `eql_v3` ORE block comparator now orders ciphertexts of any block count, not just 8.** `eql_v3.compare_ore_block_256_term` derives the block count `N` from the term length (`octet_length = 49·N + 16`) instead of hardcoding 8, so encrypted types whose native ORE width exceeds 8 blocks — `numeric` (14) and `timestamptz` (12) — order, range-query, `ORDER BY`, and `MIN`/`MAX` correctly instead of silently mis-ordering. Malformed terms (length not `49·N + 16` for `N ≥ 1`) now raise instead of returning a bogus comparison. The self-contained `eql_v3` SEM type was renamed `eql_v3.ore_block_u64_8_256 → eql_v3.ore_block_256` to reflect that it is width-agnostic (the `eql_v2` type is unchanged). No effect on existing 8-block types (a no-op for `N = 8`). ([#241](https://github.com/cipherstash/encrypt-query-language/issues/241)) +- **The `eql_v3` ORE block comparator now orders ciphertexts of any block count, not just 8.** `eql_v3.compare_ore_block_256_term` derives the block count `N` from the term length (`octet_length = 49·N + 16`) instead of hardcoding 8, so encrypted types whose native ORE width exceeds 8 blocks — `numeric` (14) and `timestamptz` (12) — order, range-query, `ORDER BY`, and `MIN`/`MAX` correctly instead of silently mis-ordering. Malformed terms (length not `49·N + 16` for `N ≥ 1`) now raise instead of returning a bogus comparison. The self-contained `eql_v3` SEM type was renamed `eql_v3.ore_block_u64_8_256 → eql_v3.ore_block_256` to reflect that it is width-agnostic (the `eql_v2` type is unchanged). No effect on existing 8-block types (a no-op for `N = 8`). ([#241](https://github.com/cipherstash/encrypt-query-language/issues/241), [#276](https://github.com/cipherstash/encrypt-query-language/pull/276)) ## [2.3.1] — 2026-05-21 diff --git a/crates/eql-scalars/src/kind.rs b/crates/eql-scalars/src/kind.rs index 69ab70f5..e91813da 100644 --- a/crates/eql-scalars/src/kind.rs +++ b/crates/eql-scalars/src/kind.rs @@ -97,11 +97,11 @@ impl ScalarKind { } /// A debug/identifier string for the kind: the canonical Rust plaintext type - /// name (`"i32"`, `"chrono::NaiveDate"`). `Numeric`/`Jsonb` have **no - /// generated SQL surface** and no catalog row, so calling this on them is a - /// programming error and panics loudly rather than returning a plausible SQL - /// token a premature caller might feed into codegen. Only call site today is - /// `crates/eql-scalars/src/tests.rs`. + /// name (`"i32"`, `"chrono::NaiveDate"`, `"rust_decimal::Decimal"`). `Jsonb` + /// has **no generated SQL surface** and no catalog row, so calling this on it + /// is a programming error and panics loudly rather than returning a plausible + /// SQL token a premature caller might feed into codegen. Only call site today + /// is `crates/eql-scalars/src/tests.rs`. pub const fn rust_type(self) -> &'static str { match self { ScalarKind::I16 => "i16", @@ -110,8 +110,9 @@ impl ScalarKind { ScalarKind::Text => "text", ScalarKind::Date => "chrono::NaiveDate", ScalarKind::Timestamptz => "chrono::DateTime", - ScalarKind::Numeric | ScalarKind::Jsonb => { - panic!("ScalarKind::rust_type: numeric/jsonb have no generated surface yet") + ScalarKind::Numeric => "rust_decimal::Decimal", + ScalarKind::Jsonb => { + panic!("ScalarKind::rust_type: jsonb has no generated surface yet") } } } diff --git a/crates/eql-scalars/src/tests.rs b/crates/eql-scalars/src/tests.rs index 1be3a65f..be5e9880 100644 --- a/crates/eql-scalars/src/tests.rs +++ b/crates/eql-scalars/src/tests.rs @@ -125,14 +125,24 @@ mod rust_tests { #[test] fn timestamptz_maps_to_datetime() { - // Temporal, non-integer, equality-only kind: it carries a rust type but - // no i128 range, so it is not `is_int()` and `as_bounded_int()` returns + // Temporal, non-integer, ordered kind: it carries a rust type but no + // i128 range, so it is not `is_int()` and `as_bounded_int()` returns // `None` — the bounded accessors are not reachable for it. assert_eq!(ScalarKind::Timestamptz.rust_type(), "chrono::DateTime"); assert!(!ScalarKind::Timestamptz.is_int()); assert_eq!(ScalarKind::Timestamptz.as_bounded_int(), None); } + #[test] + fn numeric_maps_to_decimal() { + // Ordered, non-integer, non-chrono kind (14-block ORE): carries a rust + // type but no i128 range, so it is not `is_int()` and `as_bounded_int()` + // returns `None`. Pins the now-real `rust_type` arm (it no longer panics). + assert_eq!(ScalarKind::Numeric.rust_type(), "rust_decimal::Decimal"); + assert!(!ScalarKind::Numeric.is_int()); + assert_eq!(ScalarKind::Numeric.as_bounded_int(), None); + } + /// 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. `numeric_value` would resolve to `None` for a diff --git a/docs/reference/adding-a-scalar-encrypted-domain-type.md b/docs/reference/adding-a-scalar-encrypted-domain-type.md index 9c6c107a..8c0bdf6e 100644 --- a/docs/reference/adding-a-scalar-encrypted-domain-type.md +++ b/docs/reference/adding-a-scalar-encrypted-domain-type.md @@ -204,14 +204,16 @@ there is no `_VALUES` const; the SQLx harness parses the catalog strings into A **temporal** scalar (`date` is the *ordered* temporal reference) 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). **`timestamptz` is the exception: it is equality-only, not -ordered** — its catalog row uses `EQ_ONLY_DOMAINS` (storage + `_eq`, no -`_ord`/`_ord_ore`), the eq-only shape of §3, because cipherstash encrypts -`Plaintext::Timestamp` at native 12-block ORE width while EQL's only comparator -(`eql_v2.compare_ore_block_u64_8_256_term`) is hardcoded to 8 blocks, so an -ordered `timestamptz` domain would silently mis-order (see the catalog comment on -the `TIMESTAMPTZ` spec). Its value-wiring is still the temporal path below; only -its domain set differs. The three divergences (for the ordered `date`): +token-driven). **`timestamptz` follows the same *ordered* temporal path as +`date`** — its catalog row carries the full ordered domain set (storage + `_eq` + +`_ord`/`_ord_ore`). cipherstash encrypts `Plaintext::Timestamp` at native +12-block ORE width, and the `eql_v3` comparator +(`eql_v3.compare_ore_block_256_term`) now derives its block count `N` from the +term length instead of assuming 8, so the 12-block ciphertexts order correctly +(see the N-block ORE comparator entry in the `CHANGELOG.md` and the catalog +comment on the `TIMESTAMPTZ` spec). Its value-wiring is the temporal path below; +the only practical difference from `date` is that values are UTC-normalized. The +three divergences (for the ordered `date`): - **String-backed fixtures.** `eql-scalars` stays zero-dependency, so the catalog stores ISO strings (`Fixture::Date("1970-01-01")`), not `chrono` @@ -359,8 +361,8 @@ it also needs: `tests/sqlx/Cargo.toml` (in `[dependencies]`, not `[dev-dependencies]` — the `Decimal` impls live in the crate's library code). -See `docs/plans/2026-06-11-ore-block-comparator-n-blocks-design.md` for the full -worked example (and the N-block ORE comparator change the wide term relies on). +See the N-block ORE comparator entry in the `CHANGELOG.md` for the comparator +change the wide `numeric` / `timestamptz` terms rely on. ### New-capability domains (e.g. `_match` / `Bloom`) diff --git a/src/v3/sem/ore_block_256/functions.sql b/src/v3/sem/ore_block_256/functions.sql index 83bd42e1..3eca4f1d 100644 --- a/src/v3/sem/ore_block_256/functions.sql +++ b/src/v3/sem/ore_block_256/functions.sql @@ -6,7 +6,7 @@ --! @file v3/sem/ore_block_256/functions.sql --! @brief ORE block construction, extraction, and comparison (eql_v3 SEM). --! ---! jsonb-only subset of src/ore_block_256/functions.sql. The +--! 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 diff --git a/tests/codegen/reference/numeric/numeric_eq_functions.sql b/tests/codegen/reference/numeric/numeric_eq_functions.sql new file mode 100644 index 00000000..2b20d70b --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_eq_functions.sql @@ -0,0 +1,407 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/hmac_256/functions.sql + +--! @file encrypted_domain/numeric/numeric_eq_functions.sql +--! @brief Functions for eql_v3.numeric_eq. + +--! @brief Index extractor for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @return eql_v3.hmac_256 +CREATE FUNCTION eql_v3.eq_term(a eql_v3.numeric_eq) +RETURNS eql_v3.hmac_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.hmac_256(a::jsonb) $$; + +--! @brief Operator wrapper for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.eq_term(a) = eql_v3.eq_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric_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.numeric_eq) $$; + +--! @brief Operator wrapper for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.eq_term(a::eql_v3.numeric_eq) = eql_v3.eq_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.eq_term(a) <> eql_v3.eq_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric_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.numeric_eq) $$; + +--! @brief Operator wrapper for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.eq_term(a::eql_v3.numeric_eq) <> eql_v3.eq_term(b) $$; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric_eq, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.numeric_eq) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param selector text +--! @return eql_v3.numeric_eq +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric_eq, selector text) +RETURNS eql_v3.numeric_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param selector integer +--! @return eql_v3.numeric_eq +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric_eq, selector integer) +RETURNS eql_v3.numeric_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param selector eql_v3.numeric_eq +--! @return eql_v3.numeric_eq +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.numeric_eq) +RETURNS eql_v3.numeric_eq IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param selector text +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric_eq, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param selector integer +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric_eq, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param selector eql_v3.numeric_eq +--! @return text +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.numeric_eq) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text +--! @return boolean +CREATE FUNCTION eql_v3."?"(a eql_v3.numeric_eq, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?|"(a eql_v3.numeric_eq, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?&"(a eql_v3.numeric_eq, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@?"(a eql_v3.numeric_eq, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@@"(a eql_v3.numeric_eq, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#>"(a eql_v3.numeric_eq, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text[] +--! @return text +CREATE FUNCTION eql_v3."#>>"(a eql_v3.numeric_eq, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_eq, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b integer +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_eq, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_eq, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#-"(a eql_v3.numeric_eq, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b eql_v3.numeric_eq +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric_eq, b eql_v3.numeric_eq) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a eql_v3.numeric_eq +--! @param b jsonb +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric_eq, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_eq. +--! @param a jsonb +--! @param b eql_v3.numeric_eq +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.numeric_eq) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_eq'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/numeric/numeric_eq_operators.sql b/tests/codegen/reference/numeric/numeric_eq_operators.sql new file mode 100644 index 00000000..6ec23570 --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_eq_operators.sql @@ -0,0 +1,234 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_eq_functions.sql + +--! @file encrypted_domain/numeric/numeric_eq_operators.sql +--! @brief Operators for eql_v3.numeric_eq. + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = integer +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = integer +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR ? ( + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text +); + +CREATE OPERATOR ?| ( + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text[] +); + +CREATE OPERATOR ?& ( + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text[] +); + +CREATE OPERATOR @? ( + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonpath +); + +CREATE OPERATOR @@ ( + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonpath +); + +CREATE OPERATOR #> ( + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text[] +); + +CREATE OPERATOR #>> ( + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text[] +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = integer +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text[] +); + +CREATE OPERATOR #- ( + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = text[] +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = eql_v3.numeric_eq +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric_eq, RIGHTARG = jsonb +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_eq +); diff --git a/tests/codegen/reference/numeric/numeric_functions.sql b/tests/codegen/reference/numeric/numeric_functions.sql new file mode 100644 index 00000000..9c6146ab --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_functions.sql @@ -0,0 +1,404 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/functions.sql + +--! @file encrypted_domain/numeric/numeric_functions.sql +--! @brief Functions for eql_v3.numeric. + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '>=', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.numeric) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param selector text +--! @return eql_v3.numeric +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric, selector text) +RETURNS eql_v3.numeric IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param selector integer +--! @return eql_v3.numeric +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric, selector integer) +RETURNS eql_v3.numeric IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param selector eql_v3.numeric +--! @return eql_v3.numeric +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.numeric) +RETURNS eql_v3.numeric IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param selector text +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param selector integer +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param selector eql_v3.numeric +--! @return text +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.numeric) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text +--! @return boolean +CREATE FUNCTION eql_v3."?"(a eql_v3.numeric, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?|"(a eql_v3.numeric, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?&"(a eql_v3.numeric, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@?"(a eql_v3.numeric, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@@"(a eql_v3.numeric, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#>"(a eql_v3.numeric, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text[] +--! @return text +CREATE FUNCTION eql_v3."#>>"(a eql_v3.numeric, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b integer +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#-"(a eql_v3.numeric, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b eql_v3.numeric +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric, b eql_v3.numeric) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a eql_v3.numeric +--! @param b jsonb +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric. +--! @param a jsonb +--! @param b eql_v3.numeric +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.numeric) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/numeric/numeric_operators.sql b/tests/codegen/reference/numeric/numeric_operators.sql new file mode 100644 index 00000000..b62f419e --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_operators.sql @@ -0,0 +1,228 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_functions.sql + +--! @file encrypted_domain/numeric/numeric_operators.sql +--! @brief Operators for eql_v3.numeric. + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric, RIGHTARG = text +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric, RIGHTARG = integer +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric, RIGHTARG = text +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric, RIGHTARG = integer +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR ? ( + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.numeric, RIGHTARG = text +); + +CREATE OPERATOR ?| ( + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.numeric, RIGHTARG = text[] +); + +CREATE OPERATOR ?& ( + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.numeric, RIGHTARG = text[] +); + +CREATE OPERATOR @? ( + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.numeric, RIGHTARG = jsonpath +); + +CREATE OPERATOR @@ ( + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.numeric, RIGHTARG = jsonpath +); + +CREATE OPERATOR #> ( + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.numeric, RIGHTARG = text[] +); + +CREATE OPERATOR #>> ( + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.numeric, RIGHTARG = text[] +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric, RIGHTARG = text +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric, RIGHTARG = integer +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric, RIGHTARG = text[] +); + +CREATE OPERATOR #- ( + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.numeric, RIGHTARG = text[] +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric, RIGHTARG = eql_v3.numeric +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric, RIGHTARG = jsonb +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric +); diff --git a/tests/codegen/reference/numeric/numeric_ord_aggregates.sql b/tests/codegen/reference/numeric/numeric_ord_aggregates.sql new file mode 100644 index 00000000..03bf02c1 --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_ord_aggregates.sql @@ -0,0 +1,63 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_ord_functions.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_ord_operators.sql + +--! @file encrypted_domain/numeric/numeric_ord_aggregates.sql +--! @brief Aggregates for eql_v3.numeric_ord. + +--! @brief State function for min on eql_v3.numeric_ord. +--! @param state eql_v3.numeric_ord +--! @param value eql_v3.numeric_ord +--! @return eql_v3.numeric_ord +CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.numeric_ord, value eql_v3.numeric_ord) +RETURNS eql_v3.numeric_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 min aggregate for eql_v3.numeric_ord. +--! @param input eql_v3.numeric_ord +--! @return eql_v3.numeric_ord +CREATE AGGREGATE eql_v3.min(eql_v3.numeric_ord) ( + sfunc = eql_v3.min_sfunc, + stype = eql_v3.numeric_ord, + combinefunc = eql_v3.min_sfunc, + parallel = safe +); + +--! @brief State function for max on eql_v3.numeric_ord. +--! @param state eql_v3.numeric_ord +--! @param value eql_v3.numeric_ord +--! @return eql_v3.numeric_ord +CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.numeric_ord, value eql_v3.numeric_ord) +RETURNS eql_v3.numeric_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 max aggregate for eql_v3.numeric_ord. +--! @param input eql_v3.numeric_ord +--! @return eql_v3.numeric_ord +CREATE AGGREGATE eql_v3.max(eql_v3.numeric_ord) ( + sfunc = eql_v3.max_sfunc, + stype = eql_v3.numeric_ord, + combinefunc = eql_v3.max_sfunc, + parallel = safe +); diff --git a/tests/codegen/reference/numeric/numeric_ord_functions.sql b/tests/codegen/reference/numeric/numeric_ord_functions.sql new file mode 100644 index 00000000..5467f834 --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_ord_functions.sql @@ -0,0 +1,396 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/operators.sql + +--! @file encrypted_domain/numeric/numeric_ord_functions.sql +--! @brief Functions for eql_v3.numeric_ord. + +--! @brief Index extractor for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @return eql_v3.ore_block_256 +CREATE FUNCTION eql_v3.ord_term(a eql_v3.numeric_ord) +RETURNS eql_v3.ore_block_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ore_block_256(a::jsonb) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric_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.numeric_ord) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric_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.numeric_ord) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric_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.numeric_ord) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric_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.numeric_ord) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric_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.numeric_ord) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric_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.numeric_ord) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord) >= eql_v3.ord_term(b) $$; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric_ord, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric_ord, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.numeric_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param selector text +--! @return eql_v3.numeric_ord +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric_ord, selector text) +RETURNS eql_v3.numeric_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param selector integer +--! @return eql_v3.numeric_ord +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric_ord, selector integer) +RETURNS eql_v3.numeric_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a jsonb +--! @param selector eql_v3.numeric_ord +--! @return eql_v3.numeric_ord +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.numeric_ord) +RETURNS eql_v3.numeric_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param selector text +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric_ord, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param selector integer +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric_ord, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a jsonb +--! @param selector eql_v3.numeric_ord +--! @return text +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.numeric_ord) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text +--! @return boolean +CREATE FUNCTION eql_v3."?"(a eql_v3.numeric_ord, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?|"(a eql_v3.numeric_ord, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?&"(a eql_v3.numeric_ord, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@?"(a eql_v3.numeric_ord, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@@"(a eql_v3.numeric_ord, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#>"(a eql_v3.numeric_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text[] +--! @return text +CREATE FUNCTION eql_v3."#>>"(a eql_v3.numeric_ord, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_ord, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b integer +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_ord, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#-"(a eql_v3.numeric_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b eql_v3.numeric_ord +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric_ord, b eql_v3.numeric_ord) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a eql_v3.numeric_ord +--! @param b jsonb +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric_ord, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord. +--! @param a jsonb +--! @param b eql_v3.numeric_ord +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.numeric_ord) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_ord'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/numeric/numeric_ord_operators.sql b/tests/codegen/reference/numeric/numeric_ord_operators.sql new file mode 100644 index 00000000..847bf6a1 --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_ord_operators.sql @@ -0,0 +1,246 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_ord_functions.sql + +--! @file encrypted_domain/numeric/numeric_ord_operators.sql +--! @brief Operators for eql_v3.numeric_ord. + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = integer +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = integer +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord +); + +CREATE OPERATOR ? ( + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text +); + +CREATE OPERATOR ?| ( + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text[] +); + +CREATE OPERATOR ?& ( + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text[] +); + +CREATE OPERATOR @? ( + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonpath +); + +CREATE OPERATOR @@ ( + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonpath +); + +CREATE OPERATOR #> ( + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text[] +); + +CREATE OPERATOR #>> ( + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text[] +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = integer +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text[] +); + +CREATE OPERATOR #- ( + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = text[] +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = eql_v3.numeric_ord +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric_ord, RIGHTARG = jsonb +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord +); diff --git a/tests/codegen/reference/numeric/numeric_ord_ore_aggregates.sql b/tests/codegen/reference/numeric/numeric_ord_ore_aggregates.sql new file mode 100644 index 00000000..e78a6ffb --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_ord_ore_aggregates.sql @@ -0,0 +1,63 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_ord_ore_functions.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_ord_ore_operators.sql + +--! @file encrypted_domain/numeric/numeric_ord_ore_aggregates.sql +--! @brief Aggregates for eql_v3.numeric_ord_ore. + +--! @brief State function for min on eql_v3.numeric_ord_ore. +--! @param state eql_v3.numeric_ord_ore +--! @param value eql_v3.numeric_ord_ore +--! @return eql_v3.numeric_ord_ore +CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.numeric_ord_ore, value eql_v3.numeric_ord_ore) +RETURNS eql_v3.numeric_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 min aggregate for eql_v3.numeric_ord_ore. +--! @param input eql_v3.numeric_ord_ore +--! @return eql_v3.numeric_ord_ore +CREATE AGGREGATE eql_v3.min(eql_v3.numeric_ord_ore) ( + sfunc = eql_v3.min_sfunc, + stype = eql_v3.numeric_ord_ore, + combinefunc = eql_v3.min_sfunc, + parallel = safe +); + +--! @brief State function for max on eql_v3.numeric_ord_ore. +--! @param state eql_v3.numeric_ord_ore +--! @param value eql_v3.numeric_ord_ore +--! @return eql_v3.numeric_ord_ore +CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.numeric_ord_ore, value eql_v3.numeric_ord_ore) +RETURNS eql_v3.numeric_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 max aggregate for eql_v3.numeric_ord_ore. +--! @param input eql_v3.numeric_ord_ore +--! @return eql_v3.numeric_ord_ore +CREATE AGGREGATE eql_v3.max(eql_v3.numeric_ord_ore) ( + sfunc = eql_v3.max_sfunc, + stype = eql_v3.numeric_ord_ore, + combinefunc = eql_v3.max_sfunc, + parallel = safe +); diff --git a/tests/codegen/reference/numeric/numeric_ord_ore_functions.sql b/tests/codegen/reference/numeric/numeric_ord_ore_functions.sql new file mode 100644 index 00000000..a62ab25a --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_ord_ore_functions.sql @@ -0,0 +1,396 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/operators.sql + +--! @file encrypted_domain/numeric/numeric_ord_ore_functions.sql +--! @brief Functions for eql_v3.numeric_ord_ore. + +--! @brief Index extractor for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @return eql_v3.ore_block_256 +CREATE FUNCTION eql_v3.ord_term(a eql_v3.numeric_ord_ore) +RETURNS eql_v3.ore_block_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ore_block_256(a::jsonb) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.numeric_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.numeric_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord_ore) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.numeric_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.numeric_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord_ore) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.numeric_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.numeric_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord_ore) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.numeric_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.numeric_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord_ore) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.numeric_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.numeric_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord_ore) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.numeric_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.numeric_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.numeric_ord_ore) >= eql_v3.ord_term(b) $$; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.numeric_ord_ore, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.numeric_ord_ore, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param selector text +--! @return eql_v3.numeric_ord_ore +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric_ord_ore, selector text) +RETURNS eql_v3.numeric_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param selector integer +--! @return eql_v3.numeric_ord_ore +CREATE FUNCTION eql_v3."->"(a eql_v3.numeric_ord_ore, selector integer) +RETURNS eql_v3.numeric_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param selector eql_v3.numeric_ord_ore +--! @return eql_v3.numeric_ord_ore +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.numeric_ord_ore) +RETURNS eql_v3.numeric_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param selector text +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric_ord_ore, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param selector integer +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.numeric_ord_ore, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param selector eql_v3.numeric_ord_ore +--! @return text +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.numeric_ord_ore) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text +--! @return boolean +CREATE FUNCTION eql_v3."?"(a eql_v3.numeric_ord_ore, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?|"(a eql_v3.numeric_ord_ore, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?&"(a eql_v3.numeric_ord_ore, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@?"(a eql_v3.numeric_ord_ore, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@@"(a eql_v3.numeric_ord_ore, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#>"(a eql_v3.numeric_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text[] +--! @return text +CREATE FUNCTION eql_v3."#>>"(a eql_v3.numeric_ord_ore, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_ord_ore, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b integer +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_ord_ore, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.numeric_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#-"(a eql_v3.numeric_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b eql_v3.numeric_ord_ore +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric_ord_ore, b eql_v3.numeric_ord_ore) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a eql_v3.numeric_ord_ore +--! @param b jsonb +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.numeric_ord_ore, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.numeric_ord_ore. +--! @param a jsonb +--! @param b eql_v3.numeric_ord_ore +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.numeric_ord_ore) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.numeric_ord_ore'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/numeric/numeric_ord_ore_operators.sql b/tests/codegen/reference/numeric/numeric_ord_ore_operators.sql new file mode 100644 index 00000000..e449a61a --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_ord_ore_operators.sql @@ -0,0 +1,246 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_types.sql +-- REQUIRE: src/v3/scalars/numeric/numeric_ord_ore_functions.sql + +--! @file encrypted_domain/numeric/numeric_ord_ore_operators.sql +--! @brief Operators for eql_v3.numeric_ord_ore. + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = integer +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = integer +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore +); + +CREATE OPERATOR ? ( + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR ?| ( + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR ?& ( + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR @? ( + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonpath +); + +CREATE OPERATOR @@ ( + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonpath +); + +CREATE OPERATOR #> ( + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR #>> ( + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = integer +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR #- ( + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = eql_v3.numeric_ord_ore +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.numeric_ord_ore, RIGHTARG = jsonb +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.numeric_ord_ore +); diff --git a/tests/codegen/reference/numeric/numeric_types.sql b/tests/codegen/reference/numeric/numeric_types.sql new file mode 100644 index 00000000..6c9b85d3 --- /dev/null +++ b/tests/codegen/reference/numeric/numeric_types.sql @@ -0,0 +1,73 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql + +--! @file v3/scalars/numeric/numeric_types.sql +--! @brief Encrypted-domain types for numeric. + +DO $$ +BEGIN + --! @brief Encrypted domain eql_v3.numeric. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'numeric' AND typnamespace = 'eql_v3'::regnamespace + ) THEN + CREATE DOMAIN eql_v3.numeric AS jsonb + CHECK ( + jsonb_typeof(VALUE) = 'object' + AND VALUE ? 'v' + AND VALUE ? 'i' + AND VALUE ? 'c' + AND VALUE->>'v' = '2' + ); + END IF; + + --! @brief Encrypted domain eql_v3.numeric_eq. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'numeric_eq' AND typnamespace = 'eql_v3'::regnamespace + ) THEN + CREATE DOMAIN eql_v3.numeric_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 Encrypted domain eql_v3.numeric_ord_ore. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'numeric_ord_ore' AND typnamespace = 'eql_v3'::regnamespace + ) THEN + CREATE DOMAIN eql_v3.numeric_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 Encrypted domain eql_v3.numeric_ord. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'numeric_ord' AND typnamespace = 'eql_v3'::regnamespace + ) THEN + CREATE DOMAIN eql_v3.numeric_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 +$$; diff --git a/tests/codegen/reference/timestamptz/timestamptz_ord_aggregates.sql b/tests/codegen/reference/timestamptz/timestamptz_ord_aggregates.sql new file mode 100644 index 00000000..637fb338 --- /dev/null +++ b/tests/codegen/reference/timestamptz/timestamptz_ord_aggregates.sql @@ -0,0 +1,63 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_types.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_ord_functions.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_ord_operators.sql + +--! @file encrypted_domain/timestamptz/timestamptz_ord_aggregates.sql +--! @brief Aggregates for eql_v3.timestamptz_ord. + +--! @brief State function for min on eql_v3.timestamptz_ord. +--! @param state eql_v3.timestamptz_ord +--! @param value eql_v3.timestamptz_ord +--! @return eql_v3.timestamptz_ord +CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.timestamptz_ord, value eql_v3.timestamptz_ord) +RETURNS eql_v3.timestamptz_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 min aggregate for eql_v3.timestamptz_ord. +--! @param input eql_v3.timestamptz_ord +--! @return eql_v3.timestamptz_ord +CREATE AGGREGATE eql_v3.min(eql_v3.timestamptz_ord) ( + sfunc = eql_v3.min_sfunc, + stype = eql_v3.timestamptz_ord, + combinefunc = eql_v3.min_sfunc, + parallel = safe +); + +--! @brief State function for max on eql_v3.timestamptz_ord. +--! @param state eql_v3.timestamptz_ord +--! @param value eql_v3.timestamptz_ord +--! @return eql_v3.timestamptz_ord +CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.timestamptz_ord, value eql_v3.timestamptz_ord) +RETURNS eql_v3.timestamptz_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 max aggregate for eql_v3.timestamptz_ord. +--! @param input eql_v3.timestamptz_ord +--! @return eql_v3.timestamptz_ord +CREATE AGGREGATE eql_v3.max(eql_v3.timestamptz_ord) ( + sfunc = eql_v3.max_sfunc, + stype = eql_v3.timestamptz_ord, + combinefunc = eql_v3.max_sfunc, + parallel = safe +); diff --git a/tests/codegen/reference/timestamptz/timestamptz_ord_functions.sql b/tests/codegen/reference/timestamptz/timestamptz_ord_functions.sql new file mode 100644 index 00000000..518e8e8d --- /dev/null +++ b/tests/codegen/reference/timestamptz/timestamptz_ord_functions.sql @@ -0,0 +1,396 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/operators.sql + +--! @file encrypted_domain/timestamptz/timestamptz_ord_functions.sql +--! @brief Functions for eql_v3.timestamptz_ord. + +--! @brief Index extractor for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @return eql_v3.ore_block_256 +CREATE FUNCTION eql_v3.ord_term(a eql_v3.timestamptz_ord) +RETURNS eql_v3.ore_block_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ore_block_256(a::jsonb) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.timestamptz_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.timestamptz_ord) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.timestamptz_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.timestamptz_ord) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.timestamptz_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.timestamptz_ord) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.timestamptz_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.timestamptz_ord) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.timestamptz_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.timestamptz_ord) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.timestamptz_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.timestamptz_ord) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord) >= eql_v3.ord_term(b) $$; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.timestamptz_ord, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.timestamptz_ord, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.timestamptz_ord) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param selector text +--! @return eql_v3.timestamptz_ord +CREATE FUNCTION eql_v3."->"(a eql_v3.timestamptz_ord, selector text) +RETURNS eql_v3.timestamptz_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param selector integer +--! @return eql_v3.timestamptz_ord +CREATE FUNCTION eql_v3."->"(a eql_v3.timestamptz_ord, selector integer) +RETURNS eql_v3.timestamptz_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param selector eql_v3.timestamptz_ord +--! @return eql_v3.timestamptz_ord +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.timestamptz_ord) +RETURNS eql_v3.timestamptz_ord IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param selector text +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.timestamptz_ord, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param selector integer +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.timestamptz_ord, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param selector eql_v3.timestamptz_ord +--! @return text +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.timestamptz_ord) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text +--! @return boolean +CREATE FUNCTION eql_v3."?"(a eql_v3.timestamptz_ord, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?|"(a eql_v3.timestamptz_ord, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?&"(a eql_v3.timestamptz_ord, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@?"(a eql_v3.timestamptz_ord, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@@"(a eql_v3.timestamptz_ord, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#>"(a eql_v3.timestamptz_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text[] +--! @return text +CREATE FUNCTION eql_v3."#>>"(a eql_v3.timestamptz_ord, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.timestamptz_ord, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b integer +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.timestamptz_ord, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.timestamptz_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#-"(a eql_v3.timestamptz_ord, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b eql_v3.timestamptz_ord +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.timestamptz_ord, b eql_v3.timestamptz_ord) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a eql_v3.timestamptz_ord +--! @param b jsonb +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.timestamptz_ord, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.timestamptz_ord) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.timestamptz_ord'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/timestamptz/timestamptz_ord_operators.sql b/tests/codegen/reference/timestamptz/timestamptz_ord_operators.sql new file mode 100644 index 00000000..3a05729b --- /dev/null +++ b/tests/codegen/reference/timestamptz/timestamptz_ord_operators.sql @@ -0,0 +1,246 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_types.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_ord_functions.sql + +--! @file encrypted_domain/timestamptz/timestamptz_ord_operators.sql +--! @brief Operators for eql_v3.timestamptz_ord. + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = integer +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = integer +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord +); + +CREATE OPERATOR ? ( + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text +); + +CREATE OPERATOR ?| ( + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text[] +); + +CREATE OPERATOR ?& ( + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text[] +); + +CREATE OPERATOR @? ( + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonpath +); + +CREATE OPERATOR @@ ( + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonpath +); + +CREATE OPERATOR #> ( + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text[] +); + +CREATE OPERATOR #>> ( + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text[] +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = integer +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text[] +); + +CREATE OPERATOR #- ( + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = text[] +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = eql_v3.timestamptz_ord +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.timestamptz_ord, RIGHTARG = jsonb +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord +); diff --git a/tests/codegen/reference/timestamptz/timestamptz_ord_ore_aggregates.sql b/tests/codegen/reference/timestamptz/timestamptz_ord_ore_aggregates.sql new file mode 100644 index 00000000..73bae985 --- /dev/null +++ b/tests/codegen/reference/timestamptz/timestamptz_ord_ore_aggregates.sql @@ -0,0 +1,63 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_types.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_ord_ore_functions.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_ord_ore_operators.sql + +--! @file encrypted_domain/timestamptz/timestamptz_ord_ore_aggregates.sql +--! @brief Aggregates for eql_v3.timestamptz_ord_ore. + +--! @brief State function for min on eql_v3.timestamptz_ord_ore. +--! @param state eql_v3.timestamptz_ord_ore +--! @param value eql_v3.timestamptz_ord_ore +--! @return eql_v3.timestamptz_ord_ore +CREATE FUNCTION eql_v3.min_sfunc(state eql_v3.timestamptz_ord_ore, value eql_v3.timestamptz_ord_ore) +RETURNS eql_v3.timestamptz_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 min aggregate for eql_v3.timestamptz_ord_ore. +--! @param input eql_v3.timestamptz_ord_ore +--! @return eql_v3.timestamptz_ord_ore +CREATE AGGREGATE eql_v3.min(eql_v3.timestamptz_ord_ore) ( + sfunc = eql_v3.min_sfunc, + stype = eql_v3.timestamptz_ord_ore, + combinefunc = eql_v3.min_sfunc, + parallel = safe +); + +--! @brief State function for max on eql_v3.timestamptz_ord_ore. +--! @param state eql_v3.timestamptz_ord_ore +--! @param value eql_v3.timestamptz_ord_ore +--! @return eql_v3.timestamptz_ord_ore +CREATE FUNCTION eql_v3.max_sfunc(state eql_v3.timestamptz_ord_ore, value eql_v3.timestamptz_ord_ore) +RETURNS eql_v3.timestamptz_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 max aggregate for eql_v3.timestamptz_ord_ore. +--! @param input eql_v3.timestamptz_ord_ore +--! @return eql_v3.timestamptz_ord_ore +CREATE AGGREGATE eql_v3.max(eql_v3.timestamptz_ord_ore) ( + sfunc = eql_v3.max_sfunc, + stype = eql_v3.timestamptz_ord_ore, + combinefunc = eql_v3.max_sfunc, + parallel = safe +); diff --git a/tests/codegen/reference/timestamptz/timestamptz_ord_ore_functions.sql b/tests/codegen/reference/timestamptz/timestamptz_ord_ore_functions.sql new file mode 100644 index 00000000..c93ddef7 --- /dev/null +++ b/tests/codegen/reference/timestamptz/timestamptz_ord_ore_functions.sql @@ -0,0 +1,396 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_types.sql +-- REQUIRE: src/v3/scalars/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/functions.sql +-- REQUIRE: src/v3/sem/ore_block_256/operators.sql + +--! @file encrypted_domain/timestamptz/timestamptz_ord_ore_functions.sql +--! @brief Functions for eql_v3.timestamptz_ord_ore. + +--! @brief Index extractor for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @return eql_v3.ore_block_256 +CREATE FUNCTION eql_v3.ord_term(a eql_v3.timestamptz_ord_ore) +RETURNS eql_v3.ore_block_256 +LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ore_block_256(a::jsonb) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.eq(a eql_v3.timestamptz_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.timestamptz_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.eq(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord_ore) = eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.neq(a eql_v3.timestamptz_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.timestamptz_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.neq(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord_ore) <> eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lt(a eql_v3.timestamptz_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.timestamptz_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lt(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord_ore) < eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.lte(a eql_v3.timestamptz_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.timestamptz_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.lte(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord_ore) <= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gt(a eql_v3.timestamptz_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.timestamptz_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gt(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord_ore) > eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a) >= eql_v3.ord_term(b) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.gte(a eql_v3.timestamptz_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.timestamptz_ord_ore) $$; + +--! @brief Operator wrapper for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.gte(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v3.ord_term(a::eql_v3.timestamptz_ord_ore) >= eql_v3.ord_term(b) $$; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contains(a eql_v3.timestamptz_ord_ore, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contains(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a eql_v3.timestamptz_ord_ore, b jsonb) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return boolean +CREATE FUNCTION eql_v3.contained_by(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '<@', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param selector text +--! @return eql_v3.timestamptz_ord_ore +CREATE FUNCTION eql_v3."->"(a eql_v3.timestamptz_ord_ore, selector text) +RETURNS eql_v3.timestamptz_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param selector integer +--! @return eql_v3.timestamptz_ord_ore +CREATE FUNCTION eql_v3."->"(a eql_v3.timestamptz_ord_ore, selector integer) +RETURNS eql_v3.timestamptz_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param selector eql_v3.timestamptz_ord_ore +--! @return eql_v3.timestamptz_ord_ore +CREATE FUNCTION eql_v3."->"(a jsonb, selector eql_v3.timestamptz_ord_ore) +RETURNS eql_v3.timestamptz_ord_ore IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param selector text +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.timestamptz_ord_ore, selector text) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param selector integer +--! @return text +CREATE FUNCTION eql_v3."->>"(a eql_v3.timestamptz_ord_ore, selector integer) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param selector eql_v3.timestamptz_ord_ore +--! @return text +CREATE FUNCTION eql_v3."->>"(a jsonb, selector eql_v3.timestamptz_ord_ore) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '->>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text +--! @return boolean +CREATE FUNCTION eql_v3."?"(a eql_v3.timestamptz_ord_ore, b text) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?|"(a eql_v3.timestamptz_ord_ore, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?|', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text[] +--! @return boolean +CREATE FUNCTION eql_v3."?&"(a eql_v3.timestamptz_ord_ore, b text[]) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '?&', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@?"(a eql_v3.timestamptz_ord_ore, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@?', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonpath +--! @return boolean +CREATE FUNCTION eql_v3."@@"(a eql_v3.timestamptz_ord_ore, b jsonpath) +RETURNS boolean IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '@@', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#>"(a eql_v3.timestamptz_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text[] +--! @return text +CREATE FUNCTION eql_v3."#>>"(a eql_v3.timestamptz_ord_ore, b text[]) +RETURNS text IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#>>', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.timestamptz_ord_ore, b text) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b integer +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.timestamptz_ord_ore, b integer) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."-"(a eql_v3.timestamptz_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '-', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b text[] +--! @return jsonb +CREATE FUNCTION eql_v3."#-"(a eql_v3.timestamptz_ord_ore, b text[]) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '#-', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b eql_v3.timestamptz_ord_ore +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.timestamptz_ord_ore, b eql_v3.timestamptz_ord_ore) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a eql_v3.timestamptz_ord_ore +--! @param b jsonb +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a eql_v3.timestamptz_ord_ore, b jsonb) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; + +--! @brief Unsupported operator blocker for eql_v3.timestamptz_ord_ore. +--! @param a jsonb +--! @param b eql_v3.timestamptz_ord_ore +--! @return jsonb +CREATE FUNCTION eql_v3."||"(a jsonb, b eql_v3.timestamptz_ord_ore) +RETURNS jsonb IMMUTABLE PARALLEL SAFE +AS $$ BEGIN RAISE EXCEPTION 'operator % is not supported for %', '||', 'eql_v3.timestamptz_ord_ore'; END; $$ +LANGUAGE plpgsql; diff --git a/tests/codegen/reference/timestamptz/timestamptz_ord_ore_operators.sql b/tests/codegen/reference/timestamptz/timestamptz_ord_ore_operators.sql new file mode 100644 index 00000000..d5b7980c --- /dev/null +++ b/tests/codegen/reference/timestamptz/timestamptz_ord_ore_operators.sql @@ -0,0 +1,246 @@ +-- REFERENCE: hand-maintained parity baseline for crates/eql-codegen - see ../README.md +-- AUTOMATICALLY GENERATED FILE. +-- REQUIRE: src/v3/schema.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_types.sql +-- REQUIRE: src/v3/scalars/timestamptz/timestamptz_ord_ore_functions.sql + +--! @file encrypted_domain/timestamptz/timestamptz_ord_ore_operators.sql +--! @brief Operators for eql_v3.timestamptz_ord_ore. + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR = ( + FUNCTION = eql_v3.eq, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR <> ( + FUNCTION = eql_v3.neq, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR < ( + FUNCTION = eql_v3.lt, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR <= ( + FUNCTION = eql_v3.lte, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + FUNCTION = eql_v3.gt, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR >= ( + FUNCTION = eql_v3.gte, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore, + COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargesel, JOIN = scalargejoinsel +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb +); + +CREATE OPERATOR @> ( + FUNCTION = eql_v3.contains, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb +); + +CREATE OPERATOR <@ ( + FUNCTION = eql_v3.contained_by, + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = integer +); + +CREATE OPERATOR -> ( + FUNCTION = eql_v3."->", + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = integer +); + +CREATE OPERATOR ->> ( + FUNCTION = eql_v3."->>", + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore +); + +CREATE OPERATOR ? ( + FUNCTION = eql_v3."?", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR ?| ( + FUNCTION = eql_v3."?|", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR ?& ( + FUNCTION = eql_v3."?&", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR @? ( + FUNCTION = eql_v3."@?", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonpath +); + +CREATE OPERATOR @@ ( + FUNCTION = eql_v3."@@", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonpath +); + +CREATE OPERATOR #> ( + FUNCTION = eql_v3."#>", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR #>> ( + FUNCTION = eql_v3."#>>", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = integer +); + +CREATE OPERATOR - ( + FUNCTION = eql_v3."-", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR #- ( + FUNCTION = eql_v3."#-", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = text[] +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = eql_v3.timestamptz_ord_ore +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = eql_v3.timestamptz_ord_ore, RIGHTARG = jsonb +); + +CREATE OPERATOR || ( + FUNCTION = eql_v3."||", + LEFTARG = jsonb, RIGHTARG = eql_v3.timestamptz_ord_ore +); diff --git a/tests/codegen/reference/timestamptz/timestamptz_types.sql b/tests/codegen/reference/timestamptz/timestamptz_types.sql index 61445e87..38930d16 100644 --- a/tests/codegen/reference/timestamptz/timestamptz_types.sql +++ b/tests/codegen/reference/timestamptz/timestamptz_types.sql @@ -37,5 +37,37 @@ BEGIN AND VALUE->>'v' = '2' ); END IF; + + --! @brief Encrypted domain eql_v3.timestamptz_ord_ore. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'timestamptz_ord_ore' AND typnamespace = 'eql_v3'::regnamespace + ) THEN + CREATE DOMAIN eql_v3.timestamptz_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 Encrypted domain eql_v3.timestamptz_ord. + IF NOT EXISTS ( + SELECT 1 FROM pg_type + WHERE typname = 'timestamptz_ord' AND typnamespace = 'eql_v3'::regnamespace + ) THEN + CREATE DOMAIN eql_v3.timestamptz_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 $$; diff --git a/tests/sqlx/tests/ore_block_comparator_tests.rs b/tests/sqlx/tests/ore_block_comparator_tests.rs index 6003d6d5..7fcc5eb3 100644 --- a/tests/sqlx/tests/ore_block_comparator_tests.rs +++ b/tests/sqlx/tests/ore_block_comparator_tests.rs @@ -51,6 +51,28 @@ async fn comparator_rejects_sixteen_byte_term(pool: PgPool) -> Result<()> { Ok(()) } +/// Cross-width footgun: now that N is derived per-term, comparing terms of two +/// different (individually valid) widths must raise via the equal-length guard, +/// not silently compare the shared prefix. Both lengths here are well-formed — +/// 408 = 49*8 + 16 (the int4 width, N=8) and 702 = 49*14 + 16 (the numeric +/// width, N=14) — so the only thing that fires is the different-lengths check, +/// ahead of the malformed-length guard. Creds-free (hand-built bytea). +#[sqlx::test] +async fn comparator_rejects_mismatched_block_widths(pool: PgPool) -> Result<()> { + let sql = "SELECT eql_v3.compare_ore_block_256_term( \ + ROW(repeat('a', 408)::bytea)::eql_v3.ore_block_256_term, \ + ROW(repeat('b', 702)::bytea)::eql_v3.ore_block_256_term)"; + let err = sqlx::query_scalar::<_, i32>(sql) + .fetch_one(&pool) + .await + .expect_err("an 8-block vs 14-block ORE term comparison must raise"); + assert!( + err.to_string().to_lowercase().contains("different lengths"), + "expected different-lengths error, got: {err}" + ); + Ok(()) +} + /// Width: a numeric ORE term must be 14 blocks => 49*14 + 16 = 702 bytes. #[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_numeric")))] async fn numeric_term_is_14_blocks(pool: PgPool) -> Result<()> { From cf93a1a2ed1a23b7d8c7f59d5d18ad54c83e5167 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Mon, 15 Jun 2026 20:56:39 +1000 Subject: [PATCH 5/9] fix(v3): reject non-array ore 'ob' payloads at the extractor boundary has_ore_block_256 used "val ->> 'ob' IS NOT NULL", which stringifies a scalar/object 'ob' and reports it present. ore_block_256 then fed the malformed payload into jsonb_array_to_ore_block_256, which returns NULL instead of raising, silently degrading a structurally invalid ORE term into a NULL comparison/index term. Tighten the guard to require a JSON array (jsonb_typeof(val->'ob') = 'array'); a present-but-non-array 'ob' now RAISEs at the extractor boundary. '{}' (absent ob) and '{"ob": null}' (JSON null) remain absent (false). Adds T5 characterization cases for the scalar/object 'ob' presence checks and the extractor RAISE. Addresses CodeRabbit review thread on PR #276. --- src/v3/sem/ore_block_256/functions.sql | 21 +++++++++++----- .../sqlx/tests/encrypted_domain/family/sem.rs | 24 +++++++++++++++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/v3/sem/ore_block_256/functions.sql b/src/v3/sem/ore_block_256/functions.sql index 3eca4f1d..0cd323d8 100644 --- a/src/v3/sem/ore_block_256/functions.sql +++ b/src/v3/sem/ore_block_256/functions.sql @@ -22,9 +22,10 @@ --! 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. +--! NULL here instead of raising. The sole caller (`ore_block_256`) only reaches +--! this when `has_ore_block_256(val)` is true, which now requires `val->'ob'` +--! to be a JSON array, so the non-array branch is unreachable in practice; +--! empty array still returns NULL exactly as before (pinned by T7). CREATE FUNCTION eql_v3.jsonb_array_to_ore_block_256(val jsonb) RETURNS eql_v3.ore_block_256 IMMUTABLE @@ -67,16 +68,24 @@ AS $$ $$ LANGUAGE plpgsql; ---! @brief Check if JSONB payload contains ORE block index term +--! @brief Check if JSONB payload contains an ORE block index term --! @param val jsonb containing encrypted EQL payload ---! @return boolean True if 'ob' field is present and non-null +--! @return boolean True only if the 'ob' field is present and is a JSON array +--! @note A well-formed ORE index term is always a JSON array of block terms, so +--! this guard treats a present-but-non-array `ob` (a scalar or object) as +--! absent. That makes the extractor `ore_block_256(val)` RAISE on a +--! structurally invalid `ob` payload at the boundary instead of silently +--! degrading it to a NULL index term in `jsonb_array_to_ore_block_256`. The +--! previous `val ->> 'ob' IS NOT NULL` form stringified scalars/objects and so +--! reported them as present. `{}` (absent `ob`) and `{"ob": null}` (JSON null) +--! both remain `false`. CREATE FUNCTION eql_v3.has_ore_block_256(val jsonb) RETURNS boolean IMMUTABLE STRICT PARALLEL SAFE SET search_path = pg_catalog, extensions, public AS $$ BEGIN - RETURN val ->> 'ob' IS NOT NULL; + RETURN COALESCE(jsonb_typeof(val -> 'ob') = 'array', false); END; $$ LANGUAGE plpgsql; diff --git a/tests/sqlx/tests/encrypted_domain/family/sem.rs b/tests/sqlx/tests/encrypted_domain/family/sem.rs index fe2593b9..fd675673 100644 --- a/tests/sqlx/tests/encrypted_domain/family/sem.rs +++ b/tests/sqlx/tests/encrypted_domain/family/sem.rs @@ -178,7 +178,8 @@ async fn ore_terms_array_null_and_empty_base_cases(pool: PgPool) -> Result<()> { } /// T5 — SEM presence checks (`has_ore_block_256`, `has_hmac_256`), the -/// extractor's missing-`ob` RAISE, and its NULL-jsonb short-circuit. +/// extractor's missing-`ob` and non-array-`ob` RAISEs, and its NULL-jsonb +/// short-circuit. #[sqlx::test] async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<()> { let bool_cases = [ @@ -187,11 +188,20 @@ async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<() true, ), (r#"SELECT eql_v3.has_ore_block_256('{}'::jsonb)"#, false), - // json-null `ob` → `->>` yields NULL → absent. + // json-null `ob` is typed `'null'`, not `'array'` → absent. ( r#"SELECT eql_v3.has_ore_block_256('{"ob":null}'::jsonb)"#, false, ), + // Present-but-non-array `ob` is rejected as absent: a well-formed ORE + // term is always a JSON array of block terms, so a scalar and an object + // both → false. This is the boundary that makes `ore_block_256` RAISE on + // a malformed `ob` instead of degrading it to a NULL index term. + (r#"SELECT eql_v3.has_ore_block_256('{"ob":5}'::jsonb)"#, false), + ( + r#"SELECT eql_v3.has_ore_block_256('{"ob":{}}'::jsonb)"#, + false, + ), (r#"SELECT eql_v3.has_hmac_256('{"hm":"abc"}'::jsonb)"#, true), (r#"SELECT eql_v3.has_hmac_256('{}'::jsonb)"#, false), ]; @@ -209,6 +219,16 @@ async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<() ) .await?; + // Present-but-non-array `ob` → RAISE at the extractor boundary, NOT a silent + // NULL index term (`has_ore_block_256` reports it absent). + assert_raises( + &pool, + r#"SELECT eql_v3.ore_block_256('{"ob":5}'::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_256(NULL::jsonb) IS NULL") .fetch_one(&pool) From 807b7939e0208278aca40bab0b80d4ec7da661ca Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 16 Jun 2026 09:26:54 +1000 Subject: [PATCH 6/9] style(v3): cargo fmt sem.rs has_ore_block_256 test case --- tests/sqlx/tests/encrypted_domain/family/sem.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/sqlx/tests/encrypted_domain/family/sem.rs b/tests/sqlx/tests/encrypted_domain/family/sem.rs index fd675673..d13847e5 100644 --- a/tests/sqlx/tests/encrypted_domain/family/sem.rs +++ b/tests/sqlx/tests/encrypted_domain/family/sem.rs @@ -197,7 +197,10 @@ async fn sem_presence_checks_and_missing_ob_behaviour(pool: PgPool) -> Result<() // term is always a JSON array of block terms, so a scalar and an object // both → false. This is the boundary that makes `ore_block_256` RAISE on // a malformed `ob` instead of degrading it to a NULL index term. - (r#"SELECT eql_v3.has_ore_block_256('{"ob":5}'::jsonb)"#, false), + ( + r#"SELECT eql_v3.has_ore_block_256('{"ob":5}'::jsonb)"#, + false, + ), ( r#"SELECT eql_v3.has_ore_block_256('{"ob":{}}'::jsonb)"#, false, From 5e37e6045437f0fb38d920930622fe28fdae84bc Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 16 Jun 2026 09:36:22 +1000 Subject: [PATCH 7/9] test(v3): always-on ORE comparator coverage + numeric scale-collision fixture Strengthen the N-block ORE comparator tests so they run creds-free on no-creds CI shards, sourcing real ORE terms from committed fixtures: - assert_orders_like_oracle: all-pairs oracle agreement + antisymmetry (replaces the adjacent-pair-only ascending chain), with a row-count drift guard against the catalog fixture order - comparator_length_guard_sweep: boundary/off-by lengths for the 49*N+16 guard across N=1..14 - wide_block_term_compares_equal_to_itself: reflexive path at N=14/12 - numeric_scale_equivalents_collide: 1 == 1.0 ORE collision via the new hand-written v3_numeric_collision fixture (the value-equal pair the catalog distinctness guard forbids in eql_v2_numeric) Also fix the stale timestamptz comment in scalar_domains.rs (now ordered, not eq-only). --- .gitignore | 1 + tests/sqlx/src/fixtures/mod.rs | 7 + .../sqlx/src/fixtures/v3_numeric_collision.rs | 77 ++++ tests/sqlx/src/scalar_domains.rs | 6 +- tests/sqlx/tests/generate_all_fixtures.rs | 9 + .../sqlx/tests/ore_block_comparator_tests.rs | 342 ++++++++++++++++-- 6 files changed, 404 insertions(+), 38 deletions(-) create mode 100644 tests/sqlx/src/fixtures/v3_numeric_collision.rs diff --git a/.gitignore b/.gitignore index 37af0cf2..98b64012 100644 --- a/.gitignore +++ b/.gitignore @@ -227,6 +227,7 @@ tests/sqlx/migrations/001_install_eql.sql tests/sqlx/fixtures/eql_v2* tests/sqlx/fixtures/v3_ste_vec.sql tests/sqlx/fixtures/v3_doc_int4.sql +tests/sqlx/fixtures/v3_numeric_collision.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 diff --git a/tests/sqlx/src/fixtures/mod.rs b/tests/sqlx/src/fixtures/mod.rs index 098db4f6..62e86713 100644 --- a/tests/sqlx/src/fixtures/mod.rs +++ b/tests/sqlx/src/fixtures/mod.rs @@ -39,6 +39,13 @@ pub mod v3_ste_vec; // jsonb-entry behaviour matrix (`JsonbEntryInt4`). pub mod v3_doc_int4; +// The numeric scale-equivalence collision fixture (`1`, `1.0`, `2`). Not a +// CATALOG scalar — the catalog distinctness guard forbids the value-equal pair +// `1`/`1.0` — so it is hand-written and registered here directly (like the +// other `v3_` fixtures). Gives the `1 == 1.0` ORE collision an always-on +// (committed-fixture) home instead of a creds-gated runtime encryption. +pub mod v3_numeric_collision; + // 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 diff --git a/tests/sqlx/src/fixtures/v3_numeric_collision.rs b/tests/sqlx/src/fixtures/v3_numeric_collision.rs new file mode 100644 index 00000000..fe58c63d --- /dev/null +++ b/tests/sqlx/src/fixtures/v3_numeric_collision.rs @@ -0,0 +1,77 @@ +//! The `v3_numeric_collision` fixture — the scale-equivalence collision pair +//! (`1`, `1.0`) plus a `2` discriminator, encrypted at numeric ORE width. +//! +//! Hand-written, non-catalog (like `v3_ste_vec` / `v3_doc_int4`, hence the +//! `v3_` prefix), because the catalog-driven `eql_v2_numeric` fixture CANNOT +//! carry it: `numeric_value_guards::fixtures_are_distinct_by_value` forbids two +//! fixtures that alias to the same `Decimal`, and `1` / `1.0` are value-equal. +//! So the `1 == 1.0` ORE collision — that scale-equivalent decimals encrypt to +//! comparison-equal ORE terms — has no catalog home. This tiny bespoke fixture +//! is the only place those two representations can coexist. +//! +//! Rows are addressed by `id` (NOT `plaintext`): `WHERE plaintext = 1` matches +//! both `1` and `1.0` (numeric equality ignores scale), so the collision test +//! fetches `id = 1` (`1`), `id = 2` (`1.0`), `id = 3` (`2`). The `id` is the +//! 1-based insertion ordinal the driver assigns over `VALUES`. +//! +//! Gitignored output: tests/sqlx/fixtures/v3_numeric_collision.sql +//! (regenerated by `mise run fixture:generate:all`). + +use anyhow::Result; +use rust_decimal::Decimal; +use std::str::FromStr; + +use super::index_kind::IndexKind; +use super::spec::FixtureSpec; + +/// The committed fixture name → table `fixtures.v3_numeric_collision`, script +/// `v3_numeric_collision.sql`, SQLx ref `scripts("v3_numeric_collision")`. +const NAME: &str = "v3_numeric_collision"; + +/// The fixture plaintexts, in insertion order. `id` is the 1-based ordinal, so +/// `1` → id 1, `1.0` → id 2, `2` → id 3. `1` and `1.0` are deliberately +/// value-equal (the collision pair); `2` is a distinct discriminator so the +/// test can prove the comparator is not a degenerate everything-collides. +fn values() -> Vec { + ["1", "1.0", "2"] + .iter() + .map(|s| Decimal::from_str(s).expect("valid decimal literal")) + .collect() +} + +/// Generate `tests/sqlx/fixtures/v3_numeric_collision.sql`. Encrypts the +/// three decimals at numeric ORE width via the standard `.run()` driver — the +/// encryption input and the `plaintext` oracle column are the same value stream, +/// so no split (`run_with_payloads`) is needed. +pub async fn generate() -> Result<()> { + let values = values(); + FixtureSpec::new(NAME) + .with_index(IndexKind::Unique) + .with_index(IndexKind::Ore) + .with_column_type("jsonb") + .with_values(&values) + .run() + .await +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_collision_pair_is_value_equal_but_distinct_in_scale() { + let v = values(); + assert_eq!(v.len(), 3, "fixture is [1, 1.0, 2]"); + // The collision pair compares equal by value... + assert_eq!(v[0], v[1], "1 and 1.0 must be value-equal (the collision)"); + // ...yet is two distinct textual representations (different scale), so + // the catalog distinctness guard would reject them together. + assert_ne!( + v[0].scale(), + v[1].scale(), + "1 (scale 0) and 1.0 (scale 1) must differ in scale" + ); + // The discriminator is genuinely larger. + assert!(v[2] > v[0], "2 must order after 1"); + } +} diff --git a/tests/sqlx/src/scalar_domains.rs b/tests/sqlx/src/scalar_domains.rs index 84bf9da2..11d0ff15 100644 --- a/tests/sqlx/src/scalar_domains.rs +++ b/tests/sqlx/src/scalar_domains.rs @@ -319,11 +319,11 @@ temporal_values! { } // `timestamptz`'s `ScalarType` wiring, generated from its catalog row by the -// same `temporal_values!` path as `date`. timestamptz is equality-only (its -// catalog row uses the eq-only domain shape), but the *value* wiring is +// same `temporal_values!` path as `date`. timestamptz is ordered (its catalog +// row uses the ordered domain shape, 12-block ORE), and the *value* wiring is // identical to any temporal scalar: RFC3339 strings parsed once into // `DateTime` behind `timestamptz_values()`. The pivots are retained as the -// three equality anchors the matrix sweeps. +// three min/mid/max anchors the matrix sweeps. temporal_values! { cell = TIMESTAMPTZ_VALUES_CELL, accessor = timestamptz_values, diff --git a/tests/sqlx/tests/generate_all_fixtures.rs b/tests/sqlx/tests/generate_all_fixtures.rs index 40db2216..dcb50c27 100644 --- a/tests/sqlx/tests/generate_all_fixtures.rs +++ b/tests/sqlx/tests/generate_all_fixtures.rs @@ -49,5 +49,14 @@ async fn generate_all() -> anyhow::Result<()> { eprintln!("Generating fixture v3_doc_int4 (scalar-shaped SteVec document)..."); eql_tests::fixtures::v3_doc_int4::generate().await?; eprintln!("Regenerated v3_doc_int4."); + + // The numeric scale-equivalence collision fixture (`1`, `1.0`, `2`). Not a + // CATALOG scalar — the distinctness guard forbids `1`/`1.0` coexisting in + // `eql_v2_numeric` — so it rides the same pipeline as a hand-written + // `FixtureSpec`. Gives the always-on `1 == 1.0` ORE collision test + // its committed fixture. + eprintln!("Generating fixture v3_numeric_collision (1 == 1.0 ORE collision)..."); + eql_tests::fixtures::v3_numeric_collision::generate().await?; + eprintln!("Regenerated v3_numeric_collision."); Ok(()) } diff --git a/tests/sqlx/tests/ore_block_comparator_tests.rs b/tests/sqlx/tests/ore_block_comparator_tests.rs index 7fcc5eb3..5a65cd85 100644 --- a/tests/sqlx/tests/ore_block_comparator_tests.rs +++ b/tests/sqlx/tests/ore_block_comparator_tests.rs @@ -1,14 +1,128 @@ -//! Direct unit tests for the generalized N-block ORE comparator -//! `eql_v3.compare_ore_block_256_term`. +//! Direct tests for the generalized N-block ORE comparator +//! `eql_v3.compare_ore_block_256_term(s)`. //! -//! The malformed-length guards here are creds-free: they construct ORE terms -//! by hand from short byte strings, so they exercise the length validation -//! without needing real (ZeroKMS-generated) ciphertexts. The wide-term ordering -//! test (added in Phase 4) uses generated numeric fixtures. +//! Every test here is **always-on** — it runs in normal (no-creds) CI, because +//! it sources real ORE terms from committed fixtures rather than encrypting at +//! runtime: +//! * The malformed-length guards build ORE terms by hand from short byte +//! strings, exercising length validation without real ciphertexts. +//! * The ordering properties (all-pairs oracle agreement + antisymmetry) read +//! the committed `eql_v2_numeric` / `eql_v2_timestamptz` fixtures, whose +//! catalog order is the strict ascending oracle. +//! * The `1 == 1.0` ORE collision reads the committed `v3_numeric_collision` +//! fixture — the one place the value-equal pair can live, since the catalog +//! distinctness guard forbids it in `eql_v2_numeric`. +//! +//! Fixtures are generated once (with creds) in the `build-archive` CI job and +//! baked into the test binaries via `include_str!`, so the no-creds shards +//! consume them directly. See `tasks/test/sqlx-archive.sh`. use anyhow::Result; use sqlx::PgPool; +/// Fetch two fixture payloads by plaintext literal, wrap each in the ordered +/// extractor, and return the ORE comparison. The single fetch + `ord_term` + +/// `compare_ore_block_256_terms` shape every chain/pair test shares; `lo`/`hi` +/// are the plaintext SQL literals (with cast), e.g. `"(-1)::numeric"`. +async fn compare_fixture_pair( + pool: &PgPool, + table: &str, + ord_domain: &str, + lo: &str, + hi: &str, +) -> Result { + let sql = format!( + "SELECT eql_v3.compare_ore_block_256_terms( \ + eql_v3.ord_term((SELECT payload FROM fixtures.{table} WHERE plaintext = {lo})::eql_v3.{ord_domain}), \ + eql_v3.ord_term((SELECT payload FROM fixtures.{table} WHERE plaintext = {hi})::eql_v3.{ord_domain}))" + ); + Ok(sqlx::query_scalar::<_, i32>(&sql).fetch_one(pool).await?) +} + +/// A hand-built ORE term of `len` bytes filled with `fill`. Creds-free — the +/// bytes are cryptographically meaningless, so this only drives length/structure +/// validation, never ordering semantics. +fn term_sql(fill: char, len: usize) -> String { + format!("ROW(repeat('{fill}', {len})::bytea)::eql_v3.ore_block_256_term") +} + +/// Assert the ORE comparator agrees with an explicit oracle order over a +/// committed fixture. `ascending[i]` is the SQL literal (with cast) for the +/// value whose oracle rank is `i`; its real ciphertext is fetched from +/// `fixtures.{table}` by `plaintext` and loaded into a connection-local +/// `ore_sample(rank, payload)`. +/// +/// Two properties, both over EVERY pair (not just adjacent): +/// * **Oracle agreement** — `rank a < rank b` ⇒ `compare(a, b) = -1`. +/// All-pairs subsumes totality and transitivity; combined with antisymmetry +/// it also pins the `>` direction, so a one-sided bug cannot hide. +/// * **Antisymmetry** — `compare(a, b) = -compare(b, a)` for all distinct +/// pairs. +/// +/// Creds-free: every term is a real committed ciphertext. The per-row +/// `rows_affected == 1` check fails loudly if the `ascending` list drifts from +/// the fixture (a removed/renamed value resolves to zero rows). +async fn assert_orders_like_oracle( + pool: &PgPool, + table: &str, + ord_domain: &str, + ascending: &[String], +) -> Result<()> { + // TEMP is connection-scoped and a pool may hand out different connections + // per query — pin everything to one acquired connection. + let mut conn = pool.acquire().await?; + sqlx::query("CREATE TEMP TABLE ore_sample (rank int, payload jsonb)") + .execute(&mut *conn) + .await?; + for (rank, literal) in ascending.iter().enumerate() { + let inserted = sqlx::query(&format!( + "INSERT INTO ore_sample (rank, payload) \ + SELECT {rank}, payload FROM fixtures.{table} WHERE plaintext = {literal}" + )) + .execute(&mut *conn) + .await? + .rows_affected(); + anyhow::ensure!( + inserted == 1, + "expected exactly 1 fixture row for {literal} (rank {rank}), got {inserted} \ + — the ascending list has drifted from fixtures.{table}" + ); + } + + // Oracle agreement: lower rank (smaller value) MUST compare -1. + let order_violations: i64 = sqlx::query_scalar(&format!( + "SELECT count(*) FROM ore_sample a JOIN ore_sample b ON a.rank < b.rank \ + WHERE eql_v3.compare_ore_block_256_terms( \ + eql_v3.ord_term(a.payload::eql_v3.{ord_domain}), \ + eql_v3.ord_term(b.payload::eql_v3.{ord_domain})) <> -1" + )) + .fetch_one(&mut *conn) + .await?; + assert_eq!( + order_violations, 0, + "ORE comparator disagreed with the oracle order on some pair" + ); + + // Antisymmetry: compare(a, b) = -compare(b, a) for every distinct pair. + let antisymmetry_violations: i64 = sqlx::query_scalar(&format!( + "SELECT count(*) FROM ore_sample a JOIN ore_sample b ON a.rank <> b.rank \ + WHERE eql_v3.compare_ore_block_256_terms( \ + eql_v3.ord_term(a.payload::eql_v3.{ord_domain}), \ + eql_v3.ord_term(b.payload::eql_v3.{ord_domain})) \ + <> - eql_v3.compare_ore_block_256_terms( \ + eql_v3.ord_term(b.payload::eql_v3.{ord_domain}), \ + eql_v3.ord_term(a.payload::eql_v3.{ord_domain}))" + )) + .fetch_one(&mut *conn) + .await?; + assert_eq!( + antisymmetry_violations, 0, + "ORE comparator violated antisymmetry on some pair" + ); + + Ok(()) +} + /// A `bytea` whose length is NOT a valid `49*N + 16` must raise, not silently /// return 0. Uses a 4-byte term (equal lengths so the equal-length guard does /// not fire first). @@ -73,6 +187,66 @@ async fn comparator_rejects_mismatched_block_widths(pool: PgPool) -> Result<()> Ok(()) } +/// Sweep the `49*N + 16` length guard across boundary/off-by lengths the +/// point-example tests above don't reach. Both operands are kept the SAME length +/// so only the malformed-length guard can fire (the different-lengths guard at +/// `bit_length` precedes it). Creds-free. +/// +/// Valid lengths are pinned to exact return values (verified against +/// `src/v3/sem/ore_block_256/functions.sql`): +/// * equal operands take the all-blocks-equal path → return **0** +/// (`functions.sql:166-168`; the `encrypt()` branch is unreachable); +/// * differing operands fall through to the `encrypt()` path → return **±1** +/// (`functions.sql:170-190`), which is the branch the length guard protects. +#[sqlx::test] +async fn comparator_length_guard_sweep(pool: PgPool) -> Result<()> { + // Invalid: not 49*N + 16 (16 and 4 are covered by the dedicated tests above). + for len in [15usize, 17, 50, 64, 66, 407, 409, 701, 703] { + let sql = format!( + "SELECT eql_v3.compare_ore_block_256_term({}, {})", + term_sql('a', len), + term_sql('b', len) + ); + let err = sqlx::query_scalar::<_, i32>(&sql) + .fetch_one(&pool) + .await + .expect_err(&format!("length {len} (not 49*N+16) must raise")); + assert!( + err.to_string() + .to_lowercase() + .contains("malformed ore term"), + "len {len}: expected malformed-term error, got: {err}" + ); + } + + // Valid: 49*N + 16 for N = 1..=14 (spans the int4/timestamp/numeric widths). + for n in 1..=14usize { + let len = 49 * n + 16; + + let eq: i32 = sqlx::query_scalar(&format!( + "SELECT eql_v3.compare_ore_block_256_term({}, {})", + term_sql('a', len), + term_sql('a', len) + )) + .fetch_one(&pool) + .await?; + assert_eq!(eq, 0, "len {len} (N={n}): identical terms must compare 0"); + + let ne: i32 = sqlx::query_scalar(&format!( + "SELECT eql_v3.compare_ore_block_256_term({}, {})", + term_sql('a', len), + term_sql('b', len) + )) + .fetch_one(&pool) + .await?; + assert!( + ne == -1 || ne == 1, + "len {len} (N={n}): differing terms must compare ±1 (encrypt path), got {ne}" + ); + } + Ok(()) +} + /// Width: a numeric ORE term must be 14 blocks => 49*14 + 16 = 702 bytes. #[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_numeric")))] async fn numeric_term_is_14_blocks(pool: PgPool) -> Result<()> { @@ -87,14 +261,17 @@ async fn numeric_term_is_14_blocks(pool: PgPool) -> Result<()> { Ok(()) } -/// Full ascending chain of 14-block numeric terms: every adjacent pair must -/// order `-1`. Spans sign, magnitude, and fractional (low-block) scale, so the -/// left blocks — not just the right blocks — decide ordering. This is the -/// regression the missed `9 -> 1+n` left-offset would fail; a single pair could -/// pass against that bug. +/// 14-block numeric terms must order like `Decimal`'s `Ord` over ALL pairs (not +/// just adjacent), plus antisymmetry. Spans sign, magnitude, and fractional +/// (low-block) scale, so the left blocks — not just the right blocks — decide +/// ordering. This is the regression the missed `9 -> 1+n` left-offset would +/// fail; the all-pairs sweep makes a single lucky pair unable to mask it. The +/// list is the strict ascending oracle (matching `NUMERIC_FIXTURES`' catalog +/// order); `assert_orders_like_oracle` fails loudly if it drifts from the +/// committed fixture. #[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_numeric")))] -async fn numeric_terms_order_in_ascending_chain(pool: PgPool) -> Result<()> { - let ascending = [ +async fn numeric_terms_order_like_decimal_ord(pool: PgPool) -> Result<()> { + let ascending: Vec = [ "-1000000000000", "-1000000", "-1.001", @@ -109,25 +286,17 @@ async fn numeric_terms_order_in_ascending_chain(pool: PgPool) -> Result<()> { "1.001", "1000000", "1000000000000", - ]; - for pair in ascending.windows(2) { - let (lo, hi) = (pair[0], pair[1]); - let cmp: i32 = sqlx::query_scalar(&format!( - "SELECT eql_v3.compare_ore_block_256_terms( \ - eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_numeric WHERE plaintext = ({lo})::numeric)::eql_v3.numeric_ord), \ - eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_numeric WHERE plaintext = ({hi})::numeric)::eql_v3.numeric_ord))" - )) - .fetch_one(&pool) - .await?; - assert_eq!(cmp, -1, "{lo} must order before {hi}"); - } - Ok(()) + ] + .iter() + .map(|v| format!("({v})::numeric")) + .collect(); + assert_orders_like_oracle(&pool, "eql_v2_numeric", "numeric_ord", &ascending).await } -/// Symmetric 12-block (timestamptz, N=12 => 604 bytes) width + ordering check. -/// 12 is the only N strictly between the working 8 and the headline 14. +/// Width + single-pair sanity for the 12-block (timestamptz, N=12 => 604 bytes) +/// term. The full ordering property is `timestamptz_terms_order_like_datetime_ord`. #[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_timestamptz")))] -async fn timestamptz_term_is_12_blocks_and_orders(pool: PgPool) -> Result<()> { +async fn timestamptz_term_is_12_blocks(pool: PgPool) -> Result<()> { let width: i32 = sqlx::query_scalar( "SELECT octet_length((((eql_v3.ord_term( \ (SELECT payload FROM fixtures.eql_v2_timestamptz WHERE plaintext = '1970-01-01T00:00:00Z'::timestamptz) \ @@ -139,14 +308,117 @@ async fn timestamptz_term_is_12_blocks_and_orders(pool: PgPool) -> Result<()> { width, 604, "timestamptz ORE term must be 12 blocks (604 bytes)" ); + Ok(()) +} - let cmp: i32 = sqlx::query_scalar( - "SELECT eql_v3.compare_ore_block_256_terms( \ - eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_timestamptz WHERE plaintext = '1900-01-01T00:00:00Z'::timestamptz)::eql_v3.timestamptz_ord), \ - eql_v3.ord_term((SELECT payload FROM fixtures.eql_v2_timestamptz WHERE plaintext = '2099-12-31T23:59:59Z'::timestamptz)::eql_v3.timestamptz_ord))", +/// 12-block (timestamptz) terms must order like `DateTime`'s `Ord` over +/// ALL pairs, plus antisymmetry. N=12 is the only width strictly between the +/// working 8 and the headline 14, so it needs the same left-block-deciding +/// coverage as numeric. The 15 values are the strict ascending oracle (matching +/// `TIMESTAMPTZ_FIXTURES`' catalog order); `assert_orders_like_oracle` fails +/// loudly if the list drifts from the committed fixture. +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_timestamptz")))] +async fn timestamptz_terms_order_like_datetime_ord(pool: PgPool) -> Result<()> { + let ascending: Vec = [ + "1900-01-01T00:00:00Z", + "1950-07-15T06:30:00Z", + "1969-12-31T23:59:59Z", + "1970-01-01T00:00:00Z", + "1970-01-01T00:00:01Z", + "1985-04-12T23:20:50Z", + "1999-12-31T23:59:59Z", + "2000-01-01T00:00:00Z", + "2004-02-29T12:00:00Z", + "2012-06-30T11:59:59Z", + "2016-03-15T08:15:30Z", + "2020-10-21T14:45:00Z", + "2024-02-29T17:30:45Z", + "2038-01-19T03:14:07Z", + "2099-12-31T23:59:59Z", + ] + .iter() + .map(|v| format!("'{v}'::timestamptz")) + .collect(); + assert_orders_like_oracle(&pool, "eql_v2_timestamptz", "timestamptz_ord", &ascending).await +} + +/// A real wide-block term must compare equal to itself — the reflexive +/// `eq`-true path (`functions.sql:166`) at N=14 and N=12, creds-free (reuses the +/// generated fixtures). Distinct from the `1 == 1.0` collision (Gap 1), which is +/// equality across *different* ciphertexts. +#[sqlx::test(fixtures(path = "../fixtures", scripts("eql_v2_numeric", "eql_v2_timestamptz")))] +async fn wide_block_term_compares_equal_to_itself(pool: PgPool) -> Result<()> { + let numeric = compare_fixture_pair( + &pool, + "eql_v2_numeric", + "numeric_ord", + "(1)::numeric", + "(1)::numeric", + ) + .await?; + assert_eq!(numeric, 0, "a 14-block numeric term must equal itself"); + + let timestamptz = compare_fixture_pair( + &pool, + "eql_v2_timestamptz", + "timestamptz_ord", + "'2000-01-01T00:00:00Z'::timestamptz", + "'2000-01-01T00:00:00Z'::timestamptz", ) - .fetch_one(&pool) .await?; - assert_eq!(cmp, -1, "1900 must order before 2099"); + assert_eq!( + timestamptz, 0, + "a 12-block timestamptz term must equal itself" + ); + Ok(()) +} + +/// Compare the collision-fixture rows addressed by `id`. The +/// `v3_numeric_collision` fixture stores `1` (id 1), `1.0` (id 2), `2` (id 3); +/// rows are fetched by `id` because `WHERE plaintext = 1` is ambiguous (numeric +/// equality matches both `1` and `1.0`). +async fn compare_collision_ids(pool: &PgPool, a: i64, b: i64) -> Result { + let sql = format!( + "SELECT eql_v3.compare_ore_block_256_terms( \ + eql_v3.ord_term((SELECT payload FROM fixtures.v3_numeric_collision WHERE id = {a})::eql_v3.numeric_ord), \ + eql_v3.ord_term((SELECT payload FROM fixtures.v3_numeric_collision WHERE id = {b})::eql_v3.numeric_ord))" + ); + Ok(sqlx::query_scalar::<_, i32>(&sql).fetch_one(pool).await?) +} + +/// Scale-equivalent decimals (`1` and `1.0`) must collide in the ORE +/// ciphertext: they are value-equal numerics, so their ORE terms must compare +/// `0`. Always-on via the committed `v3_numeric_collision` fixture — the only +/// place the value-equal pair can live, since the catalog distinctness guard +/// (`scalar_domains.rs` `numeric_value_guards`) forbids it in `eql_v2_numeric`. +/// This is the positive counterpart to that negative guard. +/// +/// Asserted in BOTH directions (a scale-biased comparator could pass a +/// one-directional check); the `1`-vs-`2` guards are load-bearing — they defeat +/// a degenerate everything-returns-0 comparator that would otherwise pass the +/// collision assertions. +#[sqlx::test(fixtures(path = "../fixtures", scripts("v3_numeric_collision")))] +async fn numeric_scale_equivalents_collide(pool: PgPool) -> Result<()> { + // ids: 1 => `1`, 2 => `1.0`, 3 => `2`. + assert_eq!( + compare_collision_ids(&pool, 1, 2).await?, + 0, + "1 and 1.0 must collide" + ); + assert_eq!( + compare_collision_ids(&pool, 2, 1).await?, + 0, + "the collision must be order-independent" + ); + assert_eq!( + compare_collision_ids(&pool, 1, 3).await?, + -1, + "1 must order before 2" + ); + assert_eq!( + compare_collision_ids(&pool, 3, 1).await?, + 1, + "2 must order after 1" + ); Ok(()) } From 61c5f503c135a9063ec2fe852f85a58f961d73aa Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Tue, 16 Jun 2026 10:22:23 +1000 Subject: [PATCH 8/9] feat(eql-types): add numeric + promote timestamptz to ordered domains The eql-types crate landed on eql_v3 (PR #236) after this branch forked, so merging the base in surfaced a catalog_parity failure: CATALOG now has the numeric family and ordered timestamptz, but v3::all() didn't. - numeric.rs: four ordered domains (storage/_eq/_ord/_ord_ore), mirroring date.rs; numeric is the first scalar with a >8-block ORE term (14) - timestamptz.rs: add the two ordered domains; the eq-only/8-block-limit rationale is gone now that eql_v3.ore_block_256 derives N from term length - mod.rs: register the six new domains in all(), in CATALOG order - terms.rs: the ob term's SQL constructor is eql_v3.ore_block_256 (renamed this branch) and is width-agnostic (8/12/14 blocks) - v3_conformance.rs: cover numeric + timestamptz ord wire shapes; drop the stale equality-only claim - README: timestamptz no longer eq-only; add numeric --- crates/eql-types/README.md | 2 +- crates/eql-types/src/v3/mod.rs | 7 ++ crates/eql-types/src/v3/numeric.rs | 112 +++++++++++++++++++++++ crates/eql-types/src/v3/terms.rs | 8 +- crates/eql-types/src/v3/timestamptz.rs | 67 ++++++++++++-- crates/eql-types/tests/v3_conformance.rs | 55 +++++++++-- 6 files changed, 231 insertions(+), 20 deletions(-) create mode 100644 crates/eql-types/src/v3/numeric.rs diff --git a/crates/eql-types/README.md b/crates/eql-types/README.md index df19baff..2940a01f 100644 --- a/crates/eql-types/README.md +++ b/crates/eql-types/README.md @@ -21,7 +21,7 @@ hand-copying. The [`src/v3/`](src/v3/) module has one type per **SQL domain** in the `eql_v3` schema — `Int4` / `Int4Eq` / `Int4Ord` / `Int4OrdOre`, and likewise -for `int2`, `int8`, `date`, `timestamptz` (eq-only), and `text` (which adds +for `int2`, `int8`, `date`, `timestamptz`, `numeric`, and `text` (which adds `TextMatch`) — each carrying its index terms as **required** fields. The capability is the type identity; `Option` never appears. A payload missing its term key fails to deserialize: the Rust analogue of the SQL domain's diff --git a/crates/eql-types/src/v3/mod.rs b/crates/eql-types/src/v3/mod.rs index 316f53e7..0b29017e 100644 --- a/crates/eql-types/src/v3/mod.rs +++ b/crates/eql-types/src/v3/mod.rs @@ -50,6 +50,7 @@ pub mod date; pub mod int2; pub mod int4; pub mod int8; +pub mod numeric; pub mod terms; pub mod text; pub mod timestamptz; @@ -130,6 +131,12 @@ pub fn all() -> Vec> { Box::new(PhantomData::), Box::new(PhantomData::), Box::new(PhantomData::), + Box::new(PhantomData::), + Box::new(PhantomData::), + Box::new(PhantomData::), + Box::new(PhantomData::), + Box::new(PhantomData::), + Box::new(PhantomData::), Box::new(PhantomData::), Box::new(PhantomData::), Box::new(PhantomData::), diff --git a/crates/eql-types/src/v3/numeric.rs b/crates/eql-types/src/v3/numeric.rs new file mode 100644 index 00000000..660833e0 --- /dev/null +++ b/crates/eql-types/src/v3/numeric.rs @@ -0,0 +1,112 @@ +//! The `numeric` encrypted-domain family — an ordered, non-integer scalar +//! backed by `rust_decimal::Decimal`. Same four-domain ordered shape as +//! [`crate::v3::int4`] (ORE compares ciphertext, so decimals order like +//! integers); see that module for the capability table. +//! +//! `numeric` is the first scalar whose native ORE term is wider than 8 blocks +//! (14 blocks): the wire shape is unchanged — the `ob` array simply carries +//! more block strings — and the generalized `eql_v3.ore_block_256` comparator +//! orders any block count, so no new type is needed here. + +use crate::v3::terms::{Ciphertext, Hmac256, OreBlock256}; +use crate::v3::DomainType; +use crate::{Identifier, SchemaVersion}; +use serde::{Deserialize, Serialize}; + +/// `eql_v3.numeric` — storage only; every operator is blocked. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Numeric { + /// Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other + /// value fails deserialization. + pub v: SchemaVersion, + /// Table/column identifier. Required by the domain CHECK. + pub i: Identifier, + /// mp_base85 source ciphertext. Required by the domain CHECK. + pub c: Ciphertext, +} + +impl DomainType for Numeric { + fn sql_domain_static() -> &'static str { + "eql_v3.numeric" + } + + fn sql_domain(&self) -> &'static str { + Self::sql_domain_static() + } +} + +/// `eql_v3.numeric_eq` — HMAC equality (`=`, `<>`). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct NumericEq { + /// Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other + /// value fails deserialization. + pub v: SchemaVersion, + /// Table/column identifier. Required by the domain CHECK. + pub i: Identifier, + /// mp_base85 source ciphertext. Required by the domain CHECK. + pub c: Ciphertext, + /// HMAC-SHA-256 equality term. + pub hm: Hmac256, +} + +impl DomainType for NumericEq { + fn sql_domain_static() -> &'static str { + "eql_v3.numeric_eq" + } + + fn sql_domain(&self) -> &'static str { + Self::sql_domain_static() + } +} + +/// `eql_v3.numeric_ord_ore` — full comparison, scheme-explicit name. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct NumericOrdOre { + /// Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other + /// value fails deserialization. + pub v: SchemaVersion, + /// Table/column identifier. Required by the domain CHECK. + pub i: Identifier, + /// mp_base85 source ciphertext. Required by the domain CHECK. + pub c: Ciphertext, + /// Block-ORE order term (14 blocks for numeric). Serves equality too. + pub ob: OreBlock256, +} + +impl DomainType for NumericOrdOre { + fn sql_domain_static() -> &'static str { + "eql_v3.numeric_ord_ore" + } + + fn sql_domain(&self) -> &'static str { + Self::sql_domain_static() + } +} + +/// `eql_v3.numeric_ord` — full comparison (`=` `<>` `<` `<=` `>` `>=`). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct NumericOrd { + /// Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other + /// value fails deserialization. + pub v: SchemaVersion, + /// Table/column identifier. Required by the domain CHECK. + pub i: Identifier, + /// mp_base85 source ciphertext. Required by the domain CHECK. + pub c: Ciphertext, + /// Block-ORE order term (14 blocks for numeric). Serves equality too. + pub ob: OreBlock256, +} + +impl DomainType for NumericOrd { + fn sql_domain_static() -> &'static str { + "eql_v3.numeric_ord" + } + + fn sql_domain(&self) -> &'static str { + Self::sql_domain_static() + } +} diff --git a/crates/eql-types/src/v3/terms.rs b/crates/eql-types/src/v3/terms.rs index f5319ed7..1036ee62 100644 --- a/crates/eql-types/src/v3/terms.rs +++ b/crates/eql-types/src/v3/terms.rs @@ -23,9 +23,11 @@ pub struct Ciphertext(pub String); #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Hmac256(pub String); -/// Block-ORE (u64, 8 blocks, 256) order term — the `ob` wire key. Backs the -/// `_ord` / `_ord_ore` domains (`=` `<>` `<` `<=` `>` `>=`); ORE is lossless -/// over the scalar's domain, so it serves equality too. SQL-side constructor: +/// Block-ORE order term — the `ob` wire key. Backs the `_ord` / `_ord_ore` +/// domains (`=` `<>` `<` `<=` `>` `>=`); ORE is lossless over the scalar's +/// domain, so it serves equality too. The block count is width-agnostic on the +/// wire (8 for the int scalars, 12 for timestamptz, 14 for numeric) — the +/// array just carries more block strings. SQL-side constructor: /// `eql_v3.ore_block_256`. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct OreBlock256(pub Vec); diff --git a/crates/eql-types/src/v3/timestamptz.rs b/crates/eql-types/src/v3/timestamptz.rs index 6c4621b7..5cb959b7 100644 --- a/crates/eql-types/src/v3/timestamptz.rs +++ b/crates/eql-types/src/v3/timestamptz.rs @@ -1,10 +1,15 @@ -//! The `timestamptz` encrypted-domain family — **equality-only** (storage + -//! `_eq`). There is no ordered domain: cipherstash encrypts timestamps at -//! native 12-block ORE width, but EQL's only ORE comparator is hardcoded to -//! 8 blocks, so an ordered timestamptz domain would silently mis-order. -//! Ordering arrives with a future wide-ORE term (see `eql-scalars`). +//! The `timestamptz` encrypted-domain family — an ordered, non-integer scalar. +//! Same four-domain ordered shape as [`crate::v3::int4`] (ORE compares +//! ciphertext, so timestamps order like integers); see that module for the +//! capability table. +//! +//! cipherstash encrypts timestamps at native 12-block ORE width. The family +//! was equality-only while EQL's ORE comparator was hardcoded to 8 blocks; +//! now that `eql_v3.ore_block_256` derives the block count from the term +//! length, the 12-block `ob` term orders correctly and the ordered domains +//! ship. The wire shape is unchanged — the `ob` array just carries 12 blocks. -use crate::v3::terms::{Ciphertext, Hmac256}; +use crate::v3::terms::{Ciphertext, Hmac256, OreBlock256}; use crate::v3::DomainType; use crate::{Identifier, SchemaVersion}; use serde::{Deserialize, Serialize}; @@ -56,3 +61,53 @@ impl DomainType for TimestamptzEq { Self::sql_domain_static() } } + +/// `eql_v3.timestamptz_ord_ore` — full comparison, scheme-explicit name. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TimestamptzOrdOre { + /// Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other + /// value fails deserialization. + pub v: SchemaVersion, + /// Table/column identifier. Required by the domain CHECK. + pub i: Identifier, + /// mp_base85 source ciphertext. Required by the domain CHECK. + pub c: Ciphertext, + /// Block-ORE order term (12 blocks for timestamptz). Serves equality too. + pub ob: OreBlock256, +} + +impl DomainType for TimestamptzOrdOre { + fn sql_domain_static() -> &'static str { + "eql_v3.timestamptz_ord_ore" + } + + fn sql_domain(&self) -> &'static str { + Self::sql_domain_static() + } +} + +/// `eql_v3.timestamptz_ord` — full comparison (`=` `<>` `<` `<=` `>` `>=`). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TimestamptzOrd { + /// Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other + /// value fails deserialization. + pub v: SchemaVersion, + /// Table/column identifier. Required by the domain CHECK. + pub i: Identifier, + /// mp_base85 source ciphertext. Required by the domain CHECK. + pub c: Ciphertext, + /// Block-ORE order term (12 blocks for timestamptz). Serves equality too. + pub ob: OreBlock256, +} + +impl DomainType for TimestamptzOrd { + fn sql_domain_static() -> &'static str { + "eql_v3.timestamptz_ord" + } + + fn sql_domain(&self) -> &'static str { + Self::sql_domain_static() + } +} diff --git a/crates/eql-types/tests/v3_conformance.rs b/crates/eql-types/tests/v3_conformance.rs index a20e6e99..df345674 100644 --- a/crates/eql-types/tests/v3_conformance.rs +++ b/crates/eql-types/tests/v3_conformance.rs @@ -168,7 +168,7 @@ fn non_int4_tokens_round_trip_every_domain() { // `catalog_parity.rs` checks domain *names* only, never the wire shape. // This sweep roundtrips every non-int4 domain and pins its catalog name, // failing the instant a token drifts from the shared envelope/term contract. - use eql_types::v3::{date::*, int2::*, int8::*, text::*}; + use eql_types::v3::{date::*, int2::*, int8::*, numeric::*, text::*}; // Wire builders for the three shapes the ordered tokens share. let storage = |t: &str| json!({ "v": 2, "i": { "t": t, "c": "x" }, "c": "ct" }); @@ -204,6 +204,13 @@ fn non_int4_tokens_round_trip_every_domain() { round_trip!(DateOrd, ord("a"), "eql_v3.date_ord"); round_trip!(DateOrdOre, ord("a"), "eql_v3.date_ord_ore"); + // numeric is the first scalar whose native ORE term exceeds 8 blocks (14); + // the wire shape is identical, so the same `ord` builder applies. + round_trip!(Numeric, storage("a"), "eql_v3.numeric"); + round_trip!(NumericEq, eq("a"), "eql_v3.numeric_eq"); + round_trip!(NumericOrd, ord("a"), "eql_v3.numeric_ord"); + round_trip!(NumericOrdOre, ord("a"), "eql_v3.numeric_ord_ore"); + // text_match is covered by `text_match_round_trips_signed_bloom_filter`. round_trip!(Text, storage("a"), "eql_v3.text"); round_trip!(TextEq, eq("a"), "eql_v3.text_eq"); @@ -213,12 +220,16 @@ fn non_int4_tokens_round_trip_every_domain() { } #[test] -fn timestamptz_round_trips_and_enforces_equality_term() { - // The one structurally-distinct token: equality-only, no `_ord`/`_ord_ore` - // (the 8-block-ORE limitation). The int4 template was copy-pasted to - // produce it, so an accidental extra `ob` field or a dropped `hm` would - // pass `catalog_parity` (domain names only) but is caught here. - use eql_types::v3::timestamptz::{Timestamptz, TimestamptzEq}; +fn timestamptz_round_trips_and_enforces_term_capabilities() { + // timestamptz is an ordered token (12-block ORE) — it carries the full + // storage/`_eq`/`_ord`/`_ord_ore` shape, the same as the int scalars. The + // int4 template was copy-pasted to produce it, so a dropped `hm`/`ob` or a + // field typo would pass `catalog_parity` (domain names only) but is caught + // here. (Was equality-only while the ORE comparator was hardcoded to 8 + // blocks; promoted once `eql_v3.ore_block_256` generalized to any width.) + use eql_types::v3::timestamptz::{ + Timestamptz, TimestamptzEq, TimestamptzOrd, TimestamptzOrdOre, + }; // Storage-only: envelope, no term. let storage = json!({ @@ -241,16 +252,40 @@ fn timestamptz_round_trips_and_enforces_equality_term() { assert_eq!(serde_json::to_value(&parsed).unwrap(), with_hm); assert_eq!(TimestamptzEq::sql_domain_static(), "eql_v3.timestamptz_eq"); - // `_eq` is the only searchable shape this token has, so its equality term - // cannot silently become optional. + // Ordered: envelope + ob (a 12-block array on the wire; shape is the same). + let with_ob = json!({ + "v": 2, + "i": { "t": "events", "c": "occurred_at" }, + "c": "mp_base85_ciphertext", + "ob": ["b0", "b1"] + }); + let parsed: TimestamptzOrd = serde_json::from_value(with_ob.clone()).unwrap(); + assert_eq!(serde_json::to_value(&parsed).unwrap(), with_ob); + assert_eq!( + TimestamptzOrd::sql_domain_static(), + "eql_v3.timestamptz_ord" + ); + let parsed: TimestamptzOrdOre = serde_json::from_value(with_ob.clone()).unwrap(); + assert_eq!(serde_json::to_value(&parsed).unwrap(), with_ob); + assert_eq!( + TimestamptzOrdOre::sql_domain_static(), + "eql_v3.timestamptz_ord_ore" + ); + + // The searchable domains cannot let their term silently become optional. let no_hm = json!({ "v": 2, "i": { "t": "events", "c": "occurred_at" }, "c": "mp_base85_ciphertext" }); - let result: Result = serde_json::from_value(no_hm); + let result: Result = serde_json::from_value(no_hm.clone()); assert!( result.is_err(), "TimestamptzEq must reject a payload with no hm" ); + let result: Result = serde_json::from_value(no_hm); + assert!( + result.is_err(), + "TimestamptzOrd must reject a payload with no ob" + ); } From 5643b7c652d68d3f2cad8f373e915c48c91aa673 Mon Sep 17 00:00:00 2001 From: Toby Hede Date: Wed, 17 Jun 2026 08:33:47 +1000 Subject: [PATCH 9/9] docs(v3): validate N-from-length ORE wire format against ore-rs 0.8.3 sources --- ...17-ore-block-256-wire-format-validation.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/analysis/2026-06-17-ore-block-256-wire-format-validation.md diff --git a/docs/analysis/2026-06-17-ore-block-256-wire-format-validation.md b/docs/analysis/2026-06-17-ore-block-256-wire-format-validation.md new file mode 100644 index 00000000..bcb90c34 --- /dev/null +++ b/docs/analysis/2026-06-17-ore-block-256-wire-format-validation.md @@ -0,0 +1,71 @@ +# ORE block-256 wire format — N-from-length validation + +- **Date:** 2026-06-17 +- **Context:** Validates the `eql_v3` N-block comparator (`eql_v3.compare_ore_block_256_term`) against a PR review claim that ore-rs always emits 8 blocks and chunks wide values into an array. +- **Verdict:** Claim does **not** hold for the pinned deps (`ore-rs 0.8.3`, `cipherstash-client 0.35.0`). Deriving block count `N` from term length is correct for what the crypto emits today. + +## Claim under review + +> "The current version of ore.rs is always 8-blocks output. To handle longer values, 8-block chunks are created in an array. ... the upcoming changes to ore.rs will support a variable length encoding." + +## Wire format (per term) + +``` +[ N PRP bytes ][ N×16B left blocks ][ 16B hash key ][ N×32B right blocks ] +octet_length = 17·N + 16 + 32·N = 49·N + 16 => N = (octet_length − 16) / 49 +``` + +Linear in `N` and invertible for `N ≥ 1`, so length recovers the exact `N` the encryptor used. + +## Evidence + +**1. ore-rs 0.8.3 — native variable width, not 8-block** ([decimal.rs](https://docs.rs/crate/ore-rs/0.8.3/source/src/decimal.rs), [chrono.rs](https://docs.rs/crate/ore-rs/0.8.3/source/src/chrono.rs)) + +```rust +//! ... feeding the 14-byte plaintext through the existing fixed-N ORE machinery (`N = 14`). // L7 +const ENCODED_LEN: usize = ::ENCODED_LEN; // L16 +type FullOutput = CipherText; // L20 +``` + +**2. cipherstash-client 0.35.0 — single full-width term, only `text` uses an array** ([ore_indexer/mod.rs](https://docs.rs/crate/cipherstash-client/0.35.0/source/src/encryption/ore_indexer/mod.rs)) + +```rust +// Decimal (14 bytes) and Timestamp (12 bytes) exceed the 8-byte block; +// ... encrypted at their native ore-rs widths. // L136–139 +Plaintext::Decimal(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())), // L140 +Plaintext::Timestamp(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())), // L141 +``` + +> doc comment: *"Strings will return an `IndexTerm::OreArray`. All other types will return a `IndexTerm::OreFull`."* + +Integers go through `pad_orderable_to_8` (→ N=8); Decimal/Timestamp do **not**. `OreFull` = one term, not chunked. + +**3. Real ZeroKMS fixtures — measured, end-to-end** (`tests/sqlx/fixtures/eql_v2_*.sql`) + +| type | `ob` array | term bytes | derived N | +|------|-----------:|-----------:|----------:| +| int4 | 1 term | 408 | 8 (`49·8+16`) | +| timestamptz | 1 term | 604 | 12 (`49·12+16`) | +| numeric | 1 term | 702 | 14 (`49·14+16`) | + +A numeric value is **one 702-byte term**, not an array of 408-byte chunks. Pinned by `numeric_term_is_14_blocks` / timestamptz-width tests in `tests/sqlx/tests/ore_block_comparator_tests.rs`. + +## Correctness of the derivation + +- Equal-length guard (`bit_length(a) != bit_length(b)` → raise) ⇒ `a`/`b` share `N`; deriving from `a` alone is sound and blocks cross-type comparison. +- Well-formedness guard is **both** clauses — `octet_length <= 16 OR (octet_length − 16) % 49 != 0`. Modulo alone admits a 16-byte term (`N=0`) that would silently return `0` (equal); `<= 16` is load-bearing. +- N=8 reduces to the old constants (`right_offset = 17·8 = 136`, left base `1+8 = 9`) ⇒ no-op for existing int types. +- Ordering verified against the plaintext oracle (`Decimal`/`DateTime` `Ord`) over **all pairs** + antisymmetry — `assert_orders_like_oracle`, `ore_block_comparator_tests.rs`. + +## Forward-looking caveat + +- The reviewer's "upcoming variable-length ore-rs" appears to already be present in 0.8.3 (native N=14/12). If a *further* format change is planned, it is a fixture-regeneration / breaking change that equally affects the existing `eql_v2` 8-block comparator — it does not make the current derivation incorrect. +- Action: confirm any tracked ore-rs format change before treating this as blocking; scope against the specific change, not an indefinite hold. + +## References + +- Design: `docs/plans/2026-06-11-ore-block-comparator-n-blocks-design.md` +- Comparator: `src/v3/sem/ore_block_256/functions.sql` +- Tests: `tests/sqlx/tests/ore_block_comparator_tests.rs` +- ore-rs 0.8.3: https://crates.io/crates/ore-rs/0.8.3 (no `repository` field; docs.rs source is authoritative) +- cipherstash-client 0.35.0: https://crates.io/crates/cipherstash-client/0.35.0 · repo declared: https://github.com/cipherstash/cipherstash-suite