Skip to content

Commit dfb7f57

Browse files
committed
feat(stack-encrypt)!: derive every order term over its ADR-0002 order key, and order floats
Only text derived its order terms over vitaminc-ore's order key. Integers, floats and bytes went through cllw-ore's own encodings, and those differ from ADR-0002 for floats: cllw-ore orders -0.0 below 0.0 and keeps NaN payloads apart. So nothing answered an ordering on a float, and about 13 EQL v4 ordered types had no order terms the way ADR-0002 defines them. Every ORE and OPE term now encrypts the value's order key for its OrderTarget (vitaminc_ore::order), under a key framing that names the key's frozen domain label, as text's already did. cllw-ore does not implement vitaminc-ore's OrderScheme yet, so the CLLW schemes encrypt the key: a fixed-width key as the unsigned integer of its width, a variable-length key as bytes. Each lands in the ciphertext type cllw-ore gives the source type, so OreTerm<T> and OpeTerm<T> keep their types. An integer's key is the bytes cllw-ore already encrypted, so integer and bytes terms move through the framing alone; float terms move through the key too. Text terms do not move. None was deployed. Floats order as Postgres's float8 does: -0.0 ties 0.0 and every NaN is one value above +Infinity. op::Ordered covers f64, whose Between judges an inverted range on the keys, and the data path answers the orderings on float32 and float64 fields. An f32 is ordered as the f64 it widens to: the data path widens a float32, and a typed OreTerm<f32> is refused with the new TermError::NarrowFloat rather than give a narrower term. The query fixture gains a float64 field queried with lt and between, and the Go generator writes the orderings on float fields: its generated queries match the fixture byte for byte, and the property tests check float32 and float64 terms against Postgres's float order. Closes #1236 Claude-Session: https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL
1 parent 54a204d commit dfb7f57

30 files changed

Lines changed: 911 additions & 173 deletions

‎languages/golang/CHANGELOG.md‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,15 @@ listed under **Breaking**. Releases are tagged `languages/golang/vX.Y.Z`.
4646
folded form (ADR-0002: case and accents folded, under Unicode 16), so
4747
`Café` and `cafe` tie and `alice` orders before `Zoe`. Text order terms
4848
change bytes from the alpha's; none was deployed.
49+
- **Ordering methods on floats.** A `float32` or `float64` field with
50+
`index=ore` or `index=ope` gets `Lt`, `Lte`, `Gt`, `Gte` and `Between`.
51+
Floats order as PostgreSQL's `float8` does (ADR-0002): `-0.0` ties `0.0`,
52+
and every NaN is one value, above `+Inf`. A `float32` orders as the
53+
`float64` it widens to. Integer and float order terms change bytes from
54+
the alpha's; none was deployed.
4955
- **stashgen notices an index no generated method queries**: an `ore` or
50-
`ope` index on bytes or a float (the engine answers the orderings over
51-
integers and text only), or the second of two order indexes on a field.
56+
`ope` index on bytes (the engine answers the orderings over integers,
57+
floats and text only), or the second of two order indexes on a field.
5258
The index is still stored; the notice is on stderr and in the file.
5359

5460
### Changed

‎languages/golang/cmd/stashgen/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ For `-type User`, the file `user_stash.go` holds:
124124
- `Fields`, with one entry for each sealed field.
125125
An opaque struct, or a struct with no sealed field outside an opaque one, gets no `Fields`: nothing in it is sealed on its own, so nothing can be queried.
126126
An entry encrypts one value, and has one query method for each operator the engine answers on the field: `Eq`, `Lt`, `Lte`, `Gt`, `Gte`, `Between` or `Matches`.
127-
stashgen asks the embedded engine which operators each field answers, and which index answers each one, so it holds no copy of those rules: an equality index answers `Eq`, a match index on a string `Matches`, and an ORE or OPE index on an integer or a string the four orderings and `Between`, through ORE when the field declares both. A string orders by its folded form: case and accents folded, under Unicode 16.
127+
stashgen asks the embedded engine which operators each field answers, and which index answers each one, so it holds no copy of those rules: an equality index answers `Eq`, a match index on a string `Matches`, and an ORE or OPE index on an integer, a float or a string the four orderings and `Between`, through ORE when the field declares both. A float orders as PostgreSQL's `float8` does (`-0.0` ties `0.0`, NaN sorts last), and a string by its folded form: case and accents folded, under Unicode 16.
128128
`encrypt_into=TextEq` answers `Eq`, which returns the type's query value, `eql.TextEqQuery`.
129129
`encrypt_into=TextMatch` answers `Matches`, which returns an `encrypt.EQLMatchQuery`: the `eql.TextMatchQuery` operand, with `Verify` and `TargetLimit`.
130130
The query of `Matches` carries `Verify` and `TargetLimit`, because its answer can hold rows that do not match; an exact operator's query carries neither.

‎languages/golang/encrypt/README.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,12 @@ after `z`. `Between("b", "A")` is refused as an inverted range, since `A`
259259
folds to `a`. Text holding a code point Unicode 16 does not assign has no
260260
fold, and its query or write is refused with `encrypt.ErrTerm`.
261261
262-
An order index on bytes or a float stores its term but answers no ordering
262+
Floats order as PostgreSQL's `float8` does (ADR-0002): `-0.0` and `0.0` are
263+
one value, and every NaN is one value, above `+Inf`, so `Between(math.NaN(),
264+
math.Inf(1))` is refused as an inverted range. A `float32` orders as the
265+
`float64` it widens to.
266+
267+
An order index on bytes stores its term but answers no ordering
263268
operator, so such a field has no `Lt`; nor does the `ope` index of a field
264269
that declares `ore` too, since `ore` answers the orderings. stashgen prints a notice for each such index, on stderr and in
265270
the generated file: the terms are stored, and only your own code can compare

‎languages/golang/encrypt/doc.go‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,10 @@
138138
// Eq returns an [EqualityTerm] (or, for a field with encrypt_into, the EQL
139139
// type's query value), an ordering an [OreTerm] or [OpeTerm], and Between a
140140
// [Range] of two. Their answers are exact. The orderings are answered on
141-
// integers and on text, which orders by its folded form (case and accents
142-
// folded, under Unicode 16), so Café and cafe tie and alice orders before
143-
// Zoe. Matches returns a [MatchQuery],
141+
// integers, on floats, which order as PostgreSQL's float8 does (-0.0 ties
142+
// 0.0, NaN sorts last), and on text, which orders by its folded form (case
143+
// and accents folded, under Unicode 16), so Café and cafe tie and alice
144+
// orders before Zoe. Matches returns a [MatchQuery],
144145
// whose answer can hold rows that do not match: size the fetch with
145146
// [MatchQuery.TargetLimit], then decrypt the rows and keep those
146147
// [MatchQuery.Verify] reports true, one call into the engine for the page.

‎languages/golang/encrypt/internal/testusers/kinds_stash.go‎

Lines changed: 25 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎languages/golang/encrypt/internal/testusers/probe_stash.go‎

Lines changed: 132 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)