Skip to content

Commit 500aa58

Browse files
committed
test(eql): run compiled predicates over Stack Encrypt documents in PostgreSQL
Documents sealed by Stack Encrypt's JSON index, predicates compiled by `match` through a data plan over eql-bindings' target table, and `eql_v4.json_tree_match` and `@@` over the column, row by row against PostgreSQL's `jsonb_path_match` over the plaintexts, NULL included, and against the WHERE written from the operands each leaf's own operator derives. The rest of the SQL's contract is pinned over the same documents and derived operands: the domain's CHECK node kind by node kind, over a compiled tree and its parts; for every predicate, the WHERE written from the compiled tree's own leaves; three-valued logic under `and`, `or` and `not`, and an ordering leaf alone; a `contains` leaf as `@>`, the root scalar's `alt` included; an `elements` leaf as the element match, a leaf the answer does not depend on left unread; and `@@` with a typed predicate, a blocked jsonpath, an untyped operand and NULLs. EQL tests run over real encrypted data, and the SQLx suite has no way to produce a JSON tree document, so these live here. Refs #1441 Claude-Session: https://claude.ai/code/session_01MrxG724qYKcAWJdcABBFNY
1 parent 9dfaa99 commit 500aa58

3 files changed

Lines changed: 990 additions & 9 deletions

File tree

‎packages/eql/tests/encryption/tests/json_tree.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,11 @@ mod blockers;
519519
#[path = "json_tree/elements.rs"]
520520
mod elements;
521521

522+
/// The compiled predicate, through a data plan, against the column and
523+
/// against `jsonb_path_match` over the plaintexts.
524+
#[path = "json_tree/predicate.rs"]
525+
mod predicate;
526+
522527
/// Needs an EQL v4 installation. Extraction by selector opens to the leaf;
523528
/// the subtree walk by a client-derived token returns exactly the nodes
524529
/// under it and reassembles; the array functions count and list elements;

‎packages/eql/tests/encryption/tests/json_tree/elements.rs‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
//! The element-scoped predicate end to end: documents sealed by Stack
22
//! Encrypt's JSON index, the `element_match` operand derived through a data
3-
//! plan over eql-bindings' target table (the path the Go guest takes), and
4-
//! `eql_v4.json_tree_element_match` over the stored column.
3+
//! plan over eql-bindings' target table, and
4+
//! `eql_v4.json_tree_element_match` over the stored column. The resolver
5+
//! here ([`Eql`]) names the document's leaf, element-scoped and compiled
6+
//! predicate domains, so the engine answers `element_match` and `match`
7+
//! through it. The Go guest's resolver names the leaf domain alone (its
8+
//! `targets` tests pin that), so the Go SDK is not offered either operator
9+
//! until the Go side takes them.
510
//!
611
//! These tests pin:
712
//!
@@ -26,10 +31,11 @@ use stack_encrypt::dynamic::{
2631
};
2732
use stack_encrypt::{KeysetCipher, Label, Pending, StackCipher};
2833

29-
/// eql-bindings' table as the engine's resolver, naming each type's leaf
30-
/// and element predicate domains, as a host that answers `element_match`
31-
/// describes it.
32-
struct Eql;
34+
/// eql-bindings' table as the engine's resolver, naming each type's leaf,
35+
/// element predicate and compiled predicate domains: a host that answers
36+
/// `element_match` and `match`. Not the Go guest's resolver, which names
37+
/// the leaf domain alone.
38+
pub(super) struct Eql;
3339

3440
fn describe(target: &Target) -> TargetDescriptor {
3541
let mut descriptor = TargetDescriptor::new(
@@ -50,6 +56,9 @@ fn describe(target: &Target) -> TargetDescriptor {
5056
if let Some(domain) = target.elements_query_sql_domain() {
5157
descriptor = descriptor.with_elements_query_sql_domain(domain);
5258
}
59+
if let Some(domain) = target.predicate_query_sql_domain() {
60+
descriptor = descriptor.with_predicate_query_sql_domain(domain);
61+
}
5362
descriptor
5463
}
5564

@@ -116,13 +125,13 @@ fn contact_plan() -> record::Plan {
116125
record::plan_with(&dynamic::frame::serde::to_vec(&wire).unwrap(), &Eql).unwrap()
117126
}
118127

119-
fn frame_of(value: &Value) -> ValueFrame {
128+
pub(super) fn frame_of(value: &Value) -> ValueFrame {
120129
ValueFrame::from_bytes(&dynamic::frame::serde::to_vec(value).unwrap()).unwrap()
121130
}
122131

123132
/// The operand of `operator` on `profile` for `input`, as the SQL binds
124133
/// it: an EQL operand's JSON text, or a list of tokens.
125-
fn derive(
134+
pub(super) fn derive(
126135
runtime: &tokio::runtime::Runtime,
127136
keyset: &KeysetCipher<'_, common::Observed>,
128137
plan: &record::Plan,
@@ -145,7 +154,7 @@ fn derive(
145154
term.operand().clone()
146155
}
147156

148-
fn eql_text(operand: &Operand) -> String {
157+
pub(super) fn eql_text(operand: &Operand) -> String {
149158
match operand {
150159
Operand::One(bytes) => String::from_utf8(bytes.clone()).unwrap(),
151160
other => panic!("one EQL operand, got {other:?}"),

0 commit comments

Comments
 (0)