Feature list

Everything SQL Deploy does today, from the comparison rules to the script it writes and the desktop application that shows the plan.


SQL Deploy is a command line (sqldeploy) and a desktop application (SqlDeployGui) over one engine. This is what the product does today, not what is planned. Where a behaviour is a deliberate limitation it says so, because most of the design is in what the tool refuses to do.

What it compares

Objects How
Stored procedures, functions, views, triggers As text, after the normalisation below, together with the ANSI_NULLS and QUOTED_IDENTIFIER each was created under
Tables, indexes, constraints, user-defined types Through a schema model, not as text — the same column added twice is not the same as adding it once — and emitted as ALTER statements
Reference data Row by row against the table, by key; only the rows that differ are deployed
Anything else Not compared. Permissions, extended properties and ownership are never examined or touched

Objects present in the database but absent from source are listed in the report and left alone. Nothing is ever dropped.

Comparison rules

The text a server hands back is not the text that was deployed to it, so a naive byte comparison reports almost everything as changed. These rules are what make the comparison mean something.

  • The CREATE prefix is normalised. CREATE PROCEDURE, CREATE OR ALTER PROCEDURE and the length-preserved blank the server stores are all one thing. Only the first occurrence is rewritten; a CREATE inside a string or comment is left alone.
  • Only the CREATE batch is compared. SET statements before it and anything after the terminating GO — grants, test calls — are not part of the object.
  • Encoding is detected from the file, byte-order mark or not, so a UTF-16 file from one editor and a UTF-8 file from another compare equal.
  • Line endings and byte-order marks are normalised, and trailing whitespace on each line is removed, so a file re-saved on another operating system is not a change.
  • Brackets are optional, and off by default. [dbo].[Customer] and dbo.Customer can be treated as the same name when the manifest asks for it.
  • Session settings are compared, not just text. The same text created under different ANSI_NULLS or QUOTED_IDENTIFIER is a different object, and the generated script always sets both explicitly before each object rather than inheriting a client default.
  • Determinism. Two runs against an unchanged database produce byte-identical output. Sources are read in ordinal filename order, results are ordered by outcome and name, and no timestamp appears outside the header. The script can be diffed between runs.

The manifest

What to deploy is one YAML file, kept with the code. Where it goes is a separate environment file beside it, so one manifest serves development, UAT and production.

In the manifest What it says
steps What is deployed, in order: each step owns one kind of object, a set of schemas, a list of source folders or single files, and optionally its own database or server
root The directory every relative path resolves against, so a manifest in deploy/ can read sources the way the repository lays them out
ignore Patterns for the backup copies, one-off fixes and tooling artifacts nobody intends to manage. Ignored objects are listed, not hidden, and a pattern that swallows a source file raises a warning
defaults Comparison settings every step inherits: bracket handling, default session settings, whether session settings are compared at all
createEnvironment The databases the environment consists of, with their creation scripts, and postCreate steps — what turns a blank server into one the steps can deploy to
servers Logical server names that the environment resolves, so a step can target a mirror or a reporting server without naming it
meta A title and description, carried into the report and the application's title bar
${NAME} Anywhere in a string: replaced from a values section or the process environment, and fails loudly if unset — so a file never holds a password
In the environment file What it says
target Server, default database, integrated or sqlLogin authentication; an empty password is a deliberate "", not an unset one
allow What the desktop application may do here: upgrade, create, drop. Left out, only upgrade
guiMode ask (confirm, the default), approved (deploy without confirming; data loss still asks) or disabled (compare and copy the script, never deploy by clicking)
defaultDatabases Which of the declared databases start ticked for creation
output Where the script and report go, their names, single or per-database scripts, the transaction wrapper, the byte-order mark
  • Environments of your own. local/<manifest>.<name>.yaml beside the committed ones is a developer's private target, created from the repository's template, never committed, and shadowing a committed environment of the same name.
  • Every step needs a database, from its own key or the target's; a step with neither fails by name. A deployment never lands in whatever database a login happens to open.
  • A step can be limited to some environments, switched off (disabled), or compared and validated but kept out of the script (validateOnly).

Step types

Type What it does
procedure, function, view, trigger — or types: [...] for a folder holding several Compares each source file to the object in the database and emits only the ones that differ. The declared type is validated: a procedure in a folder called Functions fails the step and names the file, and the type bounds the search for objects that have no source file
tables Builds a schema model from the source — strict, from the files alone, or merge, filling the gaps from the database where the repository owns only part of a schema — and emits the ALTER statements. Merge mode cannot detect removals, and its plan is written out so the exact input can be inspected; it doubles as a migration path to strict
data Rows held as CSV or SQL files, matched by key, in one of four modes: seed (insert what is missing), update, merge (insert and update) or replace. Per-table keys, mode and trigger handling
scripts Files emitted verbatim in filename order, for anything that is safe to run repeatedly
exec One statement emitted verbatim — typically a call that regenerates derived objects, placed after the objects it needs
  • Dependencies are handled, not assumed. A table step that would drop a column a view or procedure depends on takes those dependants into the plan rather than failing at run time.
  • Best effort, per step. A source file the schema model cannot hold is left out and reported instead of failing the whole step, so a repository with one awkward file still deploys the rest.
  • Several servers, several databases. A step may name its own server or database. USE is emitted when the database changes; one script is written per server, since a script cannot hop servers without client-specific directives, and each server's run is atomic on its own.

The script

The generated script is the deliverable: it can be reviewed in a merge request, attached to a change record, or handed to whoever owns the database, and it runs the same under a command-line client and in a query window.

  • Plain T-SQL with GO separators and no client directives — no :setvar, no :on error exit; manifest variables are substituted at generation time.
  • UTF-8 with a byte-order mark, because without one some command-line clients decode in the console codepage and silently corrupt every character outside ASCII, string literals included. bom: false turns it off.
  • Progress as it runs. Every step and every object announces itself with RAISERROR ... WITH NOWAIT, delivered immediately rather than buffered like PRINT.
  • One transaction, one decision point. The script opens a transaction, refuses to start if one is already open on the connection, and commits only in a clearly marked final batch. Run it without that batch and the result can be inspected on the same connection before anything is permanent.
  • A failure stops everything after it. Guards record the reason, say it immediately and set NOEXEC ON, which persists across GO; XACT_ABORT covers runtime errors; every batch is followed by an @@ERROR check so a compile error — which throws nothing — still stops the script and is named. The final batch rolls back and says why, in every client, including one that continues past errors.
  • Data loss is guarded by default. A change that would drop a column with rows in it stops the script before it gets there; --allow-data-loss removes the guard, and the exit code still says review this.
  • Per-database scripts where databases have different owners or approvers, so a failure part way through one never leaves another half applied.
  • A header that says where it came from: the manifest, the environment, the commit (--source-ref), and whether objects were left out of this run by hand.

The command line

Command What it does
sqldeploy script Writes the script and the report, and stops. The comparison for a merge request
sqldeploy deploy Writes them, then runs the script — writing the artifacts before it connects, so what ran is on disk even when the run fails
sqldeploy exec Runs .sql files that already exist, for machines with no command-line SQL client
sqldeploy create Builds the environment a manifest declares: the databases, then what they need once they exist
--validate-only Validates every step, deploys and emits nothing — the pull-request guard
--environment, --server, --database, --out, --source-ref, --mode, --stop-on-error, --best-effort, --allow-data-loss, --no-transaction, --no-bom, --max-parallel Later layers win: manifest, then environment file, then command line
Exit code script deploy, exec
0 nothing destructive completed
1 generation failed; the script is not deployable errors were raised
2 deployable, but contains operations that can lose data

Exit 2 is a signal to review rather than a failure.

The desktop application

SqlDeployGui puts the same engine behind a window. It reads the same manifests and environment files and writes and runs the same script; nothing it does is unavailable from the command line. What it adds is seeing the plan before running it, and watching it run.

  • The plan as a tree. One row per step with a checkbox, and under it every object the run would change, each with its own checkbox. Unticking an object leaves it out of the script — and the script's header says so. An unticked step is out of the next comparison entirely.
  • A diff of each change, shown after the normalisation the comparison applies, so every highlighted line is a difference that counted. A table change is listed element by element with the part of the script that does it; a data change lists the rows per table.
  • Steps are compared in parallel and fill in as they finish, so a plan takes about as long as its slowest step. Changing the environment mid-comparison abandons the old one; a server that does not answer never traps the window.
  • Environments as a drop-down, listed from the files on disk; New environment… writes a private one from the template and ignores it in git for you.
  • Deploy asks first, listing the target, every step with work to do and anything that can lose data, and lets you choose best effort — one object at a time, retried in passes, no transaction — or step through — one step per transaction, pausing between them — for that deployment only.
  • Create and deploy in one press on a blank server: the databases are created, the sources compared against what was just built, and the plan deployed; if that comparison fails, nothing is deployed.
  • Server output as it arrives, the result of the last deployment, and a fresh comparison afterwards — what still differs is what is still to do.
  • Values the configuration needs, such as a password left as ${NAME}, are asked for and kept for the session only; a name that looks like a secret is masked.
  • The manifest and environment files are editable in place, with comments and layout kept.
  • What the application may do is the environment's decision — allow and guiMode — so a production environment file cannot be used to drop a database by accident, and can be set so production is compared here and deployed only by the release process.

Packaging and automation

  • One download per operating system: SqlDeploy-win-x64-<version>.zip and SqlDeploy-linux-x64-<version>.tar.gz, each holding both the command line and the desktop application, with SHA256SUMS and a release.json manifest. Framework-dependent: the .NET 10 runtime must be installed.
  • The About box names the version, build date and commit, and where the settings live, with a Copy button for support.
  • The repository builds, checks and releases itself: every push builds and publishes both hosts for both operating systems, and a release is a versioned run uploaded to the package registry.
  • A source check guards every build against leaked environment names, local paths, private addresses and passwords, so the repository can be shared.

What is not there

Drift detection — recording what was deployed so later out-of-band changes can be noticed — is not present. Removal of objects is not performed. SQL Server is the only engine.