Lightweight 0.20260625.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 /// - `HardReset` to know which tables the migrations would have created
466 /// - `UnicodeUpgradeTables` to know which char/varchar columns the migrations
467 /// now declare wide
468 ///
469 /// Both are pure operations — `FoldRegisteredMigrations` itself never
470 /// executes a single statement. See `FoldRegisteredMigrations` for the API.
472 {
473 /// Per-table state: ordered column declarations + per-table FK list.
475 {
476 /// Ordered column declarations as they would be emitted in CREATE TABLE.
477 std::vector<SqlColumnDeclaration> columns;
478 /// Composite foreign keys declared on this table (single-column FKs are
479 /// carried inline on the corresponding `SqlColumnDeclaration::foreignKey`).
480 std::vector<SqlCompositeForeignKeyConstraint> compositeForeignKeys;
481 /// Whether the original CREATE TABLE used IF NOT EXISTS (carried through
482 /// to emitters so they can replay it verbatim).
483 bool ifNotExists = false;
484 };
485
486 /// (schema, table) → folded `TableState`. Insertion order is *not* preserved by
487 /// `std::map` — for emission order use `creationOrder` below.
488 std::map<SqlSchema::FullyQualifiedTableName, TableState> tables;
489
490 /// Tables in *creation* order (first-time-seen). Reverse for safe DROP ordering
491 /// when tearing the schema down.
492 std::vector<SqlSchema::FullyQualifiedTableName> creationOrder;
493
494 /// Indexes that survive folding (created on tables still present at end).
495 std::vector<SqlCreateIndexPlan> indexes;
496
497 /// One data step (INSERT/UPDATE/DELETE/RawSql) tagged with its source migration.
498 struct DataStep
499 {
500 /// Timestamp of the migration that contributed this data step.
502 /// Title of the migration that contributed this data step.
503 std::string sourceTitle;
504 /// The plan element itself (INSERT, UPDATE, DELETE, or RawSql).
506 };
507
508 /// Data steps in chronological order. **No coalescing** — the fold replays
509 /// every data step verbatim, exactly as if migrations were applied in order.
510 std::vector<DataStep> dataSteps;
511
512 /// Releases declared via `LIGHTWEIGHT_SQL_RELEASE` that fall within the fold range.
513 std::vector<MigrationRelease> releases;
514
515 /// Migrations that contributed to the fold (timestamp + title). Used by emitters
516 /// to write a header comment explaining what was collapsed.
517 std::vector<std::pair<MigrationTimestamp, std::string>> foldedMigrations;
518 };
519
520 /// @brief Pure plan-walk that folds the effect of every registered migration.
521 ///
522 /// Visits each migration's `Up()` plan in timestamp order (or up to
523 /// `upToInclusive` if provided) and accumulates the cumulative end-state into a
524 /// `PlanFoldingResult`. **Never** executes SQL or touches a database connection
525 /// — the supplied formatter is only used to build the in-memory plan elements.
526 ///
527 /// @param formatter Formatter used by the migration query builder while walking
528 /// each migration's `Up()`. Any standard formatter works; the
529 /// walk inspects plan element shapes, not rendered SQL.
530 /// @param upToInclusive If set, fold only migrations with `timestamp <= upToInclusive`.
531 /// If unset, fold all registered migrations.
532 ///
533 /// @return The folded snapshot. Safe to call without a `MigrationManager`-attached
534 /// data mapper.
535 [[nodiscard]] LIGHTWEIGHT_API PlanFoldingResult FoldRegisteredMigrations(
536 SqlQueryFormatter const& formatter, std::optional<MigrationTimestamp> upToInclusive = std::nullopt) const;
537
538 /// @brief Result of a `HardReset` call.
540 {
541 /// True when the populating call was a dry-run; the result describes the
542 /// would-be plan and no DDL was issued.
543 bool wasDryRun = false;
544 /// Tables the registered migrations *would* have created and were also present
545 /// in the live DB — these were dropped (or would be dropped on a real run).
546 std::vector<SqlSchema::FullyQualifiedTableName> droppedTables;
547 /// Tables registered migrations declare but that aren't in the live DB —
548 /// nothing to do for these. Reported for visibility only.
549 std::vector<SqlSchema::FullyQualifiedTableName> absentTables;
550 /// Tables in the live DB the registered migrations don't know about — left
551 /// alone (user-owned). Reported prominently so operators notice.
552 std::vector<SqlSchema::FullyQualifiedTableName> preservedTables;
553 /// Whether the `schema_migrations` table itself was dropped (always true on a
554 /// real run, false on dry-run).
556 };
557
558 /// @brief Drops every table the registered migrations would create (incl.
559 /// `schema_migrations`), preserving any user-created tables.
560 ///
561 /// Folds all registered migrations to compute the migration-owned set, intersects
562 /// with the live schema (via `SqlSchema::ReadAllTables`), then drops the matching
563 /// live tables in **reverse** creation order with `cascade=true ifExists=true`. The
564 /// `schema_migrations` table is dropped explicitly because it's created outside the
565 /// registered-migrations stream.
566 ///
567 /// Tables present in the live DB but not in the migration plan are left alone and
568 /// reported under `preservedTables` so operators can spot them.
569 ///
570 /// @param dryRun When true, returns the would-be plan without writing anything.
571 [[nodiscard]] LIGHTWEIGHT_API HardResetResult HardReset(bool dryRun = false);
572
573 /// @brief One column upgrade entry in `UnicodeUpgradeResult`.
575 {
576 /// Fully-qualified name of the table that owns the column.
577 SqlSchema::FullyQualifiedTableName table;
578 /// Name of the column being upgraded.
579 std::string column;
580 /// Live byte-counted type the column currently has.
581 SqlColumnTypeDefinition liveType;
582 /// Char-counted type the migrations now declare for this column.
583 SqlColumnTypeDefinition intendedType;
584 /// Whether the column is nullable (preserved across the type rewrite).
585 bool nullable = true;
586 };
587
588 /// @brief Result of an `UnicodeUpgradeTables` call.
590 {
591 /// True when the populating call was a dry-run; the result describes the
592 /// would-be diff and no DDL was issued.
593 bool wasDryRun = false;
594 /// Columns whose live type drifted from the intended type and were upgraded
595 /// (or would be on a real run).
596 std::vector<ColumnUpgradeEntry> columns;
597 /// Foreign keys that had to be dropped + re-added to upgrade their
598 /// participating columns. Reported so operators see the FK churn.
599 std::vector<SqlCompositeForeignKeyConstraint> rebuiltForeignKeys;
600 };
601
602 /// @brief Rewrites legacy `VARCHAR/CHAR` columns to `NVARCHAR/NCHAR` where the
603 /// registered migrations now declare wide types.
604 ///
605 /// Compares the folded plan's intended column types against `SqlSchema::ReadAllTables`
606 /// output; an upgrade is triggered iff intended is `NVarchar`/`NChar` AND live is
607 /// `Varchar`/`Char` with the same `size`. Foreign keys that touch any upgrade column
608 /// are dropped before the alter and re-added afterwards. Cross-backend; SQLite
609 /// uses the in-tree `RebuildSqliteTable` recipe under the hood.
610 ///
611 /// @param dryRun When true, returns the would-be diff without writing anything.
612 [[nodiscard]] LIGHTWEIGHT_API UnicodeUpgradeResult UnicodeUpgradeTables(bool dryRun = false);
613
614 /// @brief Re-stamps `schema_migrations.checksum` rows that have drifted.
615 ///
616 /// Applied migrations whose stored checksum no longer matches the current
617 /// `MigrationBase::ComputeChecksum` output are updated in-place. Migrations that
618 /// match are left alone. Rows that reference a migration which is no longer
619 /// registered (e.g. removed from the codebase) are surfaced via
620 /// `RewriteChecksumsResult.unregisteredTimestamps` and *not* touched.
621 ///
622 /// This is a recovery tool used when a regen of generated migrations changes
623 /// their byte-level shape but not their logical effect — running it without
624 /// understanding *why* the checksum drifted would defeat the integrity check.
625 ///
626 /// @param dryRun When true, returns the would-be diff without writing anything.
627 [[nodiscard]] LIGHTWEIGHT_API RewriteChecksumsResult RewriteChecksums(bool dryRun = false);
628
629 /// Preview SQL statements for all pending migrations without executing.
630 ///
631 /// This is useful for dry-run mode to see what SQL would be executed.
632 ///
633 /// @param feedbackCallback Optional callback to be called for each migration.
634 /// @return Vector of all SQL statements that would be executed.
635 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewPendingMigrations(
636 ExecuteCallback const& feedbackCallback = {}) const;
637
638 /// Preview SQL for pending migrations whose timestamp is <= `targetInclusive`.
639 ///
640 /// Bounded counterpart of `PreviewPendingMigrations`. Same dependency
641 /// rules as `ApplyPendingMigrationsUpTo`: if any included migration
642 /// depends on an excluded pending migration, this throws.
643 ///
644 /// @param targetInclusive Highest timestamp to preview (inclusive).
645 /// @param feedbackCallback Optional callback to be called for each migration.
646 /// @return Vector of all SQL statements that would be executed.
647 /// @throws std::runtime_error if a dependency would cross the boundary.
648 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewPendingMigrationsUpTo(
649 MigrationTimestamp targetInclusive, ExecuteCallback const& feedbackCallback = {}) const;
650
651 /// Verify checksums of all applied migrations.
652 ///
653 /// Compares the stored checksums in the database with the computed checksums
654 /// of the current migration definitions. This helps detect if migrations
655 /// have been modified after they were applied.
656 ///
657 /// @return Vector of verification results for migrations with mismatched or missing checksums.
658 [[nodiscard]] LIGHTWEIGHT_API std::vector<ChecksumVerificationResult> VerifyChecksums() const;
659
660 /// Mark a migration as applied without executing its Up() method.
661 ///
662 /// This is useful for:
663 /// - Baseline migrations when setting up an existing database
664 /// - Marking migrations that were applied manually or through other means
665 /// - Skipping migrations that are not applicable to a specific environment
666 ///
667 /// @param migration The migration to mark as applied.
668 /// @throws std::runtime_error if the migration is already applied.
669 LIGHTWEIGHT_API void MarkMigrationAsApplied(MigrationBase const& migration);
670
671 /// Revert all migrations applied after the target timestamp.
672 ///
673 /// This method reverts migrations in reverse chronological order,
674 /// rolling back from the most recent to just after the target.
675 /// The target migration itself is NOT reverted.
676 ///
677 /// @param target Target timestamp to revert to. Migrations > target are reverted.
678 /// @param feedbackCallback Optional callback for progress updates.
679 /// @return Result containing list of reverted timestamps or error information.
680 /// @note Stops on first error. Previously reverted migrations in this call are NOT rolled back.
681 [[nodiscard]] LIGHTWEIGHT_API RevertResult RevertToMigration(MigrationTimestamp target,
682 ExecuteCallback const& feedbackCallback = {});
683
684 /// Get a summary status of all migrations.
685 ///
686 /// This method provides a quick overview of the migration state without
687 /// needing to iterate through individual migrations.
688 ///
689 /// @return Status struct with counts of applied, pending, and mismatched migrations.
690 [[nodiscard]] LIGHTWEIGHT_API MigrationStatus GetMigrationStatus() const;
691
692 /// Validate that all registered migrations have satisfiable dependencies.
693 ///
694 /// For each pending migration, verifies that every declared dependency is either
695 /// already applied or itself pending (so it will be applied first due to topological
696 /// ordering). Also detects cycles among pending migrations.
697 ///
698 /// @throws std::runtime_error if any dependency is unresolved or a cycle is found.
699 LIGHTWEIGHT_API void ValidateDependencies() const;
700
701 /// Register a release marker for a software version.
702 ///
703 /// Typically called from `LIGHTWEIGHT_SQL_RELEASE(version, timestamp)` at static init time.
704 /// Releases are ordered by `highestTimestamp`; two releases may not share the same
705 /// `highestTimestamp`, and the same version string may not be registered twice.
706 ///
707 /// @param version Human-readable version, e.g. "6.7.0".
708 /// @param highestTimestamp Highest migration timestamp (inclusive) that belongs to this release.
709 /// @throws std::runtime_error on duplicate version or duplicate timestamp.
710 LIGHTWEIGHT_API void RegisterRelease(std::string version, MigrationTimestamp highestTimestamp);
711
712 /// Remove all registered releases. Useful for resetting state in tests.
713 LIGHTWEIGHT_API void RemoveAllReleases();
714
715 /// Get all registered releases, sorted ascending by `highestTimestamp`.
716 [[nodiscard]] LIGHTWEIGHT_API std::vector<MigrationRelease> const& GetAllReleases() const noexcept;
717
718 /// Find a release by exact version string match.
719 ///
720 /// @return Pointer to the matching release, or nullptr if no release with that version is registered.
721 [[nodiscard]] LIGHTWEIGHT_API MigrationRelease const* FindReleaseByVersion(std::string_view version) const noexcept;
722
723 /// Find the release whose timestamp range contains `timestamp`.
724 ///
725 /// Returns the release with the smallest `highestTimestamp` that is `>= timestamp`.
726 /// Returns nullptr if `timestamp` is greater than every registered release's highestTimestamp
727 /// (i.e., the migration is post-all-releases / unreleased).
728 [[nodiscard]] LIGHTWEIGHT_API MigrationRelease const* FindReleaseForTimestamp(
729 MigrationTimestamp timestamp) const noexcept;
730
731 /// Get all registered migrations belonging to a given release.
732 ///
733 /// A migration `M` belongs to release `R` iff
734 /// `prev_release_ts < M.timestamp <= R.highestTimestamp`, where `prev_release_ts` is the
735 /// previous release's timestamp (or 0 if `R` is the first release).
736 ///
737 /// @param version The version string to look up.
738 /// @return Migrations in the release, ordered by timestamp. Empty if the version is unknown.
739 [[nodiscard]] LIGHTWEIGHT_API MigrationList GetMigrationsForRelease(std::string_view version) const;
740
741 private:
742 /// Return the pending list in dependency-respecting order.
743 ///
744 /// Dependencies among pending migrations are resolved via topological sort.
745 /// Migrations with no dependency between them keep timestamp order.
746 /// Throws on missing deps or cycles.
747 [[nodiscard]] MigrationList TopoSortPending(MigrationList pending,
748 std::vector<MigrationTimestamp> const& applied) const;
749
750 /// @brief Builds a fresh, policy-agnostic render context. The per-migration
751 /// compat knobs are layered on by `ApplySingleMigration` / `PreviewMigrationWithContext`
752 /// before rendering a given migration's steps.
753 [[nodiscard]] static MigrationRenderContext MakeRenderContext();
754
755 /// @brief Materialises any pending virtual-applied entries as real rows
756 /// in `schema_migrations`. Creates the history table if necessary, then
757 /// inserts one row per overlay entry that maps to a registered
758 /// migration and is not yet present in the table. Clears the overlay on
759 /// success. Called as the first action of every write path so the
760 /// physical table only exists when there is at least one row to put in
761 /// it.
762 void PersistVirtualAppliedMigrations();
763
764 MigrationList _migrations;
765 std::vector<MigrationRelease> _releases;
766 mutable DataMapper* _dataMapper { nullptr };
767 CompatPolicy _compatPolicy;
768 std::string _defaultSchema; ///< Default schema applied to migration connections, empty when unset.
769
770 /// @brief In-memory overlay of applied migration timestamps that are
771 /// not (yet) persisted to `schema_migrations`. Populated by plugin
772 /// post-init hooks via `AddVirtualAppliedMigrations`; drained into the
773 /// table by `PersistVirtualAppliedMigrations` whenever the manager
774 /// transitions from a read-only state into actually writing. Always
775 /// kept sorted ascending and deduplicated.
776 std::vector<MigrationTimestamp> _virtualAppliedIds;
777
778 LogSink _logSink; ///< Optional status-message sink. Empty by default.
779 };
780
781/// Requires the user to call LIGHTWEIGHT_MIGRATION_PLUGIN() in exactly one CPP file of the migration plugin.
782///
783/// @ingroup SqlMigration
784#define LIGHTWEIGHT_MIGRATION_PLUGIN() \
785 /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
786 extern "C" LIGHTWEIGHT_EXPORT Lightweight::SqlMigration::MigrationManager* AcquireMigrationManager() \
787 { \
788 return &Lightweight::SqlMigration::MigrationManager::GetInstance(); \
789 }
790
791/// Optional hook executed once per plugin by dbtool, after migration history has been
792/// created and an active connection is available on the central manager. Plugins use
793/// this to perform one-shot bridging work that needs both a live `SqlConnection` and
794/// the merged `MigrationManager` — for example, importing legacy version tracking
795/// from a custom table into `schema_migrations`.
796///
797/// Use as:
798///
799/// @code
800/// LIGHTWEIGHT_MIGRATION_PLUGIN_POSTINIT(connection, manager)
801/// {
802/// MyPlugin::ImportLegacyVersions(manager, connection);
803/// }
804/// @endcode
805///
806/// @ingroup SqlMigration
807#define LIGHTWEIGHT_MIGRATION_PLUGIN_POSTINIT(connArg, mgrArg) \
808 extern "C" LIGHTWEIGHT_EXPORT void LightweightMigrationPluginPostInit( \
809 Lightweight::SqlConnection&(connArg), Lightweight::SqlMigration::MigrationManager&(mgrArg))
810
811 /// Represents a single unique SQL migration.
812 ///
813 /// @ingroup SqlMigration
815 {
816 public:
817 /// Default copy constructor.
818 MigrationBase(MigrationBase const&) = default;
819 MigrationBase(MigrationBase&&) = delete;
820 /// Default copy assignment operator.
823 /// Constructs a migration with the given timestamp and title.
824 MigrationBase(MigrationTimestamp timestamp, std::string_view title):
825 _timestamp { timestamp },
826 _title { title }
827 {
829 }
830
831 virtual ~MigrationBase() = default;
832
833 /// Apply the migration.
834 ///
835 /// @param plan Query builder to use for building the migration plan.
836 virtual void Up(SqlMigrationQueryBuilder& plan) const = 0;
837
838 /// Revert the migration.
839 ///
840 /// @param plan Query builder to use for building the migration plan.
841 virtual void Down(SqlMigrationQueryBuilder& plan) const
842 {
843 (void) plan;
844 }
845
846 /// Check if this migration has a Down() implementation for rollback.
847 ///
848 /// This method determines whether the migration can be safely reverted.
849 /// The default implementation returns false. Derived classes that implement
850 /// Down() should override this to return true.
851 ///
852 /// @return true if Down() is implemented and can revert this migration.
853 [[nodiscard]] virtual bool HasDownImplementation() const noexcept
854 {
855 return false;
856 }
857
858 /// Get the timestamps of migrations that this migration depends on.
859 ///
860 /// Dependencies are enforced at apply time: each declared dependency must be
861 /// registered and applied (or pending in the same run, in dependency order)
862 /// before this migration will run. The default implementation returns no
863 /// dependencies, preserving timestamp-ordered apply behavior.
864 ///
865 /// @return Vector of dependency timestamps. Empty if this migration has none.
866 [[nodiscard]] virtual std::vector<MigrationTimestamp> GetDependencies() const
867 {
868 return {};
869 }
870
871 /// Get the author of the migration, if any.
872 ///
873 /// @return Author string, empty if not set.
874 [[nodiscard]] virtual std::string_view GetAuthor() const noexcept
875 {
876 return {};
877 }
878
879 /// Get the long-form description of the migration, if any.
880 ///
881 /// This is a multi-line description in addition to the short title.
882 ///
883 /// @return Description string, empty if not set.
884 [[nodiscard]] virtual std::string_view GetDescription() const noexcept
885 {
886 return {};
887 }
888
889 /// Get the timestamp of the migration.
890 ///
891 /// @return Timestamp of the migration.
892 [[nodiscard]] MigrationTimestamp GetTimestamp() const noexcept
893 {
894 return _timestamp;
895 }
896
897 /// Get the title of the migration.
898 ///
899 /// @return Title of the migration.
900 [[nodiscard]] std::string_view GetTitle() const noexcept
901 {
902 return _title;
903 }
904
905 /// Compute SHA-256 checksum of migration's Up() SQL statements.
906 ///
907 /// The checksum is computed from the SQL statements that would be executed
908 /// by this migration. This allows detecting if a migration has been modified
909 /// after it was applied.
910 ///
911 /// @param formatter The SQL query formatter to use for generating SQL.
912 /// @return SHA-256 hex string (64 characters).
913 [[nodiscard]] LIGHTWEIGHT_API std::string ComputeChecksum(SqlQueryFormatter const& formatter) const;
914
915 private:
916 MigrationTimestamp _timestamp;
917 std::string_view _title;
918 };
919
920 /// Represents a single unique SQL migration.
921 ///
922 /// This class is a convenience class that can be used to create a migration.
923 ///
924 /// @ingroup SqlMigration
925 /// Optional metadata attached to a Migration at construction time.
926 ///
927 /// All fields are optional. Unset fields behave as if the feature
928 /// were not used (e.g. empty dependencies preserves timestamp order).
929 ///
930 /// @ingroup SqlMigration
932 {
933 /// Migrations that must be applied before this one.
934 std::vector<MigrationTimestamp> dependencies {};
935 /// Author of the migration. Recorded in schema_migrations when the migration is applied.
936 std::string_view author {};
937 /// Long-form description. Recorded in schema_migrations when the migration is applied.
938 std::string_view description {};
939 };
940
941 template <uint64_t TsValue>
942 class Migration: public MigrationBase
943 {
944 public:
945 /// The migration's timestamp, available at compile time from the type itself.
946 ///
947 /// Exposed so @ref TimestampOf can extract the timestamp without
948 /// having to repeat the literal at every use site (e.g. in dependency lists).
949 static constexpr MigrationTimestamp TimeStamp { TsValue };
950
951 /// Create a new migration.
952 ///
953 /// @param title Title of the migration.
954 /// @param up Function to apply the migration.
955 /// @param down Function to revert the migration (optional).
956 Migration(std::string_view title,
957 std::function<void(SqlMigrationQueryBuilder&)> const& up,
958 std::function<void(SqlMigrationQueryBuilder&)> const& down = {}):
959 MigrationBase(TimeStamp, title),
960 _up { up },
961 _down { down }
962 {
963 }
964
965 /// Create a new migration with optional metadata (dependencies, author, description).
966 ///
967 /// @param title Title of the migration.
968 /// @param metadata Optional metadata including dependencies, author, and description.
969 /// @param up Function to apply the migration.
970 /// @param down Function to revert the migration (optional).
971 Migration(std::string_view title,
972 MigrationMetadata metadata,
973 std::function<void(SqlMigrationQueryBuilder&)> const& up,
974 std::function<void(SqlMigrationQueryBuilder&)> const& down = {}):
975 MigrationBase(TimeStamp, title),
976 _up { up },
977 _down { down },
978 _metadata { std::move(metadata) }
979 {
980 }
981
982 /// Apply the migration.
983 ///
984 /// @param builder Query builder to use for building the migration plan.
985 void Up(SqlMigrationQueryBuilder& builder) const override
986 {
987 _up(builder);
988 }
989
990 /// Revert the migration.
991 ///
992 /// @param builder Query builder to use for building the migration plan.
993 void Down(SqlMigrationQueryBuilder& builder) const override
994 {
995 if (_down)
996 _down(builder);
997 }
998
999 /// Check if this migration has a Down() implementation.
1000 ///
1001 /// @return true if a down function was provided.
1002 [[nodiscard]] bool HasDownImplementation() const noexcept override
1003 {
1004 return static_cast<bool>(_down);
1005 }
1006
1007 /// Get the declared dependencies for this migration.
1008 [[nodiscard]] std::vector<MigrationTimestamp> GetDependencies() const override
1009 {
1010 return _metadata.dependencies;
1011 }
1012
1013 /// Get the author of this migration.
1014 [[nodiscard]] std::string_view GetAuthor() const noexcept override
1015 {
1016 return _metadata.author;
1017 }
1018
1019 /// Get the long-form description of this migration.
1020 [[nodiscard]] std::string_view GetDescription() const noexcept override
1021 {
1022 return _metadata.description;
1023 }
1024
1025 private:
1026 std::function<void(SqlMigrationQueryBuilder&)> _up;
1027 std::function<void(SqlMigrationQueryBuilder&)> _down;
1028 MigrationMetadata _metadata {};
1029 };
1030
1031 /// Extracts the @ref MigrationTimestamp from a migration type that exposes a static
1032 /// constexpr `TimeStamp` member.
1033 ///
1034 /// Works with both the class template @ref Migration and the struct generated by the
1035 /// @ref LIGHTWEIGHT_SQL_MIGRATION macro. Use via `decltype`:
1036 ///
1037 /// @code
1038 /// auto migrationA = SqlMigration::Migration<202601010001>("dep base", [](auto&){ ... });
1039 /// auto tsA = TimestampOf<decltype(migrationA)>; // MigrationTimestamp { 202601010001 }
1040 /// @endcode
1041 ///
1042 /// @tparam T A migration type exposing `static constexpr MigrationTimestamp TimeStamp`.
1043 template <typename T>
1044 inline constexpr MigrationTimestamp TimestampOf = T::TimeStamp;
1045
1046 namespace detail
1047 {
1048 /// RAII registrar used by `LIGHTWEIGHT_SQL_RELEASE` to register a release with the
1049 /// migration manager at static-initialization time. Not intended for direct use.
1050 ///
1051 /// @ingroup SqlMigration
1052 struct ReleaseRegistrar
1053 {
1054 /// Registers `{version, highestTimestamp}` with the singleton manager.
1055 ReleaseRegistrar(std::string version, MigrationTimestamp highestTimestamp)
1056 {
1057 MigrationManager::GetInstance().RegisterRelease(std::move(version), highestTimestamp);
1058 }
1059 };
1060 } // namespace detail
1061
1062} // namespace SqlMigration
1063
1064} // namespace Lightweight
1065
1066#define _LIGHTWEIGHT_CONCATENATE(s1, s2) s1##s2
1067#define _LIGHTWEIGHT_CONCATENATE_INNER(s1, s2) _LIGHTWEIGHT_CONCATENATE(s1, s2)
1068
1069/// @brief Represents the C++ migration object for a given timestamped migration.
1070/// @param timestamp Timestamp of the migration.
1071///
1072/// @ingroup SqlMigration
1073#define LIGHTWEIGHT_MIGRATION_INSTANCE(timestamp) migration_##timestamp
1074
1075/// @brief Creates a new migration.
1076/// @param timestamp Timestamp of the migration.
1077/// @param description Description of the migration.
1078///
1079/// @note The migration will be registered with the migration manager automatically.
1080///
1081/// @code
1082/// #include <Lightweight/SqlMigration.hpp>
1083///
1084/// LIGHTWEIGHT_SQL_MIGRATION(20260117234120, "Create table 'MyTable'")
1085/// {
1086/// // Use 'plan' to define the migration steps, for example creating tables.
1087/// }
1088/// @endcode
1089///
1090/// @see Lightweight::SqlMigrationQueryBuilder
1091///
1092/// @ingroup SqlMigration
1093#define LIGHTWEIGHT_SQL_MIGRATION(timestamp, description) \
1094 struct Migration_##timestamp: public Lightweight::SqlMigration::MigrationBase \
1095 { \
1096 /** The migration's timestamp, exposed so @ref TimestampOf can extract it from the type. */ \
1097 static constexpr Lightweight::SqlMigration::MigrationTimestamp TimeStamp { static_cast<uint64_t>(timestamp) }; \
1098 explicit Migration_##timestamp(): \
1099 Lightweight::SqlMigration::MigrationBase(TimeStamp, description) \
1100 { \
1101 } \
1102 \
1103 void Up(Lightweight::SqlMigrationQueryBuilder& plan) const override; \
1104 void Down(Lightweight::SqlMigrationQueryBuilder& /*plan*/) const override {} \
1105 }; \
1106 \
1107 static Migration_##timestamp _LIGHTWEIGHT_CONCATENATE(migration_, timestamp); \
1108 \
1109 void Migration_##timestamp::Up(Lightweight::SqlMigrationQueryBuilder& plan) const
1110
1111/// @brief Creates a new reversible migration whose `Down()` is defined out-of-line
1112/// via @ref LIGHTWEIGHT_SQL_MIGRATION_DOWN.
1113///
1114/// Use this variant when the migration needs to be reversible. Follow the macro with
1115/// the `Up()` body, then later use `LIGHTWEIGHT_SQL_MIGRATION_DOWN(timestamp) { ... }`
1116/// to provide the `Down()` body.
1117///
1118/// @param timestamp Timestamp of the migration.
1119/// @param description Description of the migration.
1120///
1121/// @code
1122/// LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE(20260117234120, "Create table 'MyTable'")
1123/// {
1124/// plan.CreateTable("MyTable").PrimaryKey("id", Integer());
1125/// }
1126///
1127/// LIGHTWEIGHT_SQL_MIGRATION_DOWN(20260117234120)
1128/// {
1129/// plan.DropTable("MyTable");
1130/// }
1131/// @endcode
1132///
1133/// @ingroup SqlMigration
1134#define LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE(timestamp, description) \
1135 struct Migration_##timestamp: public Lightweight::SqlMigration::MigrationBase \
1136 { \
1137 explicit Migration_##timestamp(): \
1138 Lightweight::SqlMigration::MigrationBase(Lightweight::SqlMigration::MigrationTimestamp { timestamp }, \
1139 description) \
1140 { \
1141 } \
1142 \
1143 void Up(Lightweight::SqlMigrationQueryBuilder& plan) const override; \
1144 void Down(Lightweight::SqlMigrationQueryBuilder& plan) const override; \
1145 \
1146 [[nodiscard]] bool HasDownImplementation() const noexcept override \
1147 { \
1148 return true; \
1149 } \
1150 }; \
1151 \
1152 static Migration_##timestamp _LIGHTWEIGHT_CONCATENATE(migration_, timestamp); \
1153 \
1154 void Migration_##timestamp::Up(Lightweight::SqlMigrationQueryBuilder& plan) const
1155
1156/// @brief Companion to @ref LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE — defines the out-of-line `Down()` body.
1157///
1158/// Must be used in the same translation unit and follow the matching
1159/// `LIGHTWEIGHT_SQL_MIGRATION_REVERSIBLE(timestamp, ...)` declaration.
1160///
1161/// @param timestamp Timestamp of the migration whose Down() body follows.
1162///
1163/// @ingroup SqlMigration
1164#define LIGHTWEIGHT_SQL_MIGRATION_DOWN(timestamp) \
1165 void Migration_##timestamp::Down(Lightweight::SqlMigrationQueryBuilder& plan) const
1166
1167/// @brief Associates a software release (version string) with the highest migration timestamp
1168/// present at the time of that release.
1169///
1170/// Declare one `LIGHTWEIGHT_SQL_RELEASE` per cut release, alongside the migrations that belong to it.
1171/// The macro registers with the migration manager at static-initialization time. Multiple releases
1172/// may coexist in the same translation unit.
1173///
1174/// @param version A string literal, e.g. `"6.7.0"`.
1175/// @param highestTimestamp An unsigned integer literal matching the timestamp format used by migrations.
1176///
1177/// @code
1178/// LIGHTWEIGHT_SQL_MIGRATION(20260101120000, "Initial schema") { ... }
1179/// LIGHTWEIGHT_SQL_RELEASE("6.6.0", 20260101120000);
1180///
1181/// LIGHTWEIGHT_SQL_MIGRATION(20260501120000, "Add orders table") { ... }
1182/// LIGHTWEIGHT_SQL_RELEASE("6.7.0", 20260501120000);
1183/// @endcode
1184///
1185/// @ingroup SqlMigration
1186#define LIGHTWEIGHT_SQL_RELEASE(version, highestTimestamp) \
1187 static ::Lightweight::SqlMigration::detail::ReleaseRegistrar _LIGHTWEIGHT_CONCATENATE_INNER(_lw_release_, __COUNTER__) \
1188 { \
1189 (version), ::Lightweight::SqlMigration::MigrationTimestamp \
1190 { \
1191 (highestTimestamp) \
1192 } \
1193 }
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:485
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:32
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.