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.