Repository navigation
fix(service-analytics): the ObjectQL strategy serves two dataset dimensions over one column - #28762
Conversation
…en two dimensions read it - objectql-strategy.ts: a module-level pushGroupKey() appends a groupBy key unless an identical one (same column, or same column at the same granularity) is already there. The direct path and the cross-object path both use it, replacing #28692's two FK-only checks. Two dataset dimensions over one column now share its group, and the existing row mapping keys the answer by every dimension name. A column raw beside its bucket stays two keys, so the engine's duplicate-column refusal still answers it. - analytics-service.ts (review S1): one whereLeafMembers(where) reader for namedQueryFields, relationshipMembers and previewWhereColumns, in place of three copies of the guarded collectFilterLeaves read. - Tests (review S2 and #28744): a filter on a measure key over a plain column, two dimensions over one column on the direct path, beside a month bucket and beside a cross-object dimension, and the raw-beside-bucket refusal, all on real SQLite. Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift Check6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 50ba4986c729f66193c72c4d80556ce8dde40ebb && git checkout 50ba4986c729f66193c72c4d80556ce8dde40ebb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 706ac52d570096a47b64283e970bc1a8e569bc91 cb122c705f9028be052cd78d5027176a122b886d && git checkout -B drift-repro 706ac52d570096a47b64283e970bc1a8e569bc91 && git merge --no-ff cb122c705f9028be052cd78d5027176a122b886d
node scripts/docs-audit/affected-docs.mjs --json 706ac52d570096a47b64283e970bc1a8e569bc91 |
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH Contract reviewServed-tier: Face: declaration ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH 席位核对 · ACCEPT · PR #28762 · 2026-11-01T18:33ZHead-sha: 维护者速读(席位终稿)
核对六项
|
domain:server#3 · os-project-manager · session_01FEkQ5kbUJTTWAxjV9nqr1P
Requested by Leehom · project thread
Fixes #28744
Clause-②: yes (widening)
Metadata-protocol: none
Skills-docs: none — no file under
skills/**orcontent/docssays the ObjectQL strategy refuses two dimensions over one column.Card thread:
session_01FEkQ5kbUJTTWAxjV9nqr1P.维护者速读
region,以及声明在region上的zone),同时按这两个维度查询时,ObjectQL 策略现在只对这一列分组一次,结果按两个维度名都回填,与原生 SQL 和草稿预览一致。直连路径、带月份桶、带跨对象维度(account.name)三种情况都覆盖。顺带并入上次评审的 S1(analytics-service.ts里三份相同的 where 叶子读取合成一个whereLeafMembers)和 S2(补一条「按度量键筛选」的测试)。INVALID_QUERY。packages/objectql未改),同一列的原值和它的月份桶仍是两个键,引擎照旧拒绝,有测试钉住。回滚即 revert。packages/services/service-analytics/src/strategies/objectql-strategy.ts新的pushGroupKey及execute、executeCrossObject两处调用;analytics-service.ts的whereLeafMembers。pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/preview-time-filter-fk-parity.test.ts,预期 23 passed。What changes
strategies/objectql-strategy.ts. A module-levelpushGroupKey(groupBy, item)appends a group key unless an identical one is already there: the same column, or the same column bucketed at the same granularity. The direct path (execute) and the cross-object path (executeCrossObject) both build theirgroupBythrough it. Two dataset dimensions over one column share its group, and the row mapping that already reads each projected dimension from its resolved column keys the answer by both names, so no re-key step was needed. In the cross-object path the helper replaces analytics: the ObjectQL strategy refuses a backed account.id dimension with INVALID_QUERY because the cross-object expand groups by the related id twice #28692's two FK-only checks (an FK column under two cross dimensions, a base dimension over the FK column), which are the same rule.closed_atwithOBJECT.closed_atbucketed by month) is two different keys under one output name, so both are handed on and the engine's duplicate-column refusal answersINVALID_QUERY/ 400 on both live faces, as before. Nothing inpackages/objectqlchanges.analytics-service.ts. OnewhereLeafMembers(where)reader (the guardedcollectFilterLeaves(normalizeAnalyticsFilterTree(...))read) fornamedQueryFields,relationshipMembersandpreviewWhereColumns, in place of three copies. Same behaviour: a non-object or refusedwherenames nothing. The fourth read in thewherefield gate passes the whole query and returns early on a refusal, so it is left as it is.runtimeFilteron the measure keytotal(overamount) is read asamount > 60on every face, the'any'arm ofdeclaredMemberEntrythat PR fix(service-analytics): the draft preview groups a bucketed time dimension and reads a qualified filter member as the live faces do; ObjectQL serves a backed RELATION.id dimension #28742 added for the preview.src/__tests__/preview-time-filter-fk-parity.test.ts(now 23, real SQLite)..changeset/28744-objectql-two-dimensions-one-column.md,@objectstack/service-analyticspatch.Why
Measured on real SQLite (the #28201 / #28555 / #28690 harness), dataset over a deal object with dimensions
regionandzone(both overregion), rows amer 100, amer 50, emea 30:region,zone(either order)INVALID_QUERY(groupBy.1 names 'region')closed_atmonthINVALID_QUERYaccount.nameINVALID_QUERYclosed_atraw +OBJECT.closed_atmonthINVALID_QUERYINVALID_QUERYINVALID_QUERY(unchanged)Tests
Red first, green, ablations
objectql-strategy.tsrestored to272fec05(the file is unchanged up toorigin/main7a48bf73), tests at HEAD:Tests 4 failed | 19 passed (23); the four analytics: the ObjectQL strategy refuses two dataset dimensions over one column with INVALID_QUERY (duplicate groupBy), while native and the preview serve both names #28744 cases fail on the ObjectQL leg with the engine's duplicate-column message. Restored withgit checkout HEAD --,git statusclean. The S2 case is green on base too (PR fix(service-analytics): the draft preview groups a bucketed time dimension and reads a qualified filter member as the live faces do; ObjectQL serves a backed RELATION.id dimension #28742 shipped that arm); its red leg is A-S2 below.cb122c70:23 passed (23).cb122c70(node scripts/ablation-replace.mjs, fix committed, each anchor 1 → 0, restore blob == HEAD,git diff HEADempty; the suite importssrc):pushGroupKeyback togroupBy.push:3 failed | 20 passed(the three direct-path analytics: the ObjectQL strategy refuses two dataset dimensions over one column with INVALID_QUERY (duplicate groupBy), while native and the preview serve both names #28744 cases).pushGroupKeyback togroupBy.push:2 failed | 21 passed(accountafteraccount.name, analytics: the ObjectQL strategy refuses two dataset dimensions over one column with INVALID_QUERY (duplicate groupBy), while native and the preview serve both names #28744 besideaccount.name).pushGroupKeyappending unconditionally:7 failed | 16 passed(every shared-column case of analytics: the ObjectQL strategy refuses a backed account.id dimension with INVALID_QUERY because the cross-object expand groups by the related id twice #28692 and analytics: the ObjectQL strategy refuses two dataset dimensions over one column with INVALID_QUERY (duplicate groupBy), while native and the preview serve both names #28744).1 failed | 22 passed(the raw-beside-bucket refusal pin).--deletethepreviewWhereColumns(q, cube)argument:8 failed | 15 passed(the S2 case among the analytics: the dataset draft preview answers [] for a runtimeFilter member spelled OBJECT.region that both live strategies resolve to the declared dimension #28691 cases).pnpm --filter @objectstack/service-analytics exec vitest run:Test Files 203 passed (203),Tests 4862 passed | 282 skipped(onc8c555f7, before the rebase onto7a48bf73, which touches no service-analytics file).pnpm --filter @objectstack/rest exec vitest run src/analyticsatcb122c70after a full build:Test Files 23 passed (23),Tests 298 passed | 22 skipped.pnpm --filter @objectstack/service-analytics typecheckatcb122c70exit 0;eslinton the three touched source and test files exit 0.Acceptance notes
analytics-service.ts, not the strategy file, but it is three call sites in one file and behaviour-neutral (full suite green).closed_atraw besideOBJECT.closed_atbucketed by month, both live faces refuseINVALID_QUERY/ 400 (two values under one name), while the draft preview serves both columns. Reported to the seat.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 62 families derived oncb122c70(4 paths vs merge base7a48bf73); each run with stdin from/dev/null, exit captured before any pipe, after a fullturbo run build --filter=!@objectstack/docs --concurrency=2(lock VERDICT command-exit 0).--ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN, every oneexit 0.node scripts/check-platform-checklist.mjs --changed-from origin/mainexit 0;node scripts/check-commit-card-trailers.mjs --range origin/main..HEADexit 0.!and no narrowing arm.The 62 derived families and their exits
node scripts/check-adr-0087-registration.mjs --base origin/main:: exit 0node scripts/check-adr-0087-registration.mjs --self-test:: exit 0node scripts/check-changeset-no-major.mjs --base origin/main:: exit 0node scripts/check-closing-keyword-parity.mjs:: exit 0node scripts/check-closing-keyword-parity.mjs --self-test:: exit 0node scripts/check-comment-mask-adoption.mjs:: exit 0node scripts/check-comment-mask-adoption.mjs --self-test:: exit 0node scripts/check-comment-mask-corpus.mjs:: exit 0node scripts/check-dts-emitted.mjs --self-test:: exit 0node scripts/check-empty-changeset.mjs --base origin/main:: exit 0node scripts/check-empty-changeset.mjs --self-test:: exit 0node scripts/check-issue-citations.mjs:: exit 0node scripts/check-plugin-teardown-shape.mjs:: exit 0node scripts/check-plugin-teardown-shape.mjs --self-test:: exit 0node scripts/check-tenant-audit-census.mjs:: exit 0node scripts/check-tenant-audit-census.mjs --self-test:: exit 0node scripts/ci/assert-turbo-remote-cache.mjs --self-test:: exit 0node scripts/ci/plan-test-core-shards.mjs --self-test:: exit 0node scripts/ci/prune-turbo-cache.mjs --self-test:: exit 0node scripts/docs-audit/check-affected-docs.mjs:: exit 0node scripts/docs-audit/check-drift-comment.mjs:: exit 0node scripts/pm/release-rehearsal-clone.mjs --self-test:: exit 0node scripts/release-pending-publish.mjs --self-test:: exit 0pnpm --filter @objectstack/spec run check:duration-unit-keys:: exit 0pnpm check:changeset-gate-self-tests:: exit 0pnpm check:ci-filter-parity:: exit 0pnpm check:closed-ceremony-text:: exit 0pnpm check:cross-package-test-inputs:: exit 0pnpm check:dispatcher-error-vocabulary:: exit 0pnpm check:doc-authoring:: exit 0pnpm check:driver-memory-census:: exit 0pnpm check:dts-closure:: exit 0pnpm check:dual-build-cjs-loads:: exit 0pnpm check:engine-double-contract:: exit 0pnpm check:error-status-conformance:: exit 0pnpm check:gitlink-declared:: exit 0pnpm check:issue-citations:: exit 0pnpm check:keyed-text-bounds:: exit 0pnpm check:lean-entry-closure:: exit 0pnpm check:logger-receiver-detach:: exit 0pnpm check:nul-bytes:: exit 0pnpm check:objectql-double-limit:: exit 0pnpm check:org-identifier:: exit 0pnpm check:page-declaration-shape:: exit 0pnpm check:platform-object-tenancy-census:: exit 0pnpm check:pm-changeset-deadline-census:: exit 0pnpm check:published-files:: exit 0pnpm check:query-options-erasure:: exit 0pnpm check:refd-timer-probe:: exit 0pnpm check:registry-log-declared:: exit 0pnpm check:rest-log-spy-declared:: exit 0pnpm check:slot-lookup:: exit 0pnpm check:sourcemap-no-sources-content:: exit 0pnpm check:swallow-census-controls:: exit 0pnpm check:system-context-census:: exit 0pnpm check:test-source-alias:: exit 0pnpm check:tier-file-adoption:: exit 0pnpm check:type-check-coverage:: exit 0pnpm check:type-check-debt:: exit 0pnpm check:undeclared-dep-imports:: exit 0pnpm check:watch-hint-literal:: exit 0pnpm check:where-matcher:: exit 0Human Minimum
packages/specholds no runtime logic. (Nopackages/specchange.)pnpm lintpasses (scoped run above; the repo-wide run is CI's).main, one pull request.packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5kbUJTTWAxjV9nqr1P