freezeSnapshot function

SchemaSnapshot freezeSnapshot(
  1. SchemaSnapshot snapshot
)

Copies and validates a snapshot without retaining mutable application data.

SQLite physical identifiers use ASCII case folding; PostgreSQL quoted names preserve case and must fit within 63 UTF-8 bytes. SQLite's sqlite_ table prefix and the migration history table are reserved. String defaults must not contain NUL.

Implementation

SchemaSnapshot freezeSnapshot(SchemaSnapshot snapshot) {
  final tables = <TableDefinition>[];
  final tableNames = <String>{};
  for (final table in snapshot.tables) {
    _validatePhysicalIdentifier(snapshot.engine, table.name);
    final tableIdentity = physicalIdentity(snapshot.engine, table.name);
    if (tableIdentity == '_orm_migrations' ||
        (snapshot.engine == Engine.sqlite &&
            tableIdentity.startsWith('sqlite_')) ||
        !tableNames.add(tableIdentity)) {
      throw ArgumentError('Duplicate or reserved table ${table.name}.');
    }
    if (table.columns.isEmpty) {
      throw ArgumentError('Table ${table.name} has no columns.');
    }
    final names = <String>{};
    final fields = <String>{};
    var primaryKeys = 0;
    final columns = <ColumnDefinition>[];
    for (final column in table.columns) {
      _validatePhysicalIdentifier(snapshot.engine, column.name);
      if (!names.add(physicalIdentity(snapshot.engine, column.name)) ||
          !fields.add(column.field)) {
        throw ArgumentError('Duplicate column or field in ${table.name}.');
      }
      if (column.field.isEmpty) {
        throw ArgumentError('Column field must not be empty.');
      }
      if (column.primaryKey) primaryKeys++;
      if (column.primaryKey && column.nullable) {
        throw ArgumentError('Primary keys cannot be nullable.');
      }
      if (column.identity &&
          (!column.primaryKey ||
              column.type != ScalarType.integer ||
              column.defaultValue != null)) {
        throw ArgumentError(
          'Identity requires an integer primary key without a default.',
        );
      }
      final reference = column.references;
      if (reference != null) {
        _validatePhysicalIdentifier(snapshot.engine, reference.table);
        _validatePhysicalIdentifier(snapshot.engine, reference.column);
        if (!const {
          'restrict',
          'cascade',
          'set null',
          'no action',
        }.contains(reference.onDelete.toLowerCase())) {
          throw ArgumentError('Unsupported ON DELETE ${reference.onDelete}.');
        }
        if (reference.onDelete.toLowerCase() == 'set null' &&
            !column.nullable) {
          throw ArgumentError('SET NULL requires a nullable foreign key.');
        }
      }
      final value = column.defaultValue;
      validateDefault(column);
      columns.add(
        ColumnDefinition(
          name: column.name,
          field: column.field,
          type: column.type,
          nullable: column.nullable,
          primaryKey: column.primaryKey,
          identity: column.identity,
          unique: column.unique,
          defaultValue: value is Uint8List
              ? Uint8List.fromList(value).asUnmodifiableView()
              : value,
          references: reference,
        ),
      );
    }
    if (primaryKeys > 1) {
      throw ArgumentError('Composite primary keys are not supported.');
    }
    tables.add(TableDefinition(table.name, List.unmodifiable(columns)));
  }
  final result = SchemaSnapshot(
    engine: snapshot.engine,
    tables: List.unmodifiable(tables),
  );
  for (final table in result.tables) {
    for (final column in table.columns) {
      final reference = column.references;
      if (reference == null) continue;
      final target = result.tables
          .where(
            (t) =>
                physicalIdentity(snapshot.engine, t.name) ==
                physicalIdentity(snapshot.engine, reference.table),
          )
          .firstOrNull;
      final key = target?.columns
          .where(
            (c) =>
                physicalIdentity(snapshot.engine, c.name) ==
                physicalIdentity(snapshot.engine, reference.column),
          )
          .firstOrNull;
      if (key == null ||
          (!key.primaryKey && !key.unique) ||
          key.type != column.type) {
        throw ArgumentError(
          'Foreign key ${table.name}.${column.name} needs a matching unique target.',
        );
      }
    }
  }
  return result;
}