Lightweight 0.20260617.0
Loading...
Searching...
No Matches
SqlMigration.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "Api.hpp"
6#include "DataMapper/DataMapper.hpp"
7#include "SqlError.hpp"
8#include "SqlQuery/Migrate.hpp"
9#include "SqlQuery/MigrationPlan.hpp"
10#include "SqlSchema.hpp"
11#include "SqlTransaction.hpp"
12
13#include <cstddef>
14#include <cstdint>
15#include <functional>
16#include <list>
17#include <map>
18#include <optional>
19#include <set>
20#include <span>
21#include <string>
22#include <string_view>
23#include <vector>
24
25namespace Lightweight
26{
27
28class SqlConnection;
29
30/// @defgroup SqlMigration SQL Migration
31/// @brief Classes and functions for SQL schema migrations.
32
33namespace SqlMigration
34{
35 class MigrationBase;
36
37 /// Represents a unique timestamp of a migration.
38 ///
39 /// This struct is used to identify migrations and is used as a key in the migration history table.
40 ///
41 /// Note, a recommended format for the timestamp is a human readable format like YYYYMMDDHHMMSS
42 ///
43 /// @code
44 /// MigrationTimestamp { 2026'01'17'00'31'20 };
45 /// @endcode
46 ///
47 /// @ingroup SqlMigration
49 {
50 /// The numeric timestamp value identifying the migration.
51 uint64_t value {};
52
53 /// Three-way comparison operator.
54 constexpr std::weak_ordering operator<=>(MigrationTimestamp const& other) const noexcept = default;
55 };
56
57 /// Exception thrown when applying or reverting a single migration fails.
58 ///
59 /// Carries structured diagnostic context so callers (CLI, GUI) can render
60 /// the *which migration*, *which step*, *which SQL statement* and the
61 /// underlying driver error as separate fields instead of parsing one
62 /// opaque message string.
63 ///
64 /// @ingroup SqlMigration
65 class MigrationException: public SqlException
66 {
67 public:
68 /// Whether the failure happened while applying (Up) or reverting (Down).
69 enum class Operation : std::uint8_t
70 {
71 Apply,
72 Revert,
73 };
74
75 /// Constructs a migration exception that wraps a driver error with
76 /// the migration identity and the exact SQL statement that failed.
77 ///
78 /// @param operation Whether the failure happened during apply or revert.
79 /// @param timestamp The migration that failed.
80 /// @param title Human-readable migration title.
81 /// @param stepIndex Zero-based step index inside the migration plan.
82 /// @param failedSql The SQL statement that produced the driver error.
83 /// @param driverError The ODBC-level error info as received from the driver.
84 LIGHTWEIGHT_API MigrationException(Operation operation,
85 MigrationTimestamp timestamp,
86 std::string title,
87 std::size_t stepIndex,
88 std::string failedSql,
89 SqlErrorInfo driverError);
90
91 /// Whether the failure occurred while applying or reverting.
92 [[nodiscard]] Operation GetOperation() const noexcept
93 {
94 return _operation;
95 }
96 /// Timestamp of the failing migration.
97 [[nodiscard]] MigrationTimestamp GetMigrationTimestamp() const noexcept
98 {
99 return _timestamp;
100 }
101 /// Human-readable title of the failing migration.
102 [[nodiscard]] std::string const& GetMigrationTitle() const noexcept
103 {
104 return _title;
105 }
106 /// Zero-based step index inside the plan of the failing migration.
107 [[nodiscard]] std::size_t GetStepIndex() const noexcept
108 {
109 return _stepIndex;
110 }
111 /// The exact SQL statement that the driver rejected.
112 [[nodiscard]] std::string const& GetFailedSql() const noexcept
113 {
114 return _failedSql;
115 }
116 /// Raw driver error message, without the migration context prefix that
117 /// `what()` and `info().message` decorate it with.
118 [[nodiscard]] std::string const& GetDriverMessage() const noexcept
119 {
120 return _driverMessage;
121 }
122
123 private:
124 Operation _operation;
125 MigrationTimestamp _timestamp;
126 std::string _title;
127 std::size_t _stepIndex;
128 std::string _failedSql;
129 std::string _driverMessage;
130 };
131
132 /// Result of verifying a migration's checksum.
133 ///
134 /// @ingroup SqlMigration
136 {
137 MigrationTimestamp timestamp; ///< The timestamp of the verified migration.
138 std::string_view title; ///< The title of the verified migration.
139 std::string storedChecksum; ///< The checksum stored in the database.
140 std::string computedChecksum; ///< The checksum computed from the current migration definition.
141 bool matches; ///< Whether the stored and computed checksums match.
142 };
143
144 /// Result of reverting multiple migrations.
145 ///
146 /// @ingroup SqlMigration
148 {
149 std::vector<MigrationTimestamp> revertedTimestamps; ///< Successfully reverted migrations
150 std::optional<MigrationTimestamp> failedAt; ///< Migration that failed, if any
151 std::string errorMessage; ///< Short error message if failed (driver message only)
152
153 /// Title of the migration that failed (if any). Empty when no failure or
154 /// when the failure happened before the migration could be located.
155 std::string failedTitle;
156
157 /// Zero-based step index inside the failed migration's plan. Meaningful
158 /// only when `failedAt` is set and the failure came from a driver error
159 /// (not from e.g. a missing registered migration).
160 std::size_t failedStepIndex {};
161
162 /// The exact SQL statement that failed, if available. Empty when the
163 /// failure happened outside of SQL execution (e.g. missing Down()
164 /// implementation, unregistered migration).
165 std::string failedSql;
166
167 /// SQLSTATE diagnostic code from the driver, if available.
168 std::string sqlState;
169
170 /// Native driver error code, if available.
171 SQLINTEGER nativeErrorCode {};
172 };
173
174 /// Status summary of migrations.
175 ///
176 /// @ingroup SqlMigration
178 {
179 size_t appliedCount {}; ///< Number of migrations that have been applied
180 size_t pendingCount {}; ///< Number of migrations waiting to be applied
181 size_t mismatchCount {}; ///< Number of applied migrations with checksum mismatches
182 size_t unknownAppliedCount {}; ///< Number of applied migrations not found in registered list
183 size_t totalRegistered {}; ///< Total number of registered migrations
184 };
185
186 /// Associates a software release with the highest migration timestamp present at release time.
187 ///
188 /// Releases are declared in source (typically a migration plugin) via
189 /// `LIGHTWEIGHT_SQL_RELEASE(version, highestTimestamp)`. They let tools answer questions like
190 /// "which migrations belong to release 6.7.0?" or "roll back everything applied after 6.7.0".
191 ///
192 /// The `highestTimestamp` is an inclusive upper bound: a migration `M` belongs to the release
193 /// iff `prev_release_ts < M.timestamp <= highestTimestamp`, where `prev_release_ts` is the
194 /// previous release's timestamp (or 0 if there is none).
195 ///
196 /// @ingroup SqlMigration
198 {
199 /// Human-readable version string, e.g. "6.7.0".
200 std::string version;
201 /// Highest migration timestamp contained in this release (inclusive).
203 };
204
205 /// Main API to use for managing SQL migrations
206 ///
207 /// This class is a singleton and can be accessed using the GetInstance() method.
208 ///
209 /// @ingroup SqlMigration
211 {
212 public:
213 /// Type alias for a list of migration pointers.
214 using MigrationList = std::list<MigrationBase const*>;
215
216 /// Get the singleton instance of the migration manager.
217 ///
218 /// @return Reference to the migration manager.
219 /// Get the singleton instance of the migration manager.
220 ///
221 /// @return Reference to the migration manager.
222 LIGHTWEIGHT_API static MigrationManager& GetInstance();
223
224 /// Add a migration to the manager.
225 ///
226 /// @param migration Pointer to the migration to add.
227 LIGHTWEIGHT_API void AddMigration(MigrationBase const* migration);
228
229 /// Get all migrations that have been added to the manager.
230 ///
231 /// @return List of migrations.
232 [[nodiscard]] LIGHTWEIGHT_API MigrationList const& GetAllMigrations() const noexcept;
233
234 /// Get a migration by timestamp.
235 ///
236 /// @param timestamp Timestamp of the migration to get.
237 /// @return Pointer to the migration if found, nullptr otherwise.
238 [[nodiscard]] LIGHTWEIGHT_API MigrationBase const* GetMigration(MigrationTimestamp timestamp) const noexcept;
239
240 /// Remove all migrations from the manager.
241 ///
242 /// This function is useful if the migration manager should be reset.
243 LIGHTWEIGHT_API void RemoveAllMigrations();
244
245 /// Get all migrations that have not been applied yet.
246 ///
247 /// @return List of pending migrations.
248 [[nodiscard]] LIGHTWEIGHT_API std::list<MigrationBase const*> GetPending() const noexcept;
249
250 /// Callback type invoked during migration execution to report progress.
252 std::function<void(MigrationBase const& /*migration*/, size_t /*current*/, size_t /*total*/)>;
253
254 /// Apply a single migration from a migration object.
255 ///
256 /// @param migration Pointer to the migration to apply.
257 LIGHTWEIGHT_API void ApplySingleMigration(MigrationBase const& migration);
258
259 /// @brief Variant of `ApplySingleMigration` that threads a caller-owned render
260 /// context through to `ToSql`. Use this from loops over multiple migrations so
261 /// the column-width cache (and other per-run compat state) accumulates across
262 /// the sequence.
263 LIGHTWEIGHT_API void ApplySingleMigration(MigrationBase const& migration, MigrationRenderContext& context);
264
265 /// Revert a single migration from a migration object.
266 ///
267 /// @param migration Pointer to the migration to revert.
268 LIGHTWEIGHT_API void RevertSingleMigration(MigrationBase const& migration);
269
270 /// Apply all migrations that have not been applied yet.
271 ///
272 /// @param feedbackCallback Callback to be called for each migration.
273 /// @return Number of applied migrations.
274 LIGHTWEIGHT_API size_t ApplyPendingMigrations(ExecuteCallback const& feedbackCallback = {});
275
276 /// Apply pending migrations whose timestamp is <= `targetInclusive`.
277 ///
278 /// Honors dependency-respecting topological order, just like
279 /// `ApplyPendingMigrations`, and threads a single render context across
280 /// the run so column-width state from earlier CREATE TABLEs is visible
281 /// to later compat-aware INSERT/UPDATE rendering.
282 ///
283 /// If a pending migration with `ts <= target` declares a dependency on
284 /// a migration whose timestamp is `> target` (i.e. excluded by the
285 /// bound) and is not already applied, this throws — partial states
286 /// that violate dependencies are refused up front.
287 ///
288 /// @param targetInclusive Highest timestamp to apply (inclusive).
289 /// @param feedbackCallback Callback to be called for each migration.
290 /// @return Number of applied migrations (may be zero if already past `targetInclusive`).
291 /// @throws std::runtime_error if a dependency would cross the boundary.
292 LIGHTWEIGHT_API size_t ApplyPendingMigrationsUpTo(MigrationTimestamp targetInclusive,
293 ExecuteCallback const& feedbackCallback = {});
294
295 /// Create the migration history table if it does not exist.
296 LIGHTWEIGHT_API void CreateMigrationHistory();
297
298 /// @brief Sets the default schema applied to migration connections.
299 ///
300 /// When non-empty, installs a chained `SqlConnection::SetPostConnectedHook`
301 /// that executes the formatter's `SetDefaultSchemaStatement(schema)`
302 /// (e.g. `SET search_path TO "<schema>", public` on PostgreSQL) for
303 /// every new connection. SQL Server and SQLite have no portable
304 /// session-level "default schema" command — for SQL Server, the
305 /// connecting login's server-side `DEFAULT_SCHEMA` is what determines
306 /// where unqualified DDL/DML lands.
307 ///
308 /// `schema` is validated against a `[A-Za-z0-9_]` whitelist; rejecting
309 /// invalid input by throwing a `std::invalid_argument`. Passing an
310 /// empty string clears any previously installed schema hook.
311 ///
312 /// Calling this **after** a connection has already been opened on the
313 /// current thread does not retroactively apply the schema — the hook
314 /// fires on subsequent `SqlConnection::Connect` calls. For dbtool and
315 /// the GUI this is invoked before the first connection.
316 ///
317 /// @param schema The default schema to use. Empty disables.
318 LIGHTWEIGHT_API void SetDefaultSchema(std::string schema);
319
320 /// @brief Returns the default schema currently installed on the manager
321 /// (empty when unset).
322 [[nodiscard]] LIGHTWEIGHT_API std::string_view DefaultSchema() const noexcept;
323
324 /// Get all applied migration IDs.
325 ///
326 /// Returns the union of timestamps physically present in `schema_migrations`
327 /// and the in-memory "virtual applied" overlay (see `AddVirtualAppliedMigrations`),
328 /// sorted ascending and deduplicated. The accessor never creates the
329 /// `schema_migrations` table — when the table does not exist the database
330 /// half of the union is the empty set, so a pure-overlay state is fully
331 /// observable here without any side effects.
332 ///
333 /// @return Vector of applied migration IDs (database rows ∪ overlay).
334 [[nodiscard]] LIGHTWEIGHT_API std::vector<MigrationTimestamp> GetAppliedMigrationIds() const;
335
336 /// Contribute "virtual" applied migration IDs that participate in every
337 /// `GetAppliedMigrationIds()` read but are not persisted to
338 /// `schema_migrations` until the manager actually performs a write
339 /// (`ApplySingleMigration`, `MarkMigrationAsApplied`, or
340 /// `RevertSingleMigration`). Designed for plugin post-init hooks that
341 /// need to bridge a foreign version-tracking table (e.g. the legacy
342 /// `LASTRADA_PROPERTIES NR=4` row) into the modern applied-set view
343 /// without paying for table creation on read-only commands like
344 /// `status` or `list-applied`.
345 ///
346 /// The input is merged into an internal sorted, deduplicated vector;
347 /// passing the same timestamp twice is a no-op.
348 ///
349 /// @param timestamps Timestamps to add to the overlay.
350 LIGHTWEIGHT_API void AddVirtualAppliedMigrations(std::span<MigrationTimestamp const> timestamps);
351
352 /// Drop every entry from the in-memory virtual-applied overlay. Intended
353 /// for test setup/teardown and for callers that want to re-derive the
354 /// overlay from scratch (e.g. a `dbtool-gui` reconnect against a
355 /// different profile). Has no effect on `schema_migrations` rows.
356 LIGHTWEIGHT_API void ClearVirtualAppliedMigrations();
357
358 /// Optional sink for informational status messages emitted by plugin
359 /// hooks (e.g. the "Transitioning from LASTRADA_PROPERTIES …" banner).
360 /// `dbtool` installs a sink that prints to `stdout` to preserve the
361 /// historical CLI output; `dbtool-gui` installs a sink that routes
362 /// through its `LogInfo` channel so the banner shows up in the
363 /// activity log instead of going to a hidden stream.
364 using LogSink = std::function<void(std::string_view)>;
365
366 /// Install a log sink. Pass `{}` to clear.
367 ///
368 /// @param sink Callable invoked once per status message.
369 LIGHTWEIGHT_API void SetLogSink(LogSink sink);
370
371 /// Emit a status message via the installed sink. No-op when no sink is
372 /// installed — silent by default keeps the library suitable for use in
373 /// embedded contexts that have no obvious output stream.
374 ///
375 /// @param message The message to deliver.
376 LIGHTWEIGHT_API void Log(std::string_view message) const;
377
378 /// Get the data mapper used for migrations.
379 [[nodiscard]] LIGHTWEIGHT_API DataMapper& GetDataMapper();
380
381 /// Get the data mapper used for migrations.
382 [[nodiscard]] LIGHTWEIGHT_API DataMapper& GetDataMapper() const
383 {
384 return const_cast<MigrationManager*>(this)->GetDataMapper();
385 }
386
387 /// Close the data mapper.
388 ///
389 /// This function is useful if explicitly closing the connection is desired before
390 /// the migration manager is destroyed.
391 LIGHTWEIGHT_API void CloseDataMapper();
392
393 /// @brief Per-migration compat policy.
394 ///
395 /// Given a migration about to be applied (or previewed), returns the set of compat
396 /// flags that should be active while rendering it. Plugins that ship legacy
397 /// migrations use this to scope compat behaviour to their own timestamp range
398 /// (e.g. a plugin enables `lup-truncate` for migrations predating a given
399 /// release). Unset means "strict for every migration".
400 using CompatPolicy = std::function<std::set<std::string>(MigrationBase const&)>;
401
402 /// @brief Installs a per-migration compat policy. Pass `{}` to clear.
403 ///
404 /// Typically called once from a plugin's static initializer. Replaces any policy
405 /// previously installed — composition is the caller's responsibility for now.
406 LIGHTWEIGHT_API void SetCompatPolicy(CompatPolicy policy);
407
408 /// @brief Returns a view of the installed policy so it can be propagated across
409 /// managers (plugin → central). Empty callable if no policy was installed.
410 [[nodiscard]] LIGHTWEIGHT_API CompatPolicy const& GetCompatPolicy() const noexcept;
411
412 /// @brief Composes an additional policy with the currently installed one. If no
413 /// policy is installed, the argument becomes the policy as-is; otherwise both
414 /// policies are consulted and their flag sets unioned per migration.
415 LIGHTWEIGHT_API void ComposeCompatPolicy(CompatPolicy policy);
416
417 /// @brief Returns the compat flags the current policy assigns to `migration`, or
418 /// an empty set if no policy is installed.
419 [[nodiscard]] LIGHTWEIGHT_API std::set<std::string> CompatFlagsFor(MigrationBase const& migration) const;
420
421 /// Get a transaction for the data mapper.
422 ///
423 /// @return Transaction.
424 LIGHTWEIGHT_API SqlTransaction Transaction();
425
426 /// Preview SQL statements for a single migration without executing.
427 ///
428 /// This is useful for dry-run mode to see what SQL would be executed.
429 ///
430 /// @param migration The migration to preview.
431 /// @return Vector of SQL statements that would be executed.
432 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewMigration(MigrationBase const& migration) const;
433
434 /// @brief Variant of `PreviewMigration` that threads a caller-owned render
435 /// context so the column-width cache accumulates across a sequence of preview
436 /// calls. Used by `PreviewPendingMigrations` to render compat-aware dry runs.
437 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewMigrationWithContext(
438 MigrationBase const& migration, MigrationRenderContext& context) const;
439
440 /// @brief One row in the `RewriteChecksumsResult.entries` list.
442 {
443 MigrationTimestamp timestamp; ///< The migration whose stored checksum was rewritten.
444 std::string_view title; ///< Title of the registered migration (or "(Unknown Migration)").
445 std::string oldChecksum; ///< Stored checksum before the rewrite. Empty if there was none.
446 std::string newChecksum; ///< Stored checksum after the rewrite.
447 };
448
449 /// @brief Result of a `RewriteChecksums` call.
451 {
452 std::vector<ChecksumRewriteEntry> entries; ///< One entry per rewritten or would-be-rewritten row.
453 std::vector<MigrationTimestamp>
454 unregisteredTimestamps; ///< Applied rows whose migration is no longer registered.
455 bool wasDryRun = false; ///< True if the call ran in dry-run mode.
456 };
457
458 /// @brief Snapshot of the schema the registered migrations *intend* to produce.
459 ///
460 /// Pure plan-walk — never executes SQL, never opens a connection. Folds the
461 /// effects of every registered migration (up to an optional cut-off timestamp)
462 /// into a per-table view of "the final shape" plus a chronological list of
463 /// data steps and surviving indexes/releases. Used by:
464 ///
465 /// - `dbtool fold` to emit a self-contained baseline (`.cpp` plugin or `.sql`)
466 /// - `HardReset` to know which tables the migrations would have created
467 /// - `UnicodeUpgradeTables` to know which char/varchar columns the migrations
468 /// now declare wide
469 ///
470 /// All three are pure operations — `FoldRegisteredMigrations` itself never
471 /// executes a single statement. See `FoldRegisteredMigrations` for the API.
473 {
474 /// Per-table state: ordered column declarations + per-table FK list.
476 {
477 /// Ordered column declarations as they would be emitted in CREATE TABLE.
478 std::vector<SqlColumnDeclaration> columns;
479 /// Composite foreign keys declared on this table (single-column FKs are
480 /// carried inline on the corresponding `SqlColumnDeclaration::foreignKey`).
481 std::vector<SqlCompositeForeignKeyConstraint> compositeForeignKeys;
482 /// Whether the original CREATE TABLE used IF NOT EXISTS (carried through
483 /// to emitters so they can replay it verbatim).
484 bool ifNotExists = false;
485 };
486
487 /// (schema, table) → folded `TableState`. Insertion order is *not* preserved by
488 /// `std::map` — for emission order use `creationOrder` below.
489 std::map<SqlSchema::FullyQualifiedTableName, TableState> tables;
490
491 /// Tables in *creation* order (first-time-seen). Reverse for safe DROP ordering
492 /// when tearing the schema down.
493 std::vector<SqlSchema::FullyQualifiedTableName> creationOrder;
494
495 /// Indexes that survive folding (created on tables still present at end).
496 std::vector<SqlCreateIndexPlan> indexes;
497
498 /// One data step (INSERT/UPDATE/DELETE/RawSql) tagged with its source migration.
499 struct DataStep
500 {
501 /// Timestamp of the migration that contributed this data step.
503 /// Title of the migration that contributed this data step.
504 std::string sourceTitle;
505 /// The plan element itself (INSERT, UPDATE, DELETE, or RawSql).
507 };
508
509 /// Data steps in chronological order. **No coalescing** — the fold replays
510 /// every data step verbatim, exactly as if migrations were applied in order.
511 std::vector<DataStep> dataSteps;
512
513 /// Releases declared via `LIGHTWEIGHT_SQL_RELEASE` that fall within the fold range.
514 std::vector<MigrationRelease> releases;
515
516 /// Migrations that contributed to the fold (timestamp + title). Used by emitters
517 /// to write a header comment explaining what was collapsed.
518 std::vector<std::pair<MigrationTimestamp, std::string>> foldedMigrations;
519 };
520
521 /// @brief Pure plan-walk that folds the effect of every registered migration.
522 ///
523 /// Visits each migration's `Up()` plan in timestamp order (or up to
524 /// `upToInclusive` if provided) and accumulates the cumulative end-state into a
525 /// `PlanFoldingResult`. **Never** executes SQL or touches a database connection
526 /// — the supplied formatter is only used to build the in-memory plan elements.
527 ///
528 /// @param formatter Formatter used by the migration query builder while walking
529 /// each migration's `Up()`. Any standard formatter works; the
530 /// walk inspects plan element shapes, not rendered SQL.
531 /// @param upToInclusive If set, fold only migrations with `timestamp <= upToInclusive`.
532 /// If unset, fold all registered migrations.
533 ///
534 /// @return The folded snapshot. Safe to call without a `MigrationManager`-attached
535 /// data mapper.
536 [[nodiscard]] LIGHTWEIGHT_API PlanFoldingResult FoldRegisteredMigrations(
537 SqlQueryFormatter const& formatter, std::optional<MigrationTimestamp> upToInclusive = std::nullopt) const;
538
539 /// @brief Result of a `HardReset` call.
541 {
542 /// True when the populating call was a dry-run; the result describes the
543 /// would-be plan and no DDL was issued.
544 bool wasDryRun = false;
545 /// Tables the registered migrations *would* have created and were also present
546 /// in the live DB — these were dropped (or would be dropped on a real run).
547 std::vector<SqlSchema::FullyQualifiedTableName> droppedTables;
548 /// Tables registered migrations declare but that aren't in the live DB —
549 /// nothing to do for these. Reported for visibility only.
550 std::vector<SqlSchema::FullyQualifiedTableName> absentTables;
551 /// Tables in the live DB the registered migrations don't know about — left
552 /// alone (user-owned). Reported prominently so operators notice.
553 std::vector<SqlSchema::FullyQualifiedTableName> preservedTables;
554 /// Whether the `schema_migrations` table itself was dropped (always true on a
555 /// real run, false on dry-run).
557 };
558
559 /// @brief Drops every table the registered migrations would create (incl.
560 /// `schema_migrations`), preserving any user-created tables.
561 ///
562 /// Folds all registered migrations to compute the migration-owned set, intersects
563 /// with the live schema (via `SqlSchema::ReadAllTables`), then drops the matching
564 /// live tables in **reverse** creation order with `cascade=true ifExists=true`. The
565 /// `schema_migrations` table is dropped explicitly because it's created outside the
566 /// registered-migrations stream.
567 ///
568 /// Tables present in the live DB but not in the migration plan are left alone and
569 /// reported under `preservedTables` so operators can spot them.
570 ///
571 /// @param dryRun When true, returns the would-be plan without writing anything.
572 [[nodiscard]] LIGHTWEIGHT_API HardResetResult HardReset(bool dryRun = false);
573
574 /// @brief One column upgrade entry in `UnicodeUpgradeResult`.
576 {
577 /// Fully-qualified name of the table that owns the column.
578 SqlSchema::FullyQualifiedTableName table;
579 /// Name of the column being upgraded.
580 std::string column;
581 /// Live byte-counted type the column currently has.
582 SqlColumnTypeDefinition liveType;
583 /// Char-counted type the migrations now declare for this column.
584 SqlColumnTypeDefinition intendedType;
585 /// Whether the column is nullable (preserved across the type rewrite).
586 bool nullable = true;
587 };
588
589 /// @brief Result of an `UnicodeUpgradeTables` call.
591 {
592 /// True when the populating call was a dry-run; the result describes the
593 /// would-be diff and no DDL was issued.
594 bool wasDryRun = false;
595 /// Columns whose live type drifted from the intended type and were upgraded
596 /// (or would be on a real run).
597 std::vector<ColumnUpgradeEntry> columns;
598 /// Foreign keys that had to be dropped + re-added to upgrade their
599 /// participating columns. Reported so operators see the FK churn.
600 std::vector<SqlCompositeForeignKeyConstraint> rebuiltForeignKeys;
601 };
602
603 /// @brief Rewrites legacy `VARCHAR/CHAR` columns to `NVARCHAR/NCHAR` where the
604 /// registered migrations now declare wide types.
605 ///
606 /// Compares the folded plan's intended column types against `SqlSchema::ReadAllTables`
607 /// output; an upgrade is triggered iff intended is `NVarchar`/`NChar` AND live is
608 /// `Varchar`/`Char` with the same `size`. Foreign keys that touch any upgrade column
609 /// are dropped before the alter and re-added afterwards. Cross-backend; SQLite
610 /// uses the in-tree `RebuildSqliteTable` recipe under the hood.
611 ///
612 /// @param dryRun When true, returns the would-be diff without writing anything.
613 [[nodiscard]] LIGHTWEIGHT_API UnicodeUpgradeResult UnicodeUpgradeTables(bool dryRun = false);
614
615 /// @brief Re-stamps `schema_migrations.checksum` rows that have drifted.
616 ///
617 /// Applied migrations whose stored checksum no longer matches the current
618 /// `MigrationBase::ComputeChecksum` output are updated in-place. Migrations that
619 /// match are left alone. Rows that reference a migration which is no longer
620 /// registered (e.g. removed from the codebase) are surfaced via
621 /// `RewriteChecksumsResult.unregisteredTimestamps` and *not* touched.
622 ///
623 /// This is a recovery tool used when a regen of generated migrations changes
624 /// their byte-level shape but not their logical effect — running it without
625 /// understanding *why* the checksum drifted would defeat the integrity check.
626 ///
627 /// @param dryRun When true, returns the would-be diff without writing anything.
628 [[nodiscard]] LIGHTWEIGHT_API RewriteChecksumsResult RewriteChecksums(bool dryRun = false);
629
630 /// Preview SQL statements for all pending migrations without executing.
631 ///
632 /// This is useful for dry-run mode to see what SQL would be executed.
633 ///
634 /// @param feedbackCallback Optional callback to be called for each migration.
635 /// @return Vector of all SQL statements that would be executed.
636 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewPendingMigrations(
637 ExecuteCallback const& feedbackCallback = {}) const;
638
639 /// Preview SQL for pending migrations whose timestamp is <= `targetInclusive`.
640 ///
641 /// Bounded counterpart of `PreviewPendingMigrations`. Same dependency
642 /// rules as `ApplyPendingMigrationsUpTo`: if any included migration
643 /// depends on an excluded pending migration, this throws.
644 ///
645 /// @param targetInclusive Highest timestamp to preview (inclusive).
646 /// @param feedbackCallback Optional callback to be called for each migration.
647 /// @return Vector of all SQL statements that would be executed.
648 /// @throws std::runtime_error if a dependency would cross the boundary.
649 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewPendingMigrationsUpTo(
650 MigrationTimestamp targetInclusive, ExecuteCallback const& feedbackCallback = {}) const;
651
652 /// Verify checksums of all applied migrations.
653 ///
654 /// Compares the stored checksums in the database with the computed checksums
655 /// of the current migration definitions. This helps detect if migrations
656 /// have been modified after they were applied.
657 ///
658 /// @return Vector of verification results for migrations with mismatched or missing checksums.
659 [[nodiscard]] LIGHTWEIGHT_API std::vector<ChecksumVerificationResult> VerifyChecksums() const;
660
661 /// Mark a migration as applied without executing its Up() method.
662 ///
663 /// This is useful for:
664 /// - Baseline migrations when setting up an existing database
665 /// - Marking migrations that were applied manually or through other means
666 /// - Skipping migrations that are not applicable to a specific environment
667 ///
668 /// @param migration The migration to mark as applied.
669 /// @throws std::runtime_error if the migration is already applied.
670 LIGHTWEIGHT_API void MarkMigrationAsApplied(MigrationBase const& migration);
671
672 /// Revert all migrations applied after the target timestamp.
673 ///
674 /// This method reverts migrations in reverse chronological order,
675 /// rolling back from the most recent to just after the target.
676 /// The target migration itself is NOT reverted.
677 ///
678 /// @param target Target timestamp to revert to. Migrations > target are reverted.
679 /// @param feedbackCallback Optional callback for progress updates.
680 /// @return Result containing list of reverted timestamps or error information.
681 /// @note Stops on first error. Previously reverted migrations in this call are NOT rolled back.
682 [[nodiscard]] LIGHTWEIGHT_API RevertResult RevertToMigration(MigrationTimestamp target,
683 ExecuteCallback const& feedbackCallback = {});
684
685 /// Get a summary status of all migrations.
686 ///
687 /// This method provides a quick overview of the migration state without
688 /// needing to iterate through individual migrations.
689 ///
690 /// @return Status struct with counts of applied, pending, and mismatched migrations.
691 [[nodiscard]] LIGHTWEIGHT_API MigrationStatus GetMigrationStatus() const;
692
693 /// Validate that all registered migrations have satisfiable dependencies.
694 ///
695 /// For each pending migration, verifies that every declared dependency is either
696 /// already applied or itself pending (so it will be applied first due to topological
697 /// ordering). Also detects cycles among pending migrations.
698 ///
699 /// @throws std::runtime_error if any dependency is unresolved or a cycle is found.
700 LIGHTWEIGHT_API void ValidateDependencies() const;
701
702 /// Register a release marker for a software version.
703 ///
704 /// Typically called from `LIGHTWEIGHT_SQL_RELEASE(version, timestamp)` at static init time.
705 /// Releases are ordered by `highestTimestamp`; two releases may not share the same
706 /// `highestTimestamp`, and the same version string may not be registered twice.
707 ///
708 /// @param version Human-readable version, e.g. "6.7.0".
709 /// @param highestTimestamp Highest migration timestamp (inclusive) that belongs to this release.
710 /// @throws std::runtime_error on duplicate version or duplicate timestamp.
711 LIGHTWEIGHT_API void RegisterRelease(std::string version, MigrationTimestamp highestTimestamp);
712
713 /// Remove all registered releases. Useful for resetting state in tests.
714 LIGHTWEIGHT_API void RemoveAllReleases();
715
716 /// Get all registered releases, sorted ascending by `highestTimestamp`.
717 [[nodiscard]] LIGHTWEIGHT_API std::vector<MigrationRelease> const& GetAllReleases() const noexcept;
718
719 /// Find a release by exact version string match.
720 ///
721 /// @return Pointer to the matching release, or nullptr if no release with that version is registered.
722 [[nodiscard]] LIGHTWEIGHT_API MigrationRelease const* FindReleaseByVersion(std::string_view version) const noexcept;
723
724 /// Find the release whose timestamp range contains `timestamp`.
725 ///
726 /// Returns the release with the smallest `highestTimestamp` that is `>= timestamp`.
727 /// Returns nullptr if `timestamp` is greater than every registered release's highestTimestamp
728 /// (i.e., the migration is post-all-releases / unreleased).
729 [[nodiscard]] LIGHTWEIGHT_API MigrationRelease const* FindReleaseForTimestamp(
730 MigrationTimestamp timestamp) const noexcept;
731
732 /// Get all registered migrations belonging to a given release.
733 ///
734 /// A migration `M` belongs to release `R` iff
735 /// `prev_release_ts < M.timestamp <= R.highestTimestamp`, where `prev_release_ts` is the
736 /// previous release's timestamp (or 0 if `R` is the first release).
737 ///
738 /// @param version The version string to look up.
739 /// @return Migrations in the release, ordered by timestamp. Empty if the version is unknown.
740 [[nodiscard]] LIGHTWEIGHT_API MigrationList GetMigrationsForRelease(std::string_view version) const;
741
742 private:
743 /// Return the pending list in dependency-respecting order.
744 ///
745 /// Dependencies among pending migrations are resolved via topological sort.
746 /// Migrations with no dependency between them keep timestamp order.
747 /// Throws on missing deps or cycles.
748 [[nodiscard]] MigrationList TopoSortPending(MigrationList pending,
749 std::vector<MigrationTimestamp> const& applied) const;
750
751 /// @brief Builds a fresh, policy-agnostic render context. The per-migration
752 /// compat knobs are layered on by `ApplySingleMigration` / `PreviewMigrationWithContext`
753 /// before rendering a given migration's steps.
754 [[nodiscard]] static MigrationRenderContext MakeRenderContext();
755
756 /// @brief Materialises any pending virtual-applied entries as real rows
757 /// in `schema_migrations`. Creates the history table if necessary, then
758 /// inserts one row per overlay entry that maps to a registered
759 /// migration and is not yet present in the table. Clears the overlay on
760 /// success. Called as the first action of every write path so the
761 /// physical table only exists when there is at least one row to put in
762 /// it.
763 void PersistVirtualAppliedMigrations();
764
765 MigrationList _migrations;
766 std::vector<MigrationRelease> _releases;
767 mutable DataMapper* _dataMapper { nullptr };
768 CompatPolicy _compatPolicy;
769 std::string _defaultSchema; ///< Default schema applied to migration connections, empty when unset.
770
771 /// @brief In-memory overlay of applied migration timestamps that are
772 /// not (yet) persisted to `schema_migrations`. Populated by plugin
773 /// post-init hooks via `AddVirtualAppliedMigrations`; drained into the
774 /// table by `PersistVirtualAppliedMigrations` whenever the manager
775 /// transitions from a read-only state into actually writing. Always
776 /// kept sorted ascending and deduplicated.
777 std::vector<MigrationTimestamp> _virtualAppliedIds;
778
779 LogSink _logSink; ///< Optional status-message sink. Empty by default.
780 };
781
782/// Requires the user to call LIGHTWEIGHT_MIGRATION_PLUGIN() in exactly one CPP file of the migration plugin.
783///
784/// @ingroup SqlMigration
785#define LIGHTWEIGHT_MIGRATION_PLUGIN() \
786 /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
787 extern "C" LIGHTWEIGHT_EXPORT Lightweight::SqlMigration::MigrationManager* AcquireMigrationManager() \
788 { \
789 return &Lightweight::SqlMigration::MigrationManager::GetInstance(); \
790 }
791
792/// Optional hook executed once per plugin by dbtool, after migration history has been
793/// created and an active connection is available on the central manager. Plugins use
794/// this to perform one-shot bridging work that needs both a live `SqlConnection` and
795/// the merged `MigrationManager` — for example, importing legacy version tracking
796/// from a custom table into `schema_migrations`.
797///
798/// Use as:
799///
800/// @code
801/// LIGHTWEIGHT_MIGRATION_PLUGIN_POSTINIT(connection, manager)
802/// {
803/// MyPlugin::ImportLegacyVersions(manager, connection);
804/// }
805/// @endcode
806///
807/// @ingroup SqlMigration
808#define LIGHTWEIGHT_MIGRATION_PLUGIN_POSTINIT(connArg, mgrArg) \
809 extern "C" LIGHTWEIGHT_EXPORT void LightweightMigrationPluginPostInit( \
810 Lightweight::SqlConnection&(connArg), Lightweight::SqlMigration::MigrationManager&(mgrArg))
811
812 /// Represents a single unique SQL migration.
813 ///
814 /// @ingroup SqlMigration
816 {
817 public:
818 /// Default copy constructor.
819 MigrationBase(MigrationBase const&) = default;
820 MigrationBase(MigrationBase&&) = delete;
821 /// Default copy assignment operator.
824 /// Constructs a migration with the given timestamp and title.
825 MigrationBase(MigrationTimestamp timestamp, std::string_view title):
826 _timestamp { timestamp },
827 _title { title }
828 {
830 }
831
832 virtual ~MigrationBase() = default;
833
834 /// Apply the migration.
835 ///
836 /// @param plan Query builder to use for building the migration plan.
837 virtual void Up(SqlMigrationQueryBuilder& plan) const = 0;
838
839 /// Revert the migration.
840 ///
841 /// @param plan Query builder to use for building the migration plan.
842 virtual void Down(SqlMigrationQueryBuilder& plan) const
843 {
844 (void) plan;
845 }
846
847 /// Check if this migration has a Down() implementation for rollback.
848 ///
849 /// This method determines whether the migration can be safely reverted.
850 /// The default implementation returns false. Derived classes that implement
851 /// Down() should override this to return true.
852 ///
853 /// @return true if Down() is implemented and can revert this migration.
854 [[nodiscard]] virtual bool HasDownImplementation() const noexcept
855 {
856 return false;
857 }
858
859 /// Get the timestamps of migrations that this migration depends on.
860 ///
861 /// Dependencies are enforced at apply time: each declared dependency must be
862 /// registered and applied (or pending in the same run, in dependency order)
863 /// before this migration will run. The default implementation returns no
864 /// dependencies, preserving timestamp-ordered apply behavior.
865 ///
866 /// @return Vector of dependency timestamps. Empty if this migration has none.
867 [[nodiscard]] virtual std::vector<MigrationTimestamp> GetDependencies() const
868 {
869 return {};
870 }
871
872 /// Get the author of the migration, if any.
873 ///
874 /// @return Author string, empty if not set.
875 [[nodiscard]] virtual std::string_view GetAuthor() const noexcept
876 {
877 return {};
878 }
879
880 /// Get the long-form description of the migration, if any.
881 ///
882 /// This is a multi-line description in addition to the short title.
883 ///
884 /// @return Description string, empty if not set.
885 [[nodiscard]] virtual std::string_view GetDescription() const noexcept
886 {
887 return {};
888 }
889
890 /// Get the timestamp of the migration.
891 ///
892 /// @return Timestamp of the migration.
893 [[nodiscard]] MigrationTimestamp GetTimestamp() const noexcept
894 {
895 return _timestamp;
896 }
897
898 /// Get the title of the migration.
899 ///
900 /// @return Title of the migration.
901 [[nodiscard]] std::string_view GetTitle() const noexcept
902 {
903 return _title;
904 }
905
906 /// Compute SHA-256 checksum of migration's Up() SQL statements.
907 ///
908 /// The checksum is computed from the SQL statements that would be executed
909 /// by this migration. This allows detecting if a migration has been modified
910 /// after it was applied.
911 ///
912 /// @param formatter The SQL query formatter to use for generating SQL.
913 /// @return SHA-256 hex string (64 characters).
914 [[nodiscard]] LIGHTWEIGHT_API std::string ComputeChecksum(SqlQueryFormatter const& formatter) const;
915
916 private:
917 MigrationTimestamp _timestamp;
918 std::string_view _title;
919 };
920
921 /// Represents a single unique SQL migration.
922 ///
923 /// This class is a convenience class that can be used to create a migration.
924 ///
925 /// @ingroup SqlMigration
926 /// Optional metadata attached to a Migration at construction time.
927 ///
928 /// All fields are optional. Unset fields behave as if the feature
929 /// were not used (e.g. empty dependencies preserves timestamp order).
930 ///
931 /// @ingroup SqlMigration
933 {
934 /// Migrations that must be applied before this one.
935 std::vector<MigrationTimestamp> dependencies {};
936 /// Author of the migration. Recorded in schema_migrations when the migration is applied.
937 std::string_view author {};
938 /// Long-form description. Recorded in schema_migrations when the migration is applied.
939 std::string_view description {};
940 };
941
942 template <uint64_t TsValue>
943 class Migration: public MigrationBase
944 {
945 public:
946 /// The migration's timestamp, available at compile time from the type itself.
947 ///
948 /// Exposed so @ref TimestampOf can extract the timestamp without
949 /// having to repeat the literal at every use site (e.g. in dependency lists).
950 static constexpr MigrationTimestamp TimeStamp { TsValue };
951
952 /// Create a new migration.
953 ///
954 /// @param title Title of the migration.
955 /// @param up Function to apply the migration.
956 /// @param down Function to revert the migration (optional).
957 Migration(std::string_view title,
958 std::function<void(SqlMigrationQueryBuilder&)> const& up,
959 std::function<void(SqlMigrationQueryBuilder&)> const& down = {}):
960 MigrationBase(TimeStamp, title),
961 _up { up },
962 _down { down }
963 {
964 }
965
966 /// Create a new migration with optional metadata (dependencies, author, description).
967 ///
968 /// @param title Title of the migration.
969 /// @param metadata Optional metadata including dependencies, author, and description.
970 /// @param up Function to apply the migration.
971 /// @param down Function to revert the migration (optional).
972 Migration(std::string_view title,
973 MigrationMetadata metadata,
974 std::function<void(SqlMigrationQueryBuilder&)> const& up,
975 std::function<void(SqlMigrationQueryBuilder&)> const& down = {}):
976 MigrationBase(TimeStamp, title),
977 _up { up },
978 _down { down },
979 _metadata { std::move(metadata) }
980 {
981 }
982
983 /// Apply the migration.
984 ///
985 /// @param builder Query builder to use for building the migration plan.
986 void Up(SqlMigrationQueryBuilder& builder) const override
987 {
988 _up(builder);
989 }
990
991 /// Revert the migration.
992 ///
993 /// @param builder Query builder to use for building the migration plan.
994 void Down(SqlMigrationQueryBuilder& builder) const override
995 {
996 if (_down)
997 _down(builder);
998 }
999
1000 /// Check if this migration has a Down() implementation.
1001 ///
1002 /// @return true if a down function was provided.
1003 [[nodiscard]] bool HasDownImplementation() const noexcept override
1004 {
1005 return static_cast<bool>(_down);
1006 }
1007
1008 /// Get the declared dependencies for this migration.
1009 [[nodiscard]] std::vector<MigrationTimestamp> GetDependencies() const override
1010 {
1011 return _metadata.dependencies;
1012 }
1013
1014 /// Get the author of this migration.
1015 [[nodiscard]] std::string_view GetAuthor() const noexcept override
1016 {
1017 return _metadata.author;
1018 }
1019
1020 /// Get the long-form description of this migration.
1021 [[nodiscard]] std::string_view GetDescription() const noexcept override
1022 {
1023 return _metadata.description;
1024 }
1025
1026 private:
1027 std::function<void(SqlMigrationQueryBuilder&)> _up;
1028 std::function<void(SqlMigrationQueryBuilder&)> _down;
1029 MigrationMetadata _metadata {};
1030 };
1031
1032 /// Extracts the @ref MigrationTimestamp from a migration type that exposes a static
1033 /// constexpr `TimeStamp` member.
1034 ///
1035 /// Works with both the class template @ref Migration and the struct generated by the
1036 /// @ref LIGHTWEIGHT_SQL_MIGRATION macro. Use via `decltype`:
1037 ///
1038 /// @code
1039 /// auto migrationA = SqlMigration::Migration<202601010001>("dep base", [](auto&){ ... });
1040 /// auto tsA = TimestampOf<decltype(migrationA)>; // MigrationTimestamp { 202601010001 }
1041 /// @endcode
1042 ///
1043 /// @tparam T A migration type exposing `static constexpr MigrationTimestamp TimeStamp`.
1044 template <typename T>
1045 inline constexpr MigrationTimestamp TimestampOf = T::TimeStamp;
1046
1047 namespace detail
1048 {
1049 /// RAII registrar used by `LIGHTWEIGHT_SQL_RELEASE` to register a release with the
1050 /// migration manager at static-initialization time. Not intended for direct use.
1051 ///
1052 /// @ingroup SqlMigration
1053 struct ReleaseRegistrar
1054 {
1055 /// Registers `{version, highestTimestamp}` with the singleton manager.
1056 ReleaseRegistrar(std::string version, MigrationTimestamp highestTimestamp)
1057 {
1058 MigrationManager::GetInstance().RegisterRelease(std::move(version), highestTimestamp);
1059 }
1060 };
1061 } // namespace detail
1062
1063} // namespace SqlMigration
1064
1065} // namespace Lightweight
1066
1067#define _LIGHTWEIGHT_CONCATENATE(s1, s2) s1##s2
1068#define _LIGHTWEIGHT_CONCATENATE_INNER(s1, s2) _LIGHTWEIGHT_CONCATENATE(s1, s2)
1069
1070/// @brief Represents the C++ migration object for a given timestamped migration.
1071/// @param timestamp Timestamp of the migration.
1072///
1073/// @ingroup SqlMigration
1074#define LIGHTWEIGHT_MIGRATION_INSTANCE(timestamp) migration_##timestamp
1075
1076/// @brief Creates a new migration.
1077/// @param timestamp Timestamp of the migration.
1078/// @param description Description of the migration.
1079///
1080/// @note The migration will be registered with the migration manager automatically.
1081///
1082/// @code
1083/// #include <Lightweight/SqlMigration.hpp>
1084///
1085/// LIGHTWEIGHT_SQL_MIGRATION(20260117234120, "Create table 'MyTable'")
1086/// {
1087/// // Use 'plan' to define the migration steps, for example creating tables.
1088/// }
1089/// @endcode
1090///
1091/// @see Lightweight::SqlMigrationQueryBuilder
1092///
1093/// @ingroup SqlMigration
1094#define LIGHTWEIGHT_SQL_MIGRATION(timestamp, description) \
1095 struct Migration_##timestamp: public Lightweight::SqlMigration::MigrationBase \
1096 { \
1097 /** The migration's timestamp, exposed so @ref TimestampOf can extract it from the type. */ \
1098 static constexpr Lightweight::SqlMigration::MigrationTimestamp TimeStamp { static_cast<uint64_t>(timestamp) }; \
1099 explicit Migration_##timestamp(): \
1100 Lightweight::SqlMigration::MigrationBase(TimeStamp, description) \
1101 { \
1102 } \
1103 \
1104 void Up(Lightweight::SqlMigrationQueryBuilder& plan) const override; \
1105 void Down(Lightweight::SqlMigrationQueryBuilder& /*plan*/) const override {} \
1106 }; \
1107 \
1108 static Migration_##timestamp _LIGHTWEIGHT_CONCATENATE(migration_, timestamp); \
1109 \
1110 void Migration_##timestamp::Up(Lightweight::SqlMigrationQueryBuilder& plan) const
1111
1112/// @brief Creates a new reversible migration whose `Down()` is defined out-of-line
1113/// via @ref LIGHTWEIGHT_SQL_MIGRATION_DOWN.
1114///
1115/// Use this variant when the migration needs to be reversible. Follow the macro with
1116/// the `Up()` body, then later use `LIGHTWEIGHT_SQL_MIGRATION_DOWN(timestamp) { ... }`
1117/// to provide the `Down()` body.
1118///
1119/// @param timestamp Timestamp of the migration.
1120/// @param description Description of the migration.
1121///
1122/// @code
1123/// LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE(20260117234120, "Create table 'MyTable'")
1124/// {
1125/// plan.CreateTable("MyTable").PrimaryKey("id", Integer());
1126/// }
1127///
1128/// LIGHTWEIGHT_SQL_MIGRATION_DOWN(20260117234120)
1129/// {
1130/// plan.DropTable("MyTable");
1131/// }
1132/// @endcode
1133///
1134/// @ingroup SqlMigration
1135#define LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE(timestamp, description) \
1136 struct Migration_##timestamp: public Lightweight::SqlMigration::MigrationBase \
1137 { \
1138 explicit Migration_##timestamp(): \
1139 Lightweight::SqlMigration::MigrationBase(Lightweight::SqlMigration::MigrationTimestamp { timestamp }, \
1140 description) \
1141 { \
1142 } \
1143 \
1144 void Up(Lightweight::SqlMigrationQueryBuilder& plan) const override; \
1145 void Down(Lightweight::SqlMigrationQueryBuilder& plan) const override; \
1146 \
1147 [[nodiscard]] bool HasDownImplementation() const noexcept override \
1148 { \
1149 return true; \
1150 } \
1151 }; \
1152 \
1153 static Migration_##timestamp _LIGHTWEIGHT_CONCATENATE(migration_, timestamp); \
1154 \
1155 void Migration_##timestamp::Up(Lightweight::SqlMigrationQueryBuilder& plan) const
1156
1157/// @brief Companion to @ref LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE — defines the out-of-line `Down()` body.
1158///
1159/// Must be used in the same translation unit and follow the matching
1160/// `LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE(timestamp, ...)` declaration.
1161///
1162/// @param timestamp Timestamp of the migration whose Down() body follows.
1163///
1164/// @ingroup SqlMigration
1165#define LIGHTWEIGHT_SQL_MIGRATION_DOWN(timestamp) \
1166 void Migration_##timestamp::Down(Lightweight::SqlMigrationQueryBuilder& plan) const
1167
1168/// @brief Associates a software release (version string) with the highest migration timestamp
1169/// present at the time of that release.
1170///
1171/// Declare one `LIGHTWEIGHT_SQL_RELEASE` per cut release, alongside the migrations that belong to it.
1172/// The macro registers with the migration manager at static-initialization time. Multiple releases
1173/// may coexist in the same translation unit.
1174///
1175/// @param version A string literal, e.g. `"6.7.0"`.
1176/// @param highestTimestamp An unsigned integer literal matching the timestamp format used by migrations.
1177///
1178/// @code
1179/// LIGHTWEIGHT_SQL_MIGRATION(20260101120000, "Initial schema") { ... }
1180/// LIGHTWEIGHT_SQL_RELEASE("6.6.0", 20260101120000);
1181///
1182/// LIGHTWEIGHT_SQL_MIGRATION(20260501120000, "Add orders table") { ... }
1183/// LIGHTWEIGHT_SQL_RELEASE("6.7.0", 20260501120000);
1184/// @endcode
1185///
1186/// @ingroup SqlMigration
1187#define LIGHTWEIGHT_SQL_RELEASE(version, highestTimestamp) \
1188 static ::Lightweight::SqlMigration::detail::ReleaseRegistrar _LIGHTWEIGHT_CONCATENATE_INNER(_lw_release_, __COUNTER__) \
1189 { \
1190 (version), ::Lightweight::SqlMigration::MigrationTimestamp \
1191 { \
1192 (highestTimestamp) \
1193 } \
1194 }
Main API for mapping records to and from the database using high level C++ syntax.
Query builder for building SQL migration queries.
Definition Migrate.hpp:469
virtual void Up(SqlMigrationQueryBuilder &plan) const =0
MigrationTimestamp GetTimestamp() const noexcept
std::string_view GetTitle() const noexcept
virtual std::vector< MigrationTimestamp > GetDependencies() const
virtual std::string_view GetAuthor() const noexcept
virtual std::string_view GetDescription() const noexcept
MigrationBase(MigrationBase const &)=default
Default copy constructor.
LIGHTWEIGHT_API std::string ComputeChecksum(SqlQueryFormatter const &formatter) const
MigrationBase(MigrationTimestamp timestamp, std::string_view title)
Constructs a migration with the given timestamp and title.
virtual void Down(SqlMigrationQueryBuilder &plan) const
virtual bool HasDownImplementation() const noexcept
MigrationBase & operator=(MigrationBase const &)=default
Default copy assignment operator.
std::string const & GetMigrationTitle() const noexcept
Human-readable title of the failing migration.
Operation GetOperation() const noexcept
Whether the failure occurred while applying or reverting.
std::string const & GetFailedSql() const noexcept
The exact SQL statement that the driver rejected.
std::string const & GetDriverMessage() const noexcept
Operation
Whether the failure happened while applying (Up) or reverting (Down).
MigrationTimestamp GetMigrationTimestamp() const noexcept
Timestamp of the failing migration.
LIGHTWEIGHT_API MigrationException(Operation operation, MigrationTimestamp timestamp, std::string title, std::size_t stepIndex, std::string failedSql, SqlErrorInfo driverError)
std::size_t GetStepIndex() const noexcept
Zero-based step index inside the plan of the failing migration.
LIGHTWEIGHT_API void MarkMigrationAsApplied(MigrationBase const &migration)
LIGHTWEIGHT_API void ComposeCompatPolicy(CompatPolicy policy)
Composes an additional policy with the currently installed one. If no policy is installed,...
std::function< void(MigrationBase const &, size_t, size_t)> ExecuteCallback
Callback type invoked during migration execution to report progress.
LIGHTWEIGHT_API DataMapper & GetDataMapper()
Get the data mapper used for migrations.
LIGHTWEIGHT_API RevertResult RevertToMigration(MigrationTimestamp target, ExecuteCallback const &feedbackCallback={})
LIGHTWEIGHT_API void AddMigration(MigrationBase const *migration)
LIGHTWEIGHT_API HardResetResult HardReset(bool dryRun=false)
Drops every table the registered migrations would create (incl. schema_migrations),...
std::list< MigrationBase const * > MigrationList
Type alias for a list of migration pointers.
LIGHTWEIGHT_API MigrationList const & GetAllMigrations() const noexcept
LIGHTWEIGHT_API void SetDefaultSchema(std::string schema)
Sets the default schema applied to migration connections.
LIGHTWEIGHT_API std::vector< std::string > PreviewPendingMigrationsUpTo(MigrationTimestamp targetInclusive, ExecuteCallback const &feedbackCallback={}) const
LIGHTWEIGHT_API size_t ApplyPendingMigrationsUpTo(MigrationTimestamp targetInclusive, ExecuteCallback const &feedbackCallback={})
LIGHTWEIGHT_API std::vector< std::string > PreviewPendingMigrations(ExecuteCallback const &feedbackCallback={}) const
LIGHTWEIGHT_API void RevertSingleMigration(MigrationBase const &migration)
std::function< void(std::string_view)> LogSink
LIGHTWEIGHT_API std::string_view DefaultSchema() const noexcept
Returns the default schema currently installed on the manager (empty when unset).
LIGHTWEIGHT_API void RemoveAllReleases()
Remove all registered releases. Useful for resetting state in tests.
LIGHTWEIGHT_API PlanFoldingResult FoldRegisteredMigrations(SqlQueryFormatter const &formatter, std::optional< MigrationTimestamp > upToInclusive=std::nullopt) const
Pure plan-walk that folds the effect of every registered migration.
LIGHTWEIGHT_API SqlTransaction Transaction()
LIGHTWEIGHT_API void RegisterRelease(std::string version, MigrationTimestamp highestTimestamp)
LIGHTWEIGHT_API MigrationBase const * GetMigration(MigrationTimestamp timestamp) const noexcept
LIGHTWEIGHT_API std::set< std::string > CompatFlagsFor(MigrationBase const &migration) const
Returns the compat flags the current policy assigns to migration, or an empty set if no policy is ins...
LIGHTWEIGHT_API std::list< MigrationBase const * > GetPending() const noexcept
LIGHTWEIGHT_API std::vector< ChecksumVerificationResult > VerifyChecksums() const
LIGHTWEIGHT_API MigrationRelease const * FindReleaseByVersion(std::string_view version) const noexcept
LIGHTWEIGHT_API RewriteChecksumsResult RewriteChecksums(bool dryRun=false)
Re-stamps schema_migrations.checksum rows that have drifted.
LIGHTWEIGHT_API CompatPolicy const & GetCompatPolicy() const noexcept
Returns a view of the installed policy so it can be propagated across managers (plugin → central)....
LIGHTWEIGHT_API MigrationRelease const * FindReleaseForTimestamp(MigrationTimestamp timestamp) const noexcept
LIGHTWEIGHT_API MigrationList GetMigrationsForRelease(std::string_view version) const
LIGHTWEIGHT_API std::vector< std::string > PreviewMigration(MigrationBase const &migration) const
LIGHTWEIGHT_API size_t ApplyPendingMigrations(ExecuteCallback const &feedbackCallback={})
LIGHTWEIGHT_API void CreateMigrationHistory()
Create the migration history table if it does not exist.
LIGHTWEIGHT_API MigrationStatus GetMigrationStatus() const
LIGHTWEIGHT_API void AddVirtualAppliedMigrations(std::span< MigrationTimestamp const > timestamps)
LIGHTWEIGHT_API void SetCompatPolicy(CompatPolicy policy)
Installs a per-migration compat policy. Pass {} to clear.
LIGHTWEIGHT_API void SetLogSink(LogSink sink)
LIGHTWEIGHT_API void ValidateDependencies() const
std::function< std::set< std::string >(MigrationBase const &)> CompatPolicy
Per-migration compat policy.
LIGHTWEIGHT_API UnicodeUpgradeResult UnicodeUpgradeTables(bool dryRun=false)
Rewrites legacy VARCHAR/CHAR columns to NVARCHAR/NCHAR where the registered migrations now declare wi...
LIGHTWEIGHT_API void Log(std::string_view message) const
LIGHTWEIGHT_API std::vector< MigrationRelease > const & GetAllReleases() const noexcept
Get all registered releases, sorted ascending by highestTimestamp.
LIGHTWEIGHT_API void ApplySingleMigration(MigrationBase const &migration)
LIGHTWEIGHT_API std::vector< std::string > PreviewMigrationWithContext(MigrationBase const &migration, MigrationRenderContext &context) const
Variant of PreviewMigration that threads a caller-owned render context so the column-width cache accu...
static LIGHTWEIGHT_API MigrationManager & GetInstance()
LIGHTWEIGHT_API std::vector< MigrationTimestamp > GetAppliedMigrationIds() const
LIGHTWEIGHT_API void ClearVirtualAppliedMigrations()
API to format SQL queries for different SQL dialects.
std::variant< SqlCreateTablePlan, SqlAlterTablePlan, SqlDropTablePlan, SqlCreateIndexPlan, SqlRawSqlPlan, SqlInsertDataPlan, SqlUpdateDataPlan, SqlDeleteDataPlan > SqlMigrationPlanElement
Represents a single SQL migration plan element.
Opt-in behavioural knobs that the ToSql migration-plan renderer honours.
Represents an ODBC SQL error.
Definition SqlError.hpp:33
MigrationTimestamp timestamp
The timestamp of the verified migration.
std::string computedChecksum
The checksum computed from the current migration definition.
std::string storedChecksum
The checksum stored in the database.
bool matches
Whether the stored and computed checksums match.
std::string_view title
The title of the verified migration.
One row in the RewriteChecksumsResult.entries list.
std::string_view title
Title of the registered migration (or "(Unknown Migration)").
MigrationTimestamp timestamp
The migration whose stored checksum was rewritten.
std::string oldChecksum
Stored checksum before the rewrite. Empty if there was none.
std::string newChecksum
Stored checksum after the rewrite.
One column upgrade entry in UnicodeUpgradeResult.
SqlColumnTypeDefinition liveType
Live byte-counted type the column currently has.
SqlSchema::FullyQualifiedTableName table
Fully-qualified name of the table that owns the column.
SqlColumnTypeDefinition intendedType
Char-counted type the migrations now declare for this column.
bool nullable
Whether the column is nullable (preserved across the type rewrite).
std::string column
Name of the column being upgraded.
std::vector< SqlSchema::FullyQualifiedTableName > absentTables
std::vector< SqlSchema::FullyQualifiedTableName > preservedTables
std::vector< SqlSchema::FullyQualifiedTableName > droppedTables
One data step (INSERT/UPDATE/DELETE/RawSql) tagged with its source migration.
std::string sourceTitle
Title of the migration that contributed this data step.
SqlMigrationPlanElement element
The plan element itself (INSERT, UPDATE, DELETE, or RawSql).
MigrationTimestamp sourceTimestamp
Timestamp of the migration that contributed this data step.
Per-table state: ordered column declarations + per-table FK list.
std::vector< SqlColumnDeclaration > columns
Ordered column declarations as they would be emitted in CREATE TABLE.
std::vector< SqlCompositeForeignKeyConstraint > compositeForeignKeys
Snapshot of the schema the registered migrations intend to produce.
std::vector< SqlCreateIndexPlan > indexes
Indexes that survive folding (created on tables still present at end).
std::map< SqlSchema::FullyQualifiedTableName, TableState > tables
std::vector< MigrationRelease > releases
Releases declared via LIGHTWEIGHT_SQL_RELEASE that fall within the fold range.
std::vector< SqlSchema::FullyQualifiedTableName > creationOrder
std::vector< std::pair< MigrationTimestamp, std::string > > foldedMigrations
std::vector< MigrationTimestamp > unregisteredTimestamps
Applied rows whose migration is no longer registered.
std::vector< ChecksumRewriteEntry > entries
One entry per rewritten or would-be-rewritten row.
std::vector< SqlCompositeForeignKeyConstraint > rebuiltForeignKeys
std::vector< MigrationTimestamp > dependencies
Migrations that must be applied before this one.
std::string_view author
Author of the migration. Recorded in schema_migrations when the migration is applied.
std::string_view description
Long-form description. Recorded in schema_migrations when the migration is applied.
MigrationTimestamp highestTimestamp
Highest migration timestamp contained in this release (inclusive).
std::string version
Human-readable version string, e.g. "6.7.0".
size_t mismatchCount
Number of applied migrations with checksum mismatches.
size_t appliedCount
Number of migrations that have been applied.
size_t totalRegistered
Total number of registered migrations.
size_t pendingCount
Number of migrations waiting to be applied.
size_t unknownAppliedCount
Number of applied migrations not found in registered list.
constexpr std::weak_ordering operator<=>(MigrationTimestamp const &other) const noexcept=default
Three-way comparison operator.
uint64_t value
The numeric timestamp value identifying the migration.
std::vector< MigrationTimestamp > revertedTimestamps
Successfully reverted migrations.
std::optional< MigrationTimestamp > failedAt
Migration that failed, if any.
std::string sqlState
SQLSTATE diagnostic code from the driver, if available.
std::string errorMessage
Short error message if failed (driver message only)
SQLINTEGER nativeErrorCode
Native driver error code, if available.