dvApplyGeneratedSchema function

Future<DVGeneratedSchemaReport> dvApplyGeneratedSchema(
  1. DVDatabaseAdapter database,
  2. List<Map<String, Object?>> tables, {
  3. required DVDatabaseEngine engine,
  4. String? tenant,
  5. bool orphanExistingRows = false,
})

Makes the generated tables in database and adds the columns a table made by an earlier release is missing.

On SQLite the table is asked what it has and each missing column is added; a tenant column is not added to a table that has rows unless tenant says whose they are or orphanExistingRows says nobody's. On PostgreSQL every column is added with IF NOT EXISTS. On MySQL, which has neither, the tables are created and nothing is added.

Implementation

Future<DVGeneratedSchemaReport> dvApplyGeneratedSchema(
  DVDatabaseAdapter database,
  List<Map<String, Object?>> tables, {
  required DVDatabaseEngine engine,
  String? tenant,
  bool orphanExistingRows = false,
}) async {
  final List<String> applied = <String>[];
  final List<String> created = <String>[];
  final List<String> added = <String>[];
  final List<String> needsTenant = <String>[];

  for (final Map<String, Object?> table in tables) {
    final Object? create = table['createSql'];
    if (create is! String) continue;
    final String name = '${table['table']}';
    final List<String> want = <String>[
      for (final Object? column
          in (table['columns'] as List<Object?>? ?? const <Object?>[]))
        '$column',
    ];

    switch (engine) {
      case DVDatabaseEngine.sqlite:
        final List<String> before = await _sqliteColumns(database, name);
        await database.execute(create);
        applied.add(name);
        if (before.isEmpty) {
          created.add(name);
          continue;
        }
        final List<String> missing =
            want.where((String c) => !before.contains(c)).toList();
        if (missing.isEmpty) continue;
        if (missing.contains(dvTenantColumn) &&
            !orphanExistingRows &&
            tenant == null &&
            (await database.query('SELECT 1 FROM $name LIMIT 1')).isNotEmpty) {
          // Nothing altered: the application running the old code still
          // works, which is the point of stopping before half of it.
          needsTenant.add(name);
          continue;
        }
        for (final String column in missing) {
          await database.execute(dvAddColumnSql(dvAddColumnFor(table, column)));
          added.add('$name.$column');
        }
        if (missing.contains(dvTenantColumn) && tenant != null) {
          await database.execute(
            'UPDATE $name SET $dvTenantColumn = ? '
            'WHERE $dvTenantColumn IS NULL',
            <Object?>[tenant],
          );
        }
      case DVDatabaseEngine.postgres:
        await database.execute(create);
        applied.add(name);
        final Object? types = table['columnTypes'];
        if (types is Map) {
          for (final Object? column in types.keys) {
            await database.execute(dvAddColumnSql(
              dvAddColumnFor(table, '$column'),
              ifNotExists: true,
            ));
          }
        }
      case DVDatabaseEngine.mysql:
        await database.execute(create);
        applied.add(name);
    }
  }
  return DVGeneratedSchemaReport(
    applied: applied,
    created: created,
    added: added,
    needsTenant: needsTenant,
  );
}