All versions since 4.3.1
4.3.1
Fixed
- The diff now shows an added or removed column’s default expression and identity marker, so a column definition reads the same everywhere it appears.
- DDL syntax errors now name the file the error was found in, alongside the existing line and column.
- Import no longer repeats the
CREATE SCHEMAstatement in every object file; only the per-schema header declares the schema. - Import now writes the per-schema header to
<schema>/schema.sqlinstead of<schema>.sql.
4.4.0
Added
- Unified
SCRIPTstatement.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 CONSTRAINTwith a target path); RunConditionon scripts and data migrations, carrying the parsedRUNclause.- The backend state store now carries the recorded script executions.
- A
RUN ONCEscript 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
MIGRATIONblock can now be declared inside aTEMPLATE … BEGIN … END;body with an unqualifiedtable.memberpath; 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. DdlWriterrenders deployment scripts and named data migrations in theSCRIPTform; anonymous migrations keep the legacy spelling.
Deprecated
- The
PRE|POST DEPLOYMENT '<name>' AS $$…$$;andMIGRATION ['name'] FOR <trigger> <path> AS $$…$$;forms. Both still parse into the same model, and plan/apply/validate now surface adeprecationswarning naming theSCRIPTreplacement. They will be removed in NSchema 5.0.
Fixed
- The formatter no longer re-indents the interior of a dollar-quoted body inside a
TEMPLATEblock, which grew the indentation on every pass and changed the SQL a routine or migration body carries.
4.5.0
Added
- Public state access. The recorded state is now a public model.
ISchemaStateManager, exposed asapp.State, facilitates reading and writing the recorded state, with an optionalReadRaw/WriteRawmethods for moving the serialized payload without interpreting it.
Changed
- Planning with an unreadable state payload now fails with a diagnostic instead of throwing.
4.6.0
Added
- Public desired-schema access.
IDesiredSchemaProvideris now public and exposed asapp.DesiredSchema. HashonScriptandDataMigration. For reading the canonical hash of the body.
Changed
- Refresh no longer silently replaces a state payload it cannot read: it fails unless
RefreshArguments.Forceis 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.
4.6.1
Changed
RUN ONCEdeployment scripts that have already been run no-longer show up as an informational diagnostic.
5.0.0
v5.0 is a Core rearchitecture, aiming for better project health, with clear separation between layers and a better separation of concerns.
Changed
- Any policy’s findings can be configured, not just the built-in ones.
WithDiagnostic(code, enforcement)configures one finding andWithDiagnosticsFrom(source, enforcement)a producer’s whole family, replacingDestructiveActionOptionsandDataHazardOptions— a policy no longer needs its own options type and a builder method to be configurable, so a policy you register is configurable too. A policy reports at the severity it judges natural and the engine applies the override, soWithDestructiveActionsandWithDataHazardsremain as named shortcuts for the two built-ins. - A diagnostic’s
Sourceis what produced it, and only that. The differ reported findings under four topics (scope,directives,run-once,data-migrations) where the producer was the differ; those topics now live in the code, which is where a finding’s identity belongs. - Column alterations are unified. Dialects now receive a single
AlterColumnaction for a column’s type and nullability changes, which they can render as one or more statements. - Member diffs are built by factory.
PrimaryKeyDiff,ForeignKeyDiff,UniqueConstraintDiff,CheckConstraintDiff,ExclusionConstraintDiff,IndexDiffandTriggerDiffexposeAdded(definition),Removed(name)andCommentChanged(name, change)in place of a constructor. - Object diffs are built by factory too. Every schema-level diff — table, column, view, enum, domain, sequence, routine, composite type, extension and schema is constructed through
Added/Removed/Modifiedand refined withwith, replacing constructors. - Better domain mode naming.
DatabaseandSchemaare now the main entry points into the domain model. - The SQL dialect seam is the abstract
SqlDialectclass. A dialect overrides one method per migration action. Standard SQL is rendered by the base through an overridable identifier-quoting kernel. - Identifiers are case-sensitive. An identifier’s identity is its exact written text:
usersandUsersare no-longer considered equivalent. - Identifiers can be quoted.
CREATE TABLE app."Order Details" ("weird ""col""" int)all work, and lets a name collide with a keyword. Square brackets are accepted as an alternative spelling, soCREATE TABLE app.[Order Details] ([weird ]]col]]] int)reads the same (]]escapes a bracket). Quotes and brackets are syntax, not identity: casing is significant with or without them, and either spelling names the same object. The writer (and import) quotes only names that need it, always with double quotes, and extension names now render as quoted identifiers rather than string literals. - Parsing is lossless. The syntax tree now preserves every character of the source, including comments, whitespace, and layout.
- Management directives. The language now separates declarations (what the schema is) from directives (how the difference is managed). This includes RENAME and SCRIPT.
- Every namespace has moved. Namespaces are vertically sliced of the form
NSchema.<Feature>.<Capability>. - A slice’s domain layer is
Domain, notModel. - The shared primitives live in
NSchema.Diagnostics. - The schema model is
NSchema.Modelnow. It owns the top-level domain model for databases. - The model is layered by what contains what. A
DatabaseObjectbelongs to the database, aSchemaObjectto a schema, and anObjectMemberto a schema object. - An element’s
Addressis never null. A schema object addresses itself as anObjectAddressand a member as aMemberAddress, so neither needs narrowing or a cast. - A kind belongs to one level.
DatabaseObjectKind,SchemaObjectKindandMemberKindreplace the singleObjectKind, which spanned schemas’ contents and the database’s own. - An address is named for where it sits, not for what it names.
DatabaseAddressreplacesSchemaAddressand carries the kind as data (DatabaseAddress.Schema(name),DatabaseAddress.Extension(name)), so a new database-level kind needs no new address type.MemberAddresstakes an optional kind, matchingObjectAddress. - A script is identified, not addressed. A script decorates the schema rather than living in it, so
ScriptReferencereplaces the address it used to carry andScript.ReferencereplacesScript.Address. A script’s scope is a schema name rather than an address, and the run-once ledger keys on the reference. With extensions addressed as the database objects they are, no address carries an optional schema any more. - The diff interfaces name the same levels as the model.
IDatabaseElementDiffreplacesINamedObjectDiffas what every diff node shares, andIObjectMemberDiffis the member level — so an index and a trigger diff sit with the columns and constraints rather than alongside an extension.IMigratableDiffnarrows to a member diff that can carry a script, andTableDiff.EnumerateMembers()returns the member abstraction. - A diff node’s
Changereplaces itsKind.Kindnow means what sort of thing something is, so the diff says what happened to it instead:diff.Change == ChangeKind.Add. ChangeKind.Touchedreplaces a null change. A schema in the diff only because its contents changed isTouchedrather than carrying no change at all, soSchemaDiff.Changeis always present andIDatabaseObjectDiffcovers both schemas and extensions.Touchedis the enum’s default, so an unset change reads as “unchanged” rather than “create this”.- DataMigrations are Scripts now. This reflects the syntax changes introduce in [4.4.0] so the model becomes consistent.
- Templates accept object-level directives. A
TEMPLATEbody may now contain the object-levelRENAMEdirective (table, column, view, enum, domain, type, sequence, routine) alongside its declarations and scripts. - Scripts split into
ChangeScriptandDeploymentScript.Scriptis now an abstract base carrying the common behavior (name, SQL, scope, hash, reference, run condition); - “Desired” is Project now.
IDesiredSchemaProviderbecomesIProjectProvider— the project is the desired state by definition, andapp.DesiredSchemabecomesapp.Project, named for what it hands you like every surface beside it. Progress output follows:Loading project...,Validating project..., and the plan census now readsDeclared:/Recorded state:. AddDdlSchemasisAddProjectSourcenow. The files describe the whole project (schema, scripts, templates, config), not just schema DDL.- Result
use consistency. Lots of interfaces have been neatened up to return aResult<T>instead of throwing to allow for error/warning accumulation. - The diff now includes scripts. Rather than being tacked on to the plan, scripts are now a first-class part of the diff, carried where they run rather than in a central list.
- Cohesive plan artifact. There’s now a single
MigrationPlanmodel that represents the plan in its entirety rather than being spread acrossSqlPlan,PlannedMigration, etc. - Providers are required. Providers are now required for planning, because the SQL is built into the plan model.
- Plan errors are non-blocking. Even when the plan has errors, you can now still access the resultant plan.
- Planning always diffs recorded state against the project. There is no longer an option to plan against the live database.
- A teardown is a plan towards an empty schema.
PlanTarget.EmptyreplacesPlanTarget.Teardown, and it obeysPlanArguments.Scopelike any other plan. - Teardown plans run the policies. A teardown is fully destructive, so the default destructive-action policy blocks it; the blocked result still carries the complete plan. Set the destructive-action enforcement to
Allowto apply one. - Policies are enforced at apply.
Applynow re-runs all policies against the plan diff before executing. - Policies now cover project and plan.
IProjectPolicyreplacesISchemaPolicyandIPlanPolicyreplacesIDiffPolicy. SqlDialectreplacesISqlGenerator. (registered withUseSqlDialect<T>()).IStateLockManagerreplacesIStateLockCoordinator. Lines up with withISchemaStateManager.IPlanFileManagerreplacesIPlanFileWriter. It reads saved plans too, so “writer” undersold it.UseFileStatereplacesUseFileStateStore. It registers the lock alongside the store, so “store” undersold it — matchingUseEphemeralState, which does the same.UseStateStoreandUseStateLockstill register a single seam each.TableDiff.PrimaryKeysreplacesTableDiff.PrimaryKey. It has always been a list — replacing a key is a remove and an add — so the singular name misread as one change.- The serialization types live in
NSchema.Model.Serialization. They move out ofNSchema.Model.Services, which is for services over the model. options.AddModelConverters()composes the model’s JSON conventions. A consumer serializing NSchema types adds the converters to its ownJsonSerializerOptionsrather than naming them — persistence wants indented, complete output and a console stream wants terse single lines, so only the conventions are shared. The converters themselves are internal now.IDatabaseIntrospectorreplacesISchemaProvider. More honest about what it does now that the interface doesn’t serve both the current and desired sides, and named for what it returns.- Plugin
ConfigurereturnsResult. Configuration errors are diagnostics like everything else. - Opaque SQL is
SqlTextnow. Every schema-model field carrying SQL that NSchema stores verbatim but does not interpret is typedSqlTextinstead ofstring. - A qualified type’s schema is a component now.
SqlTypecarries the schema of a user-defined type (e.g.appinapp.order_status) as a structuralSchemaproperty rather than folded into its nam. PolicyEnforcementabsorbs theDestructiveActionPolicyenum. The shared enum gainsIgnore.WithDestructiveActionsandWithDataHazardsreplaceWithDestructiveActionPolicyandWithDataHazardPolicy. They set how a built-in policy is enforced, which the old names read as registering one —AddPlanPolicy<T>()is what registers.- The state ledger field is
scriptsnow. Pre-5.0executedScriptspayloads read as an empty ledger. Refresh (or untaint) existing state under the state-format compatibility policy’s major-version rules. - Configuration is part of the language.
ENGINE,PLUGIN,DATABASEandSTATEparse as ordinary statements, in the one grammar that carries declarations and directives. Which statements a given file should hold is a consumer’s rule, not the parser’s: each subsystem picks out the statements it understands and ignores the rest. DATABASEandSTATEreplacePROVIDERandBACKEND. Each names the thing it configures rather than the role that supplies it.- Every setting is environment-overridable.
NSCHEMA_<KEYWORD>_<SETTING>overrides one setting on the matching statement —NSCHEMA_DATABASE_CONNECTION_STRINGsetsconnection_stringonDATABASE— replacing the written value or supplying one the statement omits, so a secret need not be committed. The keyword scopes the name becauseDATABASEandSTATEmay both take a setting of the same name. - Plugins receive
PluginSettings.Configuretakes the statement’s label and setting values as a flat key/value map (settings.Values, orsettings.Value(name)for one), translated from the parsed statement by the configuration assembly.settings.Get<T>()binds them onto an options type — snake_case keys match properties, dotted keys nest, identifiers map to enum members — and reports an unbindable value, an unknown setting, and a failed[Required]/[Range]as error diagnostics, so a plugin declares its options rather than parsing them. INSchemaDatabasePluginandINSchemaStatePluginreplaceINSchemaProviderPluginandINSchemaBackendPlugin. Each is named for the statement that configures it.- Plugins are resolved by capability, not by name.
INSchemaPlugin.Labelis gone — the statement kind selects the capability interface, and the label in configuration is the user’s local name for a declaredPLUGIN, never the plugin’s own.ScaffoldContext.Versionis gone with it: the host authors thePLUGINstatement (it knows the package and the resolved version), so a plugin’s scaffold template contributes only its own configuration statement. NsqlReaderreplacesDdlReaderand diagnostics are structural.NsqlReader.Read/ReadFilereturnResult<NsqlDocument, NsqlDiagnostic>, the new diagnostic-typed result, with each finding carrying its source position.DdlReader.ReadreturnsResult<DdlDocument>. A syntax error is an error diagnostic instead of a thrown exception, and the parser now recovers at statement boundaries.DatabaseSchemais pure data now.FilterjoinedCombineoff the model, into the projection machinery.DiffDocument.From(diff)replacesDiffReader.Read(diff). The reader type and its.Defaultsingleton are both gone, and the namespace isNSchema.Diff.Rendering: “reader” named the wrong direction, since aDatabaseDiffis projected into display lines rather than parsed from anything.SchemaScopereplaces bare schema-name arrays.GetProject,GetSchema, and the plan/drift/import arguments take a scope record.- State locks receive complete metadata.
IStateLockManager.AcquiretakesAcquireLockArgumentswith the operation, TTL, and skip-lock; it creates theStateLockInfoatomically recorded byIStateLock.Acquire. - Project reads report every broken file at once. An unreadable or unparseable file (and no-files-matched) is an error diagnostic on the project.
- Index keys and exclusion elements are column-or-expression now.
IndexColumnandExclusionElementcarry mutually exclusiveColumn(an identifier) andExpression(verbatim SQL) properties. - References are value objects now.
Trigger.Functioncarries aRoutineReference(optionally schema-qualified; unqualified resolves via the engine’s search path) ObjectAddressaddresses a schema-level object. Always fully qualified. Each part compares as an identifier. State and plan-file payloads serialize the address structurally.- Object names are
SqlIdentifiernow. Every name-bearing property across the schema, diff, plan, and state models carries a value object. - Template migrations are decoupled from their tables. Migrations can be declared in any template for any table.
- Scripts execute as woven statements. The linearizer weaves the diff’s scripts into the ordering so scripts are now first-class actions.
- Planning and applying now require a state store. Use the new ephemeral store if you need to run without persistent state for CI or integration tests.
- State records what NSchema manages. Alongside the full observation and the run-once ledger, state carries the managed identity set: what an apply has created or adopted. A plan only ever covers managed/declared objects, so an object missing from the project only triggers a drop when the state contains a managed object, and objects outside the managed state are never touched.
- Teardown destroys what NSchema manages.
Plan(Empty)converges the managed set — not everything ever observed — towards nothing. - Managed extensions honor removal-by-absence. A declared extension becomes managed on apply and is dropped when un-declared.
- A foreign key into an undeclared table is a warning, not an error. The target may exist unmanaged (gradual adoption), so the structural policy advises instead of blocking.
- Constraints fold into table creates. A newly-created table’s foreign keys, unique and check constraints are now rendered inline in its
CREATE TABLErather than as trailingALTER TABLE ADD CONSTRAINTstatements.
Added
- Enforcement that cannot be honored says so. Configuring a structural finding to be reported more leniently reports
cannot-be-loweredrather than quietly doing nothing, once per finding however many times it occurs. - A finding says whether it may be overruled.
DiagnosticKindmarks a finding asStructural— NSchema cannot do what was asked, so silencing it would produce something wrong rather than something permitted — orAdvisory, a judgement the caller is entitled to overrule. Enforcement can raise any finding, but can only lower an advisory one, at whatever severity it currently carries. Reporting below error severity declares a finding advisory, so a warning or an informational finding is silenceable by default and an error is not — and because the kind is fixed when the finding is created, raising one later cannot change what a caller is allowed to silence. - A diagnostic’s source is a
DiagnosticSource. The producer’s name is typed rather than a bare string, and holds to the same shape as a code — a user groups and configures by it, so it has to be usable as a settings key too. - A diagnostic carries a code.
Diagnostic.Codenames a finding independently of how it is worded, so a message can be reworded without breaking anything that refers to it. A code is restricted to hyphen-separated lowercase words, so it is usable as a settings key, and an invalid one is rejected rather than rewritten. Every code is unique across NSchema — the producer is not always known at compile time, so the code alone addresses a finding. - A diagnostic collection folds to a result.
ToResult()andToResult(value)move ontoDiagnosticCollection<TDiagnostic>, so accumulating findings and returning them needs no collector. A collection can also be built from a collection expression. - A typed result lifts like a plain one.
Result<TValue, TDiagnostic>converts implicitly from a value and from a single diagnostic, and a value-lessResultconverts from a diagnostic — so a method returns one directly instead of naming the result type to construct it. SqlDialect.CanAlterForeignKeys. A dialect states whether a foreign key can be added to, or dropped from, a table that already exists. One that cannot (SQLite) keeps every key on theCREATE TABLEthat declares it, and the plan never separates one from its table.- The comparison seam is the
SqlEquivalenceclass. A provider can register (UseSqlEquivalence<T>()) one equality comparer per comparison context, deciding when two spellings mean the same thing. The neutral base compares types structurally and defaults on cosmetics-normalized text. - Default expressions are
SqlDefaultExpressionnow.Column.DefaultExpressionandDomainType.Defaultgraduate fromSqlTextto their own value object, so equivalence rules register against the specific context. - Change-script targets are value objects.
ChangeScriptnow takes aChangeTargetinstead of separate trigger and path values. - Atomic table-fragment merge.
Table.TryMergeMembersapplies a complete table-member fragment or reports its conflicts. - SQL type conversion risk.
SqlType.ConversionRiskToassesses whether converting stored values can fail. - Ephemeral state.
UseEphemeralState()registers an in-memory state store, which serves as the lock too, intended for disposable databases. - Object-granular targeting.
PlanningScopecovers a single list ofAddresses (PlanningScope.To(addresses)/scope.Addresses), mixing whole-schema and object-level targets. - Address parsing.
NsqlReader.ReadAddressparses aschema,schema.object, orschema.object.memberfragment into anAddress, resolving quoted segments - Address containment.
Address.Covers(other)expresses downward containment (a schema covers its objects and members; an object covers its members). - Scope and identity are address-based.
PlanningScopeis scoped when it holds any address (schema or object). ObjectAddresscarries an optionalKind. A null kind addresses every kind sharing the name (kind-free targeting); a set kind disambiguates same-named objects.Token.QuotedIdentifier. Synthesizes a quoted-identifier token (decoded text, quoted-and-escaped raw), the counterpart toToken.StringLiteral.- Configuration statements are built by factory.
SettingsStatement.Database(label)/.State(label)/.Plugin(label)/.Engine()/.Lock()replace the constructor, refined withWithSetting(key, value),WithDocComment(text)andWithLeadingComment(text). - A plugin declares its scaffolding questions.
INSchemaPlugin.GetScaffoldPrompts(context)returns theScaffoldPrompts a front-end should put to the user, and the answers arrive onScaffoldContext.AnswersforGetScaffoldTemplateto build its statement from. PLUGINdeclares plugin dependencies.PLUGIN <label> ( source = '…', version = '…' );separates dependency declaration from configuration.ENGINEasserts the engine and/or host version.ENGINE ( version = '…', host_version = '…' );—versionis checked against the engine (Core),host_versionagainst the host tool.- The engine handshake.
PluginHandshake.Validatechecks a loaded plugin assembly against the hosting engine before any of its types are instantiated. ConfigurationProvider.Loadloads a configuration. One call from ordered configuration layers (a later layer overrides an earlier one) to a validatedConfigurationDefinition.- The plugin lockfile.
LockFileManagerreads and writesnschema.lock(aLOCK ( source = '…', version = '…' );grammar) as aLockFileofLockedPluginpins.LockFile.Resolve(declaration)resolves a declaration against the lock. VersionRange.IsExact/ExactVersion/Highest. Report whether a range pins a single version and the version it pins, and select the highest of a supplied set of versions that the range admits.- Plugins split from configuration. The provider interfaces stay in
NSchema.Plugins(INSchemaPluginand friends, the handshake,ScaffoldContext); everything a project declares, now lives underNSchema.Configuration.PluginSettingsis inNSchema.Configuration.Plugins.
Removed
Address.SchemaName. An address answers where it sits through its own components.PRE|POST DEPLOYMENT '<name>' AS $$…$$;andMIGRATION ['<name>'] FOR <event> <path> AS $$…$$;no longer parse.- The
DROPstatements (DROP SCHEMA|TABLE|VIEW|ENUM|DOMAIN|TYPE|SEQUENCE|FUNCTION|PROCEDURE|ROUTINE|EXTENSION) no longer parse. Remove the declaration instead. CREATE PARTIAL SCHEMAno longer parses. The managed identity set covers what it was reaching for: an object the state does not record as managed is never dropped, declared or not.RENAMED FROMclauses no longer parse. A rename is aRENAMEdirective now (see Management directives above).- The
NSCHEMAconfiguration statement no longer parses. DataMigrationhas been folded intoScriptand now requires a name for so they can maintain a stable identity.- Narrowed public surface. A variety of types that should never have been exposed have been made internal.
PolicyDiagnostics,PluginConfigureResult, and theDestructiveActionPolicyenum — all made redundant by first-class severity onResultand the sharedPolicyEnforcement.DdlSyntaxException,PlanFileDeserializationException, andStateDeserializationExceptionare now internal; the read seams surface these failures as diagnostics.MigrationAction.IsDestructivehas been removed. Destructiveness is judged from the diff byDestructiveActionPolicy, not per action.ViewDiff.DependsOnhas been removed.
Fixed
- Tables are created and dropped in foreign-key order. A table is now always created after the tables it references and dropped before them, across schemas, so a teardown no longer fails on a table another one still points at. Where tables point at each other no order can satisfy every key, so the ones it cannot are moved out of the way: on a drop the constraint goes first, and on a create it is added once both tables exist rather than riding the
CREATE TABLE. A dialect that cannot alter foreign keys says so withSqlDialect.CanAlterForeignKeys, and keeps every key on the table that declares it. - Schemas are no longer created implicitly. Creating an object without also creating its schema will no-longer auto-adopt the parent schema.
- Adoption-only plans now count as non-empty. Running a plan that changes nothing, and only imports objects into the managed set now correctly reports as having changed.
5.0.1
Fixed
- Policy enforcement ignored on apply/destroy. Running a plan or apply will now correctly respect policy enforcement adjustments, so destructive actions can be made allowed.
5.1.0
Changed
Schema.IsImplicitis now part of the provider contract. Each introspector should set it to report the schemas its engine provides. It’s also now recorded in the state payload.
5.2.0
Added
- Type references resolve against the database. A plan now verifies that every type the project references will exist once it applies.
- The type vocabulary is part of the provider contract. An introspector can capture the types the engine and its extensions provide (
NativeType) and record which extension provides an object (ProvidedBy). - Extensions now include their types. A plan that drops an extension now blocks when data still depends on its types.
- Types are a category.
TypeObjectis the common base of everything a type reference can resolve to:EnumType,DomainType,CompositeType, and nowNativeType.
Changed
IsImplicitandProvidedBylive on every database element. An implicit object, like a container-only schema, or a native type, is never created, dropped, managed, or imported, whatever its kind.
Fixed
- Schema-qualified engine types no longer block planning. A reference like
pg_catalog.tsvectoron an imported column previously demanded a declaration nobody could write.
5.3.0
Changed
- A create is a create; a replacement says so. A definition-only change to a routine or view now emits the new
ReplaceRoutine/ReplaceViewactions instead of reusingCreateRoutine/CreateView. - Routines are created after the tables they may reference. Routine actions now order after table creation and before constraints, triggers, and views.
5.4.0
Added
- Aggregates.
CREATE AGGREGATE name(args) (…)is now supported as a kind of routine. - Routine actions include signatures. Routine-based actions now include the routine signature in their arguments for engines that require a signature to drop a routine.
Changed
- Routines are created in dependency order. A routine’s definition is scanned at projection for the objects it references — the tables and views its queries read, and the routines it calls — and creates are ordered so a callee precedes its caller, across schemas. The scan is deliberately shallow and errs wide: a reference only matters when it names an object in the same plan.
Fixed
- Teardown mirrors creation. Routines now drop before the tables they may reference, an aggregate drops before the functions it is assembled from, and a dropped table sheds its triggers explicitly first.
5.5.0
While this is technically a bug fix, it does also make additive changes to the state file in the form of capturing every opaque SQL body twice, one as-written in the project, and again as the database stores it.
Fixed
- Hand-authored definitions now don’t cause drift. For objects that store a provider-specific body (triggers, routines, views, etc.), the state now records both the project-side and database-side definitions for comparisons. Planning does its best to compare the different versions symmetrically, so the two being written differently doesn’t show as drift.
5.6.0
Added
- Models answer their own dependencies.
View.Reads(schema),Routine.References(schema),Column.References(schema),CheckConstraint.References(schema), andTrigger.References(schema)(its function reference plus its scanned body andWHENpredicate). 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, andimportwrites 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.
importguards 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.
5.6.1
Added
- 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.
5.7.0
Added
- Clustered indexes.
TableIndex.Clustered,PrimaryKey.ClusteredandUniqueConstraint.Clusteredrecord 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-indexesis 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.SupportsClusteringsays whether the engine has clustered indexes at all. - XML schema collections.
Schema.XmlSchemaCollectionsholds the named XSD bundles a typedxmlcolumn is validated against, written asCREATE XML SCHEMA COLLECTION s.name AS '…'and bound from a column asxml(DOCUMENT s.name). - XML indexes.
TableIndex.Xmlcarries anXmlIndexDefinition— 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.IsSchemaBoundrecords whether a view is bound to the schema of what it reads, written in NSQL asCREATE 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.viewno longer requires the view to be materialized. CreateIndexsays whether its owner is a view. The newOnViewflag rides the action, asIsMaterializeddoes 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 VIEWdrops 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.
5.8.0
Added
- 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 fromdotnet buildoutput with no packing, feed or restore.
Changed
LockFile.Resolvetakes aPackageReferencerather than aPluginDeclaration. 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
PLUGINstatement with no attributes reports one finding, not two. It used to reportsourceandversionas separately missing; withpathas an alternative neither is required on its own, so it reportsmissing-plugin-origininstead.
5.9.0
Added
RESTRICTis a referential action in its own right.ReferentialAction.Restrictis parsed, written and rendered.SqlDialect.SupportsRestrictsays whether the engine hasRESTRICTas distinct fromNO ACTION. Providers have to opt in.- A generated column records whether it is stored.
Column.IsStoredand the NSQLSTORED/VIRTUALkeywords. SqlDialect.SupportsVirtualGeneratedColumnssays whether the engine can leave one unstored. Off by default, matching Postgres, which stores every generated column.virtual-generated-column-not-supportedis 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.IsRowGuidand the NSQLROWGUIDCOLkeyword. - A column default can carry its constraint’s name.
Column.DefaultConstraintNameand the NSQLCONSTRAINT <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 NSQLNOT FOR REPLICATIONform on both. DiagnosticSourcesdeclares every source the engine reports under, andDiagnosticSources.Alloffers 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(),EqualsandGetHashCodecover every field, asColumn’s now do. Both enumerate fields by hand, and import clones every object on its way to a file.
5.9.1
Added
- Statements contain their originating action type.
SqlStatement.Actionnames theMigrationActiona 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.
5.9.2
Fixed
SqlStatementkeeps 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.
5.9.3
Fixed
- 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.
5.10.0
Added
SqlDialect.SupportsCommentssays 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-supportedis 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.
5.10.1
Fixed
- 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.
5.10.2
Fixed
- 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.
5.11.0 Latest
Added
- .nsql extension. NSchema files can now be authored from a
.nsqlextension as well as a.sqlextension. - TextMate Grammar. There is a TextMate grammar project in the
grammardirectory in this project that will enable syntax highlighting for.nsqlfiles.
Changed
- Default file extension. The default file extension is now
.nsql.