Skip to content
Merged
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: 1 addition & 1 deletion .changeset/eql-v4-date-timestamp-numeric.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'@cipherstash/eql': minor
---

**Stack Encrypt produces the EQL v4 date, timestamp and numeric types `Date`, `DateEq`, `Timestamp`, `TimestampEq`, `Numeric` and `NumericEq`** (`eql-bindings`, `stack-encrypt` feature). Their Rust plaintexts are the crate's own `v4::plaintext::Date`, `Timestamp` and `Decimal`, which hold the integers the engine stores (days since 1970-01-01; Unix seconds and nanoseconds; a mantissa and a scale), are range checked when built and are wiped when dropped. The `chrono` and `rust_decimal` features convert to and from those crates' types. `=` on `NumericEq` normalises the scale (`1.5 = 1.50`) and on `TimestampEq` compares to the microsecond, truncating (never rounding) the nanoseconds below it; each value decrypts as it was stored. The ordered types of these families stay unproducible until the engine derives their order terms. The Go SDK's `encrypt/eql` table marks the six producible, from `encrypt.Date`, `time.Time` and `encrypt.Decimal`.
**Stack Encrypt produces the EQL v4 date, timestamp and numeric types `Date`, `DateEq`, `Timestamp`, `TimestampEq`, `Numeric` and `NumericEq`** (`eql-bindings`, `stack-encrypt` feature). Their Rust plaintexts are Stack Encrypt's `stack_encrypt::value::Date`, `Timestamp` and `Decimal`, re-exported as `v4::plaintext::Date`, `Timestamp` and `Decimal`. They hold the integers the engine stores (days since 1970-01-01; Unix seconds and nanoseconds; a mantissa and a scale), are range checked when built and are wiped when dropped. eql-bindings' `chrono` and `rust_decimal` features turn on Stack Encrypt's features of the same names, which convert to and from those crates' types. `=` on `NumericEq` normalises the scale (`1.5 = 1.50`) and on `TimestampEq` compares to the microsecond, truncating (never rounding) the nanoseconds below it; each value decrypts as it was stored. The Go SDK's `encrypt/eql` table marks the six producible, from `encrypt.Date`, `time.Time` and `encrypt.Decimal`.
5 changes: 5 additions & 0 deletions .changeset/eql-v4-ordered-date-timestamp-numeric.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cipherstash/eql': minor
---

**Stack Encrypt produces the EQL v4 ordered date, timestamp and numeric types** (`eql-bindings`, `stack-encrypt` feature): `DateOrd`, `DateOrdOpe`, `DateOrdOre`, `TimestampOrd`, `TimestampOrdOpe`, `TimestampOrdOre`, `NumericOrd`, `NumericOrdOpe` and `NumericOrdOre`. A date orders by its day, a timestamp by the microsecond (truncated, as `TimestampEq`'s `=` is) and a decimal by value, so `1.5` and `1.50` order as equal. `v4::plaintext::Date`, `Timestamp`, `Decimal` and `RangeError` are now Stack Encrypt's own types, re-exported at the same path. The Go SDK's `encrypt/eql` table marks the nine producible.
49 changes: 47 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 11 additions & 4 deletions languages/golang/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ listed under **Breaking**. Releases are tagged `languages/golang/vX.Y.Z`.

### Added

- **Decimals, dates and timestamps take an order index (#1236).** An
`encrypt.Decimal`, `encrypt.Date` or `time.Time` field takes `ore` and
`ope`, and the generated code gives it `Lt`, `Lte`, `Gt`, `Gte` and
`Between`. A decimal orders by value (`1.5` ties `1.50`), a date by its
day, and a timestamp by the microsecond, truncated as its equality term
is. The EQL types `DateOrd`, `DateOrdOpe`, `DateOrdOre`, `TimestampOrd`,
`TimestampOrdOpe`, `TimestampOrdOre`, `NumericOrd`, `NumericOrdOpe` and
`NumericOrdOre` are producible with `encrypt_into=`.
- **An optional field: a sealed field declared over a pointer (#1298).**
`DOB *encrypt.Date` with `stash:"dob,encrypt_into=Date"` generates (it
was refused as a composite, #1293), and `Decrypt` gives `nil` back for a
Expand Down Expand Up @@ -87,8 +95,7 @@ listed under **Breaking**. Releases are tagged `languages/golang/vX.Y.Z`.
`NumericEq` normalises the scale (`1.5 = 1.50`) and `TimestampEq` compares
to the microsecond (truncated, never rounded), while each value opens as it was sealed. A
`time.Time` seals its instant and opens in UTC. Their `Ord`, `OrdOpe` and
`OrdOre` types stay unproducible until the engine derives order terms
for these kinds (#1236).
`OrdOre` types follow (see above).
- **Every integer width, 128-bit integers, dates, timestamps and decimals
as sealed fields** (#1144). Each integer type seals at its own width
(`int8` as an int8, `uint16` as a uint16), so `record.Kind` and
Expand All @@ -98,8 +105,8 @@ listed under **Breaking**. Releases are tagged `languages/golang/vX.Y.Z`.
`Decimal` are vitaminc's `vcvalue` types, with `encrypt.DateOf`,
`ParseInt128`, `ParseUint128` and `ParseDecimal` (which refuses NaN and
the infinities with `ErrNonFiniteDecimal`). Equality applies to all of
them, ORE and OPE to every integer width but 8 bits. stashgen refuses an
order index on a decimal, date or timestamp, naming #1236. The value-kinds fixture
them, ORE and OPE to every integer width but 8 bits, and to decimals,
dates and timestamps (see above). The value-kinds fixture
test opens a record the Rust lowering sealed and derives the same terms.
- **Any built-in Go integer field takes an integer EQL type** (ADR-0002):
an `int16` or a `uint16` into `Integer`, an `int` into `Bigint`. The
Expand Down
4 changes: 2 additions & 2 deletions languages/golang/cmd/stashgen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ For `-type User`, the file `user_stash.go` holds:
- `Fields`, with one entry for each sealed field.
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.
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`.
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.
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 of 16 bits or more, a float, a string, an `encrypt.Decimal`, an `encrypt.Date` or a `time.Time` 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.
`encrypt_into=TextEq` answers `Eq`, which returns the type's query value, `eql.TextEqQuery`.
`encrypt_into=TextMatch` answers `Matches`, which returns an `encrypt.EQLMatchQuery`: the `eql.TextMatchQuery` operand, with `Verify` and `TargetLimit`.
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.
Expand Down Expand Up @@ -277,7 +277,7 @@ An `opaque` struct crosses as one JSON document and is one column, decoded back
A float in an opaque struct must be finite: `encoding/json` refuses NaN and the infinities, so `Encrypt` fails for the batch with `encrypt.ErrEncoding`. A sealed float field outside an opaque struct carries them.
A sealed field outside an opaque struct is one scalar, a type defined over one, or a pointer to either (see "Optional fields"); a struct, slice or map field seals only inside an opaque struct.
The scalars are `string`, `[]byte`, `bool`, every integer type (each sealed at its own width, `int` and `uint` as 64-bit), `encrypt.Int128` and `encrypt.Uint128`, `float32`, `float64`, `encrypt.Decimal`, `encrypt.Date` and `time.Time` (a timestamp). The wire type `stashgen` writes for each is the field's own: `int8` is `gensupport.Int8`, `time.Time` is `gensupport.Timestamp`, and so on.
An order index (`ore`, `ope`) on a decimal, date or timestamp is refused: those order terms are not supported yet (#1236).
A decimal orders by value, so `1.5` ties `1.50`; a `time.Time` orders by the microsecond, so instants within one microsecond tie.
The zero `encrypt.Date` is not a date: a date field must be set, and `Encrypt` refuses a zero one with `encrypt.ErrEncoding`, naming the field. An optional date is a `*encrypt.Date` field (see "Optional fields").
A nil `[]byte` in a sealed field, or a nil slice of a type defined over `[]byte`, comes back from `Decrypt` as an empty, non-nil slice; inside an opaque struct, nil comes back as nil.
A protobuf message with a `oneof` cannot be generated from a policy: protoc-gen-go puts its members in wrapper types, not in the message struct, and `Generate` refuses it.
Expand Down
4 changes: 2 additions & 2 deletions languages/golang/cmd/stashgen/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,15 @@ func TestRunAsksTheEmbeddedEngine(t *testing.T) {
t.Fatalf("the generated file does not name the EQL type:\n%s", written)
}
// A type the engine cannot produce yet: refused by name, nothing written.
unproducible := strings.Replace(userSource, "encrypt_into=TextEq", "encrypt_into=DateOrdOre", 1)
unproducible := strings.Replace(userSource, "encrypt_into=TextEq", "encrypt_into=Json", 1)
dir = writeModule(t, map[string]string{"model.go": unproducible})
stderr.Reset()
if code := run([]string{"-type", "User"}, dir, &stdout, &stderr, stashgen.GuestEngine); code != 1 {
t.Fatalf("exit %d, want 1\n%s", code, stderr.String())
}
// Refused by the generator with the engine's reason, before the engine
// is asked: se_targets lists the type as not producible.
if !strings.Contains(stderr.String(), "cannot produce the EQL type DateOrdOre yet") {
if !strings.Contains(stderr.String(), "cannot produce the EQL type Json yet") {
t.Fatalf("stderr = %q", stderr.String())
}
if _, err := os.Stat(filepath.Join(dir, "user_stash.go")); !os.IsNotExist(err) {
Expand Down
15 changes: 8 additions & 7 deletions languages/golang/encrypt/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@
// or [Int128] and [Uint128]), a float32 or float64, a [Decimal], a [Date] or a
// time.Time (a timestamp), or a type defined over one of the built-in types.
// Each integer seals at its own width. Equality applies to every one but a
// bool. ORE and OPE apply to integers of 16 bits or more, floats, text and
// bytes. A bool or an 8-bit integer takes no order index, since over so few
// values its order terms would show which rows hold which value. Decimals,
// dates and timestamps cannot take an order index yet.
// bool. ORE and OPE apply to integers of 16 bits or more, floats, text,
// bytes, decimals, dates and timestamps. A bool or an 8-bit integer takes no
// order index, since over so few values its order terms would show which
// rows hold which value.
//
// Passthrough fields never cross the binding: the engine does nothing to a
// passthrough value that a program could observe, and the FFI codec cannot
Expand Down Expand Up @@ -164,9 +164,10 @@
// type's query value), an ordering an [OreTerm] or [OpeTerm], and Between a
// [Range] of two. Their answers are exact. The orderings are answered on
// integers, on floats, which order as PostgreSQL's float8 does (-0.0 ties
// 0.0, NaN sorts last), and on text, which orders by its folded form (case
// and accents folded, under Unicode 16), so Café and cafe tie and alice
// orders before Zoe. Matches returns a [MatchQuery],
// 0.0, NaN sorts last), on text, which orders by its folded form (case and
// accents folded, under Unicode 16), so Café and cafe tie and alice orders
// before Zoe, and on a [Decimal], which orders by value (1.5 ties 1.50), a
// [Date], and a time.Time, which orders by the microsecond. Matches returns a [MatchQuery],
// whose answer can hold rows that do not match: size the fetch with
// [MatchQuery.TargetLimit], then decrypt the rows and keep those
// [MatchQuery.Verify] reports true, one call into the engine for the page.
Expand Down
Loading
Loading