Data migrations. A MIGRATION ['name'] FOR <trigger> <schema>.<table>.<member> AS $$…$$; block (via NSchema.Core 4.3.0) attaches raw SQL to an ADD COLUMN, ALTER COLUMN TYPE, or ADD CONSTRAINT change and runs only when that change is in the plan. A required column add with a matching block is applied as add-nullable → backfill → SET NOT NULL, a matching block silences the corresponding data-hazard warning, and a block matching nothing is reported as safe to delete. The plan output gains a “Data migrations” section (dataMigrations in --json). Executing a plan with a matched block requires a provider plugin at 4.3 or later.
Changed
The import command now writes the per-schema header to <schema>/schema.sql instead of <schema>.sql.
Fixed
The nschema lock release command suggested by lock status and lock acquire now carries the --environment and --directory arguments of the current invocation.
The diff now shows an added or removed column’s default expression and identity marker.
DDL syntax errors now name the file the error was found in, alongside the existing line and column.
The import command no longer repeats the CREATE SCHEMA statement in every object file; only the per-schema header declares the schema.
Unified SCRIPT statement (via NSchema.Core 4.4.0). SCRIPT '<name>' RUN [ALWAYS | ONCE] ON <event> AS $$…$$; is the new canonical form of deployment scripts and data migrations: the event is PRE DEPLOYMENT, POST DEPLOYMENT, or a structural change (ADD COLUMN / ALTER COLUMN TYPE / ADD CONSTRAINT with a target path).
Run-once scripts. A RUN ONCE script is recorded in the state backend on a successful apply and skipped by later plans; a recorded script whose body has since changed stays skipped and warns. Plan output marks run-once scripts in the pre/post-deployment sections ((run once); runCondition in --json). Recording requires a state backend — planning without one warns.
Scripts in schema templates. Both script kinds can be declared inside a TEMPLATE … BEGIN … END; body and instantiate once per applied schema, with the {schema} token substituted in the name and the SQL.
Changed
Script names must be unique across the project (they identify scripts in diagnostics and run-once tracking); a template-declared script applied to multiple schemas can include {schema} in its name.
Deprecated
The PRE|POST DEPLOYMENT '<name>' AS $$…$$; and MIGRATION ['name'] FOR <trigger> <path> AS $$…$$; forms still work, but plan/apply/validate now surface a deprecations warning naming the SCRIPT replacement. They will be removed in NSchema 5.0.
refresh --force refresh fails if it finds an unreadable state payload, instead of silently overwriting it; --force replaces it, resetting the script ledger.
state pull [file] to pull the raw recorded state payload out of the configured backend. Writes to a file or stdout.
state push <file> to push the raw recorded state payload into the configured backend. Push takes the state lock (--no-lock to skip).
script command group to manage the scripts recorded in the state:
script list shows the recorded scripts (name, execution time, body hash); --json emits them as a single array.
script hash [name] computes the body hash of the project’s script declarations, bare on stdout for one script, or a listing of all of them, for hand-editing pulled state.
script taint <name> removes a script’s record, so it runs again on the next apply.
script untaint <name> records a script as executed without running it, using the body hash from the script’s declaration. Taint and untaint take the state lock (--no-lock to skip).