diff --git a/docs/README.md b/docs/README.md index 0e056788..f96c8862 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ We use a static site generator based on `build_runner` to build the documentatio For a fast edit-refresh cycle, run ``` -dart pub global run webdev serve --auto refresh pages:9999 web:8080 +dart pub global run webdev serve --auto refresh pages:9999 test:10000 web:8080 ``` You can ignore the `pages:9999` (or use any other port), it's just required diff --git a/docs/lib/snippets/queries.dart b/docs/lib/snippets/queries.dart index fc2f08e4..face6c86 100644 --- a/docs/lib/snippets/queries.dart +++ b/docs/lib/snippets/queries.dart @@ -1,7 +1,43 @@ import 'package:drift/drift.dart'; + import 'tables/filename.dart'; +// #docregion joinIntro +// We define a data class to contain both a todo entry and the associated +// category. +class EntryWithCategory { + EntryWithCategory(this.entry, this.category); + + final Todo entry; + final Category? category; +} + +// #enddocregion joinIntro + extension GroupByQueries on MyDatabase { +// #docregion joinIntro + // in the database class, we can then load the category for each entry + Stream> entriesWithCategory() { + final query = select(todos).join([ + leftOuterJoin(categories, categories.id.equalsExp(todos.category)), + ]); + + // see next section on how to parse the result + // #enddocregion joinIntro + // #docregion results + return query.watch().map((rows) { + return rows.map((row) { + return EntryWithCategory( + row.readTable(todos), + row.readTableOrNull(categories), + ); + }).toList(); + }); + // #enddocregion results + // #docregion joinIntro + } +// #enddocregion joinIntro + // #docregion countTodosInCategories Future countTodosInCategories() async { final amountOfTodos = todos.id.count(); @@ -34,4 +70,28 @@ extension GroupByQueries on MyDatabase { return query.map((row) => row.read(avgLength)!).watchSingle(); } // #enddocregion averageItemLength + + // #docregion otherTodosInSameCategory + /// Searches for todo entries in the same category as the ones having + /// `titleQuery` in their titles. + Future> otherTodosInSameCategory(String titleQuery) async { + // Since we're adding the same table twice (once to filter for the title, + // and once to find other todos in same category), we need a way to + // distinguish the two tables. So, we're giving one of them a special name: + final otherTodos = alias(todos, 'inCategory'); + + final query = select(otherTodos).join([ + // In joins, `useColumns: false` tells drift to not add columns of the + // joined table to the result set. This is useful here, since we only join + // the tables so that we can refer to them in the where clause. + innerJoin(categories, categories.id.equalsExp(otherTodos.category), + useColumns: false), + innerJoin(todos, todos.category.equalsExp(categories.id), + useColumns: false), + ]) + ..where(todos.title.contains(titleQuery)); + + return query.map((row) => row.readTable(otherTodos)).get(); + } + // #enddocregion otherTodosInSameCategory } diff --git a/docs/lib/snippets/tables/filename.dart b/docs/lib/snippets/tables/filename.dart index 99c506cb..7a073631 100644 --- a/docs/lib/snippets/tables/filename.dart +++ b/docs/lib/snippets/tables/filename.dart @@ -1,7 +1,8 @@ // ignore_for_file: directives_ordering // #docregion open -// These imports are only needed to open the database +// To open the database, add these imports to the existing file defining the +// database class. They are used to open the database. import 'dart:io'; import 'package:drift/native.dart'; @@ -62,3 +63,19 @@ LazyDatabase _openConnection() { return NativeDatabase(file); }); } + +// #enddocregion open +// #docregion usage +Future main() async { + final database = MyDatabase(); + + // Simple insert: + await database + .into(database.categories) + .insert(CategoriesCompanion.insert(description: 'my first category')); + + // Simple select: + final allCategories = await database.select(database.categories).get(); + print('Categories in database: $allCategories'); +} +// #enddocregion usage diff --git a/docs/pages/docs/Advanced Features/builder_options.md b/docs/pages/docs/Advanced Features/builder_options.md index abcc7b17..a21b44a3 100644 --- a/docs/pages/docs/Advanced Features/builder_options.md +++ b/docs/pages/docs/Advanced Features/builder_options.md @@ -130,7 +130,7 @@ targets: modules: - json1 - fts5 - - moor_ffi + - math ``` We currently support the following extensions: @@ -138,6 +138,9 @@ We currently support the following extensions: - [json1](https://www.sqlite.org/json1.html): Support static analysis for `json_` functions in moor files - [fts5](https://www.sqlite.org/fts5.html): Support `CREATE VIRTUAL TABLE` statements for `fts5` tables and the `MATCH` operator. Functions like `highlight` or `bm25` are available as well. +- `rtree`: Static analysis support for the [R*Tree](https://www.sqlite.org/rtree.html) extension. + Enabling this option is safe when using a `NativeDatabase` with `sqlite3_flutter_libs`, + which compiles sqlite3 with the R*Tree extension enabled. - `moor_ffi`: Enables support for functions that are only available when using a `NativeDatabase`. This contains `pow`, `sqrt` and a variety of trigonometric functions. Details on those functions are available [here]({{ "../Other engines/vm.md#moor-only-functions" | pageUrl }}). - `math`: Assumes that sqlite3 was compiled with [math functions](https://www.sqlite.org/lang_mathfunc.html). diff --git a/docs/pages/docs/Advanced Features/joins.md b/docs/pages/docs/Advanced Features/joins.md index b721e43f..4320d233 100644 --- a/docs/pages/docs/Advanced Features/joins.md +++ b/docs/pages/docs/Advanced Features/joins.md @@ -8,6 +8,8 @@ aliases: template: layouts/docs/single --- +{% assign snippets = 'package:drift_docs/snippets/queries.dart.excerpt.json' | readString | json_decode %} + ## Joins Drift supports sql joins to write queries that operate on more than one table. To use that feature, start @@ -15,24 +17,11 @@ a select regular select statement with `select(table)` and then add a list of jo inner and left outer joins, a `ON` expression needs to be specified. Here's an example using the tables defined in the [example]({{ "../Getting started/index.md" | pageUrl }}). -```dart -// we define a data class to contain both a todo entry and the associated category -class EntryWithCategory { - EntryWithCategory(this.entry, this.category); +{% include "blocks/snippet" snippets = snippets name = 'joinIntro' %} - final TodoEntry entry; - final Category category; -} +Of course, you can also join multiple tables: -// in the database class, we can then load the category for each entry -Stream> entriesWithCategory() { - final query = select(todos).join([ - leftOuterJoin(categories, categories.id.equalsExp(todos.category)), - ]); - - // see next section on how to parse the result -} -``` +{% include "blocks/snippet" snippets = snippets name = 'otherTodosInSameCategory' %} ## Parsing results @@ -42,16 +31,8 @@ read. It contains a `rawData` getter to obtain the raw columns. But more importa `readTable` method can be used to read a data class from a table. In the example query above, we can read the todo entry and the category from each row like this: -```dart -return query.watch().map((rows) { - return rows.map((row) { - return EntryWithCategory( - row.readTable(todos), - row.readTableOrNull(categories), - ); - }).toList(); -}); -``` + +{% include "blocks/snippet" snippets = snippets name = 'results' %} _Note_: `readTable` will throw an `ArgumentError` when a table is not present in the row. For instance, todo entries might not be in any category. To account for that, we use `row.readTableOrNull` to load @@ -174,8 +155,6 @@ with the right table by default). ## Group by -{% assign snippets = 'package:drift_docs/snippets/queries.dart.excerpt.json' | readString | json_decode %} - Sometimes, you need to run queries that _aggregate_ data, meaning that data you're interested in comes from multiple rows. Common questions include diff --git a/docs/pages/docs/Examples/existing_databases.md b/docs/pages/docs/Examples/existing_databases.md index a657dc7d..07a8a400 100644 --- a/docs/pages/docs/Examples/existing_databases.md +++ b/docs/pages/docs/Examples/existing_databases.md @@ -44,6 +44,7 @@ to perform that work just before your drift database is opened: ```dart import 'package:drift/drift.dart'; import 'package:flutter/services.dart' show rootBundle; +import 'package:path/path.dart' as p; LazyDatabase _openConnection() { return LazyDatabase(() async { diff --git a/docs/pages/docs/Examples/index.md b/docs/pages/docs/Examples/index.md index f6e81e34..b84592fa 100644 --- a/docs/pages/docs/Examples/index.md +++ b/docs/pages/docs/Examples/index.md @@ -6,11 +6,45 @@ data: template: layouts/docs/list --- +For a full example of a cross-platform Flutter app using drift following best +practices, see [app example](https://github.com/simolus3/drift/tree/develop/examples/app) in drift's repository. +For interested users wanting to take a look at how to use drift in a Flutter app +with a modern architecture, this is perhaps the best example to use as a starting +point. -We have an [example in the repo](https://github.com/simolus3/drift/tree/master/moor_flutter/example), it's a simple todo list app, -written with drift. [Rody Davis](https://github.com/rodydavis) has built a cleaner version of the example that works on all -Flutter platforms - including Web and Desktop! You can check it out [here](https://github.com/rodydavis/moor_shared). -An updated version of this example is also present in drift's [repository](https://github.com/simolus3/drift/tree/develop/examples/app). -[Abdelrahman Mostafa Elmarakby](https://github.com/abdelrahmanelmarakby) wrote an animated version of the todo app available [here](https://github.com/abdelrahmanelmarakby/todo_with_moor_and_animation). +Drift's repository also contains a number of smaller examples showcasing select +drift features: -The [HackerNews reader app](https://github.com/filiph/hn_app) from the [Boring Flutter Show](https://www.youtube.com/playlist?list=PLjxrf2q8roU3ahJVrSgAnPjzkpGmL9Czl) also uses drift to keep a list of favorite articles. +- The [encryption] example contains a simple Flutter app using an encrypted drift + database, powered by the `sqlcipher_flutter_libs` package. +- [web_worker] and [flutter_web_worker] are small web-only apps using drift in + a shared web worker, which allows for a real-time synchronization of the + database across tabs. Of course, this pattern can only be embedded into + multi-platform apps. +- The [migration] example makes use of advanced schema migrations and shows how + to test migrations between different database schemas by using drift's + [dedicated tooling][migration tooling] for this purpose. +- [Another example][with_built_value] shows how to use drift-generated code in + other builders (here, `built_value`). + +Additional examples from our awesome community are available as well: + +- [Abdelrahman Mostafa Elmarakby](https://github.com/abdelrahmanelmarakby) wrote an animated version of the todo app available [here](https://github.com/abdelrahmanelmarakby/todo_with_moor_and_animation). +- The [HackerNews reader app](https://github.com/filiph/hn_app) from the [Boring Flutter Show](https://www.youtube.com/playlist?list=PLjxrf2q8roU3ahJVrSgAnPjzkpGmL9Czl) + also uses drift to keep a list of favorite articles. + +If you too have an open-source application using drift, feel free to reach out +and have it added to this list! + +If you are interested in seeing more drift examples, or want to contribute more +examples yourself, don't hesitate to open an issue either. +Providing more up-to-date examples would be a much appreciated contribution! + +Additional patterns are also shown and explained on this website: + +[encryption]: https://github.com/simolus3/drift/tree/develop/examples/encryption +[web_worker]: https://github.com/simolus3/drift/tree/develop/examples/web_worker_example +[flutter_web_worker]: https://github.com/simolus3/drift/tree/develop/examples/flutter_web_worker_example +[migration]: https://github.com/simolus3/drift/tree/develop/examples/migrations_example +[migration tooling]: {{ '../Advanced Features/migrations.md#verifying-migrations' | pageUrl }} +[with_built_value]: https://github.com/simolus3/drift/tree/develop/examples/with_built_value diff --git a/docs/pages/docs/Getting started/index.md b/docs/pages/docs/Getting started/index.md index 1e243e20..00b3689a 100644 --- a/docs/pages/docs/Getting started/index.md +++ b/docs/pages/docs/Getting started/index.md @@ -48,7 +48,9 @@ If you're wondering why so many packages are necessary, here's a quick overview ### Declaring tables -Using drift, you can model the structure of your tables with simple dart code: +Using drift, you can model the structure of your tables with simple dart code. +Let's write a file (simply called `filename.dart` in this snippet) containing +two simple tables and a database class using drift to get started: {% include "blocks/snippet" snippets = snippets name = "overview" %} @@ -58,15 +60,25 @@ operator and can't contain anything more than what's included in the documentati examples. Otherwise, the generator won't be able to know what's going on. ## Generating the code + Drift integrates with Dart's `build` system, so you can generate all the code needed with `flutter pub run build_runner build`. If you want to continuously rebuild the generated code where you change your code, run `flutter pub run build_runner watch` instead. -After running either command once, the drift generator will have created a class for your -database and data classes for your entities. To use it, change the `MyDatabase` class as -follows: +After running either command once, drift's generator will have created a class for your +database and data classes for your entities. To use it, change the `MyDatabase` class +defined in the earlier snippet as follows: {% include "blocks/snippet" snippets = snippets name = "open" %} +That's it! You can now use drift by creating an instance of `MyDatabase`. +In a simple app from a `main` entrypoint, this may look like the following: + +{% include "blocks/snippet" snippets = snippets name = "usage" %} + +The articles linked below explain how to use the database in actual, complete +Flutter apps. +A complete example for a Flutter app using drift is also available [here](https://github.com/simolus3/drift/tree/develop/examples/app). + ## Next steps Congratulations! You're now ready to use all of drift. See the articles below for further reading. diff --git a/docs/pages/docs/Other engines/encryption.md b/docs/pages/docs/Other engines/encryption.md index b5d5bb52..0738e85f 100644 --- a/docs/pages/docs/Other engines/encryption.md +++ b/docs/pages/docs/Other engines/encryption.md @@ -5,17 +5,22 @@ data: template: layouts/docs/single --- -There are two ways to use drift on encrypted databases. -The `encrypted_moor` package is similar to `moor_flutter` and uses a platform plugin written in +There are two ways to use drift on encrypted databases. +The `encrypted_drift` package is similar to `drift_sqflite` and uses a platform plugin written in Java. Alternatively, you can use the ffi-based implementation with the `sqlcipher_flutter_libs` package. -## Using `encrypted_moor` +For new apps, we recommend using `sqlcipher_flutter_libs` with a `NativeDatabase` +from drift. +An example of a Flutter app using the new encryption package is available +[here](https://github.com/simolus3/drift/tree/develop/examples/encryption). -Starting from 1.7, we have a version of drift that can work with encrypted databases by using the +## Using `encrypted_drift` + +The drift repository provides a version of drift that can work with encrypted databases by using the [sqflite_sqlcipher](https://pub.dev/packages/sqflite_sqlcipher) library by [@davidmartos96](https://github.com/davidmartos96). To use it, you need to -remove the dependency on `moor_flutter` from your `pubspec.yaml` and replace it +remove the dependency on `drift_sqflite` from your `pubspec.yaml` and replace it with this: {% assign versions = 'package:drift_docs/versions.json' | readString | json_decode %} @@ -23,16 +28,16 @@ with this: ```yaml dependencies: drift: ^{{ versions.drift }} - encrypted_moor: + encrypted_drift: git: url: https://github.com/simolus3/drift.git - path: extras/encryption + path: extras/encryption ``` -Instead of importing `package:moor_flutter/moor_flutter` (or `package:drift/native.dart`) in your apps, -you would then import both `package:drift/drift.dart` and `package:encrypted_moor/encrypted_moor.dart`. +Instead of importing `package:drift_sqflite/drift_sqflite.dart` (or `package:drift/native.dart`) in your apps, +you would then import both `package:drift/drift.dart` and `package:encrypted_drift/encrypted_drift.dart`. -Finally, you can replace `FlutterQueryExecutor` (or a `NativeDatabase`) with an `EncryptedExecutor`. +Finally, you can replace `SqfliteQueryExecutor` (or a `NativeDatabase`) with an `EncryptedExecutor`. ### Extra setup on Android and iOS diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index a2543f70..7b4e36aa 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.1.0 + +- Improve stack traces when using `watchSingle()` with a stream emitting a non- + singleton list at some point. +- Add `OrderingTerm.nulls` to control the `NULLS FIRST` or `NULLS LAST` clause + in Dart. + ## 2.0.2+1 - Revert the breaking change around `QueryRow.read` only returning non-nullable diff --git a/drift/lib/src/runtime/query_builder/components/order_by.dart b/drift/lib/src/runtime/query_builder/components/order_by.dart index d27d7318..4bd5392a 100644 --- a/drift/lib/src/runtime/query_builder/components/order_by.dart +++ b/drift/lib/src/runtime/query_builder/components/order_by.dart @@ -3,16 +3,28 @@ part of '../query_builder.dart'; /// Describes how to order rows enum OrderingMode { /// Ascending ordering mode (lowest items first) - asc, + asc._('ASC'), /// Descending ordering mode (highest items first) - desc + desc._('DESC'); + + final String _mode; + + const OrderingMode._(this._mode); } -const _modeToString = { - OrderingMode.asc: 'ASC', - OrderingMode.desc: 'DESC', -}; +/// Describes how to order nulls +enum NullsOrder { + /// Place NULLs at the start + first._('NULLS FIRST'), + + /// Place NULLs at the end + last._('NULLS LAST'); + + final String _order; + + const NullsOrder._(this._order); +} /// A single term in a [OrderBy] clause. The priority of this term is determined /// by its position in [OrderBy.terms]. @@ -23,18 +35,40 @@ class OrderingTerm extends Component { /// The ordering mode (ascending or descending). final OrderingMode mode; - /// Creates an ordering term by the [expression] and the [mode] (defaults to - /// ascending). - OrderingTerm({required this.expression, this.mode = OrderingMode.asc}); + /// How to order NULLs. + /// When [nulls] is [null], then it's ignored. + /// + /// Note that this feature are only available in sqlite3 version `3.30.0` and + /// newer. When using `sqlite3_flutter_libs` or a web database, this is not + /// a problem. + final NullsOrder? nulls; - /// Creates an ordering term that sorts for ascending values of [expression]. - factory OrderingTerm.asc(Expression expression) { - return OrderingTerm(expression: expression, mode: OrderingMode.asc); + /// Creates an ordering term by the [expression], the [mode] (defaults to + /// ascending) and the [nulls]. + OrderingTerm({ + required this.expression, + this.mode = OrderingMode.asc, + this.nulls, + }); + + /// Creates an ordering term that sorts for ascending values + /// of [expression] and the [nulls]. + factory OrderingTerm.asc(Expression expression, {NullsOrder? nulls}) { + return OrderingTerm( + expression: expression, + mode: OrderingMode.asc, + nulls: nulls, + ); } - /// Creates an ordering term that sorts for descending values of [expression]. - factory OrderingTerm.desc(Expression expression) { - return OrderingTerm(expression: expression, mode: OrderingMode.desc); + /// Creates an ordering term that sorts for descending values + /// of [expression] and the [nulls]. + factory OrderingTerm.desc(Expression expression, {NullsOrder? nulls}) { + return OrderingTerm( + expression: expression, + mode: OrderingMode.desc, + nulls: nulls, + ); } /// Creates an ordering term to get a number of random rows @@ -47,7 +81,11 @@ class OrderingTerm extends Component { void writeInto(GenerationContext context) { expression.writeInto(context); context.writeWhitespace(); - context.buffer.write(_modeToString[mode]); + context.buffer.write(mode._mode); + if (nulls != null) { + context.writeWhitespace(); + context.buffer.write(nulls?._order); + } } } diff --git a/drift/lib/src/utils/single_transformer.dart b/drift/lib/src/utils/single_transformer.dart index 3eff83ee..b7579548 100644 --- a/drift/lib/src/utils/single_transformer.dart +++ b/drift/lib/src/utils/single_transformer.dart @@ -3,6 +3,8 @@ import 'dart:async'; /// Transforms a stream of lists into a stream of single elements, assuming /// that each list is a singleton or empty. StreamTransformer, T?> singleElementsOrNull() { + final originTrace = StackTrace.current; + return StreamTransformer.fromHandlers(handleData: (data, sink) { T? result; @@ -11,7 +13,9 @@ StreamTransformer, T?> singleElementsOrNull() { result = data.single; } } catch (e) { - throw StateError('Expected exactly one element, but got ${data.length}'); + Error.throwWithStackTrace( + StateError('Expected exactly one element, but got ${data.length}'), + originTrace); } sink.add(result); @@ -21,13 +25,17 @@ StreamTransformer, T?> singleElementsOrNull() { /// Transforms a stream of lists into a stream of single elements, assuming /// that each list is a singleton. StreamTransformer, T> singleElements() { + final originTrace = StackTrace.current; + return StreamTransformer.fromHandlers(handleData: (data, sink) { T single; try { single = data.single; } catch (e) { - throw StateError('Expected exactly one element, but got ${data.length}'); + Error.throwWithStackTrace( + StateError('Expected exactly one element, but got ${data.length}'), + originTrace); } sink.add(single); diff --git a/drift/pubspec.yaml b/drift/pubspec.yaml index b80880bf..4ff68d41 100644 --- a/drift/pubspec.yaml +++ b/drift/pubspec.yaml @@ -1,6 +1,6 @@ name: drift description: Drift is a reactive library to store relational data in Dart and Flutter applications. -version: 2.0.2+1 +version: 2.1.0 repository: https://github.com/simolus3/drift homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/drift/issues @@ -33,3 +33,4 @@ dev_dependencies: mockito: ^5.0.7 rxdart: ^0.27.0 shelf: ^1.3.0 + stack_trace: ^1.10.0 diff --git a/drift/test/database/statements/order_by_test.dart b/drift/test/database/statements/order_by_test.dart new file mode 100644 index 00000000..1c76d1a6 --- /dev/null +++ b/drift/test/database/statements/order_by_test.dart @@ -0,0 +1,88 @@ +import 'package:drift/drift.dart' hide isNull; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +import '../../generated/todos.dart'; +import '../../test_utils/test_utils.dart'; + +void main() { + late TodoDb db; + late MockExecutor executor; + + setUp(() { + executor = MockExecutor(); + db = TodoDb(executor); + }); + + test('when nullsOrder is null it ignored', () async { + final query = db.select(db.users); + query.orderBy([(tbl) => OrderingTerm(expression: tbl.name)]); + await query.get(); + verify(executor.runSelect( + 'SELECT * FROM users ORDER BY name ASC;', + argThat(isEmpty), + )); + }); + + test('nullsOrder is last', () async { + final query = db.select(db.users); + query.orderBy([ + (tbl) => OrderingTerm( + expression: tbl.name, + nulls: NullsOrder.last, + ), + ]); + await query.get(); + verify(executor.runSelect( + 'SELECT * FROM users ORDER BY name ASC NULLS LAST;', + argThat(isEmpty), + )); + }); + + test('nullsOrder is first', () async { + final query = db.select(db.users); + query.orderBy([ + (tbl) => OrderingTerm( + expression: tbl.name, + nulls: NullsOrder.first, + ), + ]); + await query.get(); + verify(executor.runSelect( + 'SELECT * FROM users ORDER BY name ASC NULLS FIRST;', + argThat(isEmpty), + )); + }); + + test('complex order by with different nullsOrder', () async { + final query = db.select(db.users); + query.orderBy([ + (tbl) => OrderingTerm( + expression: tbl.name, + nulls: NullsOrder.first, + ), + (tbl) => OrderingTerm( + expression: tbl.creationTime, + ), + (tbl) => OrderingTerm( + expression: tbl.profilePicture, + nulls: NullsOrder.last, + ), + ]); + await query.get(); + verify(executor.runSelect( + 'SELECT * FROM users ORDER BY name ASC NULLS FIRST, creation_time ASC, profile_picture ASC NULLS LAST;', + argThat(isEmpty), + )); + }); + + test('works with helper factories', () { + final table = db.users; + + expect(OrderingTerm.asc(table.id), generates('id ASC')); + expect(OrderingTerm.asc(table.id, nulls: NullsOrder.last), + generates('id ASC NULLS LAST')); + expect(OrderingTerm.desc(table.id, nulls: NullsOrder.first), + generates('id DESC NULLS FIRST')); + }); +} diff --git a/drift/test/generated/custom_tables.g.dart b/drift/test/generated/custom_tables.g.dart index 0ab23137..1a50dffc 100644 --- a/drift/test/generated/custom_tables.g.dart +++ b/drift/test/generated/custom_tables.g.dart @@ -1606,12 +1606,13 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { } Selectable readMultiple(List var1, - {OrderBy Function(ConfigTable config) clause = _$moor$default$0}) { + {ReadMultiple$clause? clause}) { var $arrayStartIndex = 1; final expandedvar1 = $expandVar($arrayStartIndex, var1.length); $arrayStartIndex += var1.length; - final generatedclause = - $write(clause(this.config), startIndex: $arrayStartIndex); + final generatedclause = $write( + clause?.call(this.config) ?? const OrderBy.nothing(), + startIndex: $arrayStartIndex); $arrayStartIndex += generatedclause.amountOfVariables; return customSelect( 'SELECT * FROM config WHERE config_key IN ($expandedvar1) ${generatedclause.sql}', @@ -1625,12 +1626,11 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { }).asyncMap(config.mapFromRow); } - Selectable readDynamic( - {Expression Function(ConfigTable config) predicate = - _$moor$default$1}) { + Selectable readDynamic({ReadDynamic$predicate? predicate}) { var $arrayStartIndex = 1; - final generatedpredicate = - $write(predicate(this.config), startIndex: $arrayStartIndex); + final generatedpredicate = $write( + predicate?.call(this.config) ?? const CustomExpression('(TRUE)'), + startIndex: $arrayStartIndex); $arrayStartIndex += generatedpredicate.amountOfVariables; return customSelect('SELECT * FROM config WHERE ${generatedpredicate.sql}', variables: [ @@ -1643,10 +1643,11 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { } Selectable typeConverterVar(SyncType? var1, List var2, - {Expression Function(ConfigTable config) pred = _$moor$default$2}) { + {TypeConverterVar$pred? pred}) { var $arrayStartIndex = 2; - final generatedpred = - $write(pred(this.config), startIndex: $arrayStartIndex); + final generatedpred = $write( + pred?.call(this.config) ?? const CustomExpression('(TRUE)'), + startIndex: $arrayStartIndex); $arrayStartIndex += generatedpred.amountOfVariables; final expandedvar2 = $expandVar($arrayStartIndex, var2.length); $arrayStartIndex += var2.length; @@ -1694,9 +1695,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { }); } - Selectable multiple( - {required Expression Function(WithDefaults d, WithConstraints c) - predicate}) { + Selectable multiple({required Multiple$predicate predicate}) { var $arrayStartIndex = 1; final generatedpredicate = $write( predicate( @@ -1734,8 +1733,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { }).asyncMap(email.mapFromRow); } - Selectable readRowId( - {required Expression Function(ConfigTable config) expr}) { + Selectable readRowId({required ReadRowId$expr expr}) { var $arrayStartIndex = 1; final generatedexpr = $write(expr(this.config), startIndex: $arrayStartIndex); @@ -1865,11 +1863,9 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { const DriftDatabaseOptions(storeDateTimeAsText: true); } -OrderBy _$moor$default$0(ConfigTable _) => const OrderBy.nothing(); -Expression _$moor$default$1(ConfigTable _) => - const CustomExpression('(TRUE)'); -Expression _$moor$default$2(ConfigTable _) => - const CustomExpression('(TRUE)'); +typedef ReadMultiple$clause = OrderBy Function(ConfigTable config); +typedef ReadDynamic$predicate = Expression Function(ConfigTable config); +typedef TypeConverterVar$pred = Expression Function(ConfigTable config); class JsonResult extends CustomResultSet { final String key; @@ -1927,6 +1923,9 @@ class MultipleResult extends CustomResultSet { } } +typedef Multiple$predicate = Expression Function( + WithDefaults d, WithConstraints c); + class ReadRowIdResult extends CustomResultSet { final int rowid; final String configKey; @@ -1966,6 +1965,8 @@ class ReadRowIdResult extends CustomResultSet { } } +typedef ReadRowId$expr = Expression Function(ConfigTable config); + class NestedResult extends CustomResultSet { final WithDefault defaults; final List nestedQuery0; diff --git a/drift/test/utils/single_transformer_test.dart b/drift/test/utils/single_transformer_test.dart index 091291de..0dfbb399 100644 --- a/drift/test/utils/single_transformer_test.dart +++ b/drift/test/utils/single_transformer_test.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:drift/src/utils/single_transformer.dart'; +import 'package:stack_trace/stack_trace.dart'; import 'package:test/test.dart'; void main() { @@ -44,6 +45,44 @@ void main() { final stream = Stream.value([]); expect(stream.transform(singleElementsOrNull()), emits(isNull)); }); + + test('stack traces reflect where the transformer was created', () { + final controller = StreamController>(); + final stream = controller.stream.transform(singleElements()); + + final error = isStateError.having( + (e) => e.stackTrace, + 'stackTrace', + // Make sure that the predicate points to where the transformation was + // applied instead of just containing asynchronous gaps and drift-internal + // frames. + predicate((trace) { + final parsed = Trace.from(trace as StackTrace); + + return parsed.frames.any( + (f) => + f.uri.path.contains('single_transformer_test.dart') && + f.line == 51, + ); + }), + ); + + expectLater( + stream, + emitsInOrder([ + 1, + emitsError(error), + 2, + emitsDone, + ]), + ); + + controller + ..add([1]) + ..add([2, 3]) + ..add([2]) + ..close(); + }, testOn: 'vm'); } Matcher _stateErrorWithTrace = diff --git a/drift_dev/CHANGELOG.md b/drift_dev/CHANGELOG.md index 005ff519..f2abee9b 100644 --- a/drift_dev/CHANGELOG.md +++ b/drift_dev/CHANGELOG.md @@ -1,3 +1,15 @@ +## 2.1.0 + +- Analysis support `fts5` tables with external content tables. +- Analysis support for the `rtree` module. +- Prepare for an upcoming breaking analyzer change around how classes are mapped + to elements. + +## 2.0.2 + +- Generate public typedefs for the signatures of `scoped_dart_components`, + making it easier to re-use them for own methods. + ## 2.0.1 - Recognize options for an applied `not_shared` builder when exporting schemas. diff --git a/drift_dev/lib/src/analyzer/drift/entity_handler.dart b/drift_dev/lib/src/analyzer/drift/entity_handler.dart index eebd833f..417ed306 100644 --- a/drift_dev/lib/src/analyzer/drift/entity_handler.dart +++ b/drift_dev/lib/src/analyzer/drift/entity_handler.dart @@ -4,6 +4,7 @@ import 'package:drift_dev/src/analyzer/errors.dart'; import 'package:drift_dev/src/analyzer/runner/results.dart'; import 'package:drift_dev/src/analyzer/runner/steps.dart'; import 'package:drift_dev/src/analyzer/sql_queries/query_analyzer.dart'; +import 'package:source_span/source_span.dart'; import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/utils/find_referenced_tables.dart'; @@ -39,6 +40,11 @@ class EntityHandler extends BaseAnalyzer { final node = _handleMoorDeclaration(entity, _tables); _lint(node, entity.sqlName); + + final parserTable = entity.parserTable; + if (parserTable is Fts5Table) { + _checkFts5References(entity, parserTable); + } } else if (entity is MoorTrigger) { entity.clearResolvedReferences(); @@ -73,6 +79,63 @@ class EntityHandler extends BaseAnalyzer { lintContext(context, displayName); } + void _checkFts5References(DriftTable drift, Fts5Table rawTable) { + FileSpan? span; + final declaration = drift.declaration; + if (declaration is DriftTableDeclaration) { + span = declaration.node.tableNameToken?.span; + } + + final contentTable = rawTable.contentTable; + final contentRowId = rawTable.contentRowId; + + if (contentTable != null && contentTable.isNotEmpty) { + final referenced = _resolveTableOrView(contentTable); + + if (referenced != null) { + drift.references.add(referenced); + + // Check that fts5 columns also exist in the reference table. + for (final column in rawTable.resultColumns) { + final name = column.name.toLowerCase(); + final foundColumn = + referenced.columns.any((c) => c.name.name.toLowerCase() == name); + + if (!foundColumn) { + step.reportError(ErrorInDriftFile( + severity: Severity.error, + span: span, + message: 'The content table has no column `${column.name}`, but ' + 'this fts5 table references it', + )); + } + } + + // If a custom rowid is set, check that it exists + if (contentRowId != null && !aliasesForRowId.contains(contentRowId)) { + final name = contentRowId.toLowerCase(); + final foundColumn = referenced.columns + .firstWhereOrNull((c) => c.name.name.toLowerCase() == name); + + if (foundColumn == null) { + step.reportError(ErrorInDriftFile( + severity: Severity.error, + span: span, + message: 'The content table has no column `$contentRowId`, but ' + 'this fts5 table is declared to use it as a row id', + )); + } + } + } else { + step.reportError(ErrorInDriftFile( + severity: Severity.error, + span: span, + message: 'Content table `$contentTable` could not be found.', + )); + } + } + } + Iterable _findTables(AstNode node) { return findReferences(node, includeViews: false).cast(); } @@ -105,6 +168,18 @@ class EntityHandler extends BaseAnalyzer { MoorIndex? _inducedIndex(CreateIndexStatement stmt) { return _indexes[stmt]; } + + DriftTable? _resolveTable(String name) { + final lower = name.toLowerCase(); + return tables.firstWhereOrNull((t) => t.sqlName.toLowerCase() == lower); + } + + DriftEntityWithResultSet? _resolveTableOrView(String name) { + final lower = name.toLowerCase(); + + return _resolveTable(name) ?? + views.firstWhereOrNull((v) => v.name.toLowerCase() == lower); + } } class _ReferenceResolvingVisitor extends RecursiveVisitor { @@ -113,8 +188,7 @@ class _ReferenceResolvingVisitor extends RecursiveVisitor { _ReferenceResolvingVisitor(this.handler); DriftTable? _resolveTable(TableReference reference) { - return handler.tables - .firstWhereOrNull((t) => t.sqlName == reference.tableName); + return handler._resolveTable(reference.tableName); } @override diff --git a/drift_dev/lib/src/analyzer/drift/find_dart_class.dart b/drift_dev/lib/src/analyzer/drift/find_dart_class.dart index ffed6d37..d7b702b7 100644 --- a/drift_dev/lib/src/analyzer/drift/find_dart_class.dart +++ b/drift_dev/lib/src/analyzer/drift/find_dart_class.dart @@ -23,7 +23,7 @@ Future findDartClass( } final foundElement = library.exportNamespace.get(identifier); - if (foundElement is ClassElement) { + if (foundElement is InterfaceElement) { return FoundDartClass(foundElement, null); } else if (foundElement is TypeAliasElement) { final innerType = foundElement.aliasedType; diff --git a/drift_dev/lib/src/analyzer/errors.dart b/drift_dev/lib/src/analyzer/errors.dart index 968c7fc7..0e157184 100644 --- a/drift_dev/lib/src/analyzer/errors.dart +++ b/drift_dev/lib/src/analyzer/errors.dart @@ -78,7 +78,7 @@ class ErrorInDartCode extends DriftError { } class ErrorInDriftFile extends DriftError { - final FileSpan span; + final FileSpan? span; ErrorInDriftFile( {required this.span, @@ -108,7 +108,12 @@ class ErrorInDriftFile extends DriftError { @override void writeDescription(LogFunction log) { - log(span.message(message, color: isError)); + final errorSpan = span; + if (errorSpan != null) { + log(errorSpan.message(message, color: isError)); + } else { + log(message); + } } } diff --git a/drift_dev/lib/src/analyzer/helper.dart b/drift_dev/lib/src/analyzer/helper.dart index fa7c9483..5b563dd7 100644 --- a/drift_dev/lib/src/analyzer/helper.dart +++ b/drift_dev/lib/src/analyzer/helper.dart @@ -22,13 +22,13 @@ class HelperLibrary { /// Returns `null` if [type] is not a subtype of `TypeConverter`. InterfaceType? asTypeConverter(DartType type) { final converter = - helperLibrary.exportNamespace.get('TypeConverter') as ClassElement; + helperLibrary.exportNamespace.get('TypeConverter') as InterfaceElement; return type.asInstanceOf(converter); } bool isJsonAwareTypeConverter(DartType? type, LibraryElement context) { - final jsonMixin = - helperLibrary.exportNamespace.get('JsonTypeConverter') as ClassElement; + final jsonMixin = helperLibrary.exportNamespace.get('JsonTypeConverter') + as InterfaceElement; final jsonConverterType = jsonMixin.instantiate( typeArguments: [ context.typeProvider.dynamicType, diff --git a/drift_dev/lib/src/analyzer/options.dart b/drift_dev/lib/src/analyzer/options.dart index 13b6edf0..b2559ff9 100644 --- a/drift_dev/lib/src/analyzer/options.dart +++ b/drift_dev/lib/src/analyzer/options.dart @@ -275,4 +275,8 @@ enum SqlModule { /// /// [math funs]: https://www.sqlite.org/lang_mathfunc.html math, + + /// Enables support for the rtree module and its functions when parsing sql + /// queries. + rtree, } diff --git a/drift_dev/lib/src/analyzer/options.g.dart b/drift_dev/lib/src/analyzer/options.g.dart index 86843646..e0af0af8 100644 --- a/drift_dev/lib/src/analyzer/options.g.dart +++ b/drift_dev/lib/src/analyzer/options.g.dart @@ -140,6 +140,7 @@ const _$SqlModuleEnumMap = { SqlModule.fts5: 'fts5', SqlModule.moor_ffi: 'moor_ffi', SqlModule.math: 'math', + SqlModule.rtree: 'rtree', }; DialectOptions _$DialectOptionsFromJson(Map json) => $checkedCreate( diff --git a/drift_dev/lib/src/analyzer/runner/steps/parse_dart.dart b/drift_dev/lib/src/analyzer/runner/steps/parse_dart.dart index 1a9c3b8e..ef832f8e 100644 --- a/drift_dev/lib/src/analyzer/runner/steps/parse_dart.dart +++ b/drift_dev/lib/src/analyzer/runner/steps/parse_dart.dart @@ -122,15 +122,18 @@ class ParseDartStep extends Step { Future> parseTables( Iterable types, Element initializedBy) { return Future.wait(types.map((type) { - if (!_tableTypeChecker.isAssignableFromType(type)) { + final element = type is InterfaceType ? type.element2 : null; + + if (!_tableTypeChecker.isAssignableFromType(type) || + element is! ClassElement) { reportError(ErrorInDartCode( severity: Severity.criticalError, - message: 'The type $type is not a moor table', + message: 'The type $type is not a drift table class.', affectedElement: initializedBy, )); return Future.value(null); } else { - return _parseTable((type as InterfaceType).element2 as ClassElement); + return _parseTable(element); } })).then((list) { // only keep tables that were resolved successfully @@ -145,16 +148,18 @@ class ParseDartStep extends Step { Future> parseViews(Iterable types, Element initializedBy, List tables) { return Future.wait(types.map((type) { - if (!_viewTypeChecker.isAssignableFromType(type)) { + final element = type is InterfaceType ? type.element2 : null; + + if (!_viewTypeChecker.isAssignableFromType(type) || + element is! ClassElement) { reportError(ErrorInDartCode( severity: Severity.criticalError, - message: 'The type $type is not a drift view', + message: 'The type $type is not a drift view class.', affectedElement: initializedBy, )); return Future.value(null); } else { - return _parseView( - (type as InterfaceType).element2 as ClassElement, tables); + return _parseView(element, tables); } })).then((list) { // only keep tables that were resolved successfully diff --git a/drift_dev/lib/src/analyzer/session.dart b/drift_dev/lib/src/analyzer/session.dart index 010baa14..148973f9 100644 --- a/drift_dev/lib/src/analyzer/session.dart +++ b/drift_dev/lib/src/analyzer/session.dart @@ -45,6 +45,7 @@ class MoorSession { if (options.hasModule(SqlModule.json1)) const Json1Extension(), if (options.hasModule(SqlModule.moor_ffi)) const MoorFfiExtension(), if (options.hasModule(SqlModule.math)) const BuiltInMathExtension(), + if (options.hasModule(SqlModule.rtree)) const RTreeExtension(), ], version: options.sqliteVersion, ); diff --git a/drift_dev/lib/src/backends/plugin/services/errors.dart b/drift_dev/lib/src/backends/plugin/services/errors.dart index 8e833200..40d1a271 100644 --- a/drift_dev/lib/src/backends/plugin/services/errors.dart +++ b/drift_dev/lib/src/backends/plugin/services/errors.dart @@ -42,11 +42,13 @@ class ErrorService { Location _findLocationForError(DriftError error, String path) { if (error is ErrorInDriftFile) { final span = error.span; - final start = span.start; - final end = span.end; - return Location( - path, start.offset, span.length, start.line + 1, start.column + 1, - endLine: end.line + 1, endColumn: end.column + 1); + if (span != null) { + final start = span.start; + final end = span.end; + return Location( + path, start.offset, span.length, start.line + 1, start.column + 1, + endLine: end.line + 1, endColumn: end.column + 1); + } } return Location(path, 0, 0, 0, 0); diff --git a/drift_dev/lib/src/model/base_entity.dart b/drift_dev/lib/src/model/base_entity.dart index 5359261b..62c520a4 100644 --- a/drift_dev/lib/src/model/base_entity.dart +++ b/drift_dev/lib/src/model/base_entity.dart @@ -37,6 +37,9 @@ abstract class DriftEntityWithResultSet extends DriftSchemaEntity { @Deprecated('Use dartTypeCode instead') String get dartTypeName; + /// The (unescaped) name of this table when stored in the database + String get sqlName; + /// The type name of the Dart row class for this result set. /// /// This may contain generics. diff --git a/drift_dev/lib/src/model/sql_query.dart b/drift_dev/lib/src/model/sql_query.dart index d42e2db4..9fc946ef 100644 --- a/drift_dev/lib/src/model/sql_query.dart +++ b/drift_dev/lib/src/model/sql_query.dart @@ -827,6 +827,12 @@ class FoundDartPlaceholder extends FoundElement { type is ExpressionDartPlaceholderType && (type as ExpressionDartPlaceholderType).defaultValue != null; + bool get hasDefaultOrImplicitFallback => + hasDefault || + (type is SimpleDartPlaceholderType && + (type as SimpleDartPlaceholderType).kind == + SimpleDartPlaceholderKind.orderBy); + FoundDartPlaceholder(this.type, this.name, this.availableResultSets); @override diff --git a/drift_dev/lib/src/model/table.dart b/drift_dev/lib/src/model/table.dart index ce38d553..4db476f2 100644 --- a/drift_dev/lib/src/model/table.dart +++ b/drift_dev/lib/src/model/table.dart @@ -44,7 +44,7 @@ class DriftTable extends DriftEntityWithResultSet { @override final List columns; - /// The (unescaped) name of this table when stored in the database + @override final String sqlName; /// The name for the data class associated with this table @@ -120,7 +120,7 @@ class DriftTable extends DriftEntityWithResultSet { final List? overrideTableConstraints; @override - final Set references = {}; + final Set references = {}; /// Returns whether this table was created from a `CREATE VIRTUAL TABLE` /// statement in a moor file diff --git a/drift_dev/lib/src/model/view.dart b/drift_dev/lib/src/model/view.dart index 4beacc6f..e268aceb 100644 --- a/drift_dev/lib/src/model/view.dart +++ b/drift_dev/lib/src/model/view.dart @@ -21,6 +21,9 @@ class MoorView extends DriftEntityWithResultSet { final String name; + @override + String get sqlName => name; + @override List references = []; diff --git a/drift_dev/lib/src/writer/queries/query_writer.dart b/drift_dev/lib/src/writer/queries/query_writer.dart index 786f1a86..3cad971a 100644 --- a/drift_dev/lib/src/writer/queries/query_writer.dart +++ b/drift_dev/lib/src/writer/queries/query_writer.dart @@ -4,6 +4,7 @@ import 'package:drift_dev/src/analyzer/sql_queries/explicit_alias_transformer.da import 'package:drift_dev/src/analyzer/sql_queries/nested_queries.dart'; import 'package:drift_dev/src/utils/string_escaper.dart'; import 'package:drift_dev/writer.dart'; +import 'package:recase/recase.dart'; import 'package:sqlparser/sqlparser.dart' hide ResultColumn; import 'sql_writer.dart'; @@ -254,20 +255,25 @@ class QueryWriter { void _writeParameters(SqlQuery query) { final namedElements = []; + String scopedTypeName(FoundDartPlaceholder element) { + return '${ReCase(query.name).pascalCase}\$${ReCase(element.name).camelCase}'; + } + String typeFor(FoundElement element) { - var type = element.dartTypeCode(); + return element.dartTypeCode(); + } - if (element is FoundDartPlaceholder && - element.writeAsScopedFunction(options)) { - // Generate a function providing result sets that are in scope as args + String writeScopedTypeFor(FoundDartPlaceholder element) { + final root = scope.root; + final type = typeFor(element); + final scopedType = scopedTypeName(element); - final args = element.availableResultSets - .map((e) => '${e.argumentType} ${e.name}') - .join(', '); - type = '$type Function($args)'; - } + final args = element.availableResultSets + .map((e) => '${e.argumentType} ${e.name}') + .join(', '); + root.leaf().write('typedef $scopedType = $type Function($args);'); - return type; + return scopedType; } var needsComma = false; @@ -285,7 +291,11 @@ class QueryWriter { } else { if (needsComma) _buffer.write(', '); - final type = typeFor(element); + var type = typeFor(element); + if (element is FoundDartPlaceholder && + element.writeAsScopedFunction(options)) { + type = writeScopedTypeFor(element); + } _buffer.write('$type ${element.dartParameterName}'); needsComma = true; } @@ -302,62 +312,27 @@ class QueryWriter { needsComma = true; String? defaultCode; + var isNullable = false; + var type = typeFor(optional); if (optional is FoundDartPlaceholder) { - final kind = optional.type; - if (kind is ExpressionDartPlaceholderType && - kind.defaultValue != null) { - // Wrap the default expression in parentheses to avoid issues with - // the surrounding precedence in SQL. - final sql = SqlWriter(scope.options) - .writeNodeIntoStringLiteral(Parentheses(kind.defaultValue!)); - defaultCode = 'const CustomExpression($sql)'; - } else if (kind is SimpleDartPlaceholderType && - kind.kind == SimpleDartPlaceholderKind.orderBy) { - defaultCode = 'const OrderBy.nothing()'; - } + // If optional Dart placeholders are written as functions, they are + // generated as nullable parameters. The default is handled with a + // `??` in the method's body. + if (optional.writeAsScopedFunction(options)) { + isNullable = optional.hasDefaultOrImplicitFallback; + type = writeScopedTypeFor(optional); - // If the parameter is converted to a scoped function, we also need to - // generate a scoped function as a default value. Since defaults have - // to be constants, we generate a top-level function which is then - // used as a tear-off. - if (optional.writeAsScopedFunction(options) && defaultCode != null) { - final root = scope.root; - final counter = root.counter++; - // ignore: prefer_interpolation_to_compose_strings - final functionName = r'_$moor$default$' + counter.toString(); - - final buffer = root.leaf() - ..write(optional.dartTypeCode(scope.generationOptions)) - ..write(' ') - ..write(functionName) - ..write('('); - var i = 0; - for (final arg in optional.availableResultSets) { - if (i != 0) buffer.write(', '); - - buffer - ..write(arg.argumentType) - ..write(' ') - ..write('_' * (i + 1)); - i++; + if (isNullable) { + type += '?'; } - buffer - ..write(') => ') - ..write(defaultCode) - ..write(';'); - - // With the function being written, the default code is just a tear- - // off of that function - defaultCode = functionName; + } else { + defaultCode = _defaultForDartPlaceholder(optional, scope); } } - final type = typeFor(optional); - // No default value, this element is required if it's not nullable var isMarkedAsRequired = false; - var isNullable = false; if (optional is FoundVariable) { isMarkedAsRequired = optional.isRequired; isNullable = optional.nullableInDart; @@ -382,7 +357,7 @@ class QueryWriter { } void _writeExpandedDeclarations(SqlQuery query) { - _ExpandedDeclarationWriter(query, options, _buffer) + _ExpandedDeclarationWriter(query, options, scope, _buffer) .writeExpandedDeclarations(); } @@ -441,13 +416,15 @@ String _converter(UsedTypeConverter converter) { class _ExpandedDeclarationWriter { final SqlQuery query; final DriftOptions options; + final Scope _scope; final StringBuffer _buffer; bool indexCounterWasDeclared = false; bool needsIndexCounter = false; int highestIndexBeforeArray = 0; - _ExpandedDeclarationWriter(this.query, this.options, this._buffer); + _ExpandedDeclarationWriter( + this.query, this.options, this._scope, this._buffer); void writeExpandedDeclarations() { // When the SQL query is written to a Dart string, we give each variable an @@ -532,7 +509,17 @@ class _ExpandedDeclarationWriter { return table; } }).join(', '); - return '${element.dartParameterName}($args)'; + + final defaultValue = _defaultForDartPlaceholder(element, _scope); + + if (defaultValue != null) { + // Optional elements written as a function are generated as nullable + // parameters. We need to emit the default if the actual value is + // null at runtime. + return '${element.dartParameterName}?.call($args) ?? $defaultValue'; + } else { + return '${element.dartParameterName}($args)'; + } } else { // We can just use the parameter directly return element.dartParameterName; @@ -694,3 +681,21 @@ class _ExpandedVariableWriter { _buffer.write('...${placeholderContextName(element)}.introducedVariables'); } } + +String? _defaultForDartPlaceholder( + FoundDartPlaceholder placeholder, Scope scope) { + final kind = placeholder.type; + if (kind is ExpressionDartPlaceholderType && kind.defaultValue != null) { + // Wrap the default expression in parentheses to avoid issues with + // the surrounding precedence in SQL. + final sql = SqlWriter(scope.options) + .writeNodeIntoStringLiteral(Parentheses(kind.defaultValue!)); + return 'const CustomExpression($sql)'; + } else if (kind is SimpleDartPlaceholderType && + kind.kind == SimpleDartPlaceholderKind.orderBy) { + return 'const OrderBy.nothing()'; + } else { + assert(!placeholder.hasDefaultOrImplicitFallback); + return null; + } +} diff --git a/drift_dev/pubspec.yaml b/drift_dev/pubspec.yaml index 124710f5..a7c8c27d 100644 --- a/drift_dev/pubspec.yaml +++ b/drift_dev/pubspec.yaml @@ -1,6 +1,6 @@ name: drift_dev description: Dev-dependency for users of drift. Contains a the generator and development tools. -version: 2.0.1 +version: 2.1.0 repository: https://github.com/simolus3/drift homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/drift/issues @@ -25,9 +25,9 @@ dependencies: io: ^1.0.3 # Drift-specific analysis and apis - drift: '>=2.0.0 <2.1.0' + drift: '>=2.0.0 <2.2.0' sqlite3: '>=0.1.6 <2.0.0' - sqlparser: ^0.23.0 + sqlparser: ^0.23.2 # Dart analysis analyzer: "^4.5.0" diff --git a/drift_dev/test/analyzer/drift/create_table_reader_test.dart b/drift_dev/test/analyzer/drift/create_table_reader_test.dart index 719dde0a..f60abdf3 100644 --- a/drift_dev/test/analyzer/drift/create_table_reader_test.dart +++ b/drift_dev/test/analyzer/drift/create_table_reader_test.dart @@ -118,7 +118,7 @@ void main() { 'message', allOf(contains('NotAnEnum'), contains('Not an enum')), ) - .having((e) => e.span.text, 'span', 'ENUM(NotAnEnum)'), + .having((e) => e.span?.text, 'span', 'ENUM(NotAnEnum)'), ), ); }); diff --git a/drift_dev/test/analyzer/drift/errors_when_importing_part_files_test.dart b/drift_dev/test/analyzer/drift/errors_when_importing_part_files_test.dart index f5eaf9ba..871329e7 100644 --- a/drift_dev/test/analyzer/drift/errors_when_importing_part_files_test.dart +++ b/drift_dev/test/analyzer/drift/errors_when_importing_part_files_test.dart @@ -43,7 +43,7 @@ void main() { 'message', contains('Is it a part file?'), ) - .having((e) => e.span.text, 'span.text', "import 'tables.dart';"), + .having((e) => e.span?.text, 'span.text', "import 'tables.dart';"), ); }); } diff --git a/drift_dev/test/analyzer/drift/fts5_test.dart b/drift_dev/test/analyzer/drift/fts5_test.dart new file mode 100644 index 00000000..27ea4e8c --- /dev/null +++ b/drift_dev/test/analyzer/drift/fts5_test.dart @@ -0,0 +1,92 @@ +import 'package:drift_dev/src/analyzer/errors.dart'; +import 'package:drift_dev/src/analyzer/options.dart'; +import 'package:test/test.dart'; + +import '../utils.dart'; + +const _options = DriftOptions.defaults(modules: [SqlModule.fts5]); + +void main() { + group('reports error', () { + test('for missing content table', () async { + final state = TestState.withContent({ + 'a|lib/main.drift': ''' +CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl); +''', + }, options: _options); + addTearDown(state.close); + + final result = await state.analyze('package:a/main.drift'); + expect(result.errors.errors, [ + const TypeMatcher().having( + (e) => e.message, + 'message', + contains('Content table `tbl` could not be found'), + ), + ]); + }); + + test('for invalid rowid of content table', () async { + final state = TestState.withContent({ + 'a|lib/main.drift': ''' +CREATE TABLE tbl (a, b, c, my_pk INTEGER PRIMARY KEY); + +CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl, content_rowid=d); +''', + }, options: _options); + addTearDown(state.close); + + final result = await state.analyze('package:a/main.drift'); + expect(result.errors.errors, [ + const TypeMatcher().having( + (e) => e.message, + 'message', + contains( + 'no column `d`, but this fts5 table is declared to use it as a row ' + 'id', + ), + ), + ]); + }); + + test('when referencing an unknown column', () async { + final state = TestState.withContent({ + 'a|lib/main.drift': ''' +CREATE TABLE tbl (a, b, c, d INTEGER PRIMARY KEY); + +CREATE VIRTUAL TABLE fts USING fts5(e, c, content=tbl, content_rowid=d); +''', + }, options: _options); + addTearDown(state.close); + + final result = await state.analyze('package:a/main.drift'); + expect(result.errors.errors, [ + const TypeMatcher().having( + (e) => e.message, + 'message', + contains('no column `e`, but this fts5 table references it'), + ), + ]); + }); + }); + + test('finds referenced table', () async { + final state = TestState.withContent({ + 'a|lib/main.drift': ''' +CREATE TABLE tbl (a, b, c, d INTEGER PRIMARY KEY); + +CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl, content_rowid=d); +CREATE VIRTUAL TABLE fts2 USING fts5(a, c, content=tbl, content_rowid=rowid); +''', + }, options: _options); + addTearDown(state.close); + + final result = await state.analyze('package:a/main.drift'); + expect(result.errors.errors, isEmpty); + final tables = result.currentResult!.declaredTables.toList(); + + expect(tables, hasLength(3)); + expect(tables[1].references, contains(tables[0])); + expect(tables[2].references, contains(tables[0])); + }); +} diff --git a/drift_dev/test/analyzer/drift/moor_ffi_extension_test.dart b/drift_dev/test/analyzer/drift/moor_ffi_extension_test.dart index 15055e48..401f54dd 100644 --- a/drift_dev/test/analyzer/drift/moor_ffi_extension_test.dart +++ b/drift_dev/test/analyzer/drift/moor_ffi_extension_test.dart @@ -117,7 +117,7 @@ wrongArgs: SELECT sin(oid, foo) FROM numbers; final fileB = await state.analyze('package:foo/b.moor'); expect(fileB.errors.errors, [ const TypeMatcher() - .having((e) => e.span.text, 'span.text', 'sin(oid, foo)') + .having((e) => e.span?.text, 'span.text', 'sin(oid, foo)') ]); }); } diff --git a/examples/README.md b/examples/README.md index 21a6d2bc..7000ac2d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,6 +3,7 @@ This collection of examples demonstrates how to use some advanced drift features. - `app`: A cross-platform Flutter app built with recommended drift options. +- `encryption`: A very simple Flutter app running an encrypted drift database. - `flutter_web_worker_example`: Asynchronously run a drift database through a web worker with Fluter. - `migrations_example`: Example showing to how to generate test utilities to verify schema migration. - `web_worker_-_example`: Asynchronously run a drift database through a web worker, without Flutter. diff --git a/examples/encryption/.gitignore b/examples/encryption/.gitignore new file mode 100644 index 00000000..a8e938c0 --- /dev/null +++ b/examples/encryption/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/examples/encryption/.metadata b/examples/encryption/.metadata new file mode 100644 index 00000000..dc63d7b5 --- /dev/null +++ b/examples/encryption/.metadata @@ -0,0 +1,42 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: android + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: ios + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: linux + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: macos + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: windows + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/examples/encryption/README.md b/examples/encryption/README.md new file mode 100644 index 00000000..ccb85555 --- /dev/null +++ b/examples/encryption/README.md @@ -0,0 +1,8 @@ +# drift_encryption_sample + +This simple example demonstrates how to use drift with an encrypted database, +based on `sqlcipher_flutter_libs`. + +At the moment, this app supports Android, iOS and macOS. + +To run it, simply use `flutter run`. diff --git a/examples/encryption/analysis_options.yaml b/examples/encryption/analysis_options.yaml new file mode 100644 index 00000000..61b6c4de --- /dev/null +++ b/examples/encryption/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/examples/encryption/android/.gitignore b/examples/encryption/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/examples/encryption/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/examples/encryption/android/app/build.gradle b/examples/encryption/android/app/build.gradle new file mode 100644 index 00000000..fea7a8c7 --- /dev/null +++ b/examples/encryption/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.drift_encryption_sample" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/examples/encryption/android/app/src/debug/AndroidManifest.xml b/examples/encryption/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..a76c27a1 --- /dev/null +++ b/examples/encryption/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/examples/encryption/android/app/src/main/AndroidManifest.xml b/examples/encryption/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8635c6f8 --- /dev/null +++ b/examples/encryption/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/examples/encryption/android/app/src/main/kotlin/com/example/drift_encryption_sample/MainActivity.kt b/examples/encryption/android/app/src/main/kotlin/com/example/drift_encryption_sample/MainActivity.kt new file mode 100644 index 00000000..be3d6745 --- /dev/null +++ b/examples/encryption/android/app/src/main/kotlin/com/example/drift_encryption_sample/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.drift_encryption_sample + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/examples/encryption/android/app/src/main/res/drawable-v21/launch_background.xml b/examples/encryption/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/examples/encryption/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/encryption/android/app/src/main/res/drawable/launch_background.xml b/examples/encryption/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/examples/encryption/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/encryption/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/encryption/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/examples/encryption/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/examples/encryption/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/encryption/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/examples/encryption/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/examples/encryption/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/encryption/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/examples/encryption/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/examples/encryption/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/encryption/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/examples/encryption/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/examples/encryption/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/encryption/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/examples/encryption/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/examples/encryption/android/app/src/main/res/values-night/styles.xml b/examples/encryption/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/examples/encryption/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/encryption/android/app/src/main/res/values/styles.xml b/examples/encryption/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/examples/encryption/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/encryption/android/app/src/profile/AndroidManifest.xml b/examples/encryption/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..a76c27a1 --- /dev/null +++ b/examples/encryption/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/examples/encryption/android/build.gradle b/examples/encryption/android/build.gradle new file mode 100644 index 00000000..83ae2200 --- /dev/null +++ b/examples/encryption/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/examples/encryption/android/gradle.properties b/examples/encryption/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/examples/encryption/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/examples/encryption/android/gradle/wrapper/gradle-wrapper.properties b/examples/encryption/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..cc5527d7 --- /dev/null +++ b/examples/encryption/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/examples/encryption/android/settings.gradle b/examples/encryption/android/settings.gradle new file mode 100644 index 00000000..44e62bcf --- /dev/null +++ b/examples/encryption/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/examples/encryption/ios/.gitignore b/examples/encryption/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/examples/encryption/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/examples/encryption/ios/Flutter/AppFrameworkInfo.plist b/examples/encryption/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..8d4492f9 --- /dev/null +++ b/examples/encryption/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/examples/encryption/ios/Flutter/Debug.xcconfig b/examples/encryption/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/examples/encryption/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/examples/encryption/ios/Flutter/Release.xcconfig b/examples/encryption/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/examples/encryption/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/examples/encryption/ios/Runner.xcodeproj/project.pbxproj b/examples/encryption/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..97f5f6fa --- /dev/null +++ b/examples/encryption/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.driftEncryptionSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.driftEncryptionSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.driftEncryptionSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/examples/encryption/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/encryption/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/encryption/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..c87d15a3 --- /dev/null +++ b/examples/encryption/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/encryption/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/encryption/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/examples/encryption/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/encryption/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/encryption/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/examples/encryption/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/encryption/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/encryption/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/examples/encryption/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/encryption/ios/Runner/AppDelegate.swift b/examples/encryption/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/examples/encryption/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..28c6bf03 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..2ccbfd96 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..f091b6b0 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cde1211 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..d0ef06e7 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..dcdc2306 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..2ccbfd96 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..c8f9ed8f Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..a6d6b860 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..a6d6b860 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..75b2d164 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..c4df70d3 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..6a84f41e Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..d0e1f585 Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/examples/encryption/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/examples/encryption/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/encryption/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/examples/encryption/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/encryption/ios/Runner/Base.lproj/Main.storyboard b/examples/encryption/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/examples/encryption/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/encryption/ios/Runner/Info.plist b/examples/encryption/ios/Runner/Info.plist new file mode 100644 index 00000000..9e239bc3 --- /dev/null +++ b/examples/encryption/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Drift Encryption Sample + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + drift_encryption_sample + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/examples/encryption/ios/Runner/Runner-Bridging-Header.h b/examples/encryption/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/examples/encryption/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/examples/encryption/lib/database.dart b/examples/encryption/lib/database.dart new file mode 100644 index 00000000..c8ca5205 --- /dev/null +++ b/examples/encryption/lib/database.dart @@ -0,0 +1,64 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +part 'database.g.dart'; + +// This database is kep simple on purpose, this example only demonstrates how to +// use drift with an encrypted database. +// For advanced drift features, see the other examples in this project. + +const _encryptionPassword = 'drift.example.unsafe_password'; + +class Notes extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get content => text()(); +} + +@DriftDatabase(tables: [Notes]) +class MyEncryptedDatabase extends _$MyEncryptedDatabase { + MyEncryptedDatabase() : super(_openDatabase()); + + @override + int get schemaVersion => 1; + + @override + MigrationStrategy get migration { + return MigrationStrategy( + beforeOpen: (details) async {}, + ); + } +} + +QueryExecutor _openDatabase() { + return LazyDatabase(() async { + final path = await getApplicationDocumentsDirectory(); + + return NativeDatabase( + File(p.join(path.path, 'app.db.enc')), + setup: (db) { + // Check that we're actually running with SQLCipher by quering the + // cipher_version pragma. + final result = db.select('pragma cipher_version'); + if (result.isEmpty) { + throw UnsupportedError( + 'This database needs to run with SQLCipher, but that library is ' + 'not available!', + ); + } + + // Then, apply the key to encrypt the database. Unfortunately, this + // pragma doesn't seem to support prepared statements so we inline the + // key. + final escapedKey = _encryptionPassword.replaceAll("'", "''"); + db.execute("pragma key = '$escapedKey'"); + + // Test that the key is correct by selecting from a table + db.execute('select count(*) from sqlite_master'); + }, + ); + }); +} diff --git a/examples/encryption/lib/database.g.dart b/examples/encryption/lib/database.g.dart new file mode 100644 index 00000000..3c3ad0af --- /dev/null +++ b/examples/encryption/lib/database.g.dart @@ -0,0 +1,184 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ************************************************************************** +// DriftDatabaseGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +class Note extends DataClass implements Insertable { + final int id; + final String content; + const Note({required this.id, required this.content}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['content'] = Variable(content); + return map; + } + + NotesCompanion toCompanion(bool nullToAbsent) { + return NotesCompanion( + id: Value(id), + content: Value(content), + ); + } + + factory Note.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Note( + id: serializer.fromJson(json['id']), + content: serializer.fromJson(json['content']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'content': serializer.toJson(content), + }; + } + + Note copyWith({int? id, String? content}) => Note( + id: id ?? this.id, + content: content ?? this.content, + ); + @override + String toString() { + return (StringBuffer('Note(') + ..write('id: $id, ') + ..write('content: $content') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, content); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Note && other.id == this.id && other.content == this.content); +} + +class NotesCompanion extends UpdateCompanion { + final Value id; + final Value content; + const NotesCompanion({ + this.id = const Value.absent(), + this.content = const Value.absent(), + }); + NotesCompanion.insert({ + this.id = const Value.absent(), + required String content, + }) : content = Value(content); + static Insertable custom({ + Expression? id, + Expression? content, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (content != null) 'content': content, + }); + } + + NotesCompanion copyWith({Value? id, Value? content}) { + return NotesCompanion( + id: id ?? this.id, + content: content ?? this.content, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (content.present) { + map['content'] = Variable(content.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('NotesCompanion(') + ..write('id: $id, ') + ..write('content: $content') + ..write(')')) + .toString(); + } +} + +class $NotesTable extends Notes with TableInfo<$NotesTable, Note> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $NotesTable(this.attachedDatabase, [this._alias]); + final VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); + final VerificationMeta _contentMeta = const VerificationMeta('content'); + @override + late final GeneratedColumn content = GeneratedColumn( + 'content', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, content]; + @override + String get aliasedName => _alias ?? 'notes'; + @override + String get actualTableName => 'notes'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('content')) { + context.handle(_contentMeta, + content.isAcceptableOrUnknown(data['content']!, _contentMeta)); + } else if (isInserting) { + context.missing(_contentMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Note map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Note( + id: attachedDatabase.options.types + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + content: attachedDatabase.options.types + .read(DriftSqlType.string, data['${effectivePrefix}content'])!, + ); + } + + @override + $NotesTable createAlias(String alias) { + return $NotesTable(attachedDatabase, alias); + } +} + +abstract class _$MyEncryptedDatabase extends GeneratedDatabase { + _$MyEncryptedDatabase(QueryExecutor e) : super(e); + late final $NotesTable notes = $NotesTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [notes]; +} diff --git a/examples/encryption/lib/main.dart b/examples/encryption/lib/main.dart new file mode 100644 index 00000000..25c38d12 --- /dev/null +++ b/examples/encryption/lib/main.dart @@ -0,0 +1,130 @@ +import 'dart:ffi'; + +import 'package:drift/drift.dart'; +import 'package:flutter/material.dart'; +import 'package:sqlite3/open.dart'; +import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart'; + +import 'database.dart'; + +void main() { + open + ..overrideFor(OperatingSystem.android, openCipherOnAndroid) + ..overrideFor( + OperatingSystem.linux, () => DynamicLibrary.open('libsqlcipher.so')) + ..overrideFor( + OperatingSystem.windows, () => DynamicLibrary.open('sqlcipher.dll')); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Encrypted drift application', + theme: ThemeData( + primarySwatch: Colors.blue, + useMaterial3: true, + ), + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + final _database = MyEncryptedDatabase(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Encrypted drift example'), + ), + body: StreamBuilder>( + stream: _database.notes.select().watch(), + builder: (context, state) { + if (state.hasError) { + debugPrintStack( + label: state.error.toString(), + stackTrace: state.stackTrace, + ); + } + + if (!state.hasData) { + return const Align( + child: CircularProgressIndicator(), + ); + } + + return ListView( + children: [ + for (final entry in state.data!) + Text( + entry.content, + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ); + }, + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () { + showDialog( + context: context, + builder: (context) => _AddEntryDialog(database: _database), + ); + }, + ), + ); + } +} + +class _AddEntryDialog extends StatefulWidget { + // You should really use a proper package like riverpod or get_it to pass the + // database around, but tiny example only wants to show how to use encryption. + final MyEncryptedDatabase database; + + const _AddEntryDialog({Key? key, required this.database}) : super(key: key); + + @override + State<_AddEntryDialog> createState() => _AddEntryDialogState(); +} + +class _AddEntryDialogState extends State<_AddEntryDialog> { + final TextEditingController _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Add new entry'), + content: TextField(controller: _controller), + actions: [ + TextButton( + onPressed: () { + widget.database.notes + .insertOne(NotesCompanion.insert(content: _controller.text)); + Navigator.of(context).pop(); + }, + child: const Text('Add'), + ), + ], + ); + } +} diff --git a/examples/encryption/linux/.gitignore b/examples/encryption/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/examples/encryption/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/examples/encryption/linux/CMakeLists.txt b/examples/encryption/linux/CMakeLists.txt new file mode 100644 index 00000000..44e31a81 --- /dev/null +++ b/examples/encryption/linux/CMakeLists.txt @@ -0,0 +1,138 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "drift_encryption_sample") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.drift_encryption_sample") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/examples/encryption/linux/flutter/CMakeLists.txt b/examples/encryption/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/examples/encryption/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/examples/encryption/linux/flutter/generated_plugin_registrant.cc b/examples/encryption/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/examples/encryption/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/examples/encryption/linux/flutter/generated_plugin_registrant.h b/examples/encryption/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/examples/encryption/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/encryption/linux/flutter/generated_plugins.cmake b/examples/encryption/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..226f4270 --- /dev/null +++ b/examples/encryption/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + sqlcipher_flutter_libs +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/examples/encryption/linux/main.cc b/examples/encryption/linux/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/examples/encryption/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/examples/encryption/linux/my_application.cc b/examples/encryption/linux/my_application.cc new file mode 100644 index 00000000..a22dc229 --- /dev/null +++ b/examples/encryption/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "drift_encryption_sample"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "drift_encryption_sample"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/examples/encryption/linux/my_application.h b/examples/encryption/linux/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/examples/encryption/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/examples/encryption/macos/.gitignore b/examples/encryption/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/examples/encryption/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/examples/encryption/macos/Flutter/Flutter-Debug.xcconfig b/examples/encryption/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/examples/encryption/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/examples/encryption/macos/Flutter/Flutter-Release.xcconfig b/examples/encryption/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/examples/encryption/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/examples/encryption/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/encryption/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..c83a4a72 --- /dev/null +++ b/examples/encryption/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_macos +import sqlcipher_flutter_libs + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) +} diff --git a/examples/encryption/macos/Runner.xcodeproj/project.pbxproj b/examples/encryption/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..3ae84bfd --- /dev/null +++ b/examples/encryption/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,572 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* drift_encryption_sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "drift_encryption_sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* drift_encryption_sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* drift_encryption_sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/examples/encryption/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/encryption/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/examples/encryption/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/encryption/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/encryption/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..f25d6099 --- /dev/null +++ b/examples/encryption/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/encryption/macos/Runner.xcworkspace/contents.xcworkspacedata b/examples/encryption/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/examples/encryption/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/encryption/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/encryption/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/examples/encryption/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/encryption/macos/Runner/AppDelegate.swift b/examples/encryption/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..d53ef643 --- /dev/null +++ b/examples/encryption/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..3c4935a7 Binary files /dev/null and b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..ed4cc164 Binary files /dev/null and b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..483be613 Binary files /dev/null and b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bcbf36df Binary files /dev/null and b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..9c0a6528 Binary files /dev/null and b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..e71a7261 Binary files /dev/null and b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..8a31fe2d Binary files /dev/null and b/examples/encryption/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/examples/encryption/macos/Runner/Base.lproj/MainMenu.xib b/examples/encryption/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/examples/encryption/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/encryption/macos/Runner/Configs/AppInfo.xcconfig b/examples/encryption/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..7dff7c63 --- /dev/null +++ b/examples/encryption/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = drift_encryption_sample + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.driftEncryptionSample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. diff --git a/examples/encryption/macos/Runner/Configs/Debug.xcconfig b/examples/encryption/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/examples/encryption/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/examples/encryption/macos/Runner/Configs/Release.xcconfig b/examples/encryption/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/examples/encryption/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/examples/encryption/macos/Runner/Configs/Warnings.xcconfig b/examples/encryption/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/examples/encryption/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/examples/encryption/macos/Runner/DebugProfile.entitlements b/examples/encryption/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/examples/encryption/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/examples/encryption/macos/Runner/Info.plist b/examples/encryption/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/examples/encryption/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/examples/encryption/macos/Runner/MainFlutterWindow.swift b/examples/encryption/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..2722837e --- /dev/null +++ b/examples/encryption/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/examples/encryption/macos/Runner/Release.entitlements b/examples/encryption/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/examples/encryption/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/examples/encryption/pubspec.yaml b/examples/encryption/pubspec.yaml new file mode 100644 index 00000000..bd0c6ead --- /dev/null +++ b/examples/encryption/pubspec.yaml @@ -0,0 +1,26 @@ +name: drift_encryption_sample +description: A small Flutter app demonstrating how to use drift with encryption. + +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=2.17.6 <3.0.0" +dependencies: + drift: ^2.0.2+1 + sqlcipher_flutter_libs: ^0.5.1 + flutter: + sdk: flutter + path_provider: ^2.0.11 + path: ^1.8.1 + sqlite3: ^1.8.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + drift_dev: ^2.0.2 + build_runner: ^2.2.0 + +flutter: + uses-material-design: true diff --git a/examples/encryption/pubspec_overrides.yaml b/examples/encryption/pubspec_overrides.yaml new file mode 100644 index 00000000..5913138b --- /dev/null +++ b/examples/encryption/pubspec_overrides.yaml @@ -0,0 +1,10 @@ +# We override dependencies to ensure that we always use the packages from +# this repository. In your app, just depend on the latest drift version by +# removing the `pubspec_overrides.yaml` file. +dependency_overrides: + drift: + path: ../../drift + drift_dev: + path: ../../drift_dev + sqlparser: + path: ../../sqlparser diff --git a/examples/encryption/test/widget_test.dart b/examples/encryption/test/widget_test.dart new file mode 100644 index 00000000..8b08d182 --- /dev/null +++ b/examples/encryption/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:drift_encryption_sample/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/examples/encryption/windows/.gitignore b/examples/encryption/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/examples/encryption/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/examples/encryption/windows/CMakeLists.txt b/examples/encryption/windows/CMakeLists.txt new file mode 100644 index 00000000..3b3143f9 --- /dev/null +++ b/examples/encryption/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(drift_encryption_sample LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "drift_encryption_sample") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/examples/encryption/windows/flutter/CMakeLists.txt b/examples/encryption/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..930d2071 --- /dev/null +++ b/examples/encryption/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/examples/encryption/windows/flutter/generated_plugin_registrant.cc b/examples/encryption/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/examples/encryption/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/examples/encryption/windows/flutter/generated_plugin_registrant.h b/examples/encryption/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/examples/encryption/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/encryption/windows/flutter/generated_plugins.cmake b/examples/encryption/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..f210da65 --- /dev/null +++ b/examples/encryption/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + sqlcipher_flutter_libs +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/examples/encryption/windows/runner/CMakeLists.txt b/examples/encryption/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..b9e550fb --- /dev/null +++ b/examples/encryption/windows/runner/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/examples/encryption/windows/runner/Runner.rc b/examples/encryption/windows/runner/Runner.rc new file mode 100644 index 00000000..61a6417e --- /dev/null +++ b/examples/encryption/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "drift_encryption_sample" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "drift_encryption_sample" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "drift_encryption_sample.exe" "\0" + VALUE "ProductName", "drift_encryption_sample" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/examples/encryption/windows/runner/flutter_window.cpp b/examples/encryption/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..b43b9095 --- /dev/null +++ b/examples/encryption/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/examples/encryption/windows/runner/flutter_window.h b/examples/encryption/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/examples/encryption/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/examples/encryption/windows/runner/main.cpp b/examples/encryption/windows/runner/main.cpp new file mode 100644 index 00000000..68c3b38f --- /dev/null +++ b/examples/encryption/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"drift_encryption_sample", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/examples/encryption/windows/runner/resource.h b/examples/encryption/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/examples/encryption/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/examples/encryption/windows/runner/resources/app_icon.ico b/examples/encryption/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/examples/encryption/windows/runner/resources/app_icon.ico differ diff --git a/examples/encryption/windows/runner/runner.exe.manifest b/examples/encryption/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..c977c4a4 --- /dev/null +++ b/examples/encryption/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/examples/encryption/windows/runner/utils.cpp b/examples/encryption/windows/runner/utils.cpp new file mode 100644 index 00000000..f5bf9fa0 --- /dev/null +++ b/examples/encryption/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/examples/encryption/windows/runner/utils.h b/examples/encryption/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/examples/encryption/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/examples/encryption/windows/runner/win32_window.cpp b/examples/encryption/windows/runner/win32_window.cpp new file mode 100644 index 00000000..c10f08dc --- /dev/null +++ b/examples/encryption/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/examples/encryption/windows/runner/win32_window.h b/examples/encryption/windows/runner/win32_window.h new file mode 100644 index 00000000..17ba4311 --- /dev/null +++ b/examples/encryption/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/extras/encryption/analysis_options.yaml b/extras/encryption/analysis_options.yaml new file mode 100644 index 00000000..f9b30346 --- /dev/null +++ b/extras/encryption/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/extras/encryption/android/.gitignore b/extras/encryption/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/extras/encryption/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/extras/encryption/android/app/build.gradle b/extras/encryption/android/app/build.gradle new file mode 100644 index 00000000..75d120ed --- /dev/null +++ b/extras/encryption/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.drift_sqflite" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/extras/encryption/android/app/src/debug/AndroidManifest.xml b/extras/encryption/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..9617bf02 --- /dev/null +++ b/extras/encryption/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/extras/encryption/android/app/src/main/AndroidManifest.xml b/extras/encryption/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..71b37461 --- /dev/null +++ b/extras/encryption/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/extras/encryption/android/app/src/main/kotlin/com/example/drift_sqflite/MainActivity.kt b/extras/encryption/android/app/src/main/kotlin/com/example/drift_sqflite/MainActivity.kt new file mode 100644 index 00000000..a8983c9a --- /dev/null +++ b/extras/encryption/android/app/src/main/kotlin/com/example/drift_sqflite/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.drift_sqflite + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/extras/encryption/android/app/src/main/res/drawable-v21/launch_background.xml b/extras/encryption/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/extras/encryption/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/extras/encryption/android/app/src/main/res/drawable/launch_background.xml b/extras/encryption/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/extras/encryption/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/extras/encryption/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/extras/encryption/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/extras/encryption/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/extras/encryption/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/extras/encryption/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/extras/encryption/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/extras/encryption/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/extras/encryption/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/extras/encryption/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/extras/encryption/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/extras/encryption/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/extras/encryption/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/extras/encryption/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/extras/encryption/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/extras/encryption/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/extras/encryption/android/app/src/main/res/values-night/styles.xml b/extras/encryption/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/extras/encryption/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/extras/encryption/android/app/src/main/res/values/styles.xml b/extras/encryption/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/extras/encryption/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/extras/encryption/android/app/src/profile/AndroidManifest.xml b/extras/encryption/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..9617bf02 --- /dev/null +++ b/extras/encryption/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/extras/encryption/android/build.gradle b/extras/encryption/android/build.gradle new file mode 100644 index 00000000..83ae2200 --- /dev/null +++ b/extras/encryption/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/extras/encryption/android/gradle.properties b/extras/encryption/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/extras/encryption/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/extras/encryption/android/gradle/wrapper/gradle-wrapper.properties b/extras/encryption/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..cc5527d7 --- /dev/null +++ b/extras/encryption/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/extras/encryption/android/settings.gradle b/extras/encryption/android/settings.gradle new file mode 100644 index 00000000..44e62bcf --- /dev/null +++ b/extras/encryption/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/extras/encryption/integration_test/drift_encryption_test.dart b/extras/encryption/integration_test/drift_encryption_test.dart new file mode 100644 index 00000000..178b95b6 --- /dev/null +++ b/extras/encryption/integration_test/drift_encryption_test.dart @@ -0,0 +1,100 @@ +import 'dart:io'; + +import 'package:encrypted_drift/encrypted_drift.dart'; +import 'package:drift_testcases/tests.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:path/path.dart'; +import 'package:sqflite_sqlcipher/sqflite.dart' hide Database; + +class SqfliteExecutor extends TestExecutor { + // Nested transactions are not yet + @override + bool get supportsNestedTransactions => false; + + @override + DatabaseConnection createConnection() { + return DatabaseConnection( + EncryptedExecutor.inDatabaseFolder( + password: 'default_password', + path: 'app.db', + singleInstance: false, + ), + ); + } + + @override + Future deleteData() async { + final folder = await getDatabasesPath(); + final file = File(join(folder, 'app.db')); + + if (await file.exists()) { + await file.delete(); + } + } +} + +Future main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + runAllTests(SqfliteExecutor()); + + test('can rollback transactions', () async { + final executor = EncryptedExecutor(password: 'testpw', path: ':memory:'); + final database = EmptyDb(executor); + addTearDown(database.close); + + final expectedException = Exception('oops'); + + try { + await database + .customSelect('select 1') + .getSingle(); // ensure database is open/created + + await database.transaction(() async { + await database.customSelect('select 1').watchSingle().first; + throw expectedException; + }); + } catch (e) { + expect(e, expectedException); + } finally { + await database.customSelect('select 1').getSingle().timeout( + const Duration(milliseconds: 500), + onTimeout: () => fail('deadlock?'), + ); + } + }); + + test('handles failing commits', () async { + final executor = EncryptedExecutor(password: 'testpw', path: ':memory:'); + final database = EmptyDb(executor); + addTearDown(database.close); + + await database.customStatement('PRAGMA foreign_keys = ON;'); + await database.customStatement('CREATE TABLE x (foo INTEGER PRIMARY KEY);'); + await database.customStatement('CREATE TABLE y (foo INTEGER PRIMARY KEY ' + 'REFERENCES x (foo) DEFERRABLE INITIALLY DEFERRED);'); + + await expectLater( + database.transaction(() async { + await database.customStatement('INSERT INTO y VALUES (2);'); + }), + throwsA(isA()), + ); + }); + + test('encrypts database', () async { + final executor1 = EncryptedExecutor.inDatabaseFolder( + password: 'testpw', path: 'encryption.db', logStatements: true); + await executor1.ensureOpen(EmptyDb(executor1)); + await executor1.close(); + }); +} + +class EmptyDb extends GeneratedDatabase { + EmptyDb(QueryExecutor q) : super(q); + @override + final List allTables = const []; + @override + final schemaVersion = 1; +} diff --git a/extras/encryption/lib/encrypted_moor.dart b/extras/encryption/lib/encrypted_drift.dart similarity index 95% rename from extras/encryption/lib/encrypted_moor.dart rename to extras/encryption/lib/encrypted_drift.dart index c5820f74..18371dcc 100644 --- a/extras/encryption/lib/encrypted_moor.dart +++ b/extras/encryption/lib/encrypted_drift.dart @@ -73,17 +73,6 @@ class _SqfliteDelegate extends DatabaseDelegate { return db.close(); } - @override - Future runBatched(BatchedStatements statements) async { - final batch = db.batch(); - - for (final arg in statements.arguments) { - batch.execute(statements.statements[arg.statementIndex], arg.arguments); - } - - await batch.commit(noResult: true); - } - @override Future runCustom(String statement, List args) { return db.execute(statement, args); diff --git a/extras/encryption/pubspec.yaml b/extras/encryption/pubspec.yaml index f0dc1907..39ef8a79 100644 --- a/extras/encryption/pubspec.yaml +++ b/extras/encryption/pubspec.yaml @@ -1,4 +1,4 @@ -name: encrypted_moor +name: encrypted_drift description: Encryption support for drift, built with sqflite_sqlcipher version: 1.0.0 @@ -7,8 +7,20 @@ environment: dependencies: drift: ^2.0.0 + path: ^1.8.1 sqflite_sqlcipher: ^2.0.0 +dev_dependencies: + drift_testcases: + path: ../integration_tests/drift_testcases + flutter: + sdk: flutter + flutter_lints: ^2.0.1 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + dependency_overrides: drift: path: ../../drift diff --git a/extras/integration_tests/drift_testcases/lib/database/database.g.dart b/extras/integration_tests/drift_testcases/lib/database/database.g.dart index 62d1477b..55e4b800 100644 --- a/extras/integration_tests/drift_testcases/lib/database/database.g.dart +++ b/extras/integration_tests/drift_testcases/lib/database/database.g.dart @@ -2,19 +2,6 @@ part of 'database.dart'; -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Preferences _$PreferencesFromJson(Map json) => Preferences( - json['receiveEmails'] as bool, - ); - -Map _$PreferencesToJson(Preferences instance) => - { - 'receiveEmails': instance.receiveEmails, - }; - // ************************************************************************** // DriftDatabaseGenerator // ************************************************************************** @@ -665,3 +652,16 @@ class FriendshipsOfResult { .toString(); } } + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Preferences _$PreferencesFromJson(Map json) => Preferences( + json['receiveEmails'] as bool, + ); + +Map _$PreferencesToJson(Preferences instance) => + { + 'receiveEmails': instance.receiveEmails, + }; diff --git a/sqlparser/CHANGELOG.md b/sqlparser/CHANGELOG.md index 25ac31ea..1e7b2c21 100644 --- a/sqlparser/CHANGELOG.md +++ b/sqlparser/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.23.2 + +- Support resolving the `fts5vocab` module when `fts5` is enabled - thanks to + [@FaFre](https://github.com/FaFre). +- Support the `rtree` extension - also thanks to [@FaFre](https://github.com/FaFre)! +- Improve references resolving across subqueries. + +## 0.23.1 + +- Gracefully handle tokenizer errors related to `@` or `$` variables. + ## 0.23.0 - Apply type hints for date times on textual datetime functions as well. diff --git a/sqlparser/lib/sqlparser.dart b/sqlparser/lib/sqlparser.dart index 7324143e..1421bc50 100644 --- a/sqlparser/lib/sqlparser.dart +++ b/sqlparser/lib/sqlparser.dart @@ -4,9 +4,10 @@ library sqlparser; export 'src/analysis/analysis.dart'; export 'src/analysis/types/join_analysis.dart'; export 'src/ast/ast.dart'; -export 'src/engine/module/fts5.dart' show Fts5Extension; +export 'src/engine/module/fts5.dart' show Fts5Extension, Fts5Table; export 'src/engine/module/json1.dart' show Json1Extension; export 'src/engine/module/math.dart' show BuiltInMathExtension; +export 'src/engine/module/rtree.dart' show RTreeExtension; export 'src/engine/module/module.dart'; export 'src/engine/options.dart'; export 'src/engine/sql_engine.dart'; diff --git a/sqlparser/lib/src/analysis/schema/references.dart b/sqlparser/lib/src/analysis/schema/references.dart index 2b626b3d..c0f8ca4f 100644 --- a/sqlparser/lib/src/analysis/schema/references.dart +++ b/sqlparser/lib/src/analysis/schema/references.dart @@ -24,6 +24,13 @@ mixin Referencable { abstract class ReferenceScope { RootScope get rootScope; + /// All available result sets that can also be seen in child scopes. + /// + /// Usually, this is the same list as the result sets being declared in this + /// scope. However, some exceptions apply (see e.g. [SubqueryInFromScope]). + Iterable get resultSetAvailableToChildScopes => + const Iterable.empty(); + /// The list of column to which a `*` would expand to. /// /// This is not necessary the same list of columns that could be resolved @@ -101,6 +108,30 @@ class RootScope extends ReferenceScope { final Map knownModules = CaseInsensitiveMap(); } +mixin _HasParentScope on ReferenceScope { + ReferenceScope get _parentScopeForLookups; + + @override + RootScope get rootScope => _parentScopeForLookups.rootScope; + + @override + Iterable get resultSetAvailableToChildScopes => + _parentScopeForLookups.resultSetAvailableToChildScopes; + + @override + ResultSetAvailableInStatement? resolveResultSet(String name) => + _parentScopeForLookups.resolveResultSet(name); + + @override + ResultSet? resolveResultSetToAdd(String name) => + _parentScopeForLookups.resolveResultSetToAdd(name); + + @override + List resolveUnqualifiedReference(String columnName, + {bool allowReferenceToResultColumn = false}) => + _parentScopeForLookups.resolveUnqualifiedReference(columnName); +} + /// A scope used by statements. /// /// Tables added from `FROM` clauses are added to [resultSets], CTEs are added @@ -118,9 +149,12 @@ class RootScope extends ReferenceScope { /// [SubqueryInFromScope] is insertted as an intermediatet scope to prevent /// the inner scope from seeing the outer columns. -class StatementScope extends ReferenceScope { +class StatementScope extends ReferenceScope with _HasParentScope { final ReferenceScope parent; + @override + get _parentScopeForLookups => parent; + /// Additional tables (that haven't necessarily been added in a `FROM` clause /// that are only visible in this scope). /// @@ -153,29 +187,17 @@ class StatementScope extends ReferenceScope { StatementScope(this.parent); - StatementScope? get parentStatementScope { - final parent = this.parent; - if (parent is StatementScope) { - return parent; - } else if (parent is MiscStatementSubScope) { - return parent.parent; - } else { - return null; - } + @override + Iterable get resultSetAvailableToChildScopes { + return allAvailableResultSets; } /// All result sets available in this and parent scopes. Iterable get allAvailableResultSets { final here = resultSets.values; - final parent = parentStatementScope; - return parent != null - ? here.followedBy(parent.allAvailableResultSets) - : here; + return parent.resultSetAvailableToChildScopes.followedBy(here); } - @override - RootScope get rootScope => parent.rootScope; - @override void addAlias(AstNode origin, ResultSet resultSet, String alias) { final createdAlias = TableAlias(resultSet, alias); @@ -183,22 +205,20 @@ class StatementScope extends ReferenceScope { resultSets[alias] = ResultSetAvailableInStatement(origin, createdAlias); } - @override - ResultSetAvailableInStatement? resolveResultSet(String name) { - return resultSets[name] ?? parentStatementScope?.resolveResultSet(name); - } - @override void addResolvedResultSet( String? name, ResultSetAvailableInStatement resultSet) { resultSets[name] = resultSet; } + @override + ResultSetAvailableInStatement? resolveResultSet(String name) { + return resultSets[name] ?? parent.resolveResultSet(name); + } + @override ResultSet? resolveResultSetToAdd(String name) { - return additionalKnownTables[name] ?? - parentStatementScope?.resolveResultSetToAdd(name) ?? - rootScope.knownTables[name]; + return additionalKnownTables[name] ?? parent.resolveResultSetToAdd(name); } @override @@ -212,43 +232,28 @@ class StatementScope extends ReferenceScope { } } - StatementScope? currentScope = this; + final available = resultSets.values; + final sourceColumns = {}; + final availableColumns = []; - // Search scopes for a matching column in an added result set. If a column - // reference is found in a closer scope, it takes precedence over outer - // scopes. However, it's an error if two columns with the same name are - // found in the same scope. - while (currentScope != null) { - final available = currentScope.resultSets.values; - final sourceColumns = {}; - final availableColumns = []; + for (final availableSource in available) { + final resolvedColumns = + availableSource.resultSet.resultSet?.resolvedColumns; + if (resolvedColumns == null) continue; - for (final availableSource in available) { - final resolvedColumns = - availableSource.resultSet.resultSet?.resolvedColumns; - if (resolvedColumns == null) continue; - - for (final column in resolvedColumns) { - if (column.name.toLowerCase() == columnName.toLowerCase() && - sourceColumns.add(column)) { - availableColumns.add(AvailableColumn(column, availableSource)); - } + for (final column in resolvedColumns) { + if (column.name.toLowerCase() == columnName.toLowerCase() && + sourceColumns.add(column)) { + availableColumns.add(AvailableColumn(column, availableSource)); } } - - if (availableColumns.isEmpty) { - currentScope = currentScope.parentStatementScope; - if (currentScope == null) { - // Reached the outermost scope without finding a reference target. - return const []; - } - continue; - } else { - return availableColumns; - } } - return const []; + if (availableColumns.isEmpty) { + return parent.resolveUnqualifiedReference(columnName); + } else { + return availableColumns; + } } factory StatementScope.forStatement(RootScope root, Statement statement) { @@ -269,13 +274,24 @@ class StatementScope extends ReferenceScope { /// A special intermediate scope used for subqueries appearing in a `FROM` /// clause so that the subquery can't see outer columns and tables being added. -class SubqueryInFromScope extends ReferenceScope { +class SubqueryInFromScope extends ReferenceScope with _HasParentScope { final StatementScope enclosingStatement; SubqueryInFromScope(this.enclosingStatement); @override RootScope get rootScope => enclosingStatement.rootScope; + + // This scope can't see elements from the enclosing statement, but it can see + // elements from grandparents. + @override + ReferenceScope get _parentScopeForLookups => enclosingStatement.parent; + + @override + ResultSet? resolveResultSetToAdd(String name) { + // CTEs from the enclosing statement are also available here + return enclosingStatement.resolveResultSetToAdd(name); + } } /// A rarely used sub-scope for AST nodes that belong to a statement, but may @@ -283,9 +299,12 @@ class SubqueryInFromScope extends ReferenceScope { /// /// For instance, the body of an `ON CONFLICT DO UPDATE`-clause may refer to a /// table alias `excluded` to get access to a conflicting table. -class MiscStatementSubScope extends ReferenceScope { +class MiscStatementSubScope extends ReferenceScope with _HasParentScope { final StatementScope parent; + @override + get _parentScopeForLookups => parent; + final Map additionalResultSets = CaseInsensitiveMap(); @@ -304,12 +323,6 @@ class MiscStatementSubScope extends ReferenceScope { String? name, ResultSetAvailableInStatement resultSet) { additionalResultSets[name] = resultSet; } - - @override - List resolveUnqualifiedReference(String columnName, - {bool allowReferenceToResultColumn = false}) { - return parent.resolveUnqualifiedReference(columnName); - } } /// A reference scope that only allows a single added result set. diff --git a/sqlparser/lib/src/analysis/schema/table.dart b/sqlparser/lib/src/analysis/schema/table.dart index 9b1fe0d2..3e1a5528 100644 --- a/sqlparser/lib/src/analysis/schema/table.dart +++ b/sqlparser/lib/src/analysis/schema/table.dart @@ -54,11 +54,16 @@ class Table extends NamedResultSet with HasMetaMixin implements HumanReadable { for (final column in resolvedColumns) { column.table = this; - if (_rowIdColumn == null && column.isAliasForRowId()) { - _rowIdColumn = column; - // By design, the rowid is non-nullable, even if there isn't a NOT NULL - // constraint set on the column definition. - column._type = const ResolvedType(type: BasicType.int, nullable: false); + if (_rowIdColumn == null) { + if (column is RowId) { + _rowIdColumn = column; + } else if (column.isAliasForRowId()) { + _rowIdColumn = column; + // By design, the rowid is non-nullable, even if there isn't a NOT NULL + // constraint set on the column definition. + column._type = + const ResolvedType(type: BasicType.int, nullable: false); + } } } } diff --git a/sqlparser/lib/src/engine/module/fts5.dart b/sqlparser/lib/src/engine/module/fts5.dart index d3a2629f..965e882d 100644 --- a/sqlparser/lib/src/engine/module/fts5.dart +++ b/sqlparser/lib/src/engine/module/fts5.dart @@ -6,26 +6,47 @@ class Fts5Extension implements Extension { @override void register(SqlEngine engine) { engine.registerModule(_Fts5Module()); + engine.registerModule(_Fts5VocabModule()); engine.registerFunctionHandler(const _Fts5Functions()); } } /// FTS5 module for `CREATE VIRTUAL TABLE USING fts5` support class _Fts5Module extends Module { + static final RegExp _option = RegExp(r'^(.+)\s*=\s*(.+)$'); + static final RegExp _cleanTicks = RegExp('[\'"]'); + _Fts5Module() : super('fts5'); @override Table parseTable(CreateVirtualTableStatement stmt) { - // arguments with an equals sign are parameters passed to the fts5 module. - // they're not part of the schema. - final columnNames = - stmt.argumentContent.where((arg) => !arg.contains('=')).map((c) { - // actual syntax is - return c.trim().split(' ').first; - }); + final columnNames = []; + final options = {}; - return _Fts5Table( + for (final argument in stmt.argumentContent) { + // arguments with an equals sign are parameters passed to the fts5 module. + // they're not part of the schema. + final match = _option.firstMatch(argument); + + if (match == null) { + // actual syntax is + columnNames.add(argument.trim().split(' ').first); + } else { + options[match.group(1)!.replaceAll(_cleanTicks, '')] = + match.group(2)!.replaceAll(_cleanTicks, ''); + } + } + + //No external content when null or empty String + final contentTable = + (options['content']?.isNotEmpty ?? false) ? options['content'] : null; + //Fallback to "rowid" when option is not set + final contentRowId = options['content_rowid'] ?? 'rowid'; + + return Fts5Table._( name: stmt.tableName, + contentTable: contentTable, + contentRowId: (contentTable != null) ? contentRowId : null, columns: [ for (var arg in columnNames) TableColumn(arg, const ResolvedType(type: BasicType.text)), @@ -35,14 +56,73 @@ class _Fts5Module extends Module { } } -class _Fts5Table extends Table { - _Fts5Table( - {required String name, - required List columns, - CreateVirtualTableStatement? definition}) - : super( +class _Fts5VocabModule extends Module { + _Fts5VocabModule() : super('fts5vocab'); + + @override + Table parseTable(CreateVirtualTableStatement stmt) { + if (stmt.argumentContent.length < 2 || stmt.argumentContent.length > 3) { + throw ArgumentError( + 'fts5vocab table requires at least two arguments ' + '(, ) and maximum three arguments ' + 'when using an attached database', + ); + } + + final type = stmt.argumentContent.last.replaceAll(RegExp(r'''["' ]'''), ''); + switch (type) { + case 'row': + return Table( + name: stmt.tableName, + resolvedColumns: [ + TableColumn('term', const ResolvedType(type: BasicType.text)), + TableColumn('doc', const ResolvedType(type: BasicType.int)), + TableColumn('cnt', const ResolvedType(type: BasicType.int)), + ], + definition: stmt, + isVirtual: true); + case 'col': + return Table( + name: stmt.tableName, + resolvedColumns: [ + TableColumn('term', const ResolvedType(type: BasicType.text)), + TableColumn('col', const ResolvedType(type: BasicType.text)), + TableColumn('doc', const ResolvedType(type: BasicType.int)), + TableColumn('cnt', const ResolvedType(type: BasicType.int)), + ], + definition: stmt, + isVirtual: true); + case 'instance': + return Table( + name: stmt.tableName, + resolvedColumns: [ + TableColumn('term', const ResolvedType(type: BasicType.text)), + TableColumn('doc', const ResolvedType(type: BasicType.int)), + TableColumn('col', const ResolvedType(type: BasicType.text)), + TableColumn('offset', const ResolvedType(type: BasicType.int)), + ], + definition: stmt, + isVirtual: true); + default: + throw ArgumentError('Unknown fts5vocab table type'); + } + } +} + +class Fts5Table extends Table { + final String? contentTable; + final String? contentRowId; + + Fts5Table._({ + required String name, + required List columns, + this.contentTable, + this.contentRowId, + CreateVirtualTableStatement? definition, + }) : super( name: name, resolvedColumns: [ + if (contentTable != null && contentRowId != null) RowId(), ...columns, _Fts5RankColumn(), _Fts5TableColumn(name), diff --git a/sqlparser/lib/src/engine/module/rtree.dart b/sqlparser/lib/src/engine/module/rtree.dart new file mode 100644 index 00000000..984de77a --- /dev/null +++ b/sqlparser/lib/src/engine/module/rtree.dart @@ -0,0 +1,39 @@ +import 'package:sqlparser/sqlparser.dart'; + +class RTreeExtension implements Extension { + const RTreeExtension(); + + @override + void register(SqlEngine engine) { + engine.registerModule(_RTreeModule()); + } +} + +class _RTreeModule extends Module { + _RTreeModule() : super('rtree'); + + @override + Table parseTable(CreateVirtualTableStatement stmt) { + final columnNames = stmt.argumentContent; + + if (columnNames.length < 3 || columnNames.length > 11) { + throw ArgumentError( + 'An rtree virtual table is supposed to have between 3 and 11 columns'); + } + + if (columnNames.length.isEven) { + throw ArgumentError( + 'The rtree has not been initialized with a proper dimension. ' + 'Required is an index, followed by a even number of min/max pairs'); + } + + return Table(name: stmt.tableName, resolvedColumns: [ + for (final columnName in columnNames) + //First column is always an integer primary key + //followed by n floating point values + (columnName == columnNames.first) + ? TableColumn(columnName, const ResolvedType(type: BasicType.int)) + : TableColumn(columnName, const ResolvedType(type: BasicType.real)) + ]); + } +} diff --git a/sqlparser/lib/src/reader/tokenizer/scanner.dart b/sqlparser/lib/src/reader/tokenizer/scanner.dart index c7ba8ad0..dc27decb 100644 --- a/sqlparser/lib/src/reader/tokenizer/scanner.dart +++ b/sqlparser/lib/src/reader/tokenizer/scanner.dart @@ -165,11 +165,22 @@ class Scanner { } break; case $dollar: - final name = _matchColumnName()!; + final name = _matchColumnName(); + if (name == null) { + errors.add(TokenizerError( + r'Expected identifier after `$`', _currentLocation)); + break; + } + tokens.add(DollarSignVariableToken(_currentSpan, name)); break; case $at: - final name = _matchColumnName()!; + final name = _matchColumnName(); + if (name == null) { + errors.add(TokenizerError( + r'Expected identifier after `@`', _currentLocation)); + break; + } tokens.add(AtSignVariableToken(_currentSpan, name)); break; case $semicolon: diff --git a/sqlparser/pubspec.yaml b/sqlparser/pubspec.yaml index f4df590f..c59bf1b7 100644 --- a/sqlparser/pubspec.yaml +++ b/sqlparser/pubspec.yaml @@ -1,6 +1,6 @@ name: sqlparser description: Parses sqlite statements and performs static analysis on them -version: 0.23.0 +version: 0.23.2 homepage: https://github.com/simolus3/drift/tree/develop/sqlparser #homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/drift/issues diff --git a/sqlparser/test/analysis/reference_resolver_test.dart b/sqlparser/test/analysis/reference_resolver_test.dart index db2bee62..e36518d8 100644 --- a/sqlparser/test/analysis/reference_resolver_test.dart +++ b/sqlparser/test/analysis/reference_resolver_test.dart @@ -163,13 +163,78 @@ void main() { }); }); - test('resolves sub-queries', () { - final engine = SqlEngine()..registerTable(demoTable); + group('sub-queries', () { + test('are resolved', () { + final engine = SqlEngine()..registerTable(demoTable); - final context = engine.analyze( - 'SELECT d.*, (SELECT id FROM demo WHERE id = d.id) FROM demo d;'); + final context = engine.analyze( + 'SELECT d.*, (SELECT id FROM demo WHERE id = d.id) FROM demo d;'); - expect(context.errors, isEmpty); + expect(context.errors, isEmpty); + }); + + test('cannot refer to outer tables if used in FROM', () { + final engine = SqlEngine()..registerTable(demoTable); + + final context = engine.analyze( + 'SELECT d.* FROM demo d, (SELECT * FROM demo WHERE id = d.id);'); + + context.expectError('d.id', + type: AnalysisErrorType.referencedUnknownTable); + }); + + test('can refer to CTEs if used in FROM', () { + final engine = SqlEngine()..registerTable(demoTable); + + final context = engine.analyze('WITH cte AS (SELECT * FROM demo) ' + 'SELECT d.* FROM demo d, (SELECT * FROM cte);'); + + expect(context.errors, isEmpty); + }); + + test('can nest and see outer tables if that is a subquery', () { + final engine = SqlEngine()..registerTable(demoTable); + + final context = engine.analyze(''' +SELECT + (SELECT * + FROM + demo "inner", + (SELECT * FROM demo WHERE "inner".id = "outer".id) + ) + FROM demo "outer"; +'''); + + // note that "outer".id is visible and should not report an error + context.expectError('"inner".id', + type: AnalysisErrorType.referencedUnknownTable); + }); + + test('nested via FROM cannot see outer result sets', () { + final engine = SqlEngine()..registerTable(demoTable); + + final context = engine.analyze(''' +SELECT * + FROM + demo "outer", + (SELECT * FROM demo "inner", + (SELECT * FROM demo WHERE "inner".id = "outer".id)) +'''); + + expect( + context.errors, + [ + analysisErrorWith( + lexeme: '"inner".id', + type: AnalysisErrorType.referencedUnknownTable, + ), + analysisErrorWith( + lexeme: '"outer".id', + type: AnalysisErrorType.referencedUnknownTable, + ), + ], + ); + }); }); test('resolves sub-queries as data sources', () { diff --git a/sqlparser/test/analysis/regression_test.dart b/sqlparser/test/analysis/regression_test.dart index e0ac01ae..11fb278e 100644 --- a/sqlparser/test/analysis/regression_test.dart +++ b/sqlparser/test/analysis/regression_test.dart @@ -188,4 +188,32 @@ WHERE EXISTS(SELECT * expect(result.errors, isEmpty); }); + + test('regression test for #2010', () { + // https://github.com/simolus3/drift/issues/2010 + final engine = SqlEngine() + ..registerTableFromSql('CREATE TABLE place_hierarchy (feature_id INT);') + ..registerTableFromSql('CREATE TABLE place_name (feature_id INT);') + ..registerTableFromSql('CREATE TABLE place (feature_id INT);'); + + final result = engine.analyze(''' + SELECT + ( + SELECT + true + FROM ( + SELECT + 'test' AS name + FROM + place_hierarchy ph + where + ph.feature_id = p.feature_id + ) second_select + ) first_select + FROM place_name matches + INNER JOIN place p ON p.feature_id = matches.feature_id; +'''); + + expect(result.errors, isEmpty); + }); } diff --git a/sqlparser/test/engine/module/fts5_test.dart b/sqlparser/test/engine/module/fts5_test.dart index dda01e45..11876a0c 100644 --- a/sqlparser/test/engine/module/fts5_test.dart +++ b/sqlparser/test/engine/module/fts5_test.dart @@ -18,6 +18,119 @@ void main() { final columns = table.resultColumns; expect(columns, hasLength(1)); expect(columns.single.name, 'bar'); + expect( + table, + isA() + .having((e) => e.contentTable, 'contentTable', null) + .having((e) => e.contentRowId, 'contentRowId', null), + ); + }); + + test('can create fts5 tables with content table', () { + final result = engine.analyze('CREATE VIRTUAL TABLE foo USING ' + "fts5(bar , tokenize = 'porter ascii',content=tbl, content_rowid=d)"); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect(table.name, 'foo'); + expect(table.resultColumns, + [isA().having((c) => c.name, 'name', 'bar')]); + expect(table.findColumn('oid'), isA()); + + expect( + table, + isA() + .having((e) => e.contentTable, 'contentTable', 'tbl') + .having((e) => e.contentRowId, 'contentRowId', 'd'), + ); + }); + + test('does clean option values', () { + final result = engine.analyze('CREATE VIRTUAL TABLE foo USING ' + "fts5(bar , tokenize = 'porter ascii', content='tbl', content_rowid='d')"); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect( + table, + isA() + .having((e) => e.contentTable, 'contentTable', 'tbl') + .having((e) => e.contentRowId, 'contentRowId', 'd'), + ); + }); + + test('detects empty content table', () { + final result = engine.analyze('CREATE VIRTUAL TABLE foo USING ' + "fts5(bar , tokenize = 'porter ascii',content='', content_rowid='d')"); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect( + table, + isA() + .having((e) => e.contentTable, 'contentTable', null) + .having((e) => e.contentRowId, 'contentRowId', null), + ); + }); + + test('content table undefined rowid falls back', () { + final result = engine.analyze('CREATE VIRTUAL TABLE foo USING ' + "fts5(bar , tokenize = 'porter ascii',content='tbl')"); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect( + table, + isA() + .having((e) => e.contentTable, 'contentTable', 'tbl') + .having((e) => e.contentRowId, 'contentRowId', 'rowid'), + ); + }); + + group('creating fts5vocab tables', () { + final engine = SqlEngine(_fts5Options); + + test('can create fts5vocab instance table', () { + final result = engine.analyze('CREATE VIRTUAL TABLE foo USING ' + 'fts5vocab(bar, instance)'); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect(table.name, 'foo'); + final columns = table.resultColumns; + expect(columns, hasLength(4)); + expect(columns.map((e) => e.name), contains('offset')); + }); + + test('can create fts5vocab row table', () { + final result = engine.analyze('CREATE VIRTUAL TABLE foo USING ' + 'fts5vocab(bar, row)'); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect(table.name, 'foo'); + final columns = table.resultColumns; + expect(columns, hasLength(3)); + }); + + test('can create fts5vocab col table', () { + final result = engine.analyze('CREATE VIRTUAL TABLE foo USING ' + 'fts5vocab(bar, col)'); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect(table.name, 'foo'); + final columns = table.resultColumns; + expect(columns, hasLength(4)); + expect(columns.map((e) => e.name), contains('col')); + }); }); test('handles the UNINDEXED column option', () { diff --git a/sqlparser/test/engine/module/rtree_test.dart b/sqlparser/test/engine/module/rtree_test.dart new file mode 100644 index 00000000..1d2ef88c --- /dev/null +++ b/sqlparser/test/engine/module/rtree_test.dart @@ -0,0 +1,111 @@ +import 'package:sqlparser/sqlparser.dart'; +import 'package:test/test.dart'; + +final _rtreeOptions = + EngineOptions(enabledExtensions: const [RTreeExtension()]); + +void main() { + group('creating rtree tables', () { + final engine = SqlEngine(_rtreeOptions); + + test('can create rtree table', () { + final result = engine.analyze(''' +CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate +);'''); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect(table.name, 'demo_index'); + final columns = table.resultColumns; + expect(columns, hasLength(5)); + expect(columns.first.type.type, equals(BasicType.int)); + expect(columns.last.type.type, equals(BasicType.real)); + }); + + group('validate arguments', () { + test('invalid coordinate count', () { + final result = engine.analyze(''' +CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY +);'''); + + expect( + () => const SchemaFromCreateTable() + .read(result.root as TableInducingStatement), + throwsArgumentError); + }); + + test('no coordinates', () { + final result = engine.analyze(''' +CREATE VIRTUAL TABLE demo_index USING rtree( + id -- Integer primary key +);'''); + + expect( + () => const SchemaFromCreateTable() + .read(result.root as TableInducingStatement), + throwsArgumentError); + }); + + test('too many dimensions', () { + final result = engine.analyze(''' +CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY, + minY, maxY, + minY, maxY, + minY, maxY, + minY, maxY +);'''); + + expect( + () => const SchemaFromCreateTable() + .read(result.root as TableInducingStatement), + throwsArgumentError); + }); + }); + }); + + group('type inference for function arguments', () { + late SqlEngine engine; + setUp(() { + engine = SqlEngine(_rtreeOptions); + // add an fts5 table for the following queries + final result = engine.analyze(''' +CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate +);'''); + + engine.registerTable(const SchemaFromCreateTable() + .read(result.root as TableInducingStatement)); + }); + + test('insert', () { + final result = engine.analyze('INSERT INTO demo_index VALUES ' + '(28215, -80.781227, -80.604706, 35.208813, 35.297367);'); + expect(result.errors, isEmpty); + }); + + test('select', () { + final result = engine.analyze(''' +SELECT A.* FROM demo_index AS A, demo_index AS B + WHERE A.maxX>=B.minX AND A.minX<=B.maxX + AND A.maxY>=B.minY AND A.minY<=B.maxY + AND B.id=28269;'''); + + final columns = (result.root as SelectStatement).resolvedColumns; + + expect(result.errors, isEmpty); + expect(columns, hasLength(5)); + }); + }); +} diff --git a/sqlparser/test/scanner/moor_tokens_test.dart b/sqlparser/test/scanner/drift_tokens_test.dart similarity index 100% rename from sqlparser/test/scanner/moor_tokens_test.dart rename to sqlparser/test/scanner/drift_tokens_test.dart diff --git a/sqlparser/test/scanner/scanner_test.dart b/sqlparser/test/scanner/scanner_test.dart index 9c865d63..d01b7018 100644 --- a/sqlparser/test/scanner/scanner_test.dart +++ b/sqlparser/test/scanner/scanner_test.dart @@ -41,4 +41,42 @@ void main() { isA().having((e) => e.type, 'tokenType', TokenType.eof), ]); }); + + group('reports error message', () { + test(r'for missing identifier after `$`', () { + expect( + Scanner(r'$ order').scanTokens, + throwsA( + isA().having( + (e) => e.errors, + 'errors', + contains( + isA() + .having((e) => e.message, 'message', + r'Expected identifier after `$`') + .having((e) => e.location.offset, 'location.offset', 1), + ), + ), + ), + ); + }); + + test('for missing identifier after `@`', () { + expect( + Scanner(r'@ order').scanTokens, + throwsA( + isA().having( + (e) => e.errors, + 'errors', + contains( + isA() + .having((e) => e.message, 'message', + r'Expected identifier after `@`') + .having((e) => e.location.offset, 'location.offset', 1), + ), + ), + ), + ); + }); + }); } diff --git a/tool/generate_all.sh b/tool/generate_all.sh index 32e3fd2a..e168f45e 100755 --- a/tool/generate_all.sh +++ b/tool/generate_all.sh @@ -17,6 +17,7 @@ generate 'extras/benchmarks' generate 'extras/integration_tests/drift_testcases' generate 'extras/integration_tests/web' generate 'examples/app' +generate 'examples/encryption' generate 'examples/flutter_web_worker_example' generate 'examples/migrations_example' generate 'examples/web_worker_example'