Models answer their own dependencies.View.Reads(schema), Routine.References(schema), Column.References(schema), CheckConstraint.References(schema), and Trigger.References(schema) (its function reference plus its scanned body and WHEN predicate).
SqlLexer: one lexical layer for engine SQL. A public, tolerant scanner over the amorphous “SQL grammar” — nestable block comments, '…' strings, "…" and […] identifiers with doubling escapes, etc.
Changed
The whole migration is linearized from the dependency graph. Action ordering is a priority-respecting topological sort: every create runs after what it requires and every drop before what requires it.
Fixed
Multi-statement bodies round-trip. A routine definition or view body may now be written as a single dollar-quoted block ($$ … $$), the same quoting triggers and scripts already use, and import writes that form whenever the bare text would not re-parse. A T-SQL body’s top-level ; previously ended the statement early, so an imported SQL Server routine could not be read back.
Trailing line comments no longer swallow what follows.import guards a routine definition or argument list that ends in a -- comment, which previously commented out the closing ) or ; the writer printed on the same line.
Lockfiles can be updated additively.LockFile.With(pins) applies a resolution over the existing file, replacing the entry for each source it names and appending the rest, so partial resolutions don’t clobber other pinned versions.
Fixed
Multi-line doc comments indent correctly. Every line of a --- doc comment on a column or setting now takes the member indent, rather than only the first.
A state payload with no captured schema is rejected. Reading one now fails as an unreadable payload, instead of throwing an NRE.
Clustered indexes.TableIndex.Clustered, PrimaryKey.Clustered and UniqueConstraint.Clustered record whether a relation’s rows are physically ordered by that index, written as T-SQL writes it.
A relation may declare only one clustered index.multiple-clustered-indexes is an error: a clustered index is the relation’s row order, so a second one has nothing left to order.
Clustering an engine cannot honor is reported rather than dropped.SqlDialect.SupportsClustering says whether the engine has clustered indexes at all.
XML schema collections.Schema.XmlSchemaCollections holds the named XSD bundles a typed xml column is validated against, written as CREATE XML SCHEMA COLLECTION s.name AS '…' and bound from a column as xml(DOCUMENT s.name).
XML indexes.TableIndex.Xml carries an XmlIndexDefinition — the kind (primary, path, value, property) and, for a secondary, the primary XML index whose node table it reads. Written as SQL Server writes it.
Views declare their schema binding.View.IsSchemaBound records whether a view is bound to the schema of what it reads, written in NSQL as CREATE VIEW name WITH SCHEMABINDING AS …, the spelling SQL Server uses.
Changed
An unscoped run stays unscoped. It no longer derives a schema list from the project and the recorded state. What a run may touch is the managed set’s job.
A view may carry indexes whether or not it is materialized.CREATE INDEX … ON schema.view no longer requires the view to be materialized.
CreateIndex says whether its owner is a view. The new OnView flag rides the action, as IsMaterialized does on the view actions.
Fixed
Objects managed inside an unmanaged schema are no longer stranded. An object can be managed in a schema that is not.
A comment opening an opaque body survives the round trip. A view body or routine definition whose first line is a comment lost it on re-parse. This is now fixed.
A view carrying indexes is recreated, not replaced. Indexes hang off a view’s stored form, and SQL Server’s CREATE OR ALTER VIEW drops an indexed view’s indexes outright, so redeclaring one in place destroyed them silently. A body or binding change on a view carrying indexes now drops and recreates it, rebuilding the indexes with the definition.
A plugin may be declared by path.PLUGIN db ( path = './artifacts/NSchema.Postgres.dll' ) names a built assembly instead of a package to resolve, so a provider can be exercised straight from dotnet build output with no packing, feed or restore.
Changed
LockFile.Resolve takes a PackageReference rather than a PluginDeclaration. A plugin declared by path has no version to resolve and no lockfile entry, so it is now unable to reach this rather than guarded inside it.
A PLUGIN statement with no attributes reports one finding, not two. It used to report source and version as separately missing; with path as an alternative neither is required on its own, so it reports missing-plugin-origin instead.
RESTRICT is a referential action in its own right.ReferentialAction.Restrict is parsed, written and rendered.
SqlDialect.SupportsRestrict says whether the engine has RESTRICT as distinct from NO ACTION. Providers have to opt in.
A generated column records whether it is stored.Column.IsStored and the NSQL STORED / VIRTUAL keywords.
SqlDialect.SupportsVirtualGeneratedColumns says whether the engine can leave one unstored. Off by default, matching Postgres, which stores every generated column.
virtual-generated-column-not-supported is a warning: a virtual generated column declared against an engine that always stores.
A column records whether it is the table’s row identifier for merge replication.Column.IsRowGuid and the NSQL ROWGUIDCOL keyword.
A column default can carry its constraint’s name.Column.DefaultConstraintName and the NSQL CONSTRAINT <name> DEFAULT <expr> form, for engines that make a default a named constraint.
An identity and a trigger can stand aside for a replication agent.IdentityOptions.NotForReplication, Trigger.IsNotForReplication, and the NSQL NOT FOR REPLICATION form on both.
DiagnosticSources declares every source the engine reports under, and DiagnosticSources.All offers them to a caller checking a configured name against the sources that exist.
Changed
Diagnostic sources and codes are a configuration contract, so this release settles their naming before the next CLI makes them addressable from .editorconfig.
Fixed
Trigger.Clone(), Equals and GetHashCode cover every field, as Column’s now do. Both enumerate fields by hand, and import clones every object on its way to a file.
Statements contain their originating action type.SqlStatement.Action names the MigrationAction a statement came from, and is included in the plan file.
Fixed
Invalid plan files now throw immediately. The JSON held in a plan file is now more strictly deserialized so as to catch an invalid payload earlier.
SqlStatement keeps the constructor providers were built against. 5.9.1 added the action as a positional parameter, which deletes the arity every already-compiled provider calls.
Authored check constraints settle. Check expressions are opaque SQL, rewritten by the engine, so a handwritten constraint would never match. Both definitions are now kept and declared like-for-like as they are for triggers, and routines.
SqlDialect.SupportsComments says whether the engine records a comment at all. True unless a dialect says otherwise, which is the opposite of the other capabilities: they describe something extra an engine might do, while this describes something nearly every engine does, so a provider built before the flag has to keep meaning yes.
comments-not-supported is a warning: documentation declared against an engine that records none, naming every object it covers in one finding rather than one per action skipped while rendering.
Every remaining authored expression settles. Column defaults and generated expressions, index and exclusion predicates, and a domain’s checks and default are opaque SQL, rewritten by the engine, so a handwritten one would never match. All are now kept and declared like-for-like as they are for triggers, routines etc. An expression the database no longer reports is still drift, not a spelling to restore.
Renaming a type no longer retypes the columns declared against it. The rename moved the type but left every reference naming the old one, so each column read as a retype.
Recreate is now correctly blocked by dependents. Recreating a type that’s in use now causes an error.
An identity that explicitly declares no options no longer differs from one that does so implicitly. No options at all and a set of unstated ones now compare equal.
A sequence altered for one reason no longer restates the others. The change carries the folded options, so a plan that changes the cache does not also restate a start it never asked to change.
Identity and sequence restarts now warn correctly. Restarts are data hazards: restarting the counter, means duplicate values are issued, meaning inserts collide with what is already stored.