Merge branch 'develop'

This commit is contained in:
Simon Binder 2022-01-14 19:35:27 +01:00
commit 41472d64ec
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
32 changed files with 402 additions and 66 deletions

View File

@ -5,15 +5,15 @@ _Note: Moor has been renamed to drift_
[![Build Status](https://api.cirrus-ci.com/github/simolus3/moor.svg)](https://github.com/simolus3/moor/actions/workflows/main.yml/badge.svg)
[![Chat on Gitter](https://img.shields.io/gitter/room/moor-dart/community)](https://gitter.im/moor-dart/community)
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter)
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor)
<p align="center">
<table>
<table>
<tbody>
<tr>
<td align="center">
<a href="https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter" target="_blank"><img width="250px" src="https://stream-blog.s3.amazonaws.com/blog/wp-content/uploads/fc148f0fc75d02841d017bb36e14e388/Stream-logo-with-background-.png"/></a><br/><span><a href="https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter" target="_blank">Try the Flutter Chat Tutorial &nbsp💬</a></span>
</td>
<a href="https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor" target="_blank"><img width="250px" src="https://stream-blog.s3.amazonaws.com/blog/wp-content/uploads/fc148f0fc75d02841d017bb36e14e388/Stream-logo-with-background-.png"/></a><br/><span><a href="https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor" target="_blank">Try the Flutter Chat Tutorial &nbsp💬</a></span>
</td>
</tr>
</tbody>
</table>

View File

@ -77,7 +77,7 @@ At the moment, drift supports these options:
* `new_sql_code_generation`: Generates SQL statements from the parsed AST instead of replacing substrings. This will also remove
unnecessary whitespace and comments.
If enabling this option breaks your queries, please file an issue!
* `scoped_dart_components`: Generates a function parameter for [Dart placeholders]({{ '../Using SQL/moor_files.md#dart-components-in-sql' | pageUrl }}) in SQL.
* `scoped_dart_components`: Generates a function parameter for [Dart placeholders]({{ '../Using SQL/drift_files.md#dart-components-in-sql' | pageUrl }}) in SQL.
The function has a parameter for each table that is available in the query, making it easier to get aliases right when using
Dart placeholders.
* `null_aware_type_converters`: Consider the type of applied type converters to determine nullability of columns in Dart.

View File

@ -149,7 +149,7 @@ Let's take a look at what drift generated during the build:
- a `Selectable<Todo> todosInCategory(int)` method, which runs the
`todosInCategory` query declared above. Drift has determined that the
type of the variable in that query is `int`, because that's the type
of the `category` column we're comparing it to.
of the `category` column we're comparing it to.
The method returns a `Selectable` to indicate that it can both be
used as a regular query (`Selectable.get` returns a `Future<List<Todo>>`)
or as an auto-updating stream (by using `.watch` instead of `.get()`).
@ -169,7 +169,7 @@ further guides to help you learn more:
- [Schema migrations]({{ "../Advanced Features/migrations.md" | pageUrl }})
- Writing [queries]({{ "writing_queries.md" | pageUrl }}) and
[expressions]({{ "../Advanced Features/expressions.md" | pageUrl }}) in Dart
- A more [in-depth guide]({{ "../Using SQL/moor_files.md" | pageUrl }})
- A more [in-depth guide]({{ "../Using SQL/drift_files.md" | pageUrl }})
on `drift` files, which explains `import` statements and the Dart-SQL interop.
{% block "blocks/alert" title="Using the database" %}

View File

@ -47,11 +47,11 @@ To use this feature, it's helpful to know how Dart tables are named in sql. For
override `tableName`, the name in sql will be the `snake_case` of the class name. So a Dart table
called `Categories` will be named `categories`, a table called `UserAddressInformation` would be
called `user_address_information`. The same rule applies to column getters without an explicit name.
Tables and columns declared in [Drift files]({{ "moor_files.md" | pageUrl }}) will always have the
Tables and columns declared in [Drift files]({{ "drift_files.md" | pageUrl }}) will always have the
name you specified.
{% endblock %}
You can also use `UPDATE` or `DELETE` statements here. Of course, this feature is also available for
You can also use `UPDATE` or `DELETE` statements here. Of course, this feature is also available for
[daos]({{ "../Advanced Features/daos.md" | pageUrl }}),
and it perfectly integrates with auto-updating streams by analyzing what tables you're reading from or
writing to.
@ -60,7 +60,7 @@ writing to.
If you don't want to use the statements with an generated api, you can
still send custom queries by calling `customSelect` for a one-time query or
`customSelectStream` for a query stream that automatically emits a new set of items when
the underlying data changes. Using the todo example introduced in the
the underlying data changes. Using the todo example introduced in the
[getting started guide]({{ "../Getting started/index.md" | pageUrl }}), we can
write this query which will load the amount of todo entries in each category:
```dart
@ -90,7 +90,7 @@ Stream<List<CategoryWithCount>> categoriesWithCount() {
```
For custom selects, you should use the `readsFrom` parameter to specify from which tables the query is
reading. When using a `Stream`, drift will be able to know after which updates the stream should emit
items.
items.
You can also bind SQL variables by using question-mark placeholders and the `variables` parameter:
@ -104,7 +104,7 @@ Stream<int> amountOfTodosInCategory(int id) {
}
```
Of course, you can also use indexed variables (like `?12`) - for more information on them, see
Of course, you can also use indexed variables (like `?12`) - for more information on them, see
[the sqlite3 documentation](https://sqlite.org/lang_expr.html#varparam).
## Custom update statements

View File

@ -6,6 +6,8 @@ data:
aliases:
- /docs/using-sql/custom_tables/ # Redirect from outdated "custom tables" page which has been deleted
- /docs/using-sql/moor_files/
template: layouts/docs/single
---
@ -174,27 +176,27 @@ CREATE TABLE saved_routes (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
"from" INTEGER NOT NULL REFERENCES coordinates (id),
to INTEGER NOT NULL REFERENCES coordinates (id)
"to" INTEGER NOT NULL REFERENCES coordinates (id)
);
routesWithPoints: SELECT r.id, r.name, f.*, t.* FROM routes r
INNER JOIN coordinates f ON f.id = r."from"
INNER JOIN coordinates t ON t.id = r.to;
INNER JOIN coordinates t ON t.id = r."to";
```
To match the returned column names while avoiding name clashes in Dart, drift
To match the returned column names while avoiding name clashes in Dart, drift
will generate a class having an `id`, `name`, `id1`, `lat`, `long`, `lat1` and
a `long1` field.
Of course, that's not helpful at all - was `lat1` coming from `from` or `to`
Of course, that's not helpful at all - was `lat1` coming from `from` or `to`
again? Let's rewrite the query, this time using nested results:
```sql
routesWithNestedPoints: SELECT r.id, r.name, f.**, t.** FROM routes r
INNER JOIN coordinates f ON f.id = r."from"
INNER JOIN coordinates t ON t.id = r.to;
INNER JOIN coordinates t ON t.id = r."to";
```
As you can see, we can nest a result simply by using the drift-specific
As you can see, we can nest a result simply by using the drift-specific
`table.**` syntax.
For this query, drift will generate the following class:
```dart
@ -207,7 +209,7 @@ class RoutesWithNestedPointsResult {
}
```
Great! This class matches our intent much better than the flat result class
Great! This class matches our intent much better than the flat result class
from before.
At the moment, there are some limitations with this approach:

View File

@ -25,7 +25,7 @@ path: v2
The rewritten compiler is faster than ever, supports more SQL features and gives you
more flexibility when writing database code.
[Check the updated documentation]({{ "docs/Using SQL/moor_files.md" | pageUrl }})
[Check the updated documentation]({{ "docs/Using SQL/drift_files.md" | pageUrl }})
{% endblock %}
{% endblock %}

View File

@ -21,7 +21,9 @@ Future<void> main() async {
Uri.parse('http://localhost:8080/api/')
],
{'http://localhost:8080/**'},
true,
// todo: Re-enable. Current problem is that we link new pages to their
// final url (under drift.simonbinder.eu) before they're deployed.
false,
UrlSkipper(
'', ['github.com', 'pub.dev', 'api.dart.dev', 'fonts.gstatic.com']),
false,
@ -36,7 +38,8 @@ Future<void> main() async {
var hasBrokenLinks = false;
for (final result in results.destinations) {
if (result.isBroken) {
// todo: Remove !result.isExternal after external links work again
if (result.isBroken && !result.isExternal) {
print('Broken: $result (${result.toMap()})');
hasBrokenLinks = true;
}

View File

@ -1,3 +1,13 @@
## 1.3.0
- Add the `from(table)` method to generated databases. It can be used to write
common queries more concisely.
- Make `groupConcat` nullable in the Dart API.
- Throw an exception in a `NativeDatabase` when multiple statements are run in
a single call. In previous versions, parts of the SQL string would otherwise
be ignored.
- Close the underlying database when a drift isolate is shut down.
## 1.2.0
- Properly support stream update queries on views.

View File

@ -1,14 +1,14 @@
# Drift
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter)
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor)
<p align="center">
<table>
<table>
<tbody>
<tr>
<td align="center">
<a href="https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter" target="_blank"><img width="250px" src="https://stream-blog.s3.amazonaws.com/blog/wp-content/uploads/fc148f0fc75d02841d017bb36e14e388/Stream-logo-with-background-.png"/></a><br/><span><a href="https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter" target="_blank">Try the Flutter Chat Tutorial &nbsp💬</a></span>
</td>
<a href="https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor" target="_blank"><img width="250px" src="https://stream-blog.s3.amazonaws.com/blog/wp-content/uploads/fc148f0fc75d02841d017bb36e14e388/Stream-logo-with-background-.png"/></a><br/><span><a href="https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor" target="_blank">Try the Flutter Chat Tutorial &nbsp💬</a></span>
</td>
</tr>
</tbody>
</table>

View File

@ -182,7 +182,8 @@ class _VmDelegate extends DatabaseDelegate {
@override
Future<void> runBatched(BatchedStatements statements) async {
final prepared = [
for (final stmt in statements.statements) _db.prepare(stmt),
for (final stmt in statements.statements)
_db.prepare(stmt, checkNoTail: true),
];
for (final application in statements.arguments) {
@ -202,7 +203,7 @@ class _VmDelegate extends DatabaseDelegate {
if (args.isEmpty) {
_db.execute(statement);
} else {
final stmt = _db.prepare(statement);
final stmt = _db.prepare(statement, checkNoTail: true);
stmt.execute(args);
stmt.dispose();
}
@ -227,7 +228,7 @@ class _VmDelegate extends DatabaseDelegate {
@override
Future<QueryResult> runSelect(String statement, List<Object?> args) async {
final stmt = _db.prepare(statement);
final stmt = _db.prepare(statement, checkNoTail: true);
final result = stmt.select(args);
stmt.dispose();

View File

@ -62,7 +62,7 @@ class ServerImplementation implements DriftServer {
@override
Future<void> shutdown() {
if (!_isShuttingDown) {
_done.complete();
_done.complete(connection.executor.close());
_isShuttingDown = true;
}

View File

@ -154,6 +154,17 @@ abstract class DatabaseConnectionUser {
return executor.ensureOpen(attachedDatabase).then((_) => fn(executor));
}
/// Captures a [table] or view for easier subsequent operations.
///
/// The [TableOrViewOperations] class (or the [TableOperations] extension
/// for tables) provides convenience methods that make common operations
/// easier to write than using the methods from this class directly.
@experimental
TableOrViewOperations<T, D> from<T extends HasResultSet, D>(
ResultSetImplementation<T, D> table) {
return TableOrViewOperations._(this, table);
}
/// Starts an [InsertStatement] for a given table. You can use that statement
/// to write data into the [table] by using [InsertStatement.insert].
InsertStatement<T, D> into<T extends Table, D>(TableInfo<T, D> table) {
@ -375,6 +386,10 @@ abstract class DatabaseConnectionUser {
}
/// Executes the custom sql [statement] on the database.
///
/// [statement] should contain exactly one SQL statement. Attempting to run
/// multiple statements with a single [customStatement] may not be fully
/// supported on all platforms.
Future<void> customStatement(String statement, [List<dynamic>? args]) {
final engine = resolvedEngine;
@ -539,3 +554,125 @@ abstract class DatabaseConnectionUser {
return buffer.toString();
}
}
/// A capture of a table and a generated database.
///
/// Table operations can be captured with [DatabaseConnectionUser.from], which
/// may make some common operations easier to write:
///
/// - Use `from(table).select()` or `from(table).selectOnly()` instead of
/// `select(table)` or `selectOnly(table)`, respectively.
/// - Use `from(table).insert()` instead of `insert(table)`. You can also use
/// `from(table).insertOne`, or [TableOperations.insertOnConflictUpdate] to
/// insert rows directly.
/// - Use `from(table).update()` instead of `update(table)`. You can also use
/// `from(table).replace()` to replace an existing row.
/// - Use `from(table).delete()`, `from(table).deleteOne()` or
/// `from(table).deleteWhere` to delete rows easily.
@sealed
class TableOrViewOperations<Tbl extends HasResultSet, Row> {
final DatabaseConnectionUser _user;
final ResultSetImplementation<Tbl, Row> _sourceSet;
TableOrViewOperations._(this._user, this._sourceSet);
/// Composes a `SELECT` statement on the captured table or view.
///
/// This is equivalent to calling [DatabaseConnectionUser.select].
SimpleSelectStatement<Tbl, Row> select({bool distinct = false}) {
return _user.select(_sourceSet, distinct: distinct);
}
/// Composes a `SELECT` statement only selecting a subset of columns.
///
/// This is equivalent to calling [DatabaseConnectionUser.selectOnly].
JoinedSelectStatement<Tbl, Row> selectOnly(
{bool distinct = false, bool includeJoinedTableColumns = true}) {
return _user.selectOnly(_sourceSet,
distinct: distinct,
includeJoinedTableColumns: includeJoinedTableColumns);
}
}
/// Additional methods for a [TableOrViewOperations] that are only available on
/// tables.
extension TableOperations<Tbl extends Table, Row>
on TableOrViewOperations<Tbl, Row> {
TableInfo<Tbl, Row> get _table => _sourceSet as TableInfo<Tbl, Row>;
/// Creates an insert statment to be used to compose an insert on the captured
/// table.
///
/// This is equivalent to calling [DatabaseConnectionUser.into] on the
/// captured table. See that method for more information.
InsertStatement<Tbl, Row> insert() => _user.into(_table);
/// Inserts one row into the captured table.
///
/// This is equivalent to calling [InsertStatement.insert] - see that method
/// for more information.
Future<int> insertOne(
Insertable<Row> row, {
InsertMode? mode,
UpsertClause<Tbl, Row>? onConflict,
}) {
return insert().insert(row, mode: mode, onConflict: onConflict);
}
/// Inserts one row into the captured table, replacing an existing row if it
/// exists already.
///
/// Please note that this method is only available on recent sqlite3 versions.
/// See also [InsertStatement.insertOnConflictUpdate].
Future<int> insertOnConflictUpdate(Insertable<Row> row) {
return insert().insertOnConflictUpdate(row);
}
/// Inserts one row into the captured table and returns it, along with auto-
/// generated fields.
///
/// Please note that this method is only available on recent sqlite3 versions.
/// See also [InsertStatement.insertReturning].
Future<Row> insertReturning(
Insertable<Row> row, {
InsertMode? mode,
UpsertClause<Tbl, Row>? onConflict,
}) {
return insert().insertReturning(
row,
mode: mode,
onConflict: onConflict,
);
}
/// Creates a statement to compose an `UPDATE` into the database.
///
/// This is equivalent to calling [DatabaseConnectionUser.update] with the
/// captured table.
UpdateStatement<Tbl, Row> update() => _user.update(_table);
/// Replaces a single row with an update statement.
///
/// See also [UpdateStatement.replace].
Future<void> replace(Insertable<Row> row) {
return update().replace(row);
}
/// Creates a statement to compose a `DELETE` from the database.
///
/// This is equivalent to calling [DatabaseConnectionUser.delete] with the
/// captured table.
DeleteStatement<Tbl, Row> delete() => _user.delete(_table);
/// Deletes the [row] from the captured table.
Future<bool> deleteOne(Insertable<Row> row) async {
return await (delete()..whereSamePrimaryKey(row)).go() != 0;
}
/// Deletes all rows matching the [filter] from the table.
///
/// See also [SingleTableQueryMixin.where].
Future<int> deleteWhere(Expression<bool?> Function(Tbl tbl) filter) {
return (delete()..where(filter)).go();
}
}

View File

@ -33,12 +33,13 @@ extension BaseAggregate<DT> on Expression<DT> {
/// Returns the concatenation of all non-null values in the current group,
/// joined by the [separator].
///
/// The order of the concatenated elements is arbitrary.
/// The order of the concatenated elements is arbitrary. If no non-null values
/// exist in the group, `NULL` is returned.
///
/// See also:
/// - the sqlite documentation: https://www.sqlite.org/lang_aggfunc.html#groupconcat
/// - the conceptually similar [Iterable.join]
Expression<String> groupConcat({String separator = ','}) {
Expression<String?> groupConcat({String separator = ','}) {
const sqliteDefaultSeparator = ',';
if (separator == sqliteDefaultSeparator) {
return _AggregateExpression('GROUP_CONCAT', this);

View File

@ -326,7 +326,7 @@ class Migrator {
} else if (view.query != null) {
final context = GenerationContext.fromDb(_db);
context.generatingForView = view.entityName;
context.buffer.write('CREATE VIEW ${view.entityName} AS ');
context.buffer.write('CREATE VIEW IF NOT EXISTS ${view.entityName} AS ');
view.query!.writeInto(context);
await _issueCustomQuery(context.sql, const []);
}

View File

@ -93,8 +93,15 @@ class SqlJsDatabase {
/// Calls `run(sql, args)` on the underlying js api
void runWithArgs(String sql, List<dynamic> args) {
final ar = JsArray.from(args);
_obj.callMethod('run', [sql, ar]);
if (args.isEmpty) {
// Call run without providing arguments. sql.js will then use sqlite3_exec
// internally, which supports running multiple statements at once. This
// matches the behavior from a `NativeDatabase`.
_obj.callMethod('run', [sql]);
} else {
final ar = JsArray.from(args);
_obj.callMethod('run', [sql, ar]);
}
}
/// Returns the amount of rows affected by the most recent INSERT, UPDATE or

View File

@ -1,6 +1,6 @@
name: drift
description: Drift is a reactive library to store relational data in Dart and Flutter applications.
version: 1.2.0
version: 1.3.0
repository: https://github.com/simolus3/moor
homepage: https://drift.simonbinder.eu/
issue_tracker: https://github.com/simolus3/moor/issues
@ -14,7 +14,7 @@ dependencies:
collection: ^1.15.0
meta: ^1.3.0
stream_channel: ^2.1.0
sqlite3: ^1.0.0
sqlite3: ^1.5.1
dev_dependencies:
build_test: ^2.0.0

View File

@ -70,4 +70,26 @@ void main() {
verifyNever(streamQueries.handleTableUpdates(any));
});
});
group('delete with from()', () {
test('delete()', () async {
await db.from(db.users).delete().go();
verify(executor.runDelete('DELETE FROM users;', []));
});
test('deleteOne()', () async {
await db.from(db.users).deleteOne(const UsersCompanion(id: Value(3)));
verify(executor.runDelete('DELETE FROM users WHERE id = ?;', [3]));
});
test('deleteWhere', () async {
await db
.from(db.users)
.deleteWhere((tbl) => tbl.id.isSmallerThanValue(3));
verify(executor.runDelete('DELETE FROM users WHERE id < ?;', [3]));
});
});
}

View File

@ -1,5 +1,5 @@
@TestOn('vm')
import 'package:drift/drift.dart';
import 'package:drift/drift.dart' hide isNull;
import 'package:drift/native.dart';
import 'package:test/test.dart';
@ -139,6 +139,16 @@ void main() {
expect(eval(noMatch), completion(isFalse));
});
test('groupConcat is nullable', () async {
final ids = db.users.id.groupConcat();
final query = db.selectOnly(db.users)
..where(db.users.id.equals(999))
..addColumns([ids]);
final result = await query.getSingle();
expect(result.read(ids), isNull);
});
test('subqueries cause updates to stream queries', () async {
await db
.into(db.categories)

View File

@ -25,6 +25,40 @@ void main() {
underlying.dispose();
});
});
group('checks for trailing statement content', () {
late NativeDatabase db;
setUp(() async {
db = NativeDatabase.memory();
await db.ensureOpen(_FakeExecutorUser());
});
tearDown(() => db.close());
test('multiple statements are allowed for runCustom without args', () {
return db.runCustom('SELECT 1; SELECT 2;');
});
test('throws for runCustom with args', () async {
expect(db.runCustom('SELECT ?; SELECT ?;', [1, 2]), throwsArgumentError);
});
test('in runSelect', () async {
expect(db.runSelect('SELECT ?; SELECT ?;', [1, 2]), throwsArgumentError);
});
test('in runBatched', () {
expect(
db.runBatched(BatchedStatements([
'SELECT ?; SELECT ?;'
], [
ArgumentsForBatchedStatement(0, []),
])),
throwsArgumentError,
);
});
});
}
class _FakeExecutorUser extends QueryExecutorUser {

View File

@ -402,4 +402,56 @@ void main() {
['description', 1],
));
});
group('with from()', () {
test('insert', () async {
await db
.from(db.categories)
.insert()
.insert(CategoriesCompanion.insert(description: 'description'));
verify(executor.runInsert(
'INSERT INTO categories ("desc") VALUES (?)', ['description']));
});
test('insertOne', () async {
await db.from(db.categories).insertOne(
CategoriesCompanion.insert(description: 'description'),
mode: InsertMode.insertOrReplace);
verify(executor.runInsert(
'INSERT OR REPLACE INTO categories ("desc") VALUES (?)',
['description']));
});
test('insertOnConflictUpdate', () async {
when(executor.runSelect(any, any)).thenAnswer(
(_) => Future.value([
{
'id': 1,
'desc': 'description',
'description_in_upper_case': 'DESCRIPTION',
'priority': 1,
},
]),
);
final row = await db.from(db.categories).insertReturning(
CategoriesCompanion.insert(description: 'description'));
expect(
row,
Category(
id: 1,
description: 'description',
descriptionInUpperCase: 'DESCRIPTION',
priority: CategoryPriority.medium,
),
);
verify(executor.runSelect(
'INSERT INTO categories ("desc") VALUES (?) RETURNING *',
['description'],
));
});
});
}

View File

@ -5,9 +5,11 @@ import 'dart:isolate';
import 'package:drift/drift.dart';
import 'package:drift/isolate.dart';
import 'package:drift/native.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'data/tables/todos.dart';
import 'data/utils/mocks.dart';
void main() {
// Using the DriftIsolate apis without actually running on a background
@ -105,6 +107,15 @@ void main() {
final drift = await spawned.first as DriftIsolate;
await drift.shutdownAll();
}, tags: 'background_isolate');
test('shutting down will close the underlying executor', () async {
final mockExecutor = MockExecutor();
final isolate = DriftIsolate.inCurrent(
() => DatabaseConnection.fromExecutor(mockExecutor));
await isolate.shutdownAll();
verify(mockExecutor.close());
});
}
void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,

View File

@ -66,7 +66,7 @@ void main() {
[]));
verify(mockExecutor.runCustom(
'CREATE VIEW category_todo_count_view AS SELECT '
'CREATE VIEW IF NOT EXISTS category_todo_count_view AS SELECT '
'categories."desc" AS "categories.desc", '
'COUNT(todos.id) AS "item_count" '
'FROM categories '
@ -75,7 +75,7 @@ void main() {
[]));
verify(mockExecutor.runCustom(
'CREATE VIEW todo_with_category_view AS SELECT '
'CREATE VIEW IF NOT EXISTS todo_with_category_view AS SELECT '
'todos.title AS "todos.title", '
'categories."desc" AS "categories.desc" '
'FROM todos '

View File

@ -211,4 +211,17 @@ void main() {
await pumpEventQueue();
db.markTablesUpdated([db.categories]);
});
test('can create select statements with from()', () async {
when(executor.runSelect(any, any)).thenAnswer((_) => Future.value([]));
final result = await db.from(db.todosTable).select().get();
expect(result, isEmpty);
final anotherResult = await db.from(db.todosTable).selectOnly().get();
expect(anotherResult, isEmpty);
verify(executor.runSelect('SELECT * FROM todos;', []));
verify(executor.runSelect('SELECT FROM todos;', []));
});
}

View File

@ -160,4 +160,24 @@ void main() {
{const TableUpdate('users'), const TableUpdate('todos')}));
});
});
group('update with from()', () {
test('update()', () async {
await db
.from(db.users)
.update()
.write(const UsersCompanion(id: Value(3)));
verify(executor.runUpdate('UPDATE users SET id = ?;', [3]));
});
test('replace', () async {
await db.from(db.categories).replace(const CategoriesCompanion(
id: Value(3), description: Value('new name')));
verify(executor.runUpdate(
'UPDATE categories SET "desc" = ?, priority = 0 WHERE id = ?;',
['new name', 3]));
});
});
}

View File

@ -1,3 +1,7 @@
## 1.3.0
- Support `drift` version `1.3.x`.
## 1.2.1
- Support the latest `analyzer` and `analyzer_plugin` packages.

View File

@ -1,6 +1,6 @@
name: drift_dev
description: Dev-dependency for users of drift. Contains a the generator and development tools.
version: 1.2.1
version: 1.3.0
repository: https://github.com/simolus3/moor
homepage: https://drift.simonbinder.eu/
issue_tracker: https://github.com/simolus3/moor/issues
@ -25,7 +25,7 @@ dependencies:
io: ^1.0.3
# Drift-specific analysis and apis
drift: '>=1.2.0 <1.3.0'
drift: '>=1.3.0 <1.4.0'
sqlite3: '>=0.1.6 <2.0.0'
sqlparser: ^0.19.0

View File

@ -11,14 +11,12 @@ dev_dependencies:
test: ^1.5.0
build_runner:
build_web_compilers:
moor_generator:
drift_dev: ^1.0.0
dependency_overrides:
drift:
path: ../../../drift
moor:
path: ../../../moor
moor_generator:
path: ../../../moor_generator
drift_dev:
path: ../../../drift_dev
sqlparser:
path: ../../../sqlparser

View File

@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:moor/moor.dart';
import 'package:moor/moor_web.dart';
import 'package:drift/drift.dart';
import 'package:drift/web.dart';
import 'package:test/test.dart';
@ -184,23 +184,23 @@ AAAAAAAAAAAAAAAAAAAAAAANAQIjaGVsbG8gd29ybGQ=
void main() {
test('can initialize database when absent', () async {
await _testWith(const MoorWebStorage('name'));
await _testWith(const DriftWebStorage('name'));
});
test('can initialize database when absent - IndexedDB', () async {
await _testWith(
MoorWebStorage.indexedDb('name', migrateFromLocalStorage: false));
DriftWebStorage.indexedDb('name', migrateFromLocalStorage: false));
});
}
Future<void> _testWith(MoorWebStorage storage) async {
Future<void> _testWith(DriftWebStorage storage) async {
var didCallInitializer = false;
final executor = WebDatabase.withStorage(storage, initializer: () async {
didCallInitializer = true;
return base64.decode(_rawDataBase64.replaceAll('\n', ''));
});
moorRuntimeOptions.dontWarnAboutMultipleDatabases = true;
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
final attachedDb = _FakeDatabase(executor);
await executor.ensureOpen(attachedDb);

View File

@ -1,7 +1,7 @@
@TestOn('browser')
import 'dart:html';
import 'package:moor/moor_web.dart';
import 'package:drift/web.dart';
import 'package:test/test.dart';
import 'package:tests/tests.dart';
@ -30,7 +30,7 @@ class WebExecutorIndexedDb extends TestExecutor {
@override
DatabaseConnection createConnection() {
return DatabaseConnection.fromExecutor(
WebDatabase.withStorage(MoorWebStorage.indexedDb('foo')),
WebDatabase.withStorage(DriftWebStorage.indexedDb('foo')),
);
}
@ -48,4 +48,15 @@ void main() {
group('using IndexedDb', () {
runAllTests(WebExecutorIndexedDb());
});
test('can run multiple statements in one call', () async {
final db = Database(DatabaseConnection.fromExecutor(
WebDatabase.withStorage(DriftWebStorage.volatile())));
addTearDown(db.close);
await db.customStatement(
'CREATE TABLE x1 (a INTEGER); INSERT INTO x1 VALUES (1);');
final results = await db.customSelect('SELECT * FROM x1;').get();
expect(results.length, 1);
});
}

View File

@ -1,8 +1,8 @@
@TestOn('browser')
import 'dart:html';
import 'package:moor/moor.dart';
import 'package:moor/moor_web.dart';
import 'package:drift/drift.dart';
import 'package:drift/web.dart';
import 'package:test/test.dart';
part 'saves_after_migration_regression_test.g.dart';
@ -17,7 +17,7 @@ class Bars extends Table {
IntColumn get id => integer().autoIncrement()();
}
@UseMoor(
@DriftDatabase(
tables: [Foos, Bars],
)
class _FakeDb extends _$_FakeDb {

View File

@ -34,14 +34,14 @@ class Foo extends DataClass implements Insertable<Foo> {
factory Foo.fromJson(Map<String, dynamic> json,
{ValueSerializer serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
serializer ??= driftRuntimeOptions.defaultSerializer;
return Foo(
id: serializer.fromJson<int>(json['id']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
};
@ -175,14 +175,14 @@ class Bar extends DataClass implements Insertable<Bar> {
factory Bar.fromJson(Map<String, dynamic> json,
{ValueSerializer serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
serializer ??= driftRuntimeOptions.defaultSerializer;
return Bar(
id: serializer.fromJson<int>(json['id']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
};

View File

@ -7,15 +7,15 @@ Thank you for understanding!
__To start using Drift, read our detailed [docs](https://moor.simonbinder.eu/docs/getting-started/).__
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter)
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor)
<p align="center">
<table>
<table>
<tbody>
<tr>
<td align="center">
<a href="https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter" target="_blank"><img width="250px" src="https://stream-blog.s3.amazonaws.com/blog/wp-content/uploads/fc148f0fc75d02841d017bb36e14e388/Stream-logo-with-background-.png"/></a><br/><span><a href="https://getstream.io/chat/flutter/tutorial/?utm_source=https://github.com/simolus3/moor&utm_medium=github&utm_content=developer&utm_term=flutter" target="_blank">Try the Flutter Chat Tutorial &nbsp💬</a></span>
</td>
<a href="https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor" target="_blank"><img width="250px" src="https://stream-blog.s3.amazonaws.com/blog/wp-content/uploads/fc148f0fc75d02841d017bb36e14e388/Stream-logo-with-background-.png"/></a><br/><span><a href="https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor" target="_blank">Try the Flutter Chat Tutorial &nbsp💬</a></span>
</td>
</tr>
</tbody>
</table>