From 9d9658248b36d92c65d11cdd0b2e4db7f6f724b9 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 11 Oct 2020 11:14:02 +0200 Subject: [PATCH] Add renameColumn method to migrator --- .../en/docs/Advanced Features/migrations.md | 7 +++++ moor/CHANGELOG.md | 1 + moor/lib/sqlite_keywords.dart | 4 ++- .../src/runtime/query_builder/migration.dart | 28 +++++++++++++++++++ moor/test/schema_test.dart | 9 ++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/content/en/docs/Advanced Features/migrations.md b/docs/content/en/docs/Advanced Features/migrations.md index 852de094..70de0454 100644 --- a/docs/content/en/docs/Advanced Features/migrations.md +++ b/docs/content/en/docs/Advanced Features/migrations.md @@ -136,6 +136,13 @@ If you're renaming a column in Dart, note that the easiest way is to just rename `named`: `TextColumn newName => text().named('old_name')()`. That is fully backwards compatible and doesn't require a migration. +If you know your app runs on sqlite 3.25.0 or later (it does if you're using `sqlite3_flutter_libs`), +you can also use the `renameColumn` api in `Migrator`: + +```dart +m.renameColumn(yourTable, 'old_column_name', yourTable.newColumn); +``` + If you do want to change the actual column name in a table, you can write a `columnTransformer` to use an old column with a different name: diff --git a/moor/CHANGELOG.md b/moor/CHANGELOG.md index 10afea45..c4e358f4 100644 --- a/moor/CHANGELOG.md +++ b/moor/CHANGELOG.md @@ -12,6 +12,7 @@ - New `generate_values_in_copy_with` [build option](https://moor.simonbinder.eu/docs/advanced-features/builder_options/). It wraps nullable columns in a `Value` in `copyWith` methods so that they can be set to `null`. - Added `groupConcat`, `cast` and `coalesce` functions to the Dart query builder. +- Added `renameColumn` to `Migrator` to generate `ALTER TABLE RENAME COLUMN` statement. - Added `VmDatabase.closeExistingInstances()` to close zombie database connections after hot restarts. ## 3.3.1 diff --git a/moor/lib/sqlite_keywords.dart b/moor/lib/sqlite_keywords.dart index 060388ca..491e6586 100644 --- a/moor/lib/sqlite_keywords.dart +++ b/moor/lib/sqlite_keywords.dart @@ -147,8 +147,10 @@ const sqliteKeywords = { /// [sqliteKeywords]. bool isSqliteKeyword(String s) => sqliteKeywords.contains(s.toUpperCase()); +final _whitespace = RegExp(r'\s'); + /// Escapes [s] by wrapping it in backticks if it's an sqlite keyword. String escapeIfNeeded(String s) { - if (isSqliteKeyword(s)) return '`$s`'; + if (isSqliteKeyword(s) || s.contains(_whitespace)) return '`$s`'; return s; } diff --git a/moor/lib/src/runtime/query_builder/migration.dart b/moor/lib/src/runtime/query_builder/migration.dart index f50a5756..be6f336c 100644 --- a/moor/lib/src/runtime/query_builder/migration.dart +++ b/moor/lib/src/runtime/query_builder/migration.dart @@ -322,6 +322,34 @@ class Migrator { return _issueCustomQuery(context.sql); } + /// Changes the name of a column in a [table]. + /// + /// After renaming a column in a Dart table or a moor file and re-running the + /// generator, you can use [renameColumn] in a migration step to rename the + /// column for existing databases. + /// + /// The [table] argument must be set to the table enclosing the changed + /// column. The [oldName] must be set to the old name of the [column] in SQL. + /// For Dart tables, note that moor will transform `camelCase` column names in + /// Dart to `snake_case` column names in SQL. + /// + /// __Important compatibility information__: [renameColumn] uses an + /// `ALTER TABLE RENAME COLUMN` internally. Support for that syntax was added + /// in sqlite version 3.25.0, released on 2018-09-15. When you're using + /// Flutter and depend on `sqlite3_flutter_libs`, you're guaranteed to have + /// that version. Otherwise, please ensure that you only use [renameColumn] if + /// you know you'll run on sqlite 3.20.0 or later. + Future renameColumn( + TableInfo table, String oldName, GeneratedColumn column) async { + final context = _createContext(); + context.buffer + ..write('ALTER TABLE ${escapeIfNeeded(table.$tableName)} ') + ..write('RENAME COLUMN ${escapeIfNeeded(oldName)} ') + ..write('TO ${column.escapedName};'); + + return _issueCustomQuery(context.sql); + } + /// Executes the custom query. @Deprecated('Use customStatement in the database class') Future issueCustomQuery(String sql, [List args]) { diff --git a/moor/test/schema_test.dart b/moor/test/schema_test.dart index 3088e5e9..141e8288 100644 --- a/moor/test/schema_test.dart +++ b/moor/test/schema_test.dart @@ -101,6 +101,15 @@ void main() { 'is_awesome INTEGER NOT NULL DEFAULT 1 ' 'CHECK (is_awesome in (0, 1));')); }); + + test('renames columns', () async { + await db + .createMigrator() + .renameColumn(db.users, 'my name', db.users.name); + + verify(mockExecutor + .runCustom('ALTER TABLE users RENAME COLUMN `my name` TO name;')); + }); }); test('custom statements', () async {