Skip to content

Upgrading to v4

NSchema v4 turns providers and backends into plugins backed by independent NuGet packages. Packages are fetched at runtime, instead of compiling them directly into the CLI. That decouples a database engine provider’s release cadence from the tool’s, lets you pin an exact provider version, and lets you bring your own. This release also tidies up the command surface and removes the last of the non-config-in-SQL configuration.

This page lists everything that changed and the concrete edits you need to make. Most projects need three changes:

  1. Add a version to each PROVIDER/BACKEND block.
  2. Drop the NSCHEMA block,
  3. Update a handful of command names in your scripts and CI.

In v3 every provider shipped inside the CLI. In v4 they are separate NuGet packages that nschema restores on first use (it shells out to the .NET SDK to do so, so your standard NuGet configuration is conserved). Name and pin each one in your config:

PROVIDER postgres (
version = '4.0.0', -- new: required
connection_string = ''
);
BACKEND s3 (
version = '4.0.0', -- new: required (every backend except `file`)
bucket = 'my-state'
);

What you need to do:

  • Add a version to every PROVIDER block and every non-file BACKEND block to specify the plugin package version. The built-in local-file backend stays built in: no version, no plugin.
  • First-party labels (postgres, sqlite, sqlserver, s3) still resolve to their packages automatically. To use a third-party plugin, point a source attribute at its package id.
  • Make sure the .NET SDK is on your PATH wherever nschema runs (including CI) because the restore shells out to it. Your existing NuGet configuration (private feeds, etc.) is respected.
  • Run nschema init once up front to pre-fetch the plugins so the first real command isn’t delayed (operations restore implicitly anyway). Use --no-init to require the cache and skip the restore.

In v3, setting NSCHEMA_POSTGRES_CONNECTION_STRING alone was enough to select Postgres. In v4 a connection-string environment variable no longer self-identifies a provider: you must declare a PROVIDER block (with its version). The env var still overrides the connection string set in the block, so secrets stay out of your .sql files.

The NSCHEMA ( … ) block has been removed entirely. Configuration in v4 is just PROVIDER + BACKEND (see Configuration blocks).

  • destructive_action moved out of config. Set it with the --destructive-actions flag or the NSCHEMA_DESTRUCTIVE_ACTION_POLICY environment variable. See Destructive-action safety.
  • dialect / transaction_mode were never wired in and are gone too.

An NSCHEMA block now errors as an unknown configuration block, so delete it.

The operations are unchanged, but several command names moved. Update your scripts, aliases, and CI.

Creating a starter project is now nschema scaffold. nschema init became the plugin-restore command described above. The scaffolded PROVIDER / BACKEND blocks and sample schema are rendered by the plugins themselves.

v3 v4
nschema init nschema scaffold
(implicit) nschema init (restore plugins)

The lock verbs now live under a lock noun group:

v3 v4
nschema lock-status nschema lock status
nschema force-unlock <id> nschema lock release <id>

lock release skips its confirmation prompt with --auto-approve / -y (consistent with apply and destroy) instead of the old --force. The lock-id safety check is unchanged. v4 also adds lock acquire to hold a lock for out-of-band coordination, and a --no-lock flag on apply/refresh/destroy to run without taking the lock.

The single show command is gone, split by the thing it renders:

v3 v4
nschema show nschema state show
nschema show <plan-file> nschema plan show <file>
nschema show --online nschema db show

state show reads the recorded state offline (the state noun group will grow pull/push/move); plan show renders a saved plan file; and the live-schema view that was show --online is now its own command, db show, under a db noun group, read directly from the database through the provider.

Installing shell completion is now a pair of subcommands instead of flags:

v3 v4
nschema completion --install-autocomplete nschema completion install <shell>
nschema completion --uninstall-autocomplete nschema completion uninstall <shell>

nschema completion <shell> still prints the raw script to stdout.

  • doctor reports plugin problems as diagnostics. A provider or backend that fails to restore or configure is now surfaced by doctor as a health-check finding (all such problems at once) instead of aborting on the first one.

If you drive NSchema.Core directly instead of through the CLI, v4 reworks the application surface. See Configuration (C#) for the current shape; the breaking changes are:

  • Operations moved to app.Operations and return Result<T>. await app.Apply(args) becomes await app.Operations.Apply(args), and the like for Plan/Refresh/Validate/Drift/Import/Doctor. An expected failure (a policy violation, lock contention) now comes back as result.IsFailure with diagnostics instead of throwing.
  • Apply now requires a plan. ApplyArguments now takes a required Sql (a SqlPlan). The flow is two calls: var plan = await app.Operations.Plan(new PlanArguments { Target = PlanTarget.Live }); then await app.Operations.Apply(new ApplyArguments { Sql = plan.Value!.Sql! });.
  • Saved plans round-trip through app.PlanFile. ApplyArguments.PlanFile is gone; read a saved plan with await app.PlanFile.Read(path, ct) and apply envelope.Sql. Writing is still PlanArguments.OutFile.
  • PlanDestroy/Destroy are gone — a teardown is a Plan with Target = PlanTarget.Teardown, applied like any plan. Show/ForceUnlock are gone too: read state via app.CurrentSchema/app.PlanFile, and manage the lock via app.Locks.
  • IOperationReporter/UseReporter are gone. Structured output is in the Result<T> you serialize yourself; for live progress, register an IProgress<OperationProgress> with UseProgressReporter<T>().
  • NSchemaApplicationOptions.ExceptionBehavior is gone. Expected failures are Result failures; genuine exceptions still propagate out of the call. The engine prints nothing itself.
  • The application is reusable. The single-use guard is removed — run multiple operations through one instance, then dispose it.
  1. Add a pinned version to every PROVIDER block and non-file BACKEND block.
  2. Ensure the .NET SDK is on PATH everywhere nschema runs, then nschema init to pre-fetch plugins.
  3. Add a PROVIDER block anywhere you previously relied on a connection-string env var alone.
  4. Delete the NSCHEMA block; move destructive_action to --destructive-actions / NSCHEMA_DESTRUCTIVE_ACTION_POLICY.
  5. Update command names in scripts and CI: initscaffold, lock-statuslock status, force-unlocklock release, showstate show / plan show, and the completion install/uninstall flags → subcommands.