Skip to content

[4.0-dev] fix(parser): support PostgreSQL-style UPDATE ... SET ... FROM ... WHERE#24562

Open
ck89119 wants to merge 8 commits into
matrixorigin:4.0-devfrom
ck89119:cherry-pick-24536-to-4.0-dev
Open

[4.0-dev] fix(parser): support PostgreSQL-style UPDATE ... SET ... FROM ... WHERE#24562
ck89119 wants to merge 8 commits into
matrixorigin:4.0-devfrom
ck89119:cherry-pick-24536-to-4.0-dev

Conversation

@ck89119
Copy link
Copy Markdown
Contributor

@ck89119 ck89119 commented May 25, 2026

What type of PR is this?

  • API-change
  • BUG
  • Improvement
  • Documentation
  • Feature
  • Test and CI
  • Code Refactoring

Which issue(s) this PR fixes:

issue #23137

What this PR does / why we need it:

Cherry-picks #24536 to 4.0-dev.

This backports support for PostgreSQL-style UPDATE ... SET ... FROM ... WHERE, including parser, formatter, planner fallback-path handling, parser/planner unit coverage, and BVT coverage.

Cherry-picked commits:

  • b983152 fix(parser): support PostgreSQL-style UPDATE ... SET ... FROM ... WHERE
  • 424ed8c fix(parser): keep UPDATE ... FROM target and sources separated
  • f2cc3cd fix(plan): align UPDATE ... FROM fallback path and FROM formatter

Tests were not run locally, per the cherry-pick workflow.

ck89119 and others added 3 commits May 25, 2026 14:18
Allow UPDATE statements to introduce additional join sources via a FROM
clause between SET and WHERE, matching the standard / PostgreSQL syntax.
The grammar cross-joins the target with the FROM-clause tables so the
existing multi-table UPDATE binder/planner path handles it unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address review feedback on the initial PostgreSQL-style UPDATE support:

- Introduce Update.From so the target table stays in Tables and the FROM
  clause is preserved as its own join tree. The grammar no longer wraps
  target and sources in a cross-join.
- bind_update / buildTableUpdate still build a single inner SELECT (a
  cross-join of target and FROM sources) for column resolution, but
  dmlTableInfo only sees the target.
- This fixes four issues:
  1. Unqualified SET LHS is no longer reported as ambiguous when the
     target and a FROM source share a column name (only the target is
     considered for the LHS).
  2. The target stays a plain TableName, so bind_update no longer falls
     back to buildTableUpdate, restoring generated-column write checks
     and base-column recomputation.
  3. FROM-clause tables are not passed through checkTableType, so views
     and other read-only sources are accepted.
  4. Update.Format emits the original PG-style "from <join-tree>", so
     round-tripping preserves associativity of FROM's join tree.

Tests updated:
- parser UT switches outputs to PG-style and adds JOIN/LEFT JOIN cases.
- plan UT adds same-column-name and FROM-with-JOIN cases.
- BVT covers view as source, target/source same-named columns, generated
  column write rejection, and generated column recomputation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address the second round of review feedback:

- Update.Format now wraps any JoinTableExpr that occupies a right-operand
  position with ParenTableExpr (a shallow rewrite, the original AST is
  untouched). Previously "FROM b, c JOIN d ON ..." (AST: b CROSS (c INNER
  d)) re-parsed left-associatively as (b CROSS c) INNER d, changing the
  ON's binding.

- getUpdateTableInfo (fallback path) sets needAggFilter when stmt.From is
  present. Without this, a target row matched by multiple FROM-source
  rows would be double-written on the fallback path (e.g. FK targets);
  with it, the existing any_value() aggregation in buildUpdatePlans runs.

- getUpdateTableInfo rejects SET on a stored generated column (mirroring
  bind_update.go), and silently drops SET gen_col = DEFAULT so the
  recomputation below fills it.

- selectUpdateTables now recomputes generated columns after binding,
  matching bind_update.go's behaviour but using the fallback
  ProjectList layout. insertColPos construction moved after the
  recomputation so it picks up the new positions.

Tests:
- parser UT adds round-trip cases for "FROM b, c JOIN d ON ..." and
  "FROM b, c LEFT JOIN d ON ..." that confirm the paren wrap.
- plan UT adds a self-join case.
- BVT covers FK target with generated column (fallback path: direct
  write rejection + base column recomputation), self-join, ORDER BY /
  LIMIT rejection at parse time, and duplicate-match on the fallback
  path (verifies any_value dedup converges to a single row).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

Copy link
Copy Markdown
Contributor

@XuPeng-SH XuPeng-SH left a comment

Choose a reason for hiding this comment

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

Requesting changes due to a planner correctness regression in the fallback UPDATE path.

In pkg/sql/plan/build_update.go, the generated-column rewrite appends recomputed generated expressions to the global selectNode.ProjectList, but then increments upPlanCtx.updateColLength / tableBase as if those expressions were inserted into that target table's own contiguous slice.

That breaks later target offsets in fallback multi-table UPDATE planning:

  • multi-table UPDATE already routes through buildTableUpdate (pkg/sql/plan/build.go, pkg/sql/plan/dml_context.go)
  • generated expressions are appended globally at selectNode.ProjectList = append(selectNode.ProjectList, genExpr)
  • later targets still derive beginIdx from beginIdx = beginIdx + upPlanCtx.updateColLength + len(tableDef.Cols)
  • downstream consumers use delCtx.beginIdx + ... / delCtx.beginIdx + len(cols) + i to read the projection slice (pkg/sql/plan/build_dml_util.go)

So if an earlier target has a generated column, later targets read/write the wrong projection positions. I also could not find coverage for this multi-target + generated-column fallback combination; the added tests cover parser shape and single-target fallback/generated-column cases, but not this layout hazard.

This looks like a real correctness blocker, not a follow-up enhancement.

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 26, 2026

Merge Queue Status

  • Entered queue2026-05-26 08:05 UTC · Rule: release-4.0
  • Checks started · in-place
  • Checks failed
  • 🚫 Left the queue2026-05-26 09:40 UTC · at b375ac26e92614a92edc2d8991c977b30b64034e

This pull request spent 1 hour 34 minutes 32 seconds in the queue, with no time running CI.

Waiting for
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
All conditions
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #changes-requested-reviews-by = 0 [🛡 GitHub branch protection]
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-decision = APPROVED [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / SCA Test on Ubuntu/x86
    • check-neutral = Matrixone CI / SCA Test on Ubuntu/x86
    • check-skipped = Matrixone CI / SCA Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86

Reason

The merge conditions cannot be satisfied due to failing checks

Hint

You may have to fix your CI before adding the pull request to the queue again.
If you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 26, 2026

Merge Queue Status

  • Entered queue2026-05-26 11:16 UTC · Rule: release-4.0
  • Checks started · in-place
  • Checks failed
  • 🚫 Left the queue2026-05-26 18:48 UTC · at 2bfd1d468d2d45f0cfc32f9f1f6844bbd8384562

This pull request spent 7 hours 31 minutes 35 seconds in the queue, with no time running CI.

Waiting for
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone CI / SCA Test on Ubuntu/x86
    • check-skipped = Matrixone CI / SCA Test on Ubuntu/x86
    • check-success = Matrixone CI / SCA Test on Ubuntu/x86
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
All conditions
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone CI / SCA Test on Ubuntu/x86
    • check-skipped = Matrixone CI / SCA Test on Ubuntu/x86
    • check-success = Matrixone CI / SCA Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #changes-requested-reviews-by = 0 [🛡 GitHub branch protection]
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-decision = APPROVED [🛡 GitHub branch protection]

Reason

The merge conditions cannot be satisfied due to failing checks

Hint

You may have to fix your CI before adding the pull request to the queue again.
If you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

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

Labels

dequeued kind/bug Something isn't working size/XXL Denotes a PR that changes 2000+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants