Repository navigation
Conversation
9bc0b02 to
bbdd32f
Compare
bbdd32f to
e083da3
Compare
e1133d1 to
667ab40
Compare
Remove these sections if your commit already has a good description! ### Motivation Why does this change exist? Link to a GitHub issue, design doc, Slack thread, or explain the problem in a sentence or two. A reviewer who has no context should understand *why* after reading this section. If this implements or addresses an existing issue, it's enough to link to that: Closes <issue> Fixes <bug> etc. ### Description What does this PR actually do? Focus on the approach and any non-obvious decisions. The diff shows the code --- use this space to explain what the diff *can't* tell a reviewer. ### Verification How do you know this change is correct? Describe new or existing automated tests, or manual steps you took. --------- Co-authored-by: Amp <amp@ampcode.com>
667ab40 to
28a85a1
Compare
46b474d to
f1393da
Compare
f1393da to
e0a3ea1
Compare
QA LLM Review1. HIGH -- Rename after an
|
ggevay
left a comment
There was a problem hiding this comment.
Releasing some Claude comments for now.
The QA bot's finding reproduces on the CI image of e0a3ea1; on #40007's head the same sequences fail cleanly with 40001. Two more triggers of the same panic:
CREATE SOURCE IF NOT EXISTSon an existing source as the no-op.ALTER SCHEMA a SWAP WITH bas the later statement, while another session renamesa.
The skipped outcome also isn't checked when the no-op comes later: BEGIN; ALTER VIEW v RENAME TO w; CREATE TABLE IF NOT EXISTS t FROM SOURCE s;, then another session drops t, and COMMIT succeeds.
On the later layers' CI images, the panic is gone from #40013 on (the snapshot is captured when the transaction enters DDL mode), and the unchecked skip from #40015 on (it fails with 40001). So landing #40008 before #40013 would ship the crash.
Minor, on the description: "Changed results produce a serialization failure" could also say that a retained result whose object was dropped fails with the error serial execution would give, for example unknown catalog item 'x'.
| //! by its predecessors. Pending external work abandons that candidate. A continuation | ||
| //! rebuilds the prefix before consuming its indexed response, so successful preparation | ||
| //! and preparation errors are accepted only when their inputs still match. | ||
| //! | ||
| //! External mutations are not catalog operations. CREATE CONNECTION retains its allocated | ||
| //! ID, generated keys, and successful secret-write completion across retries. ROTATE KEYS | ||
| //! uses a pinned publication command instead of retrying its external mutation. Catalog | ||
| //! rollback cannot undo either secret writes or changes in external services. |
ggevay
left a comment
There was a problem hiding this comment.
A few smaller things on top of the earlier comments.
| "another session modified the catalog while this DDL transaction was open", | ||
| ), | ||
| AdapterError::DDLTransactionOutcomeChanged => { | ||
| f.write_str("a DDL statement would produce a different outcome at commit") |
There was a problem hiding this comment.
This error is also raised when a retained SHOW CREATE row or statement description no longer matches (CatalogResultCommand::execute, DescriptionCommand::execute), so "a DDL statement" can point the user at the wrong statement. Maybe:
| f.write_str("a DDL statement would produce a different outcome at commit") | |
| f.write_str("a statement in this transaction would produce a different result at commit") |
| Statement::ExplainPlan(_) | ||
| | Statement::ExplainPushdown(_) | ||
| | Statement::ExplainAnalyzeObject(_) | ||
| | Statement::ExplainAnalyzeCluster(_) | ||
| | Statement::ExplainSinkSchema(_) | ||
| | Statement::ExplainTimestamp(_) => { |
There was a problem hiding this comment.
Nit: the same six EXPLAIN variants are listed again in frontend_peek.rs, before the other record_explain() call. An EXPLAIN variant added to only one list would, on the other path, neither be rejected in a DDL transaction nor block later DDL. Maybe one shared is_explain(&Statement) next to record_explain?
| let stmt = portal.stmt.as_deref().cloned(); | ||
| let desc = portal.desc.clone(); |
There was a problem hiding this comment.
describe now also retains the description in the session, which is why it needs &mut Session. A knock-on: this function and verify_prepared_statement now clone the statement and its description on every call, before the revision check, where they used to borrow them. Could they compare revisions first and clone only when re-describing?
|
Aljoscha the human here: thanks, I'll fix these, and sorry, some of these came up from chopping the change up into PRs, as you can see from how the bug is fixed in later PRs. 😅 But I should and will totally fix the issue locally here in that PR! |
Motivation
DDL transactions currently reject any intervening catalog revision.
A transaction renaming one view can therefore fail because another
session creates an unrelated object.
Simply removing that check is unsafe. Precomputed writes can refer to
objects that have disappeared, miss newly created dependents, or rely
on revoked permissions. Commit must also preserve catalog results
already exposed to the client.
Description
Retain supported ALTER ... RENAME and ALTER ... SWAP statements with
their parameters and per-statement session context. Use the same command
logic for speculative execution and atomic application at commit.
At commit, evaluate the statements in order against the current catalog,
including preceding statements in the batch. Recompute name resolution,
authorization, and dependent-object rewrites there. Missing targets,
destination collisions, and authorization failures reject the entire
batch without publishing partial changes.
Names are deliberately resolved again at commit. If another session
drops a target and creates a replacement with the same name, the command
can act on that replacement, provided the outcomes already reported to
the client remain unchanged.
Preserve each rename's reported object type, change/no-op outcome, and
planning notices. An ALTER ... IF EXISTS that reported a missing object
cannot silently become a successful rename at commit.
Retain supported SHOW CREATE rows and meaningful statement descriptions
from the start of the transaction. If the transaction enters DDL mode,
validate those earlier inspections before its first mutation. Recompute
retained results at their original statement positions during commit,
not against the final catalog.
Successfully recomputed results that differ produce a serialization failure.
Replanning errors, such as a missing object, surface as they would in serial
execution. Equivalent results
remain acceptable even if object identity changes.
EXPLAIN and supported transactional DDL cannot be mixed in either order.
EXPLAIN results are not replayed, so executing EXPLAIN prevents that
transaction from subsequently entering DDL mode.
Inspections are retained only while a DDL commit remains possible.
Entering a read, write, subscription, or single-statement mode, or
executing EXPLAIN, releases buffered inspections and stops retaining
new ones.
Scope
Remove the blanket catalog-revision check only for batches whose
mutating statements are supported renames and swaps. Other mutating DDL
retains that check, including source creation that reports an IF NOT EXISTS
no-op before or after a rename. No additional statement families become eligible
for multi-statement transactions.
SHOW CREATE and statement descriptions do not themselves enter DDL
mode or restrict subsequent statements. Transactions that never enter
DDL mode do not validate retained inspections. Ordinary data reads,
object listings, and other SHOW forms remain outside this protection.
Serving a cached portal description without recomputation does not
add a retained check.
Coverage
The rename-focused CI workflow covers:
permissions, and complete rollback, compared with serial execution.
or dropped collision targets, with unchanged published state on failure.
definitions, equivalent replacements, statement-position semantics,
protocol preparation, cursor wrappers, inspection before DDL, and
unchanged non-DDL transaction behavior.
implicit execution, with transaction-state cleanup.
A session lifetime test checks that inspections are released when DDL
becomes impossible and that later inspections are not retained.
SQL tests also cover per-statement search paths and independent
concurrent renames.