Skip to content

Commit e6dc8f9

Browse files
committed
docs(eql-bindings): say a text null is not padded
The generated docs for Text and TextEq said a None is sealed as long as a value. That holds for types of fixed width only: a text null is not padded, since text's ciphertext is as long as the text, so its length may show that the value is absent. The changeset says the same. Claude-Session: https://claude.ai/code/session_01XQN1QpaWVDGjHEg39KEJF3
1 parent e219a94 commit e6dc8f9

3 files changed

Lines changed: 13 additions & 5 deletions

File tree

‎.changeset/eql-bindings-optional-storage-only.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@cipherstash/eql": patch
33
---
44

5-
`eql-bindings` (the Rust crate released with this package): an EQL v4 type whose terms a null has, a storage-only type (`Date`, `Integer`, `Text`, ...) or one whose only term is equality's (`DateEq`, `IntegerEq`, `TextEq`, ...), now also implements `EncryptFrom` and `DecryptInto` for an `Option` of its plaintext, and its query twin `EncryptFrom` for one. A `None` seals as a typed null as long as a value (vitaminc 0.8), so an optional column does not show which rows are absent, and opens as `None`; its `hm` is the one term every null shares, and the query of a `None` finds the stored nulls with `=`. A value seals exactly as before. The by-name dispatch (`encryption::targets`) reads, opens and queries these types through the `Option`; a type with an order or match term still refuses a null. eql-bindings' own `Date`, `Timestamp` and `Decimal` plaintexts declare their fixed width to vitaminc, so their `None` is padded too. The SQL bundle and the TypeScript types do not change.
5+
`eql-bindings` (the Rust crate released with this package): an EQL v4 type whose terms a null has, a storage-only type (`Date`, `Integer`, `Text`, ...) or one whose only term is equality's (`DateEq`, `IntegerEq`, `TextEq`, ...), now also implements `EncryptFrom` and `DecryptInto` for an `Option` of its plaintext, and its query twin `EncryptFrom` for one. For a type of fixed width a `None` seals as a typed null as long as a value (vitaminc 0.8), so an optional column does not show which rows are absent. For text it seals as a null that is not padded, since text's ciphertext is as long as the text, so its length may show that the value is absent. Either way it opens as `None`; its `hm` is the one term every null shares, and the query of a `None` finds the stored nulls with `=`. A value seals exactly as before. The by-name dispatch (`encryption::targets`) reads, opens and queries these types through the `Option`; a type with an order or match term still refuses a null. eql-bindings' own `Date`, `Timestamp` and `Decimal` plaintexts declare their fixed width to vitaminc, so their `None` is padded too. The SQL bundle and the TypeScript types do not change.

‎packages/eql/crates/eql-bindings/src/v4/text.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ts_rs::TS;
1515
doc = " See the [EQL text reference](https://cipherstash.com/docs/reference/eql/text) for SQL domain variants, operators, and query examples."
1616
)]
1717
///
18-
/// Plaintext input and decrypted output: [`String`](String). Implements `EncryptFrom<String>` and `DecryptInto<String>`, and both for `Option<String>`: a `None` is sealed as long as a value, and its terms are a null's, which equality has and no other index does.
18+
/// Plaintext input and decrypted output: [`String`](String). Implements `EncryptFrom<String>` and `DecryptInto<String>`, and both for `Option<String>`: a `None` is sealed as a null that is not padded, since text has no one length, so its length may show that the value is absent, and its terms are a null's, which equality has and no other index does.
1919
/// Encryption context: `NonEmpty<Identifier>`, constructed with `Identifier::for_column(table, column)` and stored in `i`.
2020
/// See the [complete encryption example](crate::encryption#example).
2121
#[derive(stack_encrypt::EncryptFrom, stack_encrypt::DecryptInto)]
@@ -65,7 +65,7 @@ impl DomainType for Text {
6565
doc = " See the [EQL text reference](https://cipherstash.com/docs/reference/eql/text) for SQL domain variants, operators, and query examples."
6666
)]
6767
///
68-
/// Plaintext input and decrypted output: [`String`](String). Implements `EncryptFrom<String>` and `DecryptInto<String>`, and both for `Option<String>`: a `None` is sealed as long as a value, and its terms are a null's, which equality has and no other index does.
68+
/// Plaintext input and decrypted output: [`String`](String). Implements `EncryptFrom<String>` and `DecryptInto<String>`, and both for `Option<String>`: a `None` is sealed as a null that is not padded, since text has no one length, so its length may show that the value is absent, and its terms are a null's, which equality has and no other index does.
6969
/// Encryption context: `NonEmpty<Identifier>`, constructed with `Identifier::for_column(table, column)` and stored in `i`.
7070
/// See the [complete encryption example](crate::encryption#example).
7171
#[derive(stack_encrypt::EncryptFrom, stack_encrypt::DecryptInto)]

‎packages/eql/crates/eql-codegen/src/bindings.rs‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -739,11 +739,19 @@ fn encryption_attrs(
739739
query terms{null}. Query operands do not decrypt."
740740
)
741741
} else if nullable_terms(domain) {
742+
// A null is padded to a value's length only where values have
743+
// one length; text's ciphertext is as long as the text.
744+
let length = if family.name == "text" {
745+
"a `None` is sealed as a null that is not padded, since text has no \
746+
one length, so its length may show that the value is absent"
747+
} else {
748+
"a `None` is sealed as long as a value"
749+
};
742750
format!(
743751
" Plaintext input and decrypted output: {named}. Implements \
744752
`EncryptFrom<{shown}>` and `DecryptInto<{shown}>`, and both for \
745-
`Option<{shown}>`: a `None` is sealed as long as a value, and its terms \
746-
are a null's, which equality has and no other index does."
753+
`Option<{shown}>`: {length}, and its terms are a null's, which equality \
754+
has and no other index does."
747755
)
748756
} else {
749757
format!(

0 commit comments

Comments
 (0)