mirror of https://github.com/AMT-Cheif/drift.git
Merge branch 'develop'
This commit is contained in:
commit
41472d64ec
|
@ -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  💬</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  💬</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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" %}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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  💬</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  💬</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class ServerImplementation implements DriftServer {
|
|||
@override
|
||||
Future<void> shutdown() {
|
||||
if (!_isShuttingDown) {
|
||||
_done.complete();
|
||||
_done.complete(connection.executor.close());
|
||||
_isShuttingDown = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 []);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'],
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 '
|
||||
|
|
|
@ -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;', []));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
## 1.3.0
|
||||
|
||||
- Support `drift` version `1.3.x`.
|
||||
|
||||
## 1.2.1
|
||||
|
||||
- Support the latest `analyzer` and `analyzer_plugin` packages.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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  💬</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  💬</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
Loading…
Reference in New Issue