fix(mapper): inference hardening — LIMIT/FETCH, UPDATE targets, unmatched statements (CIP-3700) - #439
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThe EQL Mapper now resolves UPDATE assignments against target tables, enforces native types for row-count clauses, canonicalizes resolved identifiers, and reports missing inference rules immediately. Regression tests cover these behaviors. ChangesEQL Mapper inference
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 45-49: Rewrite the three changelog entries to describe observable
SQL behavior and Proxy responses rather than resolver scope, inference timing,
or internal invariants. Mention that same-named UPDATE/FROM columns now resolve
correctly, encrypted LIMIT/OFFSET/FETCH expressions are rejected with a type
error, and unsupported statements fail immediately with an error naming the
statement.
In `@packages/eql-mapper/src/inference/infer_type_impls/query_statement.rs`:
- Around line 79-117: Move the row-count type-inference logic currently added
around the query statement handling into the appropriate module under
transformation_rules, or make the query_statement implementation delegate to
that module. Preserve the Native unification for LIMIT, OFFSET, LIMIT BY, and
FETCH quantities while keeping infer_type_impls/query_statement.rs as a thin
adapter.
- Around line 99-101: Remove the loop that calls unify_node_with_type for each
expr in limit_by within the LimitClause::LimitOffset handling. Constrain only
the global LIMIT count, leaving limit_by expressions to the normal
type-inference path so encrypted grouping keys remain valid.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a3798ef5-55b3-4e90-9884-ac36adcb4c14
📒 Files selected for processing (4)
CHANGELOG.mdpackages/eql-mapper/src/inference/infer_type_impls/query_statement.rspackages/eql-mapper/src/inference/infer_type_impls/statement.rspackages/eql-mapper/src/lib.rs
freshtonic
left a comment
There was a problem hiding this comment.
Comment-only review.\n\nThe LIMIT/FETCH native constraints, target-table resolution for UPDATE assignments, and fail-closed statement fallback look coherent.\n\nFinding: every PostgreSQL matrix job is currently failing in . The client attempts to bind a Rust where the prepared statement reports , producing . This may be an existing/base-branch issue rather than caused by the mapper hardening here, but the current head is red across all supported PostgreSQL versions and needs to be updated or the failure accounted for before merge.
freshtonic
left a comment
There was a problem hiding this comment.
Correction to my preceding review (shell formatting stripped identifiers): all matrix jobs fail in update_with_param_reused_for_storage_and_query because a Rust String is bound where the prepared statement reports eql_v3_text_search, producing WrongType.
…hed statements (CIP-3700)
Three loose ends from the InferType survey (companion to CIP-3699):
- Pin LIMIT/OFFSET/FETCH row-count expressions to Native where the Query
is inferred, instead of leaving them as unconstrained type variables
for the late unresolved-value fallback to mop up. An encrypted column
used as a row count (LIMIT enc_col) is now rejected by the mapper.
- Resolve UPDATE assignment targets against the table being updated
(the FIXME), not through the lexical scope, where a same-named column
in a FROM-joined relation made the target spuriously ambiguous.
- Replace the fail-open `_ => {}` arm in InferType<Statement> with a
fail-closed rejection stating the invariant: every variant admitted by
`requires_type_check` must have an explicit inference arm. Widening
the gate without one is now a loud error, not a silently-unconstrained
statement.
Surveyed and left unchanged: Delete's using/selection and aggregate
filter/null_treatment are already covered by ordinary Expr traversal.
…esolution (CIP-3700) Proxy loads its schema with quoted column idents behind the editable resolver, while SQL usually spells the same columns unquoted. SchemaDelta::resolve_table_column echoed the caller's spelling instead of the schema's, so the UPDATE assignment-target type carried a different ident than the scope-derived type for the same column. For a param bound in both roles (UPDATE t SET c = $1 WHERE c = $1) the two identities met in unification and failed with "cannot unify EQL terms", sending the statement to the database unmapped. Align SchemaDelta with Schema::resolve_table_column and resolve_table_columns, which already return the canonical idents, and pin the behaviour with a mapper test that uses a quoted-ident schema and the editable resolver like Proxy does.
…w counts The BY expressions in ClickHouse's LIMIT n BY expr are per-group keys, not row counts, so pinning them to Native was the wrong constraint — and leaving them to ordinary inference would let an encrypted key through without its equality term. PostgreSQL rejects the syntax anyway, so reject it up front like ORDER BY ALL. Addresses review feedback on #439.
f52244d to
561e9c6
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Implements CIP-3700: hardening for the likely-benign loose ends found in the
InferTypesurvey (companion to CIP-3699).1.
Query'slimit_clause/fetchpinned toNativeinfer_type_impls/query_statement.rsonly handledbodyandorder_by; expressions in LIMIT/OFFSET/FETCH were left as unconstrained type variables. In practice a bareLIMIT $1placeholder was quietly mopped up by the late unresolved-value fallback (Unifier::resolve_unresolved_value_nodes), so it did not fail — but the constraint now lives where the clause is inferred instead of relying on that catch-all, and it is stronger: an encrypted column used as a row count (LIMIT enc_col) is now rejected by the mapper instead of being forwarded. AllLimitClauseshapes are covered (LIMIT/OFFSET,LIMIT BY, MySQLLIMIT o, l) plusFetch::quantity.Query::locks(FOR UPDATE/SHARE) carries no expressions in the AST, so there is nothing to constrain there — noted in a comment.2.
Updateassignment targets resolve againsttable(the FIXME atstatement.rs:23)Assignment targets used to resolve through the lexical scope, which also contains the
FROMrelations, soUPDATE t1 SET x = $1 FROM t2with a same-named column ont2made the target spuriously ambiguous (AmbiguousMatch) — the shadowing trap the FIXME warned about. Targets now resolve viatable_resolver.resolve_table_columnagainst the table being updated, mirroring how INSERT resolves its columns. A non-table UPDATE target (or a joined target table, which PostgreSQL cannot produce) is rejected withUnsupportedSqlFeaturerather than guessed at.5. The fail-open
_ => {}inInferType<Statement>now fails closedAll seven variants admitted by
requires_type_checkare matched explicitly, so the wildcard was dead code — but a future widening ofrequires_type_checkwould have traversed the statement without constraining its top-level type. The arm now returns aTypeError::InternalErrornaming the statement and stating the invariant (every variantrequires_type_checkadmits must have an explicit arm). A truly exhaustive match oversqltk's ~100Statementvariants would break on every parser bump, so the invariant is stated and enforced at the wildcard instead; rejection (rather thanunreachable!) keeps an invariant break from panicking a proxy worker.Surveyed and fine — no change
Delete'susing/selection: covered by ordinaryExprtraversal.filter/null_treatment: covered by ordinaryExprtraversal.Tests
Five new mapper tests in
packages/eql-mapper/src/lib.rs:limit_and_offset_placeholders_infer_native,fetch_first_placeholder_infers_native—$nin LIMIT/OFFSET/FETCH resolves toNative(regression pins; these passed pre-change via the late fallback).encrypted_column_in_limit_is_rejected— fails without the fix.update_assignment_resolves_against_target_table_not_from_relation— same-named column on theFROMtable; the assignment gets the target table's (encrypted) type. Fails without the fix.statement_without_inference_rule_fails_closed—TRUNCATEthroughtype_checkdirectly, asserting the invariant-stating error. Fails without the fix.Verification
mise run check— clean.cargo test -p eql-mapper— 139 passed, 0 failed.cargo test -p cipherstash-proxy— all 121 unit tests pass single-threaded; theconfig::tandemenv-var races and the three doc-test failures reproduce identically on a cleanorigin/maincheckout (pre-existing, unrelated).Coordination
A concurrent branch for CIP-3699 touches other parts of
query_statement.rs(ORDER BY handling) and theinfer_type_impls; this diff is kept minimal and does not incorporate it, so a small textual overlap inquery_statement.rsis expected at rebase time.Summary by CodeRabbit
Bug Fixes
UPDATEassignments so target-table columns resolve correctly.LIMIT,OFFSET, andFETCHrow counts.LIMIT ... BYclauses are now rejected.Tests