MSSQL
For instructions on how to apply database migrations, please refer to the Getting Started documentation.
We recommend reading T-SQL Code Style since it has a major impact in how we write migrations.
SQL database project
We use a
SDK-style SQL project
(.sqlproj
) to develop the database locally -- this means we have an up-to-date representation of
the database in src/Sql
, and any modifications need to be represented there as well. SDK-style SQL
projects are available in Visual Studio Code that provides schema comparison and more. You may also
modify the .sql
files directly with any text editor.
To make a database change, start by modifying the .sql
files in src/Sql/dbo
. These changes will
also need to be applied in a migration script. Migration scripts are located in
util/Migrator/DbScripts
.
You can either generate the migration scripts automatically using the Schema Comparison functionality or by manually writing them. Do note that the automatic method will only take you so far and it will need to be manually edited to adhere to the code styles.
For added safeguards we have automated linting and validation to ensure the SQL project is always up to date with the migrations.
The separate database definitions in src/Sql/.../dbo
serve as a "master" reference for the
intended and final state of the database at that time. This is crucial because the state of database
definitions at the current moment may differ from when a migration was added in the past. These
definitions act as a lint and validation step to ensure that migrations work as expected, and the
separation helps maintain clarity and accuracy in database schema management and synchronization
processes.
Modifying the database
In accordance with the tenets of Evolutionary Database Design every change must be considered as split into two parts:
- A backwards-compatible transition migration
- A non-backwards-compatible final migration
Most changes are entirely backwards-compatible in their final form. If this is the case, only one phase of changes is required. With the use of beta testing, partial roll-outs, feature flags, etc. the often-chosen path is to spread a change across several major releases with a calculated future state that can perform a "cleanup" migration that is backwards-compatible but still represents an overall-incompatible change beyond the boundaries of what we need for individual release safety.
Backwards compatible migration
- Modify the source
.sql
files insrc/Sql/dbo
. - Write a migration script, and place it in
util/Migrator/DbScripts
. Each script must be prefixed with the current date.
Tips to ensure backwards compatibility:
- any existing stored procedure accepts the same input parameters and that new parameters have nullable defaults
- when a column is renamed the existing stored procedures first check (coalesce) the new location before falling back to the old location
- continued updating of the old data columns since in case of a rollback no data should be lost
Non-backwards compatible migration
These changes should be written from the perspective of "all data has been migrated" and any old stored procedures that were kept around for backwards compatibility should be removed. Any logic for syncing old and new data should also be removed in this step.
Since the dbo
schema represents the current state we need to introduce a "future" state that we
will call dbo_finalization
.
- Copy the relevant
.sql
files fromsrc/Sql/dbo
tosrc/Sql/dbo_finalization
. - Remove the backwards compatibility logic that is no longer needed e.g. dual reads and writes to columns.
- Write a new Migration and place it in
src/Migrator/DbScripts_finalization
. Name itYYYY-0M-FinalizationMigration.sql
.- Typically migrations are designed to be run in sequence. However since the migrations in
DbScripts_finalization
can be run out of order, care must be taken to ensure they remain compatible with the changes toDbScripts
. In order to achieve this we only keep a single migration, which executes all backwards incompatible schema changes.
- Typically migrations are designed to be run in sequence. However since the migrations in
Upon execution any finalization scripts will be automatically moved for proper history.