Repository navigation
Honor nested relationship filters and pagination - #3279
Conversation
Release cherry-pick
|
6641acb to
96fb3b8
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces support for nested relationship query constraints (conditions, sort, offset, and limit) across search and single-record GET operations. It refactors condition preparation, implements nested permission checks, and adds comprehensive unit and integration tests. The review feedback suggests hoisting static selection and sorting configuration logic out of the per-record relationshipSelection closure in Table.ts to avoid unnecessary allocations on the hot path.
kriszyp
left a comment
There was a problem hiding this comment.
I think this generally looks good, but I don't see a justification for it being a patch (and 600 lines of production code probably deserve a solid justification).
🤖 Reviewed with Claude
|
Thanks for reviewing this. I agree the patch-release case needs a customer-need justification; the fact that this corrects accepted-but-ignored syntax does not by itself justify the compatibility changes or size of the backport. I have asked Kyle whether there is a v5.3 customer/release commitment or whether we should target the next minor. I have left the milestone unchanged pending that decision. Follow-up repairs are pushed: core 8b2f0aa makes malformed nested operators client errors and marks all three helper declarations @internal; docs 71d0f8021c73077d4375c70c22df7f20aa2e35ff explicitly calls out the non-table 400 change. The core regression cases failed before the fix and pass afterward; the full focused suite passes on RocksDB (76) and LMDB (63, 13 existing engine-specific skips). Build, focused lint/format, and docs 14 tests pass. New-head CI is pending. These narrow follow-up deltas (core +14/-3 in two previously reviewed files; docs +2/-2 in two previously reviewed pages) were self-reviewed and were not sent through another outside-model round. Two existing PR-body anchors drifted after the code edit; Kyle's monitoring constraint requires author authorization before changing that body, which I have requested. The fixture-coupling tradeoff remains open in the inline replies. Comment generated by kAIle (GPT-6) |
|
Thanks, Kris. Kyle agrees with proposing the next minor release for this change rather than continuing to justify a v5.3 patch. Would you be comfortable targeting the next minor for this implementation and Document nested relationship filters and pagination? The existing v5.3 milestones are unchanged pending your release-target decision. The description now records this proposal, refreshes the two shifted code links, and discloses the narrow follow-up repairs and their verification. The two fixture-coupling discussions remain open for your decision. Comment generated by kAIle (GPT-6) |
kriszyp
left a comment
There was a problem hiding this comment.
This is pretty cool!
I have asked Kyle
Who is driving this?!? Hopefully the real Kyle!
🤖 Reviewed with Claude
Resolve the conflict with nested relationship constraints (#3279) in the .to resolver: keep the shared searchRelated scan and main's lazy parameter. #3279 routes a relationship select with nested conditions, sort, offset or limit through Table._prepareRelationshipSelection, which read each related record with a blocking getEntry and so undid this branch's async select for those queries (relationshipAsyncReads' sorted cases read synchronously after the merge). Under async iteration that path now reads the related keys (resolveKeys, or a filter's matched ids) and loads each candidate with async: true in the candidate source, ahead of the yield point, so #3279's cancellation-at-first-yield guarantee holds. The FK resolveKeys marks the request uncacheable, so every key-based read path does, and the FK resolver exposes filterMissing for the selection to skip missing records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…with relationship authorization Preserve wildcard select projection, braced sub-select fallback, and nested relationship conditions, sort, limit, and offset from #3221 and #3279 alongside relationship read authorization and RELATIONSHIPS_LISTED projection from #3316. Dispatch-Task: pr-maint-0743c90ad97aeaf93e5aa68fe961f8dd Co-Authored-By: Gemini <noreply@google.com>
…with relationship authorization Preserve wildcard select projection, braced sub-select fallback, and nested relationship conditions, sort, limit, and offset from #3221 and #3279 alongside relationship read authorization and RELATIONSHIPS_LISTED projection from #3316. Dispatch-Task: pr-maint-0743c90ad97aeaf93e5aa68fe961f8dd Co-Authored-By: Gemini <noreply@google.com>
Honor nested relationship filters, sorting and pagination consistently across collection and single-record GET requests.
⊙ Problem
REST nested relationship selections silently ignore filters and limit accepts nested constraints but returns every related record with HTTP 200. Collection and single-record GET need the same per-parent execution and authorization behavior.
💡 Solution
Apply nested predicates, ordering, offset and limit to each parent's existing relationship candidates.
select(id,children[state=open&limit(1)&select(id,state)])returns at most one open child per parent and retains parents whose child selection is empty. Eligible unsorted pagination stops before reading the unused child tail.⚖️ Alternatives
Framing-Verdict: better-alternative-exists
Gemini’s initial planning review returned
chosen-approach-soundfor the runtime approach. The follow-up reliability planning review recommended moving parity out of the runtime matrix; that smaller execution boundary is adopted. Live companion-head comparison is retained because a fixed SHA would allow later peer-only fixture drift to pass against an obsolete snapshot.Nested predicates qualify the immediately selected children. To narrow grandchildren, place constraints on that descendant's own subselect. Existing outer-join candidate restrictions remain inherited. This removes the accidental behavior where the first evaluated OR term determined which grandchildren were projected.
🔧 Changes
Shared execution and bounded work
How does each parent receive its own constrained child page?
From relationship candidates to a page
Both GET forms use one prepared selection; inherited candidates remain owned by the original query.
Table.tscondition preparation shares validation and coercion with top-level search, checks child filter/sort permissions, preserves constraint-only selections during authorization, and adds per-call lazy resolver mode while retaining ordinary accessor and comparison contracts. Sorting or unmatched predicates can still scan all candidates. Unsorted FK pagination preserves missing slots; sorting drops them.Cancellation and projection compatibility
How do nested selections preserve lifecycle and projection contracts?
Table.tsselection execution owns nested iterators until successful closure, observes asynchronous cleanup failures, and fences both pending projection resumptions and raw upstream reads. Returning the parent or abandoning its transaction at a candidate yield stops additional child reads. Offset guards recheck refreshed rows without computing output fields for skipped rows; rejected refreshed rows and source deletions do not consume the page.Resource.tsvalidates unsupported nested options recursively, shares table preparation with single-record GET, resolves asynchronous object/positional arrays including constrained grandchildren, and confines the ambient-context fallback to the new relationship path. Projection-only JSON bracket syntax remains supported. Unsupported non-relationship query constraints and non-record/full-text predicates fail explicitly.ResourceInterface.tstypes the existing nested condition/order/page fields and makes the projection optional.resources/DESIGN.mdrecords candidate ownership, immediate-child predicate scope, cache behavior, missing-value semantics, and the source-iterator cancellation invariant.Executable documentation contract
Core integration CI executes the documented schema, dataset, six REST requests and two programmatic snippets against authenticated Harper. The docs companion checks its Markdown against the same fixtures through its required Lint Check. Both repos compare fixture bytes against the companion PR head until merge, then main, so editing only the docs or only the core contract fails CI. Parity now runs once in a dedicated dependency-free job, with the read-only token restricted to its comparison step. A shared 30-second deadline and bounded transient retries retain fail-closed drift detection without putting GitHub reads in the native runtime matrix. These additions change tests, CI and guidance; the qualified runtime implementation is unchanged.
Fix REST and operations API select projection defects overlaps these projection surfaces and proposes rejecting nested filters. Its unrelated projection fixes are not superseded by this implementation.
✅ Verification
Merge with current main
Merged main abf67cd59 in 115e3828c. The count cleanup retains projection closure and main’s generation-bound read-reference release, with release in a
finallyso a close error cannot strand that reference. Three new real-table cases exercise exact/estimated counts with bounded children and projection failure; they check returned data, iterator ownership, and read-reference restoration on both engines. No new API or query policy is introduced.The outside review identified a pending relationship-provider callback that could resume after cancellation. A four-line guard closes that gap; the real-table regression fails against the merge build before the guard and passes afterward.
The WebSocket fixture failure and dedicated-worker integration failures reproduce on clean main at the merged revision. Subscription replay also failed in a standalone merged-branch control, while its clean-main control passed; that race remains unresolved. The remaining broad integration failures were not all isolated. These results are not an all-green qualification claim; fresh CI is running at the pushed head. npm 11 was used for the broad main/integration comparisons because this machine’s npm 12 rejects the existing remote dependency/lifecycle path.
Related overlap: Apply related-table role permissions to @relationship reads in REST, GraphQL and NoSQL operations shares the nested authorization boundary; Search select() returns stored nested JSON instead of running the outer table’s resolvers on it shares projection paths; Fix REST query parsing of list values, (...) groups, range values and chained conditions shares condition preparation; Stop transaction abort() from spinning forever on read references taken after commit is already integrated by this merge. None is claimed superseded. The sibling scan is partial: all 82 open PRs and the most recent 50 of 116 merged PRs were scanned; one large PR was truncated.
Kris review follow-up
_readTxnForContextdeclaration now also carries@internal, completing Kris’s sibling-helper request in 76fb61278. This one-line annotation was self-reviewed without another outside review. Build, full lint, and full formatting checks pass; the emitted.d.tspreserves the annotation and emittedTable.jsis byte-for-byte unchanged. Runtime suites were not rerun for that historical comment-only follow-up; the current merge verification is recorded above.Core 8b2f0aa56 adds
@internalto the three helper declarations and returns HTTP 400 for invalid nested operators while preserving top-level validation behavior. Three new regressions fail before the repair and pass afterward; build, focused lint/format, and relationship suites pass (76 RocksDB; 63 LMDB with 13 existing engine-specific skips). The documentation repair calls out unsupported nested constraints on custom non-table resources. These narrow repairs were self-reviewed; the earlier full outside reviews were not rerun. Main-PR CI passed at that head. Both fixture-coupling discussions were subsequently replied to and closed with the intentional tradeoffs retained following Kris’s approval. This follow-up supersedes earlier statements below that no runtime source changed or no significant concerns remain.Fixture reliability and OR follow-up
.github/workflows/integration-tests.ymlruns network reliability tests and companion parity once, independently of the native runtime matrix. Only the focused comparison step receives the existing read-only GitHub token; no new repository permissions or native-process credentials are added..github/scripts/nested-relationship-fixture-parity.test.mjsinvokes the comparison with a 35-second test timeout.integrationTests/resources/nested-relationship-docs/github-fixture-fetch.mjsauthenticates only GitHub API metadata, leaves raw downloads unauthenticated, permits at most three transient read attempts under a shared 30-second deadline, honors short Retry-After values, and fails permanent errors, long waits, closed-unmerged companions and mismatched bytes. It is the fourth paired fixture..github/scripts/github-fixture-fetch.test.mjsadds nine real-HTTP/header tests covering retry recovery and exhaustion, permanent errors, credential boundaries, companion revisions and cancellation. No fetch stubs are used. Removing authentication/retry behavior makes four checks fail; missing CI token wiring fails explicitly before reading GitHub. A real authenticated metadata read through the helper passes.integrationTests/resources/nested-relationship-docs.test.tsadds 14 native authenticated regression tests: the six exact documented REST request/response pairs, both exact programmatic snippets, repeated-filter equivalence, independently bounded parents and explicit ascending/descending tie breakers for parent names and child prices. The 14 native tests pass on both RocksDB and LMDB, including grouped OR versus AND and multirow descending-order controls. Parity now runs separately once per integration workflow. An unindexed child sort and a three-candidate window also discriminate nested sort/index and exclusive-end pagination semantics. The existingtest:integration:allglob includes this file in CI; no new runtime code is changed.integrationTests/resources/nested-relationship-docs/config.yamlenables REST/schema/custom resources;integrationTests/resources/nested-relationship-docs/schema.graphqlmatches the documented schema;integrationTests/resources/nested-relationship-docs/examples.jsonpins the dataset, URLs and full responses;integrationTests/resources/nested-relationship-docs/resources.jsexecutes both programmatic examples through a native resource. The three paired docs fixture files match byte-for-byte. In CI, the peer check reads the docs companion PR head until merge and docs main afterward; API/raw read failures or mismatches fail the check.nestedRelationshipConstraints.test.jshas 73 real-module cases covering production REST dispatch, structured GET/search, ordinary-role authorization, boolean groups/coercion, missing FK slots, async/positional/deep projection, caller immutability, inherited joins, OR commutation, source refresh/deletion, pagination refill, and cancellation. The RocksDB counter proves two limited child reads versus seven unlimited; cancellation at 2,048 candidate reads permits zero further reads. Thirteen new cases fail on pre-repair production code at43ec51cd3and pass after the repair.43ec51cd3.relationship-and-status-contract.test.tsadds an authenticated native REST filter/sort/limit case across collection and single GET; all 23 tests pass per engine. Expanded with cached-computed integration: 34 passing on RocksDB; 33 passing / 1 failing on LMDB. The LMDB cached-computed 999-versus-60 failure also reproduces on43ec51cd3.96fb3b8applied its eight commits cleanly after an import-only history correction. Its build and dedicated suite pass: 73 on RocksDB; 60 passing / 13 skipped on LMDB. Production behavior is unchanged from the reviewed artifact.96fb3b8passes Node 22/24/26 and Windows units, including resource and LMDB gates. Node 24 reports 7,189 main, 3,745 core-resource, 661 index-resource and 3,361 LMDB-resource tests passing. Main and v5.3 integration also pass; that revision completed with 53 successful checks / 3 skipped. Earlier local runs remain recorded separately: 4,385 resource passes / 54 pending / 2 native full-text capability failures reproduced on base; blob-reclamation and cached-computed failures also have base controls. Other earlier broad local failures are not all root-caused and are not claimed repaired; they were not observed in canonical CI.Companion documentation: Document nested relationship filters and pagination. REST and Resource API reference pages and the v5.4 release note explain per-parent constraints, parent retention, missing FK behavior, inherited joins, immediate-child scope, cache semantics, scan costs and rejected inputs. The docs build and full formatting checks pass; Claude and Cursor Composer reviewed them, and all six REST examples and both programmatic examples are now committed native regressions; docs CI checks their Markdown against paired offline fixtures. The docs PR depends on this implementation and marks availability as targeted for v5.4.0 and not released yet.
Additional qualification on the actual v5.3 replay (
5ab1dd904f45):harper@5.3.2: 13 passing / 0 skipped, including cold restart, writes from both versions, rollback and forward round trip. CI separately exercises 5.3.1. Command:HARPER_PREVIOUS_MINOR_PATH=<isolated harper@5.3.2 root> npm run test:integration -- integrationTests/upgrade/minor-upgrade.test.ts integrationTests/upgrade/minor-downgrade.test.ts.These checks qualify the PR surfaces exercised; they do not cut a release or replace the release owner's final sign-off.
Fixes REST nested relationship selections silently ignore filters and limit.
Comment generated by kAIle (GPT-6)
Release target: v5.4 — selected by Kris for the nested relationship correction
Related PRs: #3221 overlaps, #3316 overlaps, #3415 overlaps, #3456 overlaps, #3337 overlaps, #3443 overlaps, 18 others independent
Complexity: complicated
Review-Coverage: authored=codex; ran=claude,cursor-composer; adjudicated=domain; declined=gemini,cursor-grok,cursor-kimi,cursor-muse; rounds=6; full=6 @ 7b2778f
Review-Attention: deep ~90m (critical: Table.ts; sensitive: integration-tests.yml; hot: Resource.ts; decisions: do-less-alternative, descendant-filter-semantics, missing-fk-ordering, cross-repo-parity, internal-static-surface, release-target-vs-evidence) @ 7b2778f