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"
13#include <unordered_map>
14#include <unordered_set>
17#include <boost/pfr.hpp>
22class AddConstraintOperation;
23class DropConstraintOperation;
73 std::unordered_map<std::string, ColumnMetadata>
columns;
74 std::unordered_map<std::string, ConstraintMetadata>
constraints;
81template <schema::TableConcept Table>
85 metadata.
table_name = std::string(Table::table_name);
88 std::optional<MigrationError> error;
91 boost::pfr::for_each_field(table_instance, [&](
const auto& field) {
92 using field_type = std::remove_cvref_t<
decltype(field)>;
95 if (error.has_value()) {
102 col_meta.
name = std::string(field_type::name);
106 }
catch (
const std::exception& e) {
108 "Failed to get SQL definition for column '" + col_meta.
name +
110 std::string(Table::table_name));
114 col_meta.
sql_type = std::string(field_type::sql_type);
115 col_meta.
nullable = field_type::nullable;
117 metadata.
columns[col_meta.
name] = std::move(col_meta);
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));
134 if (sql_def.find(
"PRIMARY KEY") != std::string::npos) {
135 constraint_meta.
type =
"PRIMARY_KEY";
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";
142 }
else if (sql_def.find(
"UNIQUE") != std::string::npos) {
143 constraint_meta.
type =
"UNIQUE";
146 }
else if (sql_def.find(
"CHECK") != std::string::npos) {
147 constraint_meta.
type =
"CHECK";
150 }
else if (sql_def.find(
"INDEX") != std::string::npos) {
151 constraint_meta.
type =
"INDEX";
155 constraint_meta.
type =
"UNKNOWN";
160 metadata.
constraints[constraint_meta.
name] = std::move(constraint_meta);
165 if (error.has_value()) {
166 return std::unexpected(*error);
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)));
194template <schema::TableConcept OldTable, schema::TableConcept NewTable>
197 static_assert(std::string_view(OldTable::table_name) == std::string_view(NewTable::table_name),
198 "Table names must match for migration generation");
201 if (!old_metadata_result) {
202 return std::unexpected(old_metadata_result.error());
206 if (!new_metadata_result) {
207 return std::unexpected(new_metadata_result.error());
210 return diff_tables(*old_metadata_result, *new_metadata_result, options);
217template <schema::TableConcept Table>
219 Migration migration(
"create_" + std::string(Table::table_name));
228template <schema::TableConcept Table>
230 Migration migration(
"drop_" + std::string(Table::table_name));
239 std::string table_name_;
244 : table_name_(
std::move(table_name)), column_(
column) {}
249 "Column SQL definition cannot be empty",
250 table_name_ +
"." + column_.
name));
252 return "ALTER TABLE " + table_name_ +
" ADD COLUMN " + column_.
sql_definition +
";";
256 if (column_.
name.empty()) {
258 "Column name cannot be empty", table_name_));
260 return "ALTER TABLE " + table_name_ +
" DROP COLUMN " + column_.
name +
";";
270 std::string table_name_;
275 : table_name_(
std::move(table_name)), column_(
column) {}
278 if (column_.
name.empty()) {
280 "Column name cannot be empty", table_name_));
282 return "ALTER TABLE " + table_name_ +
" DROP COLUMN " + column_.
name +
";";
288 "Column SQL definition cannot be empty",
289 table_name_ +
"." + column_.
name));
291 return "ALTER TABLE " + table_name_ +
" ADD COLUMN " + column_.
sql_definition +
";";
301template <schema::TableConcept Table>
ADD COLUMN migration operation.
CREATE TABLE migration operation.
DROP COLUMN migration operation.
DROP TABLE migration operation.
Base class for migration operations.
Container for migration operations.
void add_operation(Args &&... args)
Add an operation to the migration.
Represents a column in a database table.
Helper to detect column members in a table.
Helper to detect constraint members in a table.
MigrationResult< TableMetadata > extract_table_metadata(const Table &table_instance)
Extract table metadata using Boost.PFR.
MigrationResult< Migration > generate_create_table_migration(const Table &table)
Generate migration to create a new table.
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.
OperationType
Enum for migration operation types.
MigrationResult< Migration > generate_migration(const OldTable &old_table, const NewTable &new_table, const MigrationOptions &options={})
Generate migration from old table to new table.
MigrationResult< Migration > generate_drop_table_migration(const Table &table)
Generate migration to drop a table.
@ MIGRATION_GENERATION_FAILED
static MigrationError make(MigrationErrorType type, const std::string &message, const std::string &context="")
Create a MigrationError with automatic formatting.
Options for controlling migration generation.
std::unordered_map< std::string, std::string > column_mappings
Map of old column name to new column name for renames.
std::unordered_map< std::string, std::string > constraint_mappings
Map of old constraint name to new constraint name for renames.
std::unordered_map< std::string, std::pair< std::string, std::string > > column_transformations
Bidirectional SQL transformations for complex column changes Key: old column name,...
bool preserve_data
Whether to preserve data during column type changes (default: true)