Lightweight 0.20251202.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 "SqlQuery/Migrate.hpp"
8#include "SqlTransaction.hpp"
9
10#include <functional>
11#include <list>
12#include <optional>
13#include <vector>
14
15namespace Lightweight
16{
17
18class SqlConnection;
19
20namespace SqlMigration
21{
22 class MigrationBase;
23
24 /// Represents a unique timestamp of a migration.
25 ///
26 /// This struct is used to identify migrations and is used as a key in the migration history table.
27 ///
28 /// Note, a recommended format for the timestamp is a human readable format like YYYYMMDDHHMMSS
29 ///
30 /// @code
31 /// MigrationTimestamp { 2026'01'17'00'31'20 };
32 /// @endcode
33 ///
34 /// @ingroup SqlMigration
36 {
37 uint64_t value {};
38
39 constexpr std::weak_ordering operator<=>(MigrationTimestamp const& other) const noexcept = default;
40 };
41
42 /// Result of verifying a migration's checksum.
43 ///
44 /// @ingroup SqlMigration
46 {
47 MigrationTimestamp timestamp;
48 std::string_view title;
49 std::string storedChecksum;
50 std::string computedChecksum;
51 bool matches;
52 };
53
54 /// Result of reverting multiple migrations.
55 ///
56 /// @ingroup SqlMigration
58 {
59 std::vector<MigrationTimestamp> revertedTimestamps; ///< Successfully reverted migrations
60 std::optional<MigrationTimestamp> failedAt; ///< Migration that failed, if any
61 std::string errorMessage; ///< Error message if failed
62 };
63
64 /// Status summary of migrations.
65 ///
66 /// @ingroup SqlMigration
68 {
69 size_t appliedCount {}; ///< Number of migrations that have been applied
70 size_t pendingCount {}; ///< Number of migrations waiting to be applied
71 size_t mismatchCount {}; ///< Number of applied migrations with checksum mismatches
72 size_t unknownAppliedCount {}; ///< Number of applied migrations not found in registered list
73 size_t totalRegistered {}; ///< Total number of registered migrations
74 };
75
76 /// Main API to use for managing SQL migrations
77 ///
78 /// This class is a singleton and can be accessed using the GetInstance() method.
79 ///
80 /// @ingroup SqlMigration
82 {
83 public:
84 using MigrationList = std::list<MigrationBase const*>;
85
86 /// Get the singleton instance of the migration manager.
87 ///
88 /// @return Reference to the migration manager.
89 /// Get the singleton instance of the migration manager.
90 ///
91 /// @return Reference to the migration manager.
92 LIGHTWEIGHT_API static MigrationManager& GetInstance();
93
94 /// Add a migration to the manager.
95 ///
96 /// @param migration Pointer to the migration to add.
97 LIGHTWEIGHT_API void AddMigration(MigrationBase const* migration);
98
99 /// Get all migrations that have been added to the manager.
100 ///
101 /// @return List of migrations.
102 [[nodiscard]] LIGHTWEIGHT_API MigrationList const& GetAllMigrations() const noexcept;
103
104 /// Get a migration by timestamp.
105 ///
106 /// @param timestamp Timestamp of the migration to get.
107 /// @return Pointer to the migration if found, nullptr otherwise.
108 [[nodiscard]] LIGHTWEIGHT_API MigrationBase const* GetMigration(MigrationTimestamp timestamp) const noexcept;
109
110 /// Remove all migrations from the manager.
111 ///
112 /// This function is useful if the migration manager should be reset.
113 LIGHTWEIGHT_API void RemoveAllMigrations();
114
115 /// Get all migrations that have not been applied yet.
116 ///
117 /// @return List of pending migrations.
118 [[nodiscard]] LIGHTWEIGHT_API std::list<MigrationBase const*> GetPending() const noexcept;
119
120 using ExecuteCallback =
121 std::function<void(MigrationBase const& /*migration*/, size_t /*current*/, size_t /*total*/)>;
122
123 /// Apply a single migration from a migration object.
124 ///
125 /// @param migration Pointer to the migration to apply.
126 LIGHTWEIGHT_API void ApplySingleMigration(MigrationBase const& migration);
127
128 /// Revert a single migration from a migration object.
129 ///
130 /// @param migration Pointer to the migration to revert.
131 LIGHTWEIGHT_API void RevertSingleMigration(MigrationBase const& migration);
132
133 /// Apply all migrations that have not been applied yet.
134 ///
135 /// @param feedbackCallback Callback to be called for each migration.
136 /// @return Number of applied migrations.
137 LIGHTWEIGHT_API size_t ApplyPendingMigrations(ExecuteCallback const& feedbackCallback = {});
138
139 /// Create the migration history table if it does not exist.
140 LIGHTWEIGHT_API void CreateMigrationHistory();
141
142 /// Get all applied migration IDs.
143 ///
144 /// @return Vector of applied migration IDs.
145 [[nodiscard]] LIGHTWEIGHT_API std::vector<MigrationTimestamp> GetAppliedMigrationIds() const;
146
147 /// Get the data mapper used for migrations.
148 [[nodiscard]] LIGHTWEIGHT_API DataMapper& GetDataMapper();
149
150 /// Get the data mapper used for migrations.
151 [[nodiscard]] LIGHTWEIGHT_API DataMapper& GetDataMapper() const
152 {
153 return const_cast<MigrationManager*>(this)->GetDataMapper();
154 }
155
156 /// Close the data mapper.
157 ///
158 /// This function is useful if explicitly closing the connection is desired before
159 /// the migration manager is destroyed.
160 LIGHTWEIGHT_API void CloseDataMapper();
161
162 /// Get a transaction for the data mapper.
163 ///
164 /// @return Transaction.
165 LIGHTWEIGHT_API SqlTransaction Transaction();
166
167 /// Preview SQL statements for a single migration without executing.
168 ///
169 /// This is useful for dry-run mode to see what SQL would be executed.
170 ///
171 /// @param migration The migration to preview.
172 /// @return Vector of SQL statements that would be executed.
173 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewMigration(MigrationBase const& migration) const;
174
175 /// Preview SQL statements for all pending migrations without executing.
176 ///
177 /// This is useful for dry-run mode to see what SQL would be executed.
178 ///
179 /// @param feedbackCallback Optional callback to be called for each migration.
180 /// @return Vector of all SQL statements that would be executed.
181 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> PreviewPendingMigrations(
182 ExecuteCallback const& feedbackCallback = {}) const;
183
184 /// Verify checksums of all applied migrations.
185 ///
186 /// Compares the stored checksums in the database with the computed checksums
187 /// of the current migration definitions. This helps detect if migrations
188 /// have been modified after they were applied.
189 ///
190 /// @return Vector of verification results for migrations with mismatched or missing checksums.
191 [[nodiscard]] LIGHTWEIGHT_API std::vector<ChecksumVerificationResult> VerifyChecksums() const;
192
193 /// Mark a migration as applied without executing its Up() method.
194 ///
195 /// This is useful for:
196 /// - Baseline migrations when setting up an existing database
197 /// - Marking migrations that were applied manually or through other means
198 /// - Skipping migrations that are not applicable to a specific environment
199 ///
200 /// @param migration The migration to mark as applied.
201 /// @throws std::runtime_error if the migration is already applied.
202 LIGHTWEIGHT_API void MarkMigrationAsApplied(MigrationBase const& migration);
203
204 /// Revert all migrations applied after the target timestamp.
205 ///
206 /// This method reverts migrations in reverse chronological order,
207 /// rolling back from the most recent to just after the target.
208 /// The target migration itself is NOT reverted.
209 ///
210 /// @param target Target timestamp to revert to. Migrations > target are reverted.
211 /// @param feedbackCallback Optional callback for progress updates.
212 /// @return Result containing list of reverted timestamps or error information.
213 /// @note Stops on first error. Previously reverted migrations in this call are NOT rolled back.
214 [[nodiscard]] LIGHTWEIGHT_API RevertResult RevertToMigration(MigrationTimestamp target,
215 ExecuteCallback const& feedbackCallback = {});
216
217 /// Get a summary status of all migrations.
218 ///
219 /// This method provides a quick overview of the migration state without
220 /// needing to iterate through individual migrations.
221 ///
222 /// @return Status struct with counts of applied, pending, and mismatched migrations.
223 [[nodiscard]] LIGHTWEIGHT_API MigrationStatus GetMigrationStatus() const;
224
225 private:
226 MigrationList _migrations;
227 mutable DataMapper* _dataMapper { nullptr };
228 };
229
230/// Requires the user to call LIGHTWEIGHT_MIGRATION_PLUGIN() in exactly one CPP file of the migration plugin.
231///
232/// @ingroup SqlMigration
233#define LIGHTWEIGHT_MIGRATION_PLUGIN() \
234 /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
235 extern "C" LIGHTWEIGHT_EXPORT Lightweight::SqlMigration::MigrationManager* AcquireMigrationManager() \
236 { \
237 return &Lightweight::SqlMigration::MigrationManager::GetInstance(); \
238 }
239
240 /// Represents a single unique SQL migration.
241 ///
242 /// @ingroup SqlMigration
244 {
245 public:
246 MigrationBase(MigrationBase const&) = default;
247 MigrationBase(MigrationBase&&) = delete;
248 MigrationBase& operator=(MigrationBase const&) = default;
249 MigrationBase& operator=(MigrationBase&&) = delete;
250 MigrationBase(MigrationTimestamp timestamp, std::string_view title):
251 _timestamp { timestamp },
252 _title { title }
253 {
255 }
256
257 virtual ~MigrationBase() = default;
258
259 /// Apply the migration.
260 ///
261 /// @param plan Query builder to use for building the migration plan.
262 virtual void Up(SqlMigrationQueryBuilder& plan) const = 0;
263
264 /// Revert the migration.
265 ///
266 /// @param plan Query builder to use for building the migration plan.
267 virtual void Down(SqlMigrationQueryBuilder& /*plan*/) const {}
268
269 /// Check if this migration has a Down() implementation for rollback.
270 ///
271 /// This method determines whether the migration can be safely reverted.
272 /// The default implementation returns false. Derived classes that implement
273 /// Down() should override this to return true.
274 ///
275 /// @return true if Down() is implemented and can revert this migration.
276 [[nodiscard]] virtual bool HasDownImplementation() const noexcept
277 {
278 return false;
279 }
280
281 /// Get the timestamp of the migration.
282 ///
283 /// @return Timestamp of the migration.
284 [[nodiscard]] MigrationTimestamp GetTimestamp() const noexcept
285 {
286 return _timestamp;
287 }
288
289 /// Get the title of the migration.
290 ///
291 /// @return Title of the migration.
292 [[nodiscard]] std::string_view GetTitle() const noexcept
293 {
294 return _title;
295 }
296
297 /// Compute SHA-256 checksum of migration's Up() SQL statements.
298 ///
299 /// The checksum is computed from the SQL statements that would be executed
300 /// by this migration. This allows detecting if a migration has been modified
301 /// after it was applied.
302 ///
303 /// @param formatter The SQL query formatter to use for generating SQL.
304 /// @return SHA-256 hex string (64 characters).
305 [[nodiscard]] LIGHTWEIGHT_API std::string ComputeChecksum(SqlQueryFormatter const& formatter) const;
306
307 private:
308 MigrationTimestamp _timestamp;
309 std::string_view _title;
310 };
311
312 /// Represents a single unique SQL migration.
313 ///
314 /// This class is a convenience class that can be used to create a migration.
315 ///
316 /// @ingroup SqlMigration
318 {
319 public:
320 /// Create a new migration.
321 ///
322 /// @param timestamp Timestamp of the migration.
323 /// @param title Title of the migration.
324 /// @param up Function to apply the migration.
325 /// @param down Function to revert the migration (optional).
327 std::string_view title,
328 std::function<void(SqlMigrationQueryBuilder&)> const& up,
329 std::function<void(SqlMigrationQueryBuilder&)> const& down = {}):
330 MigrationBase(timestamp, title),
331 _up { up },
332 _down { down }
333 {
334 }
335
336 /// Apply the migration.
337 ///
338 /// @param builder Query builder to use for building the migration plan.
339 void Up(SqlMigrationQueryBuilder& builder) const override
340 {
341 _up(builder);
342 }
343
344 /// Revert the migration.
345 ///
346 /// @param builder Query builder to use for building the migration plan.
347 void Down(SqlMigrationQueryBuilder& builder) const override
348 {
349 if (_down)
350 _down(builder);
351 }
352
353 /// Check if this migration has a Down() implementation.
354 ///
355 /// @return true if a down function was provided.
356 [[nodiscard]] bool HasDownImplementation() const noexcept override
357 {
358 return static_cast<bool>(_down);
359 }
360
361 private:
362 std::function<void(SqlMigrationQueryBuilder&)> _up;
363 std::function<void(SqlMigrationQueryBuilder&)> _down;
364 };
365
366} // namespace SqlMigration
367
368} // namespace Lightweight
369
370#define _LIGHTWEIGHT_CONCATENATE(s1, s2) s1##s2
371
372/// @brief Represents the C++ migration object for a given timestamped migration.
373/// @param timestamp Timestamp of the migration.
374///
375/// @ingroup SqlMigration
376#define LIGHTWEIGHT_MIGRATION_INSTANCE(timestamp) migration_##timestamp
377
378/// @brief Creates a new migration.
379/// @param timestamp Timestamp of the migration.
380/// @param description Description of the migration.
381///
382/// @note The migration will be registered with the migration manager automatically.
383///
384/// @code
385/// #include <Lightweight/SqlMigration.hpp>
386///
387/// LIGHTWEIGHT_SQL_MIGRATION(20260117234120, "Create table 'MyTable'")
388/// {
389/// // Use 'plan' to define the migration steps, for example creating tables.
390/// }
391/// @endcode
392///
393/// @see Lightweight::SqlMigrationQueryBuilder
394///
395/// @ingroup SqlMigration
396#define LIGHTWEIGHT_SQL_MIGRATION(timestamp, description) \
397 struct Migration_##timestamp: public Lightweight::SqlMigration::MigrationBase \
398 { \
399 explicit Migration_##timestamp(): \
400 Lightweight::SqlMigration::MigrationBase(Lightweight::SqlMigration::MigrationTimestamp { timestamp }, \
401 description) \
402 { \
403 } \
404 \
405 void Up(Lightweight::SqlMigrationQueryBuilder& plan) const override; \
406 void Down(Lightweight::SqlMigrationQueryBuilder& /*plan*/) const override {} \
407 }; \
408 \
409 static Migration_##timestamp _LIGHTWEIGHT_CONCATENATE(migration_, timestamp); \
410 \
411 void Migration_##timestamp::Up(Lightweight::SqlMigrationQueryBuilder& plan) const
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:430
virtual void Up(SqlMigrationQueryBuilder &plan) const =0
MigrationTimestamp GetTimestamp() const noexcept
std::string_view GetTitle() const noexcept
virtual void Down(SqlMigrationQueryBuilder &) const
LIGHTWEIGHT_API std::string ComputeChecksum(SqlQueryFormatter const &formatter) const
virtual bool HasDownImplementation() const noexcept
LIGHTWEIGHT_API void MarkMigrationAsApplied(MigrationBase const &migration)
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 MigrationList const & GetAllMigrations() const noexcept
LIGHTWEIGHT_API std::vector< std::string > PreviewPendingMigrations(ExecuteCallback const &feedbackCallback={}) const
LIGHTWEIGHT_API void RevertSingleMigration(MigrationBase const &migration)
LIGHTWEIGHT_API DataMapper & GetDataMapper() const
Get the data mapper used for migrations.
LIGHTWEIGHT_API SqlTransaction Transaction()
LIGHTWEIGHT_API MigrationBase const * GetMigration(MigrationTimestamp timestamp) const noexcept
LIGHTWEIGHT_API std::list< MigrationBase const * > GetPending() const noexcept
LIGHTWEIGHT_API std::vector< ChecksumVerificationResult > VerifyChecksums() 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 ApplySingleMigration(MigrationBase const &migration)
static LIGHTWEIGHT_API MigrationManager & GetInstance()
LIGHTWEIGHT_API std::vector< MigrationTimestamp > GetAppliedMigrationIds() const
void Down(SqlMigrationQueryBuilder &builder) const override
void Up(SqlMigrationQueryBuilder &builder) const override
Migration(MigrationTimestamp timestamp, std::string_view title, std::function< void(SqlMigrationQueryBuilder &)> const &up, std::function< void(SqlMigrationQueryBuilder &)> const &down={})
bool HasDownImplementation() const noexcept override
API to format SQL queries for different SQL dialects.
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.
std::vector< MigrationTimestamp > revertedTimestamps
Successfully reverted migrations.
std::optional< MigrationTimestamp > failedAt
Migration that failed, if any.
std::string errorMessage
Error message if failed.