Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/eql-v4-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

**The `eql-bindings` crate has an `eql_bindings::v4` module, with the `stack-encrypt` feature: payload types for the EQL v4 domains.** EQL v4 is the v3 SQL installed under a second name (ADR-0002), for values written by Stack Encrypt, so the module exists only with that feature and its types derive Stack Encrypt's traits without a further gate. The module is generated from the same catalog as `v3`, with `eql_v4_*` domain names and `"v": 4`. `SchemaVersion` is now generic over the version it admits: `v3::SchemaVersion` is `SchemaVersion<3>` and `v4::SchemaVersion` is `SchemaVersion<4>`, each refusing the other's payloads, and plain `SchemaVersion` still means v3. Only v4 types derive Stack Encrypt's `EncryptFrom` and `DecryptInto`, and the Stack Encrypt target table (now `eql_bindings::v4::targets`) names v4 domains. Besides `TextEq`, Stack Encrypt now produces the storage-only types `Text`, `Integer`, `Bigint`, `Boolean`, `Real` and `Double`, and `TextMatch` (3-grams, `k = 6`, a 2048-bit filter, pinned as `eql_bindings::encryption::EqlMatch`; text shorter than three characters is refused, stored or queried, and only `stored @@ query` matches until #1171 is fixed), from the Rust primitives `i32`, `i64`, `bool`, `f32` and `f64`; the Go SDK's `encrypt/eql` table marks them producible. Stack Encrypt also produces the number equality types `IntegerEq`, `BigintEq`, `RealEq` and `DoubleEq`, whose `=` compares as PostgreSQL compares the plain types: an integer has one term whatever its width, and for floats `-0.0 = 0.0` and `NaN = NaN` (ADR-0002's semantic baseline). Every type it cannot produce yet names the issue that tracks its blocker. The by-name target entry points (`eql_bindings::encryption::targets::encrypt`, `decrypt` and `query`) exchange the plaintext as Stack Encrypt's `dynamic::ValueFrame`, one value as its transport frame, rather than a value tree. They, and the generated `v4::targets` table, need the new `targets` feature, which turns on `stack-encrypt` and Stack Encrypt's `dynamic` feature. `stack-encrypt` alone no longer builds Stack Encrypt's `dynamic` layer, so a Rust program using the typed v4 types through `encrypt_as` doesn't compile it.

**The EQL v4 SQL bundle has range query domains and `eql_v4.between`, so a `BETWEEN` query is one operand.** The v4 bundle (`cipherstash-encrypt-v4.sql`) is built from this source and reaches users as the `eql-v4-install.sql` attached to each Go release; neither the npm package nor the crate ships it, and the v3 SQL they ship does not change. Every ordered family has one range query domain per order term, beside its single-bound ones: `eql_v4.query_integer_range_ore` beside `eql_v4.query_integer_ord_ore`, `eql_v4.query_integer_range_ope` beside `eql_v4.query_integer_ord_ope`, and the same for `smallint`, `bigint`, `date`, `timestamp`, `numeric`, `text`, `real` and `double`. A range is `{"v": 4, "i": …, "lo": <term>, "hi": <term>}`: each bound is the single-bound domain's `ob` (ORE) or `op` (OPE) value, in a `{v, i, lo, hi}` envelope. Stack Encrypt does not build that envelope yet; it will need to when the ordered types become producible. `eql_v4.between(column, range)` is true when the column's order term lies between the bounds, both included, and has an overload for every ordered column domain with the range of the term it stores (`_ord_ore` and `_search_ore` take the ORE range; `_ord`, `_ord_ope` and `_search` the OPE one). It is an inlinable SQL function, so a btree on the column's order term (`eql_v4.ord_term_ore(col)` or `eql_v4.ord_term(col)`) serves it as one index range scan. Write it schema-qualified (`BETWEEN` is an SQL keyword, so a bare `between(...)` does not parse), and cast the range to its exact domain (`$1::eql_v4.query_integer_range_ore`): an uncast `$1`, a `jsonb` parameter, or a range of another term or family fails with SQLSTATE 42725 ("function … is not unique"), unlike the single-bound operators, which resolve an untyped `$1`. In the crate, `eql_bindings::v4` (`stack-encrypt` feature) has a type for each range domain (`IntegerRangeOreQuery`, `TextRangeOpeQuery`, …, listed by `v4::all_range_query()`). EQL v3 has no range domains: its SQL and types are unchanged (ADR-0009, decision 7).

Nothing a released version shipped has changed. The v3 types, the v3 SQL and the TypeScript package are as they were; the `stack-encrypt` derives on the v3 text types and the `v3::targets` module were added after the last release and are moved to v4 here, not removed from a release.
13 changes: 13 additions & 0 deletions languages/golang/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ listed under **Breaking**. Releases are tagged `languages/golang/vX.Y.Z`.
type. A type wider than the EQL type (an `int64` into `Integer`) is range
checked value by value, and `Encrypt` refuses a value that does not fit
with `ErrEncoding`. `encrypt.Int128` and `Uint128` are not converted.
- **Range query types in `encrypt/eql`**, one per ordered family and order
index: `eql.IntegerRangeOreQuery`, `eql.IntegerRangeOpeQuery`,
`eql.TextRangeOreQuery`, `eql.TextRangeOpeQuery`, and the same for
`smallint`, `bigint`, `date`, `timestamp`, `numeric`, `real` and `double`.
Each holds both bounds of one `BETWEEN` (each bound the single-bound
domain's `ob` or `op` value, in a `{v, i, lo, hi}` envelope) as the JSON
value `eql_v4.between(column, $1::eql_v4.query_<family>_range_<ore|ope>)`
takes, so a range binds as one parameter. The cast to the exact range
domain is required, or the statement fails with SQLSTATE 42725. Each type
implements `driver.Valuer`, `sql.Scanner` and JSON like the other query
types. They are not in
`eql.Types`, which lists stored types, and no generated method returns one
yet: the ordered EQL types are not producible.
- **`encrypt.MatchQuery` and `encrypt.EQLMatchQuery`**: what a `Matches`
method returns. Each carries the term or EQL query value to bind,
`Verify` (check decrypted rows, since a match can return false positives)
Expand Down
10 changes: 9 additions & 1 deletion languages/golang/encrypt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,15 @@ is refused, because `Verify` checks a match query's rows by decrypting them.

Every query type implements `driver.Valuer`, so it binds as a parameter in
any library built on `database/sql` or pgx. A `Range` binds as two
parameters, `Low` and `High`.
parameters, `Low` and `High`. On an EQL column a range is one value instead:
package `encrypt/eql` has a range query type per ordered family and order
index (`eql.IntegerRangeOreQuery`, `eql.TextRangeOpeQuery`, ...), the one
operand of `eql_v4.between(column, $1::eql_v4.query_integer_range_ore)`. Keep
the cast to the exact range domain: an uncast `$1`, a `jsonb` parameter, or a
range of another term or family fails with SQLSTATE 42725 ("function ... is
not unique"), unlike the single-bound operators, which resolve an untyped
`$1`. No generated method returns one yet, because the ordered EQL types are
not producible.

`Eq`, the orderings and `Between` are exact: the server's answer is the
answer. `Matches` is probabilistic: the server answers with every row whose
Expand Down
Loading
Loading