relx 0.1.0
A Modern C++23 Type-Safe SQL Query Builder
Loading...
Searching...
No Matches
diff.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "../schema/check_constraint.hpp"
4#include "../schema/column.hpp"
5#include "../schema/foreign_key.hpp"
6#include "../schema/index.hpp"
7#include "../schema/primary_key.hpp"
8#include "../schema/table.hpp"
9#include "../schema/unique_constraint.hpp"
10#include "core.hpp"
11
12#include <string>
13#include <unordered_map>
14#include <unordered_set>
15#include <vector>
16
17#include <boost/pfr.hpp>
18
19namespace relx::migrations {
20
21// Forward declarations
22class AddConstraintOperation;
23class DropConstraintOperation;
24
28 std::unordered_map<std::string, std::string> column_mappings;
29
31 std::unordered_map<std::string, std::string> constraint_mappings;
32
34 bool preserve_data = true;
35
40 std::unordered_map<std::string, std::pair<std::string, std::string>> column_transformations;
41};
42
45 std::string name;
46 std::string sql_definition;
47 std::string sql_type;
49
50 bool operator==(const ColumnMetadata& other) const {
51 return name == other.name && sql_definition == other.sql_definition;
52 }
53
54 bool operator!=(const ColumnMetadata& other) const { return !(*this == other); }
55};
56
59 std::string name;
60 std::string sql_definition;
61 std::string type; // "PRIMARY_KEY", "FOREIGN_KEY", "UNIQUE", "CHECK", "INDEX"
62
63 bool operator==(const ConstraintMetadata& other) const {
64 return name == other.name && sql_definition == other.sql_definition && type == other.type;
65 }
66
67 bool operator!=(const ConstraintMetadata& other) const { return !(*this == other); }
68};
69
72 std::string table_name;
73 std::unordered_map<std::string, ColumnMetadata> columns;
74 std::unordered_map<std::string, ConstraintMetadata> constraints;
75};
76
81template <schema::TableConcept Table>
83 try {
84 TableMetadata metadata;
85 metadata.table_name = std::string(Table::table_name);
86
87 // Track any errors that occur during field processing
88 std::optional<MigrationError> error;
89
90 // Use boost::pfr to iterate through all fields
91 boost::pfr::for_each_field(table_instance, [&](const auto& field) {
92 using field_type = std::remove_cvref_t<decltype(field)>;
93
94 // Skip processing if we already have an error
95 if (error.has_value()) {
96 return;
97 }
98
99 if constexpr (schema::is_column<field_type>) {
100 // Extract column metadata
101 ColumnMetadata col_meta;
102 col_meta.name = std::string(field_type::name);
103
104 try {
105 col_meta.sql_definition = field.sql_definition();
106 } catch (const std::exception& e) {
108 "Failed to get SQL definition for column '" + col_meta.name +
109 "': " + e.what(),
110 std::string(Table::table_name));
111 return;
112 }
113
114 col_meta.sql_type = std::string(field_type::sql_type);
115 col_meta.nullable = field_type::nullable;
116
117 metadata.columns[col_meta.name] = std::move(col_meta);
118 } else if constexpr (schema::is_constraint<field_type>) {
119 // Extract constraint metadata
120 ConstraintMetadata constraint_meta;
121
122 try {
123 constraint_meta.sql_definition = field.sql_definition();
124 } catch (const std::exception& e) {
126 "Failed to get SQL definition for constraint: " +
127 std::string(e.what()),
128 std::string(Table::table_name));
129 return;
130 }
131
132 // Determine constraint type and generate a unique name based on SQL definition patterns
133 std::string sql_def = constraint_meta.sql_definition;
134 if (sql_def.find("PRIMARY KEY") != std::string::npos) {
135 constraint_meta.type = "PRIMARY_KEY";
136 constraint_meta.name = metadata.table_name + "_pk";
137 } else if (sql_def.find("FOREIGN KEY") != std::string::npos ||
138 sql_def.find("REFERENCES") != std::string::npos) {
139 constraint_meta.type = "FOREIGN_KEY";
140 constraint_meta.name = metadata.table_name + "_fk_" +
141 std::to_string(metadata.constraints.size());
142 } else if (sql_def.find("UNIQUE") != std::string::npos) {
143 constraint_meta.type = "UNIQUE";
144 constraint_meta.name = metadata.table_name + "_unique_" +
145 std::to_string(metadata.constraints.size());
146 } else if (sql_def.find("CHECK") != std::string::npos) {
147 constraint_meta.type = "CHECK";
148 constraint_meta.name = metadata.table_name + "_check_" +
149 std::to_string(metadata.constraints.size());
150 } else if (sql_def.find("INDEX") != std::string::npos) {
151 constraint_meta.type = "INDEX";
152 constraint_meta.name = metadata.table_name + "_idx_" +
153 std::to_string(metadata.constraints.size());
154 } else {
155 constraint_meta.type = "UNKNOWN";
156 constraint_meta.name = metadata.table_name + "_constraint_" +
157 std::to_string(metadata.constraints.size());
158 }
159
160 metadata.constraints[constraint_meta.name] = std::move(constraint_meta);
161 }
162 });
163
164 // Check if any errors occurred during field processing
165 if (error.has_value()) {
166 return std::unexpected(*error);
167 }
168
169 return metadata;
170 } catch (const std::exception& e) {
171 return std::unexpected(
173 "Failed to extract table metadata: " + std::string(e.what()),
174 std::string(Table::table_name)));
175 }
176}
177
184 const TableMetadata& new_metadata,
185 const MigrationOptions& options = {});
186
194template <schema::TableConcept OldTable, schema::TableConcept NewTable>
195MigrationResult<Migration> generate_migration(const OldTable& old_table, const NewTable& new_table,
196 const MigrationOptions& options = {}) {
197 static_assert(std::string_view(OldTable::table_name) == std::string_view(NewTable::table_name),
198 "Table names must match for migration generation");
199
200 auto old_metadata_result = extract_table_metadata(old_table);
201 if (!old_metadata_result) {
202 return std::unexpected(old_metadata_result.error());
203 }
204
205 auto new_metadata_result = extract_table_metadata(new_table);
206 if (!new_metadata_result) {
207 return std::unexpected(new_metadata_result.error());
208 }
209
210 return diff_tables(*old_metadata_result, *new_metadata_result, options);
211}
212
217template <schema::TableConcept Table>
219 Migration migration("create_" + std::string(Table::table_name));
221 return migration;
222}
223
228template <schema::TableConcept Table>
230 Migration migration("drop_" + std::string(Table::table_name));
232 return migration;
233}
234
236template <>
238private:
239 std::string table_name_;
240 ColumnMetadata column_;
241
242public:
243 AddColumnOperation(std::string table_name, const ColumnMetadata& column)
244 : table_name_(std::move(table_name)), column_(column) {}
245
247 if (column_.sql_definition.empty()) {
249 "Column SQL definition cannot be empty",
250 table_name_ + "." + column_.name));
251 }
252 return "ALTER TABLE " + table_name_ + " ADD COLUMN " + column_.sql_definition + ";";
253 }
254
256 if (column_.name.empty()) {
258 "Column name cannot be empty", table_name_));
259 }
260 return "ALTER TABLE " + table_name_ + " DROP COLUMN " + column_.name + ";";
261 }
262
263 OperationType type() const override { return OperationType::ADD_COLUMN; }
264};
265
267template <>
269private:
270 std::string table_name_;
271 ColumnMetadata column_;
272
273public:
274 DropColumnOperation(std::string table_name, const ColumnMetadata& column)
275 : table_name_(std::move(table_name)), column_(column) {}
276
278 if (column_.name.empty()) {
280 "Column name cannot be empty", table_name_));
281 }
282 return "ALTER TABLE " + table_name_ + " DROP COLUMN " + column_.name + ";";
283 }
284
286 if (column_.sql_definition.empty()) {
288 "Column SQL definition cannot be empty",
289 table_name_ + "." + column_.name));
290 }
291 return "ALTER TABLE " + table_name_ + " ADD COLUMN " + column_.sql_definition + ";";
292 }
293
294 OperationType type() const override { return OperationType::DROP_COLUMN; }
295};
296
301template <schema::TableConcept Table>
302MigrationResult<TableMetadata> extract_table_metadata(const Table& table);
303
304} // namespace relx::migrations
MigrationResult< std::string > rollback_sql() const override
Definition diff.hpp:255
AddColumnOperation(std::string table_name, const ColumnMetadata &column)
Definition diff.hpp:243
MigrationResult< std::string > to_sql() const override
Definition diff.hpp:246
ADD COLUMN migration operation.
Definition core.hpp:156
CREATE TABLE migration operation.
Definition core.hpp:88
MigrationResult< std::string > rollback_sql() const override
Definition diff.hpp:285
DropColumnOperation(std::string table_name, const ColumnMetadata &column)
Definition diff.hpp:274
MigrationResult< std::string > to_sql() const override
Definition diff.hpp:277
DROP COLUMN migration operation.
Definition core.hpp:192
DROP TABLE migration operation.
Definition core.hpp:122
Base class for migration operations.
Definition core.hpp:72
Container for migration operations.
Definition core.hpp:332
void add_operation(Args &&... args)
Add an operation to the migration.
Definition core.hpp:342
Represents a column in a database table.
Definition column.hpp:217
Helper to detect column members in a table.
Definition table.hpp:20
Helper to detect constraint members in a table.
Definition table.hpp:39
MigrationResult< TableMetadata > extract_table_metadata(const Table &table_instance)
Extract table metadata using Boost.PFR.
Definition diff.hpp:82
MigrationResult< Migration > generate_create_table_migration(const Table &table)
Generate migration to create a new table.
Definition diff.hpp:218
MigrationResult< Migration > diff_tables(const TableMetadata &old_metadata, const TableMetadata &new_metadata, const MigrationOptions &options={})
Generate migration from table metadata differences.
std::expected< T, MigrationError > MigrationResult
Result type for migration operations.
Definition core.hpp:53
OperationType
Enum for migration operation types.
Definition core.hpp:56
MigrationResult< Migration > generate_migration(const OldTable &old_table, const NewTable &new_table, const MigrationOptions &options={})
Generate migration from old table to new table.
Definition diff.hpp:195
MigrationResult< Migration > generate_drop_table_migration(const Table &table)
Generate migration to drop a table.
Definition diff.hpp:229
STL namespace.
Metadata about a column extracted from PFR analysis.
Definition diff.hpp:44
bool operator==(const ColumnMetadata &other) const
Definition diff.hpp:50
bool operator!=(const ColumnMetadata &other) const
Definition diff.hpp:54
Metadata about a constraint extracted from PFR analysis.
Definition diff.hpp:58
bool operator!=(const ConstraintMetadata &other) const
Definition diff.hpp:67
bool operator==(const ConstraintMetadata &other) const
Definition diff.hpp:63
static MigrationError make(MigrationErrorType type, const std::string &message, const std::string &context="")
Create a MigrationError with automatic formatting.
Definition core.hpp:37
Options for controlling migration generation.
Definition diff.hpp:26
std::unordered_map< std::string, std::string > column_mappings
Map of old column name to new column name for renames.
Definition diff.hpp:28
std::unordered_map< std::string, std::string > constraint_mappings
Map of old constraint name to new constraint name for renames.
Definition diff.hpp:31
std::unordered_map< std::string, std::pair< std::string, std::string > > column_transformations
Bidirectional SQL transformations for complex column changes Key: old column name,...
Definition diff.hpp:40
bool preserve_data
Whether to preserve data during column type changes (default: true)
Definition diff.hpp:34
Complete metadata about a table.
Definition diff.hpp:71
std::unordered_map< std::string, ColumnMetadata > columns
Definition diff.hpp:73
std::unordered_map< std::string, ConstraintMetadata > constraints
Definition diff.hpp:74