From 44281bebaf1d030ffc978fff419767b210696991 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 3 Dec 2021 22:11:44 +0100 Subject: [PATCH] Support for STRICT tables in drift files --- .github/workflows/main.yml | 2 +- drift/CHANGELOG.md | 1 + drift/build.yaml | 2 +- drift/lib/src/dsl/table.dart | 7 +++++ .../src/runtime/query_builder/migration.dart | 5 +++- drift/test/data/tables/custom_tables.g.dart | 2 ++ drift/test/data/tables/tables.drift | 2 +- .../integration_tests/moor_files_test.dart | 2 +- .../analyzer/moor/create_table_reader.dart | 1 + drift_dev/lib/src/model/table.dart | 5 ++++ .../lib/src/writer/tables/table_writer.dart | 6 ++++ .../analysis/schema/from_create_table.dart | 9 ++++-- sqlparser/lib/src/analysis/schema/table.dart | 3 ++ .../schema/from_create_table_test.dart | 30 +++++++++++++++++++ 14 files changed, 69 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5fe49bad..d465ce5d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: name: "Compile sqlite3 for tests" runs-on: ubuntu-20.04 env: - SQLITE_VERSION: "3350500" + SQLITE_VERSION: "3370000" steps: - uses: actions/checkout@v2 diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index baa0752f..1fe702f0 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -7,6 +7,7 @@ Thanks to [@westito](https://github.com/westito). - Allow the generator to emit correct SQL code when using arrays with the `new_sql_code_generation` option in specific scenarios. +- Add support for [strict tables](https://sqlite.org/stricttables.html) in `.drift` files. - Add the `generatedAs` method to declare generated columns for Dart tables. - Add `OrderingTerm.random` to fetch rows in a random order. - Improved support for pausing query stream subscriptions. Instead of buffering events, diff --git a/drift/build.yaml b/drift/build.yaml index f30f9857..84ec007d 100644 --- a/drift/build.yaml +++ b/drift/build.yaml @@ -17,7 +17,7 @@ targets: sql: dialect: sqlite options: - version: "3.35" + version: "3.37" modules: - json1 - fts5 diff --git a/drift/lib/src/dsl/table.dart b/drift/lib/src/dsl/table.dart index c0ed6752..f2eccaf7 100644 --- a/drift/lib/src/dsl/table.dart +++ b/drift/lib/src/dsl/table.dart @@ -34,6 +34,13 @@ abstract class Table extends HasResultSet { /// This is intended to be used by generated code only. bool get dontWriteConstraints => false; + /// Whether this table is `STRICT`. + /// + /// Strict tables enforce stronger type constraints for inserts and updates. + /// Support for strict tables was added in sqlite3 version 37. + /// This field is intended to be used by generated code only. + bool get isStrict => false; + /// Override this to specify custom primary keys: /// ```dart /// class IngredientInRecipes extends Table { diff --git a/drift/lib/src/runtime/query_builder/migration.dart b/drift/lib/src/runtime/query_builder/migration.dart index 26619ad1..c550c3e2 100644 --- a/drift/lib/src/runtime/query_builder/migration.dart +++ b/drift/lib/src/runtime/query_builder/migration.dart @@ -289,9 +289,12 @@ class Migrator { context.buffer.write(')'); // == true because of nullability - if (dslTable.withoutRowId == true) { + if (dslTable.withoutRowId) { context.buffer.write(' WITHOUT ROWID'); } + if (dslTable.isStrict) { + context.buffer.write(' STRICT'); + } context.buffer.write(';'); } diff --git a/drift/test/data/tables/custom_tables.g.dart b/drift/test/data/tables/custom_tables.g.dart index 551048c0..dc1bc64a 100644 --- a/drift/test/data/tables/custom_tables.g.dart +++ b/drift/test/data/tables/custom_tables.g.dart @@ -284,6 +284,8 @@ class ConfigTable extends Table with TableInfo { static TypeConverter $converter1 = const EnumIndexConverter(SyncType.values); @override + bool get isStrict => true; + @override bool get dontWriteConstraints => true; } diff --git a/drift/test/data/tables/tables.drift b/drift/test/data/tables/tables.drift index b4148d96..959d5bdf 100644 --- a/drift/test/data/tables/tables.drift +++ b/drift/test/data/tables/tables.drift @@ -23,7 +23,7 @@ create table config ( config_value TEXT, sync_state INTEGER MAPPED BY `const SyncTypeConverter()`, sync_state_implicit ENUM(SyncType) -) AS "Config"; +) STRICT AS "Config"; CREATE INDEX IF NOT EXISTS value_idx ON config (config_value); diff --git a/drift/test/integration_tests/moor_files_test.dart b/drift/test/integration_tests/moor_files_test.dart index 1eb4ecca..9cec4df3 100644 --- a/drift/test/integration_tests/moor_files_test.dart +++ b/drift/test/integration_tests/moor_files_test.dart @@ -22,7 +22,7 @@ const _createConfig = 'CREATE TABLE IF NOT EXISTS config (' 'config_key TEXT not null primary key, ' 'config_value TEXT, ' 'sync_state INTEGER, ' - 'sync_state_implicit INTEGER);'; + 'sync_state_implicit INTEGER) STRICT;'; const _createMyTable = 'CREATE TABLE IF NOT EXISTS mytable (' 'someid INTEGER NOT NULL, ' diff --git a/drift_dev/lib/src/analyzer/moor/create_table_reader.dart b/drift_dev/lib/src/analyzer/moor/create_table_reader.dart index c594cc4e..37eee095 100644 --- a/drift_dev/lib/src/analyzer/moor/create_table_reader.dart +++ b/drift_dev/lib/src/analyzer/moor/create_table_reader.dart @@ -228,6 +228,7 @@ class CreateTableReader { overrideDontWriteConstraints: true, declaration: MoorTableDeclaration(stmt, step.file), existingRowClass: existingRowClass, + isStrict: table.isStrict, )..parserTable = table; // Having a mapping from parser table to moor tables helps with IDE features diff --git a/drift_dev/lib/src/model/table.dart b/drift_dev/lib/src/model/table.dart index f6c7e265..e9c70061 100644 --- a/drift_dev/lib/src/model/table.dart +++ b/drift_dev/lib/src/model/table.dart @@ -103,6 +103,10 @@ class MoorTable extends MoorEntityWithResultSet { /// getter on the table class with this value. final bool? overrideWithoutRowId; + /// Whether this table is defined as `STRICT`. Support for strict tables has + /// been added in sqlite 3.37. + final bool isStrict; + /// When non-null, the generated table class will override the /// `dontWriteConstraint` getter on the table class with this value. final bool? overrideDontWriteConstraints; @@ -145,6 +149,7 @@ class MoorTable extends MoorEntityWithResultSet { this.overrideDontWriteConstraints, this.declaration, this.existingRowClass, + this.isStrict = false, }) : _overriddenName = overriddenName { _attachToConverters(); } diff --git a/drift_dev/lib/src/writer/tables/table_writer.dart b/drift_dev/lib/src/writer/tables/table_writer.dart index 293946b5..1f6852dd 100644 --- a/drift_dev/lib/src/writer/tables/table_writer.dart +++ b/drift_dev/lib/src/writer/tables/table_writer.dart @@ -444,6 +444,12 @@ class TableWriter extends TableOrViewWriter { ..write('bool get withoutRowId => $value;\n'); } + if (table.isStrict) { + buffer + ..write('@override\n') + ..write('bool get isStrict => true;\n'); + } + if (table.overrideTableConstraints != null) { final value = table.overrideTableConstraints!.map(asDartLiteral).join(', '); diff --git a/sqlparser/lib/src/analysis/schema/from_create_table.dart b/sqlparser/lib/src/analysis/schema/from_create_table.dart index 9898c5ff..ed76a3ed 100644 --- a/sqlparser/lib/src/analysis/schema/from_create_table.dart +++ b/sqlparser/lib/src/analysis/schema/from_create_table.dart @@ -74,6 +74,7 @@ class SchemaFromCreateTable { primaryKeyColumnsInStrictTable: stmt.isStrict ? primaryKey : null) ], withoutRowId: stmt.withoutRowId, + isStrict: stmt.isStrict, tableConstraints: stmt.tableConstraints, definition: stmt, ); @@ -167,11 +168,13 @@ class SchemaFromCreateTable { bool isValidTypeNameForStrictTable(String typeName) { // See https://www.sqlite.org/draft/stricttables.html const allowed = {'INT', 'INTEGER', 'REAL', 'TEXT', 'BLOB', 'ANY'}; - const alsoAllowedInMoor = {'ENUM', 'BOOL', 'DATE'}; + const alsoAllowedInMoor = {'BOOL', 'DATE'}; - if (allowed.contains(typeName.toUpperCase()) || + final upper = typeName.toUpperCase(); + + if (allowed.contains(upper) || (moorExtensions && - alsoAllowedInMoor.contains(typeName.toUpperCase()))) { + (alsoAllowedInMoor.contains(upper) || upper.contains('ENUM')))) { return true; } diff --git a/sqlparser/lib/src/analysis/schema/table.dart b/sqlparser/lib/src/analysis/schema/table.dart index 7aec245d..801a4d4b 100644 --- a/sqlparser/lib/src/analysis/schema/table.dart +++ b/sqlparser/lib/src/analysis/schema/table.dart @@ -25,6 +25,8 @@ class Table extends NamedResultSet with HasMetaMixin implements HumanReadable { /// Whether this table was created with an "WITHOUT ROWID" modifier final bool withoutRowId; + final bool isStrict; + /// Additional constraints set on this table. final List tableConstraints; @@ -44,6 +46,7 @@ class Table extends NamedResultSet with HasMetaMixin implements HumanReadable { required this.name, required this.resolvedColumns, this.withoutRowId = false, + this.isStrict = false, this.tableConstraints = const [], this.definition, this.isVirtual = false, diff --git a/sqlparser/test/analysis/schema/from_create_table_test.dart b/sqlparser/test/analysis/schema/from_create_table_test.dart index 8cd3dd3c..8795f069 100644 --- a/sqlparser/test/analysis/schema/from_create_table_test.dart +++ b/sqlparser/test/analysis/schema/from_create_table_test.dart @@ -190,4 +190,34 @@ void main() { expect(table.resolvedColumns.single.type.nullable, isFalse); }); }); + + group('sets withoutRowid and isStrict', () { + final engine = SqlEngine(EngineOptions(version: SqliteVersion.v3_37)); + + void testWith(String suffix, bool withoutRowid, bool strict) { + final stmt = + engine.parse('CREATE TABLE foo (bar TEXT) $suffix;').rootNode; + + final table = engine.schemaReader.read(stmt as CreateTableStatement); + expect(table.withoutRowId, withoutRowid); + expect(table.isStrict, strict); + } + + test('when the table is neither', () { + testWith('', false, false); + }); + + test('when the table is without rowid', () { + testWith('WITHOUT ROWID', true, false); + }); + + test('when the table is strict', () { + testWith('STRICT', false, true); + }); + + test('when the table is both', () { + testWith('WITHOUT ROWID, STRICT', true, true); + testWith('STRICT, WITHOUT ROWID', true, true); + }); + }); }