Skip to content

refactor(storage): render joined UPDATEs through the dialect - #1009

Merged
Kiran01bm merged 4 commits into
mainfrom
kiran01bm/sqlstore-joined-dml-13k
Aug 13, 2026
Merged

refactor(storage): render joined UPDATEs through the dialect#1009
Kiran01bm merged 4 commits into
mainfrom
kiran01bm/sqlstore-joined-dml-13k

Conversation

@Kiran01bm

@Kiran01bm Kiran01bm commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Moves the joined (multi-table) UPDATE statements in the apply comment and control request stores behind the storage Dialect seam. MySQL's rendering is pinned byte-identical; a future PostgreSQL dialect renders the same structured parts as UPDATE ... SET ... FROM ... WHERE. No behavior change for MySQL.

Why

The storage core is dialect-parameterized so a PostgreSQL backend can run the same store code. Multi-table UPDATE syntax is one of the few places MySQL and PostgreSQL diverge at the statement-shape level rather than the fragment level, so the seam hands the whole statement rendering to the dialect.

What

  • New Dialect.JoinedUpdate(targetTable, targetAlias, joinTable, joinAlias, joinCondition, assignments, predicate) renders a full joined UPDATE. SET assignments are structured (JoinedUpdateAssignment{Column, Expr}) with unqualified target columns, and the join condition is passed separately from the residual predicate — the two constraints that make PostgreSQL's UPDATE ... FROM shape expressible from the same inputs. The join condition must be placeholder-free (its render position differs per dialect); arguments bind assignments-first, then predicate.
  • Five lease-guarded joined UPDATEs converted: apply comments (IncrementEditCount, Supersede, ClearPendingFreeze) and control requests (CompletePending, FailPending).
  • Unit tests pin the MySQL renderings exactly, including a placeholder SET expression and the empty-assignments panic.

Joined UPDATE/DELETEs in other store files (applies, operations, tasks) are deliberately out of scope — they follow in the next slices, and a joined-DELETE seam is added there with its first caller.

Before / after

Before                                     After
┌───────────────────────────────┐          ┌───────────────────────────────────┐
│ store hardcodes:              │          │ store passes structured parts:    │
│  UPDATE apply_comments c      │          │  target, join, ON cond,           │
│   JOIN applies a ON ...       │  ──────▶ │  SET assignments, WHERE pred      │
│   SET c.x = ? WHERE ...       │          │ dialect renders the statement:    │
│  (MySQL-only statement shape) │          │  MySQL: UPDATE ... JOIN ... SET   │
└───────────────────────────────┘          │  PG (later): UPDATE ... SET ...   │
                                           │              FROM ... WHERE       │
                                           └───────────────────────────────────┘

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@morgo morgo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Approving on Morgan's behalf (automated review, escalation rules apply).

All five joined-UPDATE conversions (IncrementEditCount, Supersede, ClearPendingFreeze, CompletePending, FailPending) render token-identical MySQL to the removed hand-written SQL — same ON condition, SET order, WHERE guards including the lease-token predicate — with argument order and placeholder counts (3/3/3/5/6) verified at every call site, so the RowsAffected-based lost-lease detection is unaffected. Dialect wiring is nil-safe (everything funnels through NewWithDependencies).

One non-blocking hardening suggestion: JoinedUpdate documents that joinCondition must not contain bind placeholders, but nothing enforces it — a future caller's stray ? would silently shift every subsequent binding by one, which in these queries means the lease token binds into the wrong slot. A strings.Contains(joinCondition, "?") panic, symmetric with the existing empty-assignments panic, would pin the contract.

Heads-up: once #1010's Postgres rendering exists, these lease-guarded joined UPDATEs render as UPDATE … FROM, which inherits the fencing question flagged on #1011 — one decision for the whole stack, nothing to change in this PR.

@Kiran01bm
Kiran01bm force-pushed the kiran01bm/sqlstore-portable-sql-13j branch from 54f3ad2 to 8570883 Compare August 12, 2026 22:24
Base automatically changed from kiran01bm/sqlstore-portable-sql-13j to main August 12, 2026 22:33
Keep lease-guarded apply comment and control request updates portable
across dialect-specific joined DML syntax. No behavior change for MySQL.
The join condition's render position differs per dialect, so the
contract now forbids bind placeholders in it and defines the
assignments-then-predicate argument order. Empty assignment lists
panic at the seam instead of rendering invalid SQL.
Copilot AI lite review requested due to automatic review settings August 13, 2026 00:05
@Kiran01bm
Kiran01bm force-pushed the kiran01bm/sqlstore-joined-dml-13k branch from 41ca96f to 3a25477 Compare August 13, 2026 00:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors sqlstore’s multi-table (joined) UPDATE statement rendering to go through the Dialect interface, keeping MySQL behavior byte-identical while enabling a future PostgreSQL dialect to render the same structured update as UPDATE ... SET ... FROM ... WHERE.

Changes:

  • Adds Dialect.JoinedUpdate(...) plus JoinedUpdateAssignment and implements MySQL rendering in MySQLDialect.
  • Converts lease-guarded joined UPDATEs in apply-comment and control-request stores to call the dialect seam.
  • Adds unit tests that pin the MySQL joined-UPDATE SQL strings (including placeholder ordering and empty-assignments panic).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pkg/storage/internal/sqlstore/storage.go Wires Dialect into controlRequestStore so it can render joined UPDATEs via the seam.
pkg/storage/internal/sqlstore/dialect.go Extends Dialect with JoinedUpdate and adds the MySQL implementation + assignment type.
pkg/storage/internal/sqlstore/dialect_test.go Pins MySQL joined-UPDATE rendering and validates the empty-assignments panic behavior.
pkg/storage/internal/sqlstore/control_requests.go Renders lease-guarded joined UPDATEs (complete/fail pending) through Dialect.JoinedUpdate.
pkg/storage/internal/sqlstore/apply_comments.go Renders lease-guarded joined UPDATEs (edit count, supersede, clear pending freeze) through Dialect.JoinedUpdate.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/storage/internal/sqlstore/dialect.go
…itions

A stray placeholder in the join condition would bind into a
dialect-dependent position and silently shift every subsequent
binding, so the seam panics on it instead of trusting the doc contract.
The dialect qualifies assignment columns with the target alias itself, so
a pre-qualified column would render an invalid double-qualified reference.
@Kiran01bm
Kiran01bm enabled auto-merge (squash) August 13, 2026 05:13
@Kiran01bm
Kiran01bm merged commit cecf76b into main Aug 13, 2026
33 checks passed
@Kiran01bm
Kiran01bm deleted the kiran01bm/sqlstore-joined-dml-13k branch August 13, 2026 05:20
Kiran01bm added a commit that referenced this pull request Aug 13, 2026
…nucleus

* origin/main:
  refactor(storage): render joined UPDATEs through the dialect (#1009)
  fix(planetscale): hold the cutover when the operator defers it (#978)
  fix(observability): make telemetry resource schema-tolerant (#1014)

# Conflicts:
#	pkg/storage/internal/sqlstore/dialect.go
#	pkg/storage/internal/sqlstore/dialect_test.go
#	pkg/storage/internal/sqlstore/storage.go
Kiran01bm added a commit that referenced this pull request Aug 13, 2026
…-joined-dml-13l

* origin/main:
  refactor(storage): render joined UPDATEs through the dialect (#1009)
  fix(planetscale): hold the cutover when the operator defers it (#978)
  fix(observability): make telemetry resource schema-tolerant (#1014)
  feat(postgres): implement declarative planning via pg-sprite diffplan (#1008)
  refactor(storage): make remaining sqlstore SQL dialect-portable (#1007)
  test(e2e): deflake multi-table stop/start resume and MySQL cold starts (#1005)
  ci: verify golangci config against a vendored schema (#997)
  feat(api): fail-closed verdict gating for postgres plans (#1004)
  feat(tern): route postgres targets to the postgres engine (#1003)
  feat(storage): stamp remaining sqlstore timestamps explicitly (#1006)

# Conflicts:
#	pkg/storage/internal/sqlstore/apply_comments.go
#	pkg/storage/internal/sqlstore/dialect.go
#	pkg/storage/internal/sqlstore/dialect_test.go
#	pkg/storage/internal/sqlstore/settings.go
#	pkg/storage/internal/sqlstore/storage.go
#	pkg/storage/internal/sqlstore/updated_at_lint_test.go
Kiran01bm added a commit that referenced this pull request Aug 13, 2026
…re-public-13n

* origin/main:
  refactor(storage): portable lease-guarded joined DML for the operation store (#1011)
  fix(github): align lint warnings formatting with issues and fold long lists (#959)
  fix(github): lead with the database's operators on command-rejection comments (#960)
  docs: regenerate stale tables of contents (#968)
  fix(engine): heartbeat the row a local drive actually owns (#915)
  fix(github): scope auto-plan to the schema a pull request proposes (#1016)
  fix(vitess): dispatch task-less VSchema-only work operations over gRPC (#961)
  feat(storage): add PostgreSQL dialect nucleus to the shared store core (#1010)
  refactor(storage): render joined UPDATEs through the dialect (#1009)
  fix(planetscale): hold the cutover when the operator defers it (#978)
  fix(observability): make telemetry resource schema-tolerant (#1014)
  feat(postgres): implement declarative planning via pg-sprite diffplan (#1008)
  refactor(storage): make remaining sqlstore SQL dialect-portable (#1007)
  test(e2e): deflake multi-table stop/start resume and MySQL cold starts (#1005)
  ci: verify golangci config against a vendored schema (#997)
  feat(api): fail-closed verdict gating for postgres plans (#1004)
  feat(tern): route postgres targets to the postgres engine (#1003)
  feat(storage): stamp remaining sqlstore timestamps explicitly (#1006)
Kiran01bm added a commit that referenced this pull request Aug 13, 2026
…lect-factory-14b

* origin/main:
  feat(github): flag destructive changes to tables another open PR owns (#1017)
  feat(storage): add public postgresstore constructor (#1012)
  refactor(storage): portable lease-guarded joined DML for the operation store (#1011)
  fix(github): align lint warnings formatting with issues and fold long lists (#959)
  fix(github): lead with the database's operators on command-rejection comments (#960)
  docs: regenerate stale tables of contents (#968)
  fix(engine): heartbeat the row a local drive actually owns (#915)
  fix(github): scope auto-plan to the schema a pull request proposes (#1016)
  fix(vitess): dispatch task-less VSchema-only work operations over gRPC (#961)
  feat(storage): add PostgreSQL dialect nucleus to the shared store core (#1010)
  refactor(storage): render joined UPDATEs through the dialect (#1009)
  fix(planetscale): hold the cutover when the operator defers it (#978)
  fix(observability): make telemetry resource schema-tolerant (#1014)
  feat(postgres): implement declarative planning via pg-sprite diffplan (#1008)
  refactor(storage): make remaining sqlstore SQL dialect-portable (#1007)
  test(e2e): deflake multi-table stop/start resume and MySQL cold starts (#1005)
  ci: verify golangci config against a vendored schema (#997)
  feat(api): fail-closed verdict gating for postgres plans (#1004)
  feat(tern): route postgres targets to the postgres engine (#1003)
  feat(storage): stamp remaining sqlstore timestamps explicitly (#1006)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants