Skip to content

Mz deploy fixes#37142

Draft
sjwiesman wants to merge 6 commits into
MaterializeInc:mainfrom
sjwiesman:mz-deploy-fixes
Draft

Mz deploy fixes#37142
sjwiesman wants to merge 6 commits into
MaterializeInc:mainfrom
sjwiesman:mz-deploy-fixes

Conversation

@sjwiesman

Copy link
Copy Markdown
Contributor

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
Fixes
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.

sjwiesman and others added 6 commits June 18, 2026 11:53
…ansaction

`apply tables` failed when applying a newly-created `CREATE TABLE ... FROM
SOURCE` that carried a companion `GRANT` (or `COMMENT`):

    transactions which modify objects are restricted to just modifying objects
    (AdapterError::DDLOnlyTransaction)

`CreateTableFromSource` statements are grouped by source into a single
BEGIN/COMMIT so that multiple tables from one source are created together. The
Created path drained the CREATE, its indexes, and the reconciled grants/comments
into one `ObjectResult.statements`, so the grouped transaction wrapped the GRANT
alongside the DDL. Materialize forbids mixing a GRANT/COMMENT with object
creation in one transaction, so the apply aborted.

This was latent because the existing grant test only grants on a pre-existing
table, where the grant runs via the UpToDate path (standalone, no transaction).

Add an `ObjectResult.post_statements` bucket that `ApplyPlan::execute` runs after
the transaction commits, outside any BEGIN/COMMIT. `apply tables` now keeps only
the bare CREATE in the grouped `statements` and defers indexes, grants, and
comments to `post_statements`. The table is still reported as a single `created`
result, so per-phase action counts are unchanged. The other apply commands set
the new field empty and are unaffected.

Tests: the `basic/v2` incremental fixture's `products` table now carries a
companion grant, so the `apply-incremental` mzcompose case exercises
create-plus-grant in one apply and asserts the grant landed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…work

Objects whose name is a SQL reserved word (`table`, `order`, `select`, …)
were unusable end to end:

  - A file `table.sql` containing `CREATE TABLE "table"` failed validation
    with `ObjectNameMismatch`: `validate_ident` compared the statement name
    rendered through `Ident`'s `AstDisplay` (which re-quotes the keyword to
    `"table"`) against the unquoted file stem `table`.
  - A declared external dependency `db.schema."table"` never resolved:
    `ObjectId::from_str` split on `.` and kept the literal quotes, so the
    declared form matched neither the typecheck lookup nor `lock`'s catalog
    query (the catalog stores the unquoted `table`).

The root cause was that an object's identity was stored as a `String`
produced inconsistently — quoted (`Ident::to_string()`) when derived from
SQL, unquoted when derived from file stems and the catalog.

Store the identity as `Ident` instead, throughout `ObjectId` and
`DatabaseIdent`. `Ident`'s `Eq`/`Hash`/`Ord` compare the raw identifier
value, so the quoted/unquoted mismatch is impossible by construction;
quoting now happens only at the edges (Display, serialization, error
messages). `ObjectId::new` keeps its `String` signature (wrapping inputs
with `Ident::new_unchecked`) and the accessors keep returning `&str`, so the
~140 construction and read sites are unchanged. `from_str` now parses with
the SQL parser so quoted/reserved-word components are handled correctly, and
`from_item_name` stores the parsed `Ident` directly. The change is a no-op
for every non-keyword identifier (an `Ident` only renders differently from
its raw value when it needs quoting), so the blast radius is reserved-word
names only.

Tests: unit tests in `object_id` cover quoted parsing, quoted-Display
round-tripping, and equality across the construction paths; a new
`reserved-words` mzcompose workflow applies a `CREATE TABLE "table"` project
(path matching) and locks a reserved-word external dependency (catalog
resolution).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ship the reserved-words test project's types.lock alongside the other test
projects, and refresh the system-deps lock to the current catalog rendering
(the single-byte `"char"` type is reported quoted). Both workflows delete and
regenerate their lock before asserting, so these checked-in copies are
snapshots rather than load-bearing inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The error hints suggested a `--name` flag that no longer exists. `stage` takes
`--deploy-id` and `abort` takes a positional deploy id, so e.g. a missing
deployment now hints `mz-deploy stage . --deploy-id <id>` and the conflict
rebase hint uses `mz-deploy abort <staging-env>` (positional). Fixes the
DeploymentNotFound, DeploymentConflict, and GitShaFailed hints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-running `mz-deploy init` in a directory that is already a committed git
repo (or already an mz-deploy project) failed with "git commit failed": the
scaffold ran `git commit` unconditionally, which errors when there is nothing
to commit. It also overwrote existing files (e.g. a user's project.toml) and
relied on the ambient git identity for the committer.

Make scaffolding converge instead of fail:

  - `add_file` skips files that already exist, so re-runs only create what's
    missing and never clobber user content.
  - the commit runs only when `git diff --cached --quiet` reports staged
    changes, so an already-committed tree is a no-op rather than an error.
  - the commit sets GIT_COMMITTER_NAME/EMAIL explicitly (author was already
    hardcoded), so it doesn't depend on the user's git config being set.

Tests: scaffold the same directory twice (git path included) and confirm both
runs succeed, and that an existing project.toml is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `init` and `new` help pages listed `.agents/skills/mz-deploy/SKILL.md` and
a `.claude/skills/mz-deploy/` symlink among the scaffolded files, but the
scaffold creates neither. Remove the inaccurate bullets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant