Skip to content

Commit 6fc0c6a

Browse files
committed
feat(stack-encrypt)!: order floats through cllw-ore's F64 target
An `f32` or `f64` order term is cllw-ore's vitaminc encryption of the value at the `F64` target, whose vitaminc-ore encoding folds `-0.0` into `0.0` and every NaN into one value above `+Infinity`: Postgres's `float8` order. So the ordering operators are now answered over floats, on the typed path (`op::Ordered` for `f32` and `f64`) and over a data plan's `float32` and `float64` fields, and stashgen writes `Lt`, `Lte`, `Gt`, `Gte` and `Between` on a float field with `ore` or `ope`. An `f32` is ordered as the `f64` it widens to: its `Orderable` target is `F64` and vitaminc widens it exactly, so an `f32` and its `f64` are one term and `f32` can join `op::Ordered`. A float `Between` is not refused when inverted (a range from NaN, which sorts last, matches nothing), as SQL's `BETWEEN` is not. The Go query profile gains a sealed `Score float64` with ORE, matching the Rust query fixture, and the live PostgreSQL test stores the score and its ORE term so the rows it reads back open. Float pins, the float ordering baseline, the integer-width lengths and the `score` field in the cross-path fixture are added; the fixtures and UI snapshots are re-recorded. Closes #1236.
1 parent b98bad2 commit 6fc0c6a

28 files changed

Lines changed: 693 additions & 91 deletions

‎languages/golang/CHANGELOG.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,14 @@ listed under **Breaking**. Releases are tagged `languages/golang/vX.Y.Z`.
8585
`index=ope` gets `Lt`, `Lte`, `Gt`, `Gte` and `Between`. Text orders by its
8686
folded form (ADR-0002: case and accents folded, under Unicode 16), so
8787
`Café` and `cafe` tie and `alice` orders before `Zoe`.
88+
- **Ordering methods on floats.** A `float32` or `float64` field with
89+
`index=ore` or `index=ope` gets `Lt`, `Lte`, `Gt`, `Gte` and `Between`.
90+
Floats order as PostgreSQL's `float8` does (ADR-0002): `-0.0` ties `0.0`,
91+
and every NaN is one value, above `+Inf`. A `float32` orders as the
92+
`float64` it widens to.
8893
- **stashgen notices an index no generated method queries**: an `ore` or
89-
`ope` index on bytes or a float (the engine answers the orderings over
90-
integers and text only), or the second of two order indexes on a field.
94+
`ope` index on bytes (the engine answers the orderings over integers,
95+
floats and text only), or the second of two order indexes on a field.
9196
The index is still stored; the notice is on stderr and in the file.
9297

9398
### 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: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,14 @@ or a type defined over one (`type Email string`):
235235
| `int8`, `int16`, `int32`, `int64`, `int` | an integer of the field's own width (`int` is 64-bit) | `equality`, `ore`, `ope` | `Eq`, `Lt`, `Lte`, `Gt`, `Gte`, `Between` |
236236
| `uint8` (`byte`), `uint16`, `uint32`, `uint64`, `uint` | the same, unsigned | `equality`, `ore`, `ope` | the same |
237237
| `encrypt.Int128`, `encrypt.Uint128` | a 128-bit integer | `equality`, `ore`, `ope` | the same |
238-
| `float32`, `float64` | a float of its width | `equality`, `ore`, `ope` | `Eq` |
238+
| `float32`, `float64` | a float of its width | `equality`, `ore`, `ope` | `Eq`, `Lt`, `Lte`, `Gt`, `Gte`, `Between` |
239239
| `encrypt.Decimal` | a decimal: a 96-bit coefficient and a scale up to 28 | `equality` | `Eq` |
240240
| `encrypt.Date` | a calendar date | `equality` | `Eq` |
241241
| `time.Time` | a timestamp, to the nanosecond, in UTC | `equality` | `Eq` |
242242
243243
Each query method needs its index declared. An `ore` or `ope` index on bytes
244-
or a float stores its term, but the engine answers no ordering through it
245-
yet, so such a field has no `Lt` (see [Queries](#queries)).
244+
stores its term, but the engine answers no ordering through it, so such a
245+
field has no `Lt` (see [Queries](#queries)).
246246
247247
`encrypt.Date`, `Int128`, `Uint128` and `Decimal` are vitaminc's `vcvalue`
248248
types; build them with `encrypt.DateOf`, `ParseInt128`, `ParseUint128` and
@@ -257,8 +257,8 @@ width, and each width is passed to the order scheme as itself, so an `int8`
257257
field's terms are an `int8`'s. Equality follows vitaminc's normalisation: an
258258
integer equals itself at every width, decimals compare at any scale
259259
(`1.50 = 1.5`), and a timestamp compares to the microsecond. An order index on
260-
a decimal, date or timestamp is refused for now: those order terms wait for
261-
new cllw-ore and ore-rs releases (#1236).
260+
a decimal, date or timestamp is refused for now: the engine derives no
261+
order term for those kinds yet.
262262
263263
A `match` index stores text with no token (an empty string, separator-only
264264
text, or text shorter than the n-gram length of 3 characters) as the empty
@@ -316,7 +316,12 @@ range matches nothing. So `Between("b", "A")` matches nothing, since `A`
316316
folds to `a`, below `b`. Text holding a code point Unicode 16 does not assign has no
317317
fold, and its query or write is refused with `encrypt.ErrTerm`.
318318
319-
An order index on bytes or a float stores its term but answers no ordering
319+
Floats order as PostgreSQL's `float8` does (ADR-0002): `-0.0` and `0.0` are
320+
one value, and every NaN is one value, above `+Inf`, so `Between(math.NaN(),
321+
math.Inf(1))` matches nothing. A `float32` orders as the `float64` it
322+
widens to.
323+
324+
An order index on bytes stores its term but answers no ordering
320325
operator, so such a field has no `Lt`; nor does the `ope` index of a field
321326
that declares `ore` too, since `ore` answers the orderings. stashgen prints a notice for each such index, on stderr and in
322327
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
@@ -154,9 +154,10 @@
154154
// Eq returns an [EqualityTerm] (or, for a field with encrypt_into, the EQL
155155
// type's query value), an ordering an [OreTerm] or [OpeTerm], and Between a
156156
// [Range] of two. Their answers are exact. The orderings are answered on
157-
// integers and on text, which orders by its folded form (case and accents
158-
// folded, under Unicode 16), so Café and cafe tie and alice orders before
159-
// Zoe. Matches returns a [MatchQuery],
157+
// integers, on floats, which order as PostgreSQL's float8 does (-0.0 ties
158+
// 0.0, NaN sorts last), and on text, which orders by its folded form (case
159+
// and accents folded, under Unicode 16), so Café and cafe tie and alice
160+
// orders before Zoe. Matches returns a [MatchQuery],
160161
// whose answer can hold rows that do not match: size the fetch with
161162
// [MatchQuery.TargetLimit], then decrypt the rows and keep those
162163
// [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)