|
Lightweight 0.20260625.0
|
SQL migrations provide a structured way to evolve your database schema over time. Each migration represents a discrete change (creating tables, adding columns, etc.) that can be applied or reverted independently.
Key benefits:
The simplest way to create a migration:
The migration is automatically registered with the MigrationManager when the program starts.
For more control, including rollback support:
Migration timestamps use the format YYYYMMDDHHMMSS (14 digits):
Timestamps must be:
When creating migrations in a shared library plugin, add this macro to exactly one source file:
The LIGHTWEIGHT_MIGRATION_PLUGIN() macro exports the AcquireMigrationManager() function that dbtool uses to load migrations from the plugin.
Create a new table with various column types:
Column modifiers (chain after column declaration):
.Unique() - Add unique constraint.Index() - Create an index on this column.UniqueIndex() - Create a unique indexConditional creation:
Modify an existing table:
Available operations:
| Method | Description |
|---|---|
.AddColumn(name, type) | Add a non-nullable column |
.AddNotRequiredColumn(name, type) | Add a nullable column |
.RenameColumn(old, new) | Rename a column |
.DropColumn(name) | Remove a column |
.AlterColumn(name, type, nullable) | Change column type or nullability |
.AddIndex(column) | Create an index |
.AddUniqueIndex(column) | Create a unique index |
.DropIndex(column) | Remove an index |
.AddForeignKey(column, ref) | Add foreign key to existing column |
.AddForeignKeyColumn(name, type, ref) | Add new column with foreign key |
.DropForeignKey(column) | Remove foreign key constraint |
.RenameTo(newName) | Rename the table |
SQLite note: SQLite has no native
ALTER TABLE … ALTER COLUMNor… ADD/DROP CONSTRAINT, so.AlterColumn(...),.AddForeignKey(...), and.DropForeignKey(...)are applied by rebuilding the table. That rebuild runs only when the migration is applied throughMigrationManager(ApplyPendingMigrations()); the generatedToSql()text for these operations is a-- LIGHTWEIGHT_SQLITE_GUARD:sentinel comment that does nothing if executed directly. Applying such a migration viaSqlStatement::MigrateDirectthrows rather than silently skipping the change.
Conditional operations:
Remove a table:
Conditional drop:
Cascade drop (removes foreign key constraints):
Insert data during migrations:
Update existing data:
Remove data:
Create standalone indexes:
Composite indexes:
For database-specific features or complex operations:
| C++ Type | SQL Type | Notes |
|---|---|---|
Integer() | INTEGER | 32-bit integer |
Smallint() | SMALLINT | 16-bit integer |
Bigint() | BIGINT | 64-bit integer |
Tinyint() | TINYINT | 8-bit integer |
Real() | REAL/FLOAT | Floating point |
Bool() | BOOLEAN/BIT | Boolean |
Char(n) | CHAR(n) | Fixed-length string |
Varchar(n) | VARCHAR(n) | Variable-length string |
NChar(n) | NCHAR(n) | Fixed-length Unicode string |
NVarchar(n) | NVARCHAR(n) | Variable-length Unicode string |
Text() | TEXT | Large text |
DateTime() | DATETIME/TIMESTAMP | Date and time |
Date() | DATE | Date only |
Time() | TIME | Time only |
Guid() | UNIQUEIDENTIFIER/UUID | UUID/GUID |
Decimal(p, s) | DECIMAL(p, s) | Fixed-point number |
Binary(n) | BINARY(n) | Fixed-length binary |
VarBinary(n) | VARBINARY(n) | Variable-length binary |
Usage:
When the target database expects unqualified DDL to land in a non-default schema (e.g. lasa instead of dbo / public), tell the manager about it before opening the first connection:
SetDefaultSchema installs a post-connect hook that emits the dialect- specific "make this the session default" statement for every new connection:
SET search_path TO "lasa", public. Both schema_migrations and unqualified DDL inside migrations land in lasa.DEFAULT_SCHEMA server-side (ALTER USER … WITH DEFAULT_SCHEMA = lasa). Migrations that need to write to a specific schema regardless of the login default should use the WithSchema(...) builder.Schema names are validated against [A-Za-z0-9_] and rejected via std::invalid_argument otherwise. Passing "" clears any previously installed hook.
dbtool exposes this via the --schema flag and dbtool-gui exposes it as a "Schema" input on both the Profile and the direct-ODBC connection tabs.
Generate SQL without executing:
Revert migrations:
Mark a migration as applied without executing:
Lightweight automatically creates a schema_migrations table to track applied migrations:
| Column | Type | Description |
|---|---|---|
| version | BIGINT | Migration timestamp |
| checksum | VARCHAR(64) | SHA-256 checksum of migration SQL |
| applied_at | DATETIME | When the migration was applied |
Use SqlScopedLock (the generic distributed-lock RAII type) to prevent concurrent migrations:
SqlScopedLock is not migration-specific — any caller that needs a named cross-process token can use it (cron leadership, "only one worker
processes batch X", queue ownership, …). Pick any string for the lock name; two processes that pass the same string will serialise on it.
For structured error handling — distinguishing timeout from deadlock from driver error programmatically — use the non-throwing SqlScopedLock::TryConstruct(connection, name, timeout) factory, which returns std::expected<SqlScopedLock, SqlLockError>.
The dialect-specific primitive is selected automatically by the active SqlQueryFormatter:
sp_getapplock / sp_releaseapplockpg_advisory_lock / pg_advisory_unlock_lightweight_locks table guarded by a unique constraintOn SQLite the bookkeeping table _lightweight_locks is treated as infrastructure — dbtool hard-reset drops it alongside schema_migrations rather than mistaking it for user data.
IfNotExists/IfExists variants when idempotency is important.dbtool backup before applying migrations to production.--dry-run before applying: