Skip to content

driver-sql: update(), delete() and bulkDelete() key on literal "id", and aggregate()/distinct() on local field names, ignoring external.columnMap — a write can hit the wrong row on a federated table that also has its own "id" column #31340

Closed

Description

@objectstack-fleet

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Filing gate: ③ out-of-scope of the #31328 card thread (Oracle federation dialect), which measured both defects while building the Oracle write and aggregate paths.
Ruling-ref: ADR-0015 (external datasource federation; external.columnMap)
Dedupe (open AND closed): "columnMap aggregate", "columnMap distinct", "columnMap groupBy", "columnMap update delete id" → none.
Reader: the epic PM dispatches it.
type: Bug

维护者速读

  • 问题: 外部对象用 columnMap 把本地字段映射到远端列名时,driver-sql 有两处没走映射:
    1. update()、delete()、bulkDelete() 的条件写死了 WHERE "id" = ?。如果远端表的主键映射到别的列,同时表里又恰好有一个叫 id 的普通列,PostgreSQL、MySQL、SQLite 上会按错误的列匹配:更新返回空,删除会删掉没人指定的行。这个问题不报错。
    2. aggregate() 和 distinct() 按本地字段名分组、取值,会报「列不存在」。这个问题报错,不会悄悄出错。
  • 影响面: 只影响开启了写入的外部对象(写入需要两处都打开),而且主键做了映射。条件窄,但第 1 条会删错数据,所以单独快速落地,不等 Oracle 那张卡的契约复核。
  • 需要您做的: 无。

Task

  • Resolve the key column through remoteColumn(object, 'id', 'id') in update() (the write and its read-back), delete() (sharded and unsharded) and bulkDelete(), at packages/drivers/driver-sql/src/sql-driver.ts, as create()'s read-back already does.
  • Map groupBy, the aggregand, date buckets and distinct through remoteColumn (sql-driver-aggregation.ts, sql-driver-distinct.ts).
  • Pins on sqlite: a mapped key over a table that also has its own id column, where update and delete touch the named row only; aggregate and distinct over renamed columns. Red before the fix, green after.
  • Out of scope: upsert on a columnMap-ed object (conflict target). Measure it and file a card if it misses the map too.
  • One patch changeset per defect, naming every SQL dialect.

Clause-②: no · Metadata-protocol: none. The fix commits already exist on the #31328 branch (de3cc9da6d aggregate/distinct and a write-key commit). This card lands them alone off main, and #31328 then merges main.

Activity

  1. added
    bugSomething isn't working
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 8, 2026
  2. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Claim: PM loop round 18 2026-11-08T02:55Z
    Session: session_017ihL2c5z3q4h8inrn5kwqV (epic PM); implementing card thread session_017ihL2euiyuLXizr6FW32R3
    Account: marchtian
    Branch: claude/project-thread-on4xyn (the card thread's platform-assigned branch — measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-31340
    Domain: domain:server
    Seat: epic:#27261#1 (epic PM, which filed the card as an out-of-scope finding of #31328; no domain:* seat)
    File surface: packages/drivers/driver-sql/src/sql-driver.ts, sql-driver-aggregation.ts, sql-driver-distinct.ts, new sqlite pin tests under packages/drivers/driver-sql/src/, and .changeset/**. No packages/spec/**, no Oracle code. Stop on any other breach.
    Container & model: S, mode:thread, model: default tier (Fable unavailable; 田騏's order of 2026-10-29 19:39Z), effort: high
    Clause-②: no — a driver fix to honour an existing contract (external.columnMap); no published surface changes
    Metadata-protocol: none
    Responsibility: the card body's five Task items (upsert is measure-and-report only)
    Thread-read: none
    Premise: by reading at origin/main 30e0bd1, update(), delete() and bulkDelete() in sql-driver.ts build .where('id', id) / .whereIn('id', ids) without remoteColumn; the #31328 thread measured the aggregate/distinct miss (sqlite refusal, Oracle ORA-00904) and the write-key miss (5 red pins before its fix).
    Serial constraints: #31328 carries the same two fixes as commits on its branch and merges main after this lands; #31327 is told not to touch these paths.

  3. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2euiyuLXizr6FW32R3
    os-dev-report

    {
     "issue": 31340,
     "status": "done",
     "branch": "claude/project-thread-on4xyn",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/31349",
     "session": "session_017ihL2euiyuLXizr6FW32R3",
     "premise_still_valid": true,
     "summary": "Lands the two columnMap fixes from the Oracle card's branch alone off main, with nothing Oracle-specific. Commit f9a07140: aggregate() (GROUP BY, structured group key, date bucket, aggregand) and distinct() spell the remote column through remoteColumn; output keys, presentation and per-field registries stay the field; buildDateBucketExpr gains an optional column parameter (ported from the Oracle wip commit without the Oracle arm). Commit 8c643017 (+0ceca395 changeset wording): update() (statement and read-back), delete() and bulkDelete() resolve the key via remoteColumn(object,'id','id'). Oracle prose (ORA-00904) stripped from both changesets and both test docblocks; code byte-identical to the PM-supplied git-am patches otherwise. Two patch changesets, both name SQLite/PostgreSQL/MySQL. Merged origin/main once (dc7f5c4b4, disjoint paths) instead of rebasing, since the branch is not claude/issue-* and a rebase would need a force-push.",
     "tests": "Ablation on 0ceca395 via git restore --source=origin/main of the fix sources (tree only, restored from HEAD, status clean after): writes.test 5/5 fail with sequence-semantics.ts + bulk-operations.ts reverted; aggregate.test 8/8 fail with aggregation.ts + distinct.ts + date-bucket.ts reverted; 13/13 pass with the fix, re-run green on merge head 05ecc979. pnpm --filter @objectstack/driver-sql test: 240 files passed / 12 skipped, 3880 tests passed / 218 skipped (live PG/MySQL cells not provisioned locally; they run in Temporal Conformance on main). typecheck exit 0; eslint over the 8 changed ts files exit 0.",
     "gates": "Local, 0ceca395 (base 30e0bd16): dispatch-gates --commands derived 68; all 68 run after a packages/** turbo build, every one exit 0; dispatch-gates --ran: 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN. Merge of main touched no driver-sql path. PR check-runs: not waited at report time.",
     "dogfood_battery": "n/a, no ! in the PR title and no narrowing arm",
     "mcp_calls": "1, mcp__github__issue_read (#31340 body); no write tool",
     "api_writes": "2 relay writes as objectstack-fleet[bot]: pr_create (draft #31349, assignee marchtian) and this os-dev-report comment; plus git push of the branch",
     "open_questions": [],
     "out_of_scope_findings": [
      "filed by the PM as #31342: upsert() on a columnMap-ed object uses unmapped id as its ON CONFLICT target (no such column: id on a table without id) and as its read-back key (table with its own surrogate id: upsert of key 1 inserted CODE 1 but answered the row whose surrogate is 1)"
     ]
    }
  4. added
    priority:p1High: required for production / M2
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Nov 8, 2026
  5. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: regrade · p2 → p1 · domain:server · area:data · 2026-11-08T03:56Z

    ⛔ Not a claim.

    • update(), delete() and bulkDelete() hard-code WHERE "id" = ?. On an external object whose primary key columnMap maps to another remote column, while the table also has a plain id column, a delete removes the wrong row, silently, on PostgreSQL, MySQL and SQLite. Item 2 (aggregate / distinct) errors loudly.
    • Raised to p1: the reach is narrow (write-enabled external objects with a mapped key), but item 1 destroys data without an error, and the seat already lands it ahead of the Oracle card for that reason. The lane and the in-flight claim are unchanged. finding removed.

    Generated by Claude Code

  6. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Landed · 2026-11-08T04:22Z

    PR #31349 merged through the queue as e2e6ec7dc9 at 2026-11-08T04:21:45Z, from head 05ecc979. It has no contract face (Clause-②: no, not governed), so the review of record is CI plus the seat read in the ACCEPT.

    The fix resolves the key column through remoteColumn in update(), delete() and bulkDelete(). It also spells aggregate()'s group keys, aggregands and date buckets, and distinct()'s column, through the map. Pins: 5/5 for the write doors and 8/8 for the read doors, each red before the fix. Upsert and the paged-read tie-breaker go on in #31342.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:databugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions