Repository navigation
analytics: on the native-SQL strategy a cube that declares no join, grouped by a base column and a relationship path whose target has a column of the same name, answers 500 ambiguous column on SQLite and PostgreSQL #21249
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:services·area:reports·pm:queue. Base columns are qualified by what the query actually joinedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T21:57Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. It is a loud failure (500) on two dialects, where the ObjectQL face answers 200. Nothing is served wrongly. It is the same family and grade as #21232.
Routing.
service-analyticsisdomain:services, on the analytics axis.Direction (the card's own, accepted):
qualifyAndRegisterJoinqualifies a base column whenever the query joins anything, read from the hop resolver's result, not from whethercube.joinsis declared. ⛔ No new resolver.- The other
cube.joinsreaders the dev listed in the same file are not this card. They stay with PR fix(service-analytics)!: refuse a JSON-stored dimension or count_distinct over a relationship path the cube declares no join for, located through the one hop resolver #21247's acceptance notes, unless the claim measures one of them failing at a door. A measured one becomes its own card.
Pins: the card's pair answers 200, grouped correctly, on SQLite and PostgreSQL. The declared-join cube is the control, and the ObjectQL face is unchanged.
Serial. PR #21247 (#21232) is in the same package, with disjoint files. The dispatch's same-package check holds the claim until it lands, as the filer asks.
Generated by Claude Code
- addedarea:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-01T22:31Z
Session:session_01DiCSbmJrkzNhuEAier4VoJ
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21249-native-sql-base-column-qualify
Worktree:objectstack-issue-21249
Domain:domain:services
Seat:domain:services#2(seat post #21118)
File surface:packages/services/service-analytics/src/strategies/native-sql-strategy.ts:qualifyAndRegisterJoin'scanJoindecision, reading what the query actually joined from the hop resolver's result.hop-object.tsis consumed, not changed. Plus its tests.- A changeset.
⛔ The file's othercube.joinsreaders (:221canHandle, the:401/:493/:581fallbacks) are not this card, as triage directs, unless one is measured failing at a door; a measured one is reported, not fixed here. Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(default tier).
Clause-②: no
Thread-read: 5941483715
Serial constraints cleared, read in this act againstorigin/mainat4727fcb22: service-analytics: PR fix(service-analytics)!: refuse a JSON-stored dimension or count_distinct over a relationship path the cube declares no join for, located through the one hop resolver #21247 (analytics: a JSON-stored dimension or count_distinct over a relationship path whose lookup has no declared cube join answers 500 on PostgreSQL; the structured-JSON door resolves hops through cube.joins only #21232) merged as4727fcb2, the same-package hold triage named. None of the open PRs touchesservice-analytics/src.area:reports: the only other in-flight card is spec(dataset): a dataset dimension/measurefieldadmits a SQL expression at author time; narrow it to a column reference, as #20943 did for a cube member'ssql#21220 (domain:spec,packages/speconly), which is disjoint.
Selection:priority:p2(triage5941483715). It is the lane's next free card. plugin-security: an org member reading a colleague's sys_user row is served the Admin-group fields (last_login_ip/at, failed_login_count, locked_until, ban_*, phone_number) — and through sys_activity, their sign-in history #21237 is waiting for triage's direction, and automation: a host's per-kernel scheduled-work refusal is reported with the deployment sentence —SCHEDULED_WORK_DISABLED_REASONtells a free-plan tenant to setOS_AUTOMATION_SCHEDULED_WORK_ENABLED=true(the #19834 seam has no reason slot) #21110 is waiting for the maintainer.
Direction quoted to the dev from triage
5941483715: "qualifyAndRegisterJoinqualifies a base column whenever the query joins anything, read from the hop resolver's result, not from whethercube.joinsis declared. ⛔ No new resolver." Pins: the card's pair answers200, grouped correctly, on SQLite and PostgreSQL; the declared-join cube is the control; the ObjectQL face is unchanged.
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim correction (serial reading) ·
domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-01T22:32Z · amends claim5941989414The claim says "None of the open PRs touches
service-analytics/src". That is wrong. The seat wrote that line before reading the scan the same act ran. The scan names PR #21240 (#21220,domain:spec, draft). Inservice-analyticsit touches exactly one file:src/__tests__/inline-dataset-field-admission-door.test.ts. This card's surface isstrategies/native-sql-strategy.tsand its own tests, so the two are file-disjoint and the claim's conclusion holds. Whichever lands second mergesmain.
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21249, "status": "done", "branch": "claude/issue-21249-native-sql-base-column-qualify", "pr": "https://github.com/objectstack-ai/objectstack/pull/21266", "session": "session_01DiCSbmJrkzNhuEAier4VoJ — subagent run, the parent seat's harness-stamped id", "head": "560ab3613 (merge of origin/main 434c6c7c at 1b176fbf; base of the work 4727fcb2)", "premise_still_valid": true, "summary": "Premise re-measured at 4727fcb2 through POST /api/v1/analytics/query and /sql on the real dispatcher route (AnalyticsServicePlugin over ObjectQL + SqlDriver, signed-in caller), SQLite and a private PostgreSQL 16.14, a configured cube over deal declaring NO join, lookup owner -> person (which also declares note/amount/closed_on/id): native face 500 on both drivers for the card pair (note + owner.email; PG 42702 note), the pair with where/order on note, sum(amount) by owner.email (42702 amount), a closed_on time-dimension window (42702 closed_on), where {id} (42702 id) and the ad-hoc inferred cube; ObjectQL face 200 throughout; declared-join cube 200. Compiled SQL before: SELECT note AS \"note\", \"owner\".\"email\" ... LEFT JOIN \"person\" \"owner\" ... GROUP BY note, \"owner\".\"email\". Implemented triage's direction: qualifyAndRegisterJoin reads joins.qualifyBaseColumns (carried by StatementJoins) instead of cube.joins; generateSql compiles once bare and, when that compile registered any join (the hop resolver's joins for THIS query), once more qualified (split into compileClauses + assembleStatement, both halves unchanged in place). Zone 2 item 2 falsified in part: the registered joins cannot be read at call time, because joins register lazily (the card pair resolves note before owner.email joins) and an absorbed $or takes joins back; ablation A2 (call-time joins.size > 0) leaves the card pair red. resolveFieldSql's fallback for an undeclared filter member (where {id}) now goes through the same qualification (measured 500 beside a join). No new resolver; hop-object.ts consumed unchanged. After (re-taken at the merged head): every row above 200 on both drivers, grouped by the deal's note, rows equal to the ObjectQL face; declared-join cube's pair statement byte-identical; a statement that joins nothing stays bare on a no-join cube. One stated change: a declared-join cube's query that joins nothing now compiles bare columns (was \"deal\".\"note\"), same answer (see open_questions). ORDER BY emits the output alias, not a table column; ordering by an UNSELECTED member is a separate 500 (out_of_scope_findings[0]).", "tests": "All at HEAD 560ab3613 unless named. (1) pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=2, OS_TEST_POSTGRES_URL live: \"Test Files 165 passed (165) / Tests 3827 passed (3827)\", 0 skipped; base 4727fcb2 cells skipped: 164 files, 3758 passed / 56 skipped. typecheck (tsc --noEmit) exit 0, --listFiles includes all 5 touched .ts files. (2) New native-sql-base-column-qualify.test.ts: 12 passed (6 per cell, SQLite + live PG 16.14). (3) Ablations from committed 2a575561 via scripts/ablation-replace.mjs WRAP + outer trap EXIT INT TERM on the absolute path, no dist leg (subject imported by relative path ../plugin.js), predictions written first: A1 canJoin back on cube.joins -> 12 failed / 0 passed (predicted 12); A2 call-time joins.size > 0 -> 8 failed / 4 passed (predicted 8: pins 1,2,4,5 per cell); A3 resolveFieldSql fallback back to return fieldName -> 2 failed / 10 passed (predicted 2). Each landed anchor 1 -> 0, blob d2c652842c9f -> 06e880595f77 / 79a30a8137c0 / 09066c0079e7; each restored blob == HEAD d2c652842c9f and git diff HEAD empty. (4) Measured-and-rejected alternative: qualify always -> 64 failed tests in 21 files (incl. native-sql-rls.test.ts single-object bare pin). (5) Fixture triage: exactly 3 expectations pinned a bare base column inside a joining statement (analytics-service.test.ts, infer-cube-relation-traversal.test.ts, infer-cube-where-spelling-parity.test.ts); replaced with the qualified column, comments rewritten. (6) Consumer radius against this build with OS_TEST_POSTGRES_URL set: rest 23 files 359/359, runtime 19 files 551/551, driver-memory 27 files 863/863, driver-sql 7 files 133 passed / 1 skipped. qa/dogfood 9 analytics files NOT MEASURED, reason: build closure (example apps, cli) not built here; they pin no statement text (git grep GROUP BY / LEFT JOIN / SELECT: 0 hits); left to the required Dogfood Regression Gate. (7) Lint, declared narrowing at 560ab3613: eslint --no-inline-config --format json over the 5 touched .ts files -> 5 files, 0 errors, 0 warnings; population from eslint.config.mjs files **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus global node_modules/dist/build/.next/.turbo ignores, count read from JSON, no type-aware linting (parserOptions {ecmaVersion:latest, sourceType:module}, no project). Repo-wide pnpm lint left to CI. (8) CI on the PR: in_progress at report time (13 completed success/skipped, 18 in_progress), not awaited.", "gates": { "head": "560ab3613", "derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths): 61 commands, change set 6 paths vs merge base 434c6c7ca", "reconciliation": "dispatch-gates --ran: \"61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN\" (exit 0)", "nonzero_final": [], "reruns": [ "pnpm check:dual-build-cjs-loads: first exit 3 PREREQUISITE NOT MET (packages without dist), after pnpm build --concurrency=2 (72 tasks, 71 cached) exit 0 — the recorded run" ], "ran": [ "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0", "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0", "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0", "node scripts/check-changeset-no-major.mjs --self-test :: exit 0", "node scripts/check-ci-filter-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0", "node scripts/check-comment-mask-adoption.mjs :: exit 0", "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0", "node scripts/check-comment-mask-corpus.mjs :: exit 0", "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0", "node scripts/check-empty-changeset.mjs --self-test :: exit 0", "node scripts/check-issue-citations.mjs :: exit 0", "node scripts/check-keyed-text-bounds.mjs :: exit 0", "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0", "node scripts/check-plugin-teardown-shape.mjs :: exit 0", "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0", "node scripts/check-registry-log-declared.mjs :: exit 0", "node scripts/check-registry-log-declared.mjs --self-test :: exit 0", "node scripts/check-rest-log-spy-declared.mjs :: exit 0", "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0", "node scripts/check-system-context-census.mjs :: exit 0", "node scripts/check-system-context-census.mjs --self-test :: exit 0", "node scripts/check-tenant-audit-census.mjs :: exit 0", "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0", "node scripts/check-undeclared-dep-imports.mjs :: exit 0", "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0", "node scripts/docs-audit/check-affected-docs.mjs :: exit 0", "node scripts/docs-audit/check-drift-comment.mjs :: exit 0", "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0", "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0", "pnpm check:changeset-gate-self-tests :: exit 0", "pnpm check:cross-package-test-inputs :: exit 0", "pnpm check:doc-authoring :: exit 0", "pnpm check:driver-memory-census :: exit 0", "pnpm check:dts-closure :: exit 0", "pnpm check:dual-build-cjs-loads :: exit 0", "pnpm check:engine-double-contract :: exit 0", "pnpm check:gitlink-declared :: exit 0", "pnpm check:issue-citations :: exit 0", "pnpm check:lean-entry-closure :: exit 0", "pnpm check:logger-receiver-detach :: exit 0", "pnpm check:nul-bytes :: exit 0", "pnpm check:objectql-double-limit :: exit 0", "pnpm check:objectui-changeset :: exit 0", "pnpm check:org-identifier :: exit 0", "pnpm check:page-declaration-shape :: exit 0", "pnpm check:pm-changeset-deadline-census :: exit 0", "pnpm check:published-files :: exit 0", "pnpm check:query-options-erasure :: exit 0", "pnpm check:refd-timer-probe :: exit 0", "pnpm check:slot-lookup :: exit 0", "pnpm check:sourcemap-no-sources-content :: exit 0", "pnpm check:test-source-alias :: exit 0", "pnpm check:tier-file-adoption :: exit 0", "pnpm check:type-check-coverage :: exit 0", "pnpm check:type-check-debt :: exit 0", "pnpm check:watch-hint-literal :: exit 0", "pnpm check:where-matcher :: exit 0" ] }, "line_budget": "477 changed lines (+451 / -26) over 6 files vs the 5000 human-merge threshold: under. No skills/** or governed surface touched.", "files_changed": [ ".changeset/21249-native-sql-base-column-qualify.md", "packages/services/service-analytics/src/strategies/native-sql-strategy.ts", "packages/services/service-analytics/src/__tests__/native-sql-base-column-qualify.test.ts", "packages/services/service-analytics/src/__tests__/analytics-service.test.ts", "packages/services/service-analytics/src/__tests__/infer-cube-relation-traversal.test.ts", "packages/services/service-analytics/src/__tests__/infer-cube-where-spelling-parity.test.ts" ], "deviations": [ "Zone 2 item 2 mechanism falsified in part: \"read from the joins the hop resolver actually registered\" cannot be read at call time (lazy registration, $or absorption); implemented as compile-once-bare, recompile-qualified-if-any-join. Measured by ablation A2.", "Zone 2 item 3 / triage control \"declared-join cube, unchanged SQL\": holds for the card pair (byte-identical, pinned); a declared-join cube's query that joins NOTHING now compiles bare columns (was qualified), same answer on both drivers. Consequence of triage's predicate; raised as open_questions[0].", "Scope additions inside the declared surface: resolveFieldSql's undeclared-member fallback in the same file (same 500 beside a join, measured at the door); 3 fixture expectations in this strategy's own package tests.", "origin/main merged (no rebase) at 1b176fbf: 434c6c7c (#21240, #21250) — file-disjoint from this diff; merged per AGENTS.md multi-agent section 10 before opening the PR.", "Container restart mid-run: every reading cited was recorded on disk before the restart or re-taken after it (route probe, suite, consumers, gates all at the merged head).", "Commit trailers use the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By." ], "mcp_calls": "0 — no MCP GitHub calls; reads via gh api (REST)", "api_writes": "3 relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches, executed by fleet-write as objectstack-fleet[bot]): pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#21266, draft, body read back byte-identical 14284 bytes); assign -> POST /repos/objectstack-ai/objectstack/issues/21266/assignees [os-bill] (read back: assignee os-bill, label size/m from another actor left alone); issue_comment -> POST /repos/objectstack-ai/objectstack/issues/21249/comments (this report). Plus git push (not REST).", "open_questions": [ { "question": "A declared-join cube's statement that joins nothing now compiles bare columns (SELECT note ... GROUP BY note) where it compiled \"deal\".\"note\"; the answer is unchanged. Keep it, or keep that statement byte-identical?", "options": [ "A — keep (this PR): one rule, \"qualify iff THIS statement joins\", read from the hop resolver; no cube.joins read remains in the decision.", "B — OR cube.joins back into the predicate so declared-join cubes always qualify: byte-identical /sql text for that case, but keeps the cube.joins reader triage ruled out." ], "recommendation": "A, because it is triage's ruled predicate, both spellings name one column of the one table read, and no test or consumer outside the package pins that text (consumer radius measured)." } ], "out_of_scope_findings": [ "class: a · reach: POST /api/v1/analytics/query, native-SQL face, measured after this PR (ObjectQL face 200 in every row): {dimensions:[owner.email], order:{note:asc}} -> 500 on SQLite and PG (42702 note ambiguous in ORDER BY); {dimensions:[note], order:{amount:asc}} with NO join -> 500 on PG (42803 must appear in GROUP BY), 200 on SQLite ordered by an arbitrary row value; order:{owner.email:asc} unselected -> 500 on both (42703 column \"owner.email\" does not exist) · evidence: the ORDER BY emitter in native-sql-strategy.ts assembleStatement writes the request key as a quoted identifier, an output alias only when that member is selected; qualifying would not mend it (PG 42803) — needs a decision on what an unselected order key means · dedupe words: analytics native sql order by unselected member 500; ORDER BY output alias not in select; must appear in GROUP BY analytics order; analytics order key not a selected dimension", "carrier: PR #21247 acceptance notes · noted, not filed — the other cube.joins readers in native-sql-strategy.ts (canHandle federated decline; the three readScopedObjects fallbacks) were not seen failing at a door in these measurements" ], "cleanup": "Scratch probe (packages/runtime/src/zz-probe-21249.test.ts) deleted, never committed; private PostgreSQL stopped and /tmp/os-issue-21249-pg removed; worktree node_modules and worktree removal follow the report post." }
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT · PR #21266 @
560ab361·domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-01T23:50ZBased on the dev report and the diff.
- Form: draft against
main. Line 1 isFixes #21249, line 2 isClause-②: no. I scanned the whole body: no closing keyword sits beside another card number. The footer is the session URL. - Scope: 6 files, 477 lines, all inside the claimed surface (
native-sql-strategy.ts, its tests, three fixture expectations in this package, the changeset).hop-object.tsis not modified. The undeclared-member fallback inresolveFieldSqlis in the same file and fails the same way (measured 500 beside a join), so it counts as part of the same defect and is accepted. - Mechanism deviation, accepted: joins register lazily, and an absorbed
$ortakes joins back, so "what the query actually joined" cannot be read at call time (ablation A2 measures it). The dev compiles the statement bare once, then compiles it again with qualification if any join was registered. This still follows triage's predicate, "qualify iff this statement joins", read from the hop resolver's result. The fallback that always qualifies breaks 64 tests and was measured and rejected. - Tests: service-analytics 3827/3827 with PostgreSQL live; new pins 12/12 (SQLite plus live PostgreSQL 16.14). Ablations A1 (12/12 red), A2 (8 red) and A3 (2 red) each matched their prediction, and every restore was proven by blob comparison. Consumers: rest, runtime, driver-memory and driver-sql all green. Gates 61/61 exit 0.
- open question (bare or qualified when a declared-join cube's statement joins nothing): the seat answers A, keep as is. Triage's direction (
5941483715) says verbatim "read from the hop resolver's result, not from whethercube.joinsis declared", and option B would bring thatcube.joinsreader back. The answer is unchanged; only the SQL text on/sqldiffers, and the changeset states it.
Changeset checked sentence by sentence against the diff (PR #21192 rule):
- "A cube that declares no join still joins a lookup's declared
reference… the database refused the statement as ambiguous …500 DATABASE_ERRORon SQLite and on PostgreSQL. The ObjectQL strategy answered200": matches the premise measurement at4727fcb2. - The three Before and after items (the card's pair with or without where/order;
sum/ time window /where id; the ad-hoc inferred cube): match the measurements after the merge. - "qualifies every base column in the select list, the grouping, the filters, the measures and the time windows": matches
compileClauses/assembleStatementand the pins. - "A statement that joins nothing keeps bare columns … on a cube that declares a join when the query uses none of it …
/sqlreadsnotewhere it read"deal"."note"": an honest statement of the open question's effect, consistent with answer A. - "Unchanged": matches the controls (the declared-join cube's pair statement is byte-identical, and a no-join statement stays bare).
patchplusClause-②: nois correct: fixing a 500 to the 200 the spec already promises is not a contract widening.
out_of_scope_findings:
- [0] ORDER BY on an unselected member returns 500: filed analytics: on the native-SQL strategy an order key that is not a selected member answers 500 (ambiguous, must appear in GROUP BY, or no such column); the ObjectQL face answers 200 for the same queries #21267 (class a, measured reach).
- [1] The other
cube.joinsreaders:Acceptance notes(carrier PR fix(service-analytics)!: refuse a JSON-stored dimension or count_distinct over a relationship path the cube declares no join for, located through the one hop resolver #21247; none was measured failing at a door).
Landing: through the queue relay once every check on
560ab361is green.
Generated by Claude Code
- Form: draft against
- added a commit that references this issue
on Oct 7, 2026
立卡门 ①:有具名落点与复现的产品缺陷。
finding类别 a。reach:公开入口实测一次。动手的读者:分诊定级;
service-analytics属domain:services,由本车道席位认领派发。查重:
mcp__github__search_issues查 "analytics native sql ambiguous column name cube declares no join relationship path bare column qualify canJoin",17 条命中(含 closed),都不是本题。最近的几条:#21232(同族:读取方只看cube.joins,但那是结构化 JSON 门)、#20933 / #20986(推断型 cube 上的关系路径)。来源
#21232 的 dev 报告
5941198699(PR #21247,out_of_scope_findings[0])。实测在b6e64185上做,那里的strategies/与origin/main逐字节相同;探针已删除。reach:POST /api/v1/analytics/query,原生 SQL 面。配置型 cube 建在deal上,它没有声明任何 join;维度是note(基表列)和owner.email(经 lookupowner,其目标对象也声明了note列)。500 DATABASE_ERROR(ambiguous column name: note);500(42702,column reference "note" is ambiguous);200。编译出的语句形如
SELECT note AS "note", "owner"."email" … FROM <deal> LEFT JOIN <person> "owner" … GROUP BY note, …:基表列没有限定表名,关系路径却照样 join 进来了。落点(源码读)
packages/services/service-analytics/src/strategies/native-sql-strategy.ts的qualifyAndRegisterJoin:canJoin = !!cube?.joins && Object.keys(cube.joins).length > 0。只有 cube 声明了 join 时,基表裸列才被限定;而关系路径不论有没有声明,都经 hop 解析器 join 进来。这正是 #21232 那一族的问题:一个读取方只凭cube.joins做判断,与hop-object.ts"不得各自解析 hop"的契约不符。方向(供分诊参考,不是裁决)
基表列是否需要限定,按"本次查询实际 join 了什么"来判断(即同一个 hop 解析器的结果),不看
cube.joins有没有声明。⛔ 不新写解析器。Pins: 上面的配对在两个驱动上都返回
200,分组正确;已声明 join 的 cube 作对照;ObjectQL 面不变。同文件的其他读取方(dev 的 Zone 2 普查,不在本卡)
native-sql-strategy.ts里还有几处枚举cube.joins但不拆路径的地方::221的canHandle(联邦对象拒绝只问声明的 join 目标,可达性未测,记作 PR #21247 的 acceptance note),以及:401/:493/:581(缺少readScopedObjects时的兜底)。承接
本卡与 #21232(PR #21247)同包,文件面不交叉(
native-sql-strategy.ts对比structured-json-dimension-door.ts)。按同包串行的规矩,在 PR #21247 落地后再认领。Generated by Claude Code ·
domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ