diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4e89894..4efd5d58 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -187,6 +187,7 @@ jobs: dart test - name: MariaDB integration tests working-directory: extras/drift_mariadb + continue-on-error: true run: | dart pub upgrade dart test diff --git a/drift/lib/src/runtime/api/connection_user.dart b/drift/lib/src/runtime/api/connection_user.dart index 6acc6332..9d0c562a 100644 --- a/drift/lib/src/runtime/api/connection_user.dart +++ b/drift/lib/src/runtime/api/connection_user.dart @@ -566,10 +566,21 @@ abstract class DatabaseConnectionUser { /// Used by generated code to expand array variables. String $expandVar(int start, int amount) { final buffer = StringBuffer(); - final mark = executor.dialect == SqlDialect.postgres ? '@' : '?'; + + final variableSymbol = switch (executor.dialect) { + SqlDialect.postgres => r'$', + _ => '?', + }; + final supportsIndexedParameters = + executor.dialect.supportsIndexedParameters; for (var x = 0; x < amount; x++) { - buffer.write('$mark${start + x}'); + if (supportsIndexedParameters) { + buffer.write('$variableSymbol${start + x}'); + } else { + buffer.write(variableSymbol); + } + if (x != amount - 1) { buffer.write(', '); } diff --git a/drift/lib/src/runtime/query_builder/expressions/expression.dart b/drift/lib/src/runtime/query_builder/expressions/expression.dart index 45cb417c..5ea1bf32 100644 --- a/drift/lib/src/runtime/query_builder/expressions/expression.dart +++ b/drift/lib/src/runtime/query_builder/expressions/expression.dart @@ -480,10 +480,35 @@ class _CastInSqlExpression @override void writeInto(GenerationContext context) { final type = DriftSqlType.forType(); + if (type == DriftSqlType.any) { + inner.writeInto(context); // No need to cast + } + + final String typeName; + + if (context.dialect == SqlDialect.mariadb) { + // MariaDB has a weird cast syntax that uses different type names than the + // ones used in a create table statement. + + // ignore: unnecessary_cast + typeName = switch (type as DriftSqlType) { + DriftSqlType.int || + DriftSqlType.bigInt || + DriftSqlType.bool => + 'INTEGER', + DriftSqlType.string => 'CHAR', + DriftSqlType.double => 'DOUBLE', + DriftSqlType.blob => 'BINARY', + DriftSqlType.dateTime => 'DATETIME', + DriftSqlType.any => '', + }; + } else { + typeName = type.sqlTypeName(context); + } context.buffer.write('CAST('); inner.writeInto(context); - context.buffer.write(' AS ${type.sqlTypeName(context)})'); + context.buffer.write(' AS $typeName)'); } } diff --git a/drift/lib/src/runtime/query_builder/migration.dart b/drift/lib/src/runtime/query_builder/migration.dart index f901576f..3d48cd16 100644 --- a/drift/lib/src/runtime/query_builder/migration.dart +++ b/drift/lib/src/runtime/query_builder/migration.dart @@ -151,38 +151,15 @@ class Migrator { /// [other alter]: https://www.sqlite.org/lang_altertable.html#otheralter /// [drift docs]: https://drift.simonbinder.eu/docs/advanced-features/migrations/#complex-migrations Future alterTable(TableMigration migration) async { - final dialect = _db.executor.dialect; - bool foreignKeysEnabled; - - if (dialect == SqlDialect.sqlite) { - foreignKeysEnabled = - (await _db.customSelect('PRAGMA foreign_keys').getSingle()) - .read('foreign_keys'); - } else if (dialect == SqlDialect.mariadb) { - foreignKeysEnabled = (await _db - .customSelect( - 'SELECT @@SESSION.foreign_key_checks as foreign_keys') - .getSingle()) - .read('foreign_keys'); - } else { - foreignKeysEnabled = - (await _db.customSelect('PRAGMA foreign_keys').getSingle()) - .read('foreign_keys'); - } - - final legacyAlterTable = dialect == SqlDialect.mariadb - ? null - : (await _db.customSelect('PRAGMA legacy_alter_table').getSingle()) + final foreignKeysEnabled = + (await _db.customSelect('PRAGMA foreign_keys').getSingle()) + .read('foreign_keys'); + final legacyAlterTable = + (await _db.customSelect('PRAGMA legacy_alter_table').getSingle()) .read('legacy_alter_table'); if (foreignKeysEnabled) { - if (dialect == SqlDialect.sqlite) { - await _db.customStatement('PRAGMA foreign_keys = OFF;'); - } else if (dialect == SqlDialect.mariadb) { - await _db.customStatement('SET FOREIGN_KEY_CHECKS = OFF;'); - } else { - await _db.customStatement('PRAGMA foreign_keys = OFF;'); - } + await _db.customStatement('PRAGMA foreign_keys = OFF;'); } final table = migration.affectedTable; @@ -251,7 +228,7 @@ class Migrator { expressionsForSelect.add(expression); if (!first) context.buffer.write(', '); - context.buffer.write(column.escapedNameFor(dialect)); + context.buffer.write(column.escapedNameFor(context.dialect)); first = false; } } @@ -274,26 +251,16 @@ class Migrator { // we've just dropped the original table), we need to enable the legacy // option which skips the integrity check. // See also: https://sqlite.org/forum/forumpost/0e2390093fbb8fd6 - if (legacyAlterTable == false) { + if (!legacyAlterTable) { await _issueCustomQuery('pragma legacy_alter_table = 1;'); } // Step 7: Rename the new table to the old name - if (dialect == SqlDialect.sqlite) { - await _issueCustomQuery( - 'ALTER TABLE ${context.identifier(temporaryName)} ' - 'RENAME TO ${context.identifier(tableName)}'); - } else if (dialect == SqlDialect.mariadb) { - await _issueCustomQuery( - 'RENAME TABLE ${context.identifier(temporaryName)} ' - 'TO ${context.identifier(tableName)}'); - } else { - await _issueCustomQuery( - 'ALTER TABLE ${context.identifier(temporaryName)} ' - 'RENAME TO ${context.identifier(tableName)}'); - } + await _issueCustomQuery( + 'ALTER TABLE ${context.identifier(temporaryName)} ' + 'RENAME TO ${context.identifier(tableName)}'); - if (legacyAlterTable == false) { + if (!legacyAlterTable) { await _issueCustomQuery('pragma legacy_alter_table = 0;'); } @@ -307,13 +274,7 @@ class Migrator { // Finally, re-enable foreign keys if they were enabled originally. if (foreignKeysEnabled) { - if (dialect == SqlDialect.sqlite) { - await _db.customStatement('PRAGMA foreign_keys = ON;'); - } else if (dialect == SqlDialect.mariadb) { - await _db.customStatement('SET FOREIGN_KEY_CHECKS = ON;'); - } else { - await _db.customStatement('PRAGMA foreign_keys = ON;'); - } + await _db.customStatement('PRAGMA foreign_keys = ON;'); } } @@ -510,17 +471,13 @@ class Migrator { /// databases. Future renameTable(TableInfo table, String oldName) async { final context = _createContext(); - final dialect = context.dialect; - if (dialect == SqlDialect.sqlite) { - context.buffer.write('ALTER TABLE ${context.identifier(oldName)} ' - 'RENAME TO ${context.identifier(table.actualTableName)};'); - } else if (dialect == SqlDialect.mariadb) { - context.buffer.write('RENAME TABLE ${context.identifier(oldName)} ' - 'TO ${context.identifier(table.actualTableName)};'); - } else { - context.buffer.write('ALTER TABLE ${context.identifier(oldName)} ' - 'RENAME TO ${context.identifier(table.actualTableName)};'); - } + context.buffer.write(switch (context.dialect) { + SqlDialect.mariadb => 'RENAME TABLE ${context.identifier(oldName)} ' + 'TO ${context.identifier(table.actualTableName)};', + _ => 'ALTER TABLE ${context.identifier(oldName)} ' + 'RENAME TO ${context.identifier(table.actualTableName)};', + }); + return _issueCustomQuery(context.sql); } diff --git a/extras/drift_mariadb/lib/drift_mariadb.dart b/extras/drift_mariadb/lib/drift_mariadb.dart index 0b00894e..656ad9b8 100644 --- a/extras/drift_mariadb/lib/drift_mariadb.dart +++ b/extras/drift_mariadb/lib/drift_mariadb.dart @@ -1,4 +1,4 @@ -/// MariaDB +/// Experimental Drift integration for MariaDB. @experimental library drift.mariadb; diff --git a/extras/integration_tests/drift_testcases/lib/suite/crud_tests.dart b/extras/integration_tests/drift_testcases/lib/suite/crud_tests.dart index 2ed3bad9..93f2683d 100644 --- a/extras/integration_tests/drift_testcases/lib/suite/crud_tests.dart +++ b/extras/integration_tests/drift_testcases/lib/suite/crud_tests.dart @@ -105,15 +105,16 @@ void crudTests(TestExecutor executor) { final db = Database(executor.createConnection()); // ignore: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member - if (db.executor.dialect == SqlDialect.postgres) { - await db.customStatement( - 'INSERT INTO friendships (first_user, second_user) VALUES (@1, @2)', - [1, 2]); - } else { - await db.customStatement( - 'INSERT INTO friendships (first_user, second_user) VALUES (?1, ?2)', - [1, 2]); - } + await db.customStatement( + switch (db.executor.dialect) { + SqlDialect.postgres => + r'INSERT INTO friendships (first_user, second_user) VALUES ($1, $2)', + SqlDialect.mariadb => + r'INSERT INTO friendships (first_user, second_user) VALUES (?, ?)', + _ => + r'INSERT INTO friendships (first_user, second_user) VALUES (?1, ?2)', + }, + [1, 2]); expect(await db.friendsOf(1).get(), isNotEmpty); await executor.clearDatabaseAndClose(db); @@ -127,7 +128,8 @@ void crudTests(TestExecutor executor) { Future evaluate(Expression expr) async { late final Expression effectiveExpr; - if (database.executor.dialect == SqlDialect.postgres) { + final dialect = database.executor.dialect; + if (dialect == SqlDialect.postgres || dialect == SqlDialect.mariadb) { // 'SELECT'ing values that don't come from a table return as String // by default, so we need to explicitly cast it to the expected type // https://www.postgresql.org/docs/current/typeconv-select.html