ViewDiff.Materialized. A plain ⇄ materialized view conversion is now carried explicitly on the diff as a ValueChange<bool>, and DiffReader renders it as a label transition (view → materialized view app.name).
Fixed
Renamed tables. Constraint, index, and trigger drops (and privilege revokes) on a table being renamed now target the old table name, and execute before the rename.
Renamed schemas. The schema rename is now ordered ahead of every other action, so drops and revokes inside the renamed schema resolve correctly.
Renamed materialized views. In-place index drops on a renamed view target the old view name. A rename accompanying a recreate no longer emits a RenameView (which would have targeted a dropped view); the drop removes the old name and the recreate creates the new one.
View conversions. A plain ⇄ materialized conversion now drops the view as what it currently is, instead of using the desired side’s materialization for the drop.
Schema template declaration. A TEMPLATE name BEGIN … END; block allows you to declare a reusable group of objects once.
Schema template application. A APPLY TEMPLATE name IN SCHEMA a, b; statement instantiates a template into each named schema.
Table templates. A TEMPLATE name FOR TABLE BEGIN … END; block declares reusable table members.
Index name validation. Duplicate index and index-backed constraint names (primary key, unique, exclusion) within a schema are now rejected at validation time.
DdlDocument.Templates carries the parsed template constructs for consumers of the reader — a TemplateSet of definitions, applications, and includes.
The formatter lays out TEMPLATE blocks canonically: header and BEGIN/END on their own lines, inner statements formatted as usual and indented one level.
Data migrations. A MIGRATION ['name'] FOR <trigger> <schema>.<table>.<member> AS $$…$$; block attaches raw SQL to a structural change (ADD COLUMN, ALTER COLUMN TYPE, or ADD CONSTRAINT) and is spliced into the plan only when the matching change is planned. A block matching nothing is reported as an informational diagnostic and is safe to delete.
Decomposed NOT NULL adds. A required column add with no default and a matching FOR ADD COLUMN migration is planned as add-nullable → run the migration SQL → SET NOT NULL, so the add succeeds against a populated table.
Hazard suppression. A matching migration block silences the corresponding data-hazard diagnostic (per trigger; unique-index hazards have no trigger and are never suppressed).
ExecuteDataMigration plan action carrying the spliced SQL. Executing a plan containing one requires a provider that recognizes it (4.3+); plans with no matched blocks are unaffected.
Unified SCRIPT statement.SCRIPT '<name>' RUN [ALWAYS | ONCE] ON <event> AS $$…$$; is the new canonical form of deployment scripts and data migrations.
The event is a deployment bookend (PRE DEPLOYMENT / POST DEPLOYMENT) or a structural change (ADD COLUMN / ALTER COLUMN TYPE / ADD CONSTRAINT with a target path);
RunCondition on scripts and data migrations, carrying the parsed RUN clause.
The backend state store now carries the recorded script executions.
A RUN ONCE script is recorded on a successful apply and skipped by later plans. A recorded script whose body has since changed stays skipped and warns.
Migrations in schema templates. A MIGRATION block can now be declared inside a TEMPLATE … BEGIN … END; body with an unqualified table.member path; applying the template instantiates the block once per target schema. The {schema} token in the block’s SQL is replaced with each target schema’s name.
Changed
Script names must now be unique across the project (they identify scripts in diagnostics and run-once tracking). A named block declared in a template applied to multiple schemas can include the {schema} token in its name to keep instances distinct.
DdlWriter renders deployment scripts and named data migrations in the SCRIPT form; anonymous migrations keep the legacy spelling.
Deprecated
The PRE|POST DEPLOYMENT '<name>' AS $$…$$; and MIGRATION ['name'] FOR <trigger> <path> AS $$…$$; forms. Both still parse into the same model, and plan/apply/validate now surface a deprecations warning naming the SCRIPT replacement. They will be removed in NSchema 5.0.
Fixed
The formatter no longer re-indents the interior of a dollar-quoted body inside a TEMPLATE block, which grew the indentation on every pass and changed the SQL a routine or migration body carries.
Public state access. The recorded state is now a public model.
ISchemaStateManager, exposed as app.State, facilitates reading and writing the recorded state, with an optional ReadRaw/WriteRaw methods for moving the serialized payload without interpreting it.
Changed
Planning with an unreadable state payload now fails with a diagnostic instead of throwing.
Public desired-schema access.IDesiredSchemaProvider is now public and exposed as app.DesiredSchema.
Hash on Script and DataMigration. For reading the canonical hash of the body.
Changed
Refresh no longer silently replaces a state payload it cannot read: it fails unless RefreshArguments.Force is set, and a forced replacement carries
a warning that the run-once script ledger was reset. The state capture after an apply still replaces an unreadable payload (the SQL has already run),
with the same warning.