toSqliteRebuildSql method

String toSqliteRebuildSql(
  1. TableDefinition targetTable
)

Returns SQL for a full table rebuild when this migration requires it (column type/nullability changes or FK add/drop).

Implements SQLite's "Making Other Kinds Of Table Schema Changes" procedure. https://www.sqlite.org/lang_altertable.html#makingotherkindsofchanges

Implementation

String toSqliteRebuildSql(TableDefinition targetTable) {
  const newTablePrefix = 'new_';
  final newTableName = newTablePrefix + name;

  var out = '';

  // 1. Disable foreign key constraints (handled by the migration runner).
  // 2. Ensure the migration runs in a transaction (handled by the migration runner).
  // 3. No need to store indexes/triggers/views since we have the target definition.
  // 4. Create the new_<table_name> table in the target table format.
  out += targetTable.tableCreationToSql(
    tableNameOverride: newTableName,
    skipIndexes: true,
  );

  // 5. Transfer content from <table_name> into new_<table_name>.
  // Copy every target column that already existed on the old table: new
  // columns from this migration are omitted so SQLite fills them via DEFAULT
  // (or NULL) per INSERT rules.
  final addColumnNames = addColumns.map((c) => c.name).toSet();
  final copyColumns = targetTable.columns
      .where((c) => !addColumnNames.contains(c.name))
      .toList();
  if (copyColumns.isNotEmpty) {
    final colListNew = copyColumns.map((c) => '"${c.name}"').join(', ');
    final selectList = copyColumns
        .map((c) {
          final columnMigration = _getColumnMigrationByName(c.name);
          final sourceName = columnMigration?.columnName ?? c.name;

          return switch (columnMigration?.newType) {
            ColumnType.jsonb => 'jsonb("$sourceName")',
            ColumnType.json => 'json("$sourceName")',
            _ => '"$sourceName"',
          };
        })
        .join(', ');
    out +=
        'INSERT INTO "$newTableName" ($colListNew) '
        'SELECT $selectList FROM "$name";\n';
  }

  // 6. Drop the old table.
  out += 'DROP TABLE "$name";\n';

  // 7. Rename new_<table_name> to <table_name>.
  out += 'ALTER TABLE "$newTableName" RENAME TO "$name";\n';

  // 8. Recreate indexes from the target table definition. Triggers and views
  // are user-defined and must be handled by the user manually.
  for (var index in targetTable.indexes) {
    out += '\n';
    out += '-- Indexes\n';
    out += index.toSql(tableName: name);
  }

  // 9. Serverpod does not manager views, so no need to update related views.
  // 10. Verify foreign key constraints (handled by the migration runner).
  // 11. Commit the transaction (handled by the migration runner).
  // 12. Re-enable foreign keys. (handled by the migration runner)
  out += '\n';

  return out;
}