Skip to content

EQL v3 JSON: implement JSONPath selector-with-constraint querying (->, ->>) #623

Description

@coderdan

Context

#621 adds EQL v3 encrypted JSON (types.Json) with containment querying only — ops.contains(col, subObject) emitting the @> operator with a query_jsonb needle.

EQL v3 supports a second JSON query form that #621 does not implement: JSONPath selector with a constraint, e.g.

foo->'bar'  = $1
foo->'wee'  > $1

The eql_v3_json domain ships the operators for it — ->(text|int) and ->>(text|int) — and encryptQuery(value, { queryType: 'searchableJson' }) already auto-infers a selector term for string (JSONPath) needles. What's missing is the adapter surface: the v3 Drizzle operators expose no ->/->> selector, so a selector-with-constraint query can't be expressed, and there are no tests for it.

Proposed change

  • Add selector operators (-> / ->>) to the v3 Drizzle integration for types.Json columns.
  • Compose the extracted selector value with the existing comparison operators so col->'bar' = $1 / col->'wee' > $1 work end-to-end.
  • Add a live oracle + tests (mirroring json-contains.integration.test.ts).
  • Document the pattern in skills/stash-encryption / skills/stash-drizzle.

Notes

https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w

Activity

  1. coderdan commented on Jul 12, 2026

    @coderdan
    ContributorAuthor

    Additional design note surfaced in #621: the typed client shares one plaintext type (PlaintextForColumn<Col>) between encrypt and encryptQuery. For a json column that's now JsonDocument (object/array/null). But a selector query needs a top-level string needle ('$.user.email'), which JsonDocument excludes. So selector support needs encryptQuery's json plaintext to differ from encrypt's (document for containment | string for selector) — encrypt must still reject a bare scalar. Factor that into the typing when implementing.

  2. removed their assignment
    on Jul 15, 2026
  3. coderdan commented on Jul 15, 2026

    @coderdan
    ContributorAuthor

    Shipped in #651 (merged 2026-07-15). Everything in the proposed change landed:

    • Selector querying on types.Json columns via ops.selector(col, '$.path') returning eq/ne/gt/gte/lt/lte, emitted as eql_v3.jsonb_path_query_first(col, <selector>) compared against an encrypted operand — so col->'bar' = $1 and col->'wee' > $1 work end-to-end. Paths are validated (no wildcards/array steps, prototype-polluting segments rejected), scalar-leaf capability is guarded, and ne includes absent-path rows (OR ... IS NULL) by documented design.
    • Live oracle + tests: selector.test.ts (unit), json-selector.integration.test.ts (real Postgres + real crypto, including an absent-path row), plus type tests — and the CI gap that had left stack-drizzle/stack-supabase test:types unwired got fixed along the way.
    • Skills: the pattern is documented in stash-encryption and stash-drizzle, including the ciphertext-in-WHERE disclosure.

    Not closed by this issue, tracked separately:

    https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w

  4. self-assigned this
    on Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions