mirror of https://github.com/AMT-Cheif/drift.git
Merge branch 'develop' into refactor-type-system
This commit is contained in:
commit
3744fa0601
|
@ -16,3 +16,5 @@ flutter_export_environment.sh
|
|||
.DS_Store
|
||||
|
||||
docs/**/*.g.dart
|
||||
|
||||
*/build/
|
||||
|
|
|
@ -5,14 +5,14 @@ _Note: Moor has been renamed to drift_
|
|||
[![Build Status](https://api.cirrus-ci.com/github/simolus3/moor.svg)](https://github.com/simolus3/drift/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=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=moor)
|
||||
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22)
|
||||
|
||||
<p align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<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>
|
||||
<a href="https://getstream.io/chat/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22" 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/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22" target="_blank">Try the Flutter Chat Tutorial  💬</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import 'package:drift/drift.dart';
|
||||
|
||||
import '../tables/filename.dart';
|
||||
|
||||
part 'custom_queries.g.dart';
|
||||
|
||||
// #docregion manual
|
||||
class CategoryWithCount {
|
||||
final Category category;
|
||||
final int count; // amount of entries in this category
|
||||
|
||||
CategoryWithCount({required this.category, required this.count});
|
||||
}
|
||||
|
||||
// #enddocregion manual
|
||||
|
||||
// #docregion setup
|
||||
@DriftDatabase(
|
||||
tables: [Todos, Categories],
|
||||
queries: {
|
||||
'categoriesWithCount': 'SELECT *, '
|
||||
'(SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" '
|
||||
'FROM categories c;'
|
||||
},
|
||||
)
|
||||
class MyDatabase extends _$MyDatabase {
|
||||
// rest of class stays the same
|
||||
// #enddocregion setup
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
MyDatabase(QueryExecutor e) : super(e);
|
||||
|
||||
// #docregion run
|
||||
Future<void> useGeneratedQuery() async {
|
||||
// The generated query can be run once as a future:
|
||||
await categoriesWithCount().get();
|
||||
|
||||
// Or multiple times as a stream
|
||||
await for (final snapshot in categoriesWithCount().watch()) {
|
||||
print('Found ${snapshot.length} category results');
|
||||
}
|
||||
}
|
||||
|
||||
// #enddocregion run
|
||||
// #docregion manual
|
||||
// then, in the database class:
|
||||
Stream<List<CategoryWithCount>> allCategoriesWithCount() {
|
||||
// select all categories and load how many associated entries there are for
|
||||
// each category
|
||||
return customSelect(
|
||||
'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount"'
|
||||
' FROM categories c;',
|
||||
// used for the stream: the stream will update when either table changes
|
||||
readsFrom: {todos, categories},
|
||||
).watch().map((rows) {
|
||||
// we get list of rows here. We just have to turn the raw data from the
|
||||
// row into a CategoryWithCount instnace. As we defined the Category table
|
||||
// earlier, drift knows how to parse a category. The only thing left to do
|
||||
// manually is extracting the amount.
|
||||
return rows
|
||||
.map((row) => CategoryWithCount(
|
||||
category: categories.map(row.data),
|
||||
count: row.read<int>('amount'),
|
||||
))
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
|
||||
// #enddocregion manual
|
||||
|
||||
// #docregion setup
|
||||
}
|
||||
// #enddocregion setup
|
|
@ -2,45 +2,56 @@
|
|||
data:
|
||||
title: "Custom queries"
|
||||
weight: 10
|
||||
description: Let drift generate Dart from your SQL statements
|
||||
description: Write SQL for advanced queries that drift can't express in Dart yet.
|
||||
aliases:
|
||||
- /queries/custom
|
||||
template: layouts/docs/single
|
||||
---
|
||||
|
||||
{% assign snippets = "package:drift_docs/snippets/drift_files/custom_queries.dart.excerpt.json" | readString | json_decode %}
|
||||
|
||||
Although drift includes a fluent api that can be used to model most statements, advanced
|
||||
features like `WITH` clauses or subqueries aren't supported yet. You can
|
||||
use these features with custom statements. You don't have to miss out on other benefits
|
||||
drift brings, though: Drift helps you parse the result rows and custom queries also
|
||||
support auto-updating streams.
|
||||
features like `WITH` clauses or some subqueries aren't supported yet.
|
||||
However, you can use methods like `customSelect` and `customStatement` to run advanced
|
||||
statements on the database by writing the SQL manually.
|
||||
|
||||
For most custom queries, drift can analyze their SQL at compile time, make sure they're valid
|
||||
and generate a type-safe API for them.
|
||||
This approach can be much safer than writing custom SQL at runtime.
|
||||
|
||||
This page describes both approaches: The first section introduces methods generated by drift,
|
||||
the second section gives an example for a custom query defined at runtime.
|
||||
|
||||
## Statements with a generated api
|
||||
|
||||
You can instruct drift to automatically generate a typesafe
|
||||
API for your select, update and delete statements. Of course, you can still write custom
|
||||
sql manually. See the sections below for details.
|
||||
sql manually. See the sections below for details.
|
||||
|
||||
To use this feature, all you need to is define your queries in your `DriftDatabase` annotation:
|
||||
```dart
|
||||
@DriftDatabase(
|
||||
tables: [Todos, Categories],
|
||||
queries: {
|
||||
'categoriesWithCount':
|
||||
'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;'
|
||||
},
|
||||
)
|
||||
class MyDatabase extends _$MyDatabase {
|
||||
// rest of class stays the same
|
||||
}
|
||||
```
|
||||
After running the build step again, drift will have written the `CategoriesWithCountResult` class for you -
|
||||
it will hold the result of your query. Also, the `_$MyDatabase` class from which you inherit will have the
|
||||
methods `categoriesWithCount` (which runs the query once) and `watchCategoriesWithCount` (which returns
|
||||
an auto-updating stream).
|
||||
|
||||
Queries can have parameters in them by using the `?` or `:name` syntax. When your queries contains parameters,
|
||||
drift will figure out an appropriate type for them and include them in the generated methods. For instance,
|
||||
{% include "blocks/snippet" snippets = snippets name = "setup" %}
|
||||
|
||||
After running the build step again, drift will have written the `CategoriesWithCountResult` class for you -
|
||||
it will hold the result of your query. Also, the `_$MyDatabase` class from which you inherit will have a
|
||||
`Selectable<CategoriesWithCountResult> categoriesWithCount()` method which can be used to run the query.
|
||||
Like all `Selectable`s in drift, you can use `get()` to run the query once or `watch()` to get an auto-updating
|
||||
stream of results:
|
||||
|
||||
{% block "blocks/alert" title="Better support for custom queries in drift files" %}
|
||||
Defining SQL in the `@DriftDatabase` annotation is a great way to define a few custom queries. For apps that
|
||||
use lots of custom queries, extracting them into separate files may be more manageable.
|
||||
[Drift files]({{ "drift_files.md" | pageUrl }}), which can be included into the database, are a really great fit for this, and may be easier
|
||||
to use.
|
||||
{% endblock %}
|
||||
|
||||
{% include "blocks/snippet" snippets = snippets name = "run" %}
|
||||
|
||||
Queries can have parameters in them by using the `?` or `:name` syntax. For parameters in queries,
|
||||
drift will figure out an appropriate type and include them in the generated methods. For instance,
|
||||
`'categoryById': 'SELECT * FROM categories WHERE id = :id'` will generate the method `categoryById(int id)`.
|
||||
Drift also supports additional convenience features in custom queries, like embededding Dart expressions in
|
||||
SQL. For more details, see the documentation on [drift files]({{ 'drift_files.md' | pageUrl }}).
|
||||
|
||||
{% block "blocks/alert" title="On table names" color="info" %}
|
||||
To use this feature, it's helpful to know how Dart tables are named in sql. For tables that don't
|
||||
|
@ -63,31 +74,9 @@ still send custom queries by calling `customSelect` for a one-time query or
|
|||
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
|
||||
class CategoryWithCount {
|
||||
final Category category;
|
||||
final int count; // amount of entries in this category
|
||||
|
||||
CategoryWithCount(this.category, this.count);
|
||||
}
|
||||
{% include "blocks/snippet" snippets = snippets name = "manual" %}
|
||||
|
||||
// then, in the database class:
|
||||
Stream<List<CategoryWithCount>> categoriesWithCount() {
|
||||
// select all categories and load how many associated entries there are for
|
||||
// each category
|
||||
return customSelect(
|
||||
'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;',
|
||||
readsFrom: {todos, categories}, // used for the stream: the stream will update when either table changes
|
||||
).watch().map((rows) {
|
||||
// we get list of rows here. We just have to turn the raw data from the row into a
|
||||
// CategoryWithCount. As we defined the Category table earlier, drift knows how to parse
|
||||
// a category. The only thing left to do manually is extracting the amount
|
||||
return rows
|
||||
.map((row) => CategoryWithCount(Category.fromData(row.data, this), row.read<int>('amount')))
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
```
|
||||
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.
|
||||
|
|
|
@ -9,6 +9,14 @@
|
|||
- To describe the type a column has, use the `DriftSqlType` enum
|
||||
- To map a value from Dart to SQL and vice-versa, use an instance of `SqlTypes`,
|
||||
reachable via `database.options.types`.
|
||||
- __Breaking__: `Expression`s (including `Column`s) always have a non-nullable type
|
||||
parameter now. They are implicitly nullable, so `TypedResult.read` now returns a
|
||||
nullable value.
|
||||
- __Breaking__: `QueryRow.read` can only read non-nullable values now. To read nullable
|
||||
values, use `readNullable`.
|
||||
- __Breaking__: Remove the `includeJoinedTableColumns` parameter on `selectOnly()`.
|
||||
The method now behaves as if that parameter was turned off. To use columns from a
|
||||
joined table, add them with `addColumns`.
|
||||
- Consistently handle transaction errors like a failing `BEGIN` or `COMMIT`
|
||||
across database implementations.
|
||||
- Support nested transactions.
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# Drift
|
||||
|
||||
## 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)
|
||||
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22)
|
||||
|
||||
<p align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<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>
|
||||
<a href="https://getstream.io/chat/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22" 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/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22" target="_blank">Try the Flutter Chat Tutorial  💬</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -10,7 +10,7 @@ part of 'main.dart';
|
|||
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
|
||||
final int id;
|
||||
final String name;
|
||||
TodoCategory({required this.id, required this.name});
|
||||
const TodoCategory({required this.id, required this.name});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
|
@ -185,7 +185,7 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
|
|||
final String? content;
|
||||
final int categoryId;
|
||||
final String? generatedText;
|
||||
TodoItem(
|
||||
const TodoItem(
|
||||
{required this.id,
|
||||
required this.title,
|
||||
this.content,
|
||||
|
@ -466,7 +466,8 @@ class $TodoItemsTable extends TodoItems
|
|||
class TodoCategoryItemCountData extends DataClass {
|
||||
final String name;
|
||||
final int itemCount;
|
||||
TodoCategoryItemCountData({required this.name, required this.itemCount});
|
||||
const TodoCategoryItemCountData(
|
||||
{required this.name, required this.itemCount});
|
||||
factory TodoCategoryItemCountData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
|
@ -558,10 +559,8 @@ class $TodoCategoryItemCountView
|
|||
}
|
||||
|
||||
@override
|
||||
Query? get query => (attachedDatabase.selectOnly(todoCategories,
|
||||
includeJoinedTableColumns: false)
|
||||
..addColumns($columns))
|
||||
.join([
|
||||
Query? get query =>
|
||||
(attachedDatabase.selectOnly(todoCategories)..addColumns($columns)).join([
|
||||
innerJoin(todoItems, todoItems.categoryId.equalsExp(todoCategories.id))
|
||||
]);
|
||||
@override
|
||||
|
@ -571,7 +570,8 @@ class $TodoCategoryItemCountView
|
|||
class TodoItemWithCategoryNameViewData extends DataClass {
|
||||
final int id;
|
||||
final String title;
|
||||
TodoItemWithCategoryNameViewData({required this.id, required this.title});
|
||||
const TodoItemWithCategoryNameViewData(
|
||||
{required this.id, required this.title});
|
||||
factory TodoItemWithCategoryNameViewData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
|
@ -668,9 +668,7 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo<
|
|||
|
||||
@override
|
||||
Query? get query =>
|
||||
(attachedDatabase.selectOnly(todoItems, includeJoinedTableColumns: false)
|
||||
..addColumns($columns))
|
||||
.join([
|
||||
(attachedDatabase.selectOnly(todoItems)..addColumns($columns)).join([
|
||||
innerJoin(
|
||||
todoCategories, todoCategories.id.equalsExp(todoItems.categoryId))
|
||||
]);
|
||||
|
|
|
@ -242,13 +242,6 @@ abstract class DatabaseConnectionUser {
|
|||
/// The [distinct] parameter (defaults to false) can be used to remove
|
||||
/// duplicate rows from the result set.
|
||||
///
|
||||
/// The [includeJoinedTableColumns] parameter (defaults to true) can be used
|
||||
/// to determinate join statement's `useColumns` parameter default value. Set
|
||||
/// it to false if you don't want to include joined table columns by default.
|
||||
/// If you leave it on true and don't set `useColumns` parameter to false in
|
||||
/// join declarations, all columns of joined table will be included in query
|
||||
/// by default.
|
||||
///
|
||||
/// For simple queries, use [select].
|
||||
///
|
||||
/// See also:
|
||||
|
@ -256,10 +249,9 @@ abstract class DatabaseConnectionUser {
|
|||
/// - the documentation on [group by](https://drift.simonbinder.eu/docs/advanced-features/joins/#group-by)
|
||||
JoinedSelectStatement<T, R> selectOnly<T extends HasResultSet, R>(
|
||||
ResultSetImplementation<T, R> table,
|
||||
{bool distinct = false,
|
||||
bool includeJoinedTableColumns = true}) {
|
||||
{bool distinct = false}) {
|
||||
return JoinedSelectStatement<T, R>(
|
||||
resolvedEngine, table, [], distinct, false, includeJoinedTableColumns);
|
||||
resolvedEngine, table, [], distinct, false, false);
|
||||
}
|
||||
|
||||
/// Starts a [DeleteStatement] that can be used to delete rows from a table.
|
||||
|
@ -625,11 +617,8 @@ class TableOrViewOperations<Tbl extends HasResultSet, Row> {
|
|||
/// 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);
|
||||
JoinedSelectStatement<Tbl, Row> selectOnly({bool distinct = false}) {
|
||||
return _user.selectOnly(_sourceSet, distinct: distinct);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,8 +102,8 @@ class Migrator {
|
|||
}
|
||||
}
|
||||
|
||||
GenerationContext _createContext() {
|
||||
return GenerationContext.fromDb(_db);
|
||||
GenerationContext _createContext({bool supportsVariables = false}) {
|
||||
return GenerationContext.fromDb(_db, supportsVariables: supportsVariables);
|
||||
}
|
||||
|
||||
/// Creates the given table if it doesn't exist
|
||||
|
@ -197,7 +197,7 @@ class Migrator {
|
|||
await createTable(temporaryTable);
|
||||
|
||||
// Step 5: Transfer old content into the new table
|
||||
final context = _createContext();
|
||||
final context = _createContext(supportsVariables: true);
|
||||
final expressionsForSelect = <Expression>[];
|
||||
|
||||
context.buffer.write('INSERT INTO $temporaryName (');
|
||||
|
|
|
@ -14,11 +14,8 @@ extension TableOrViewStatements<Tbl extends HasResultSet, Row>
|
|||
/// 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 attachedDatabase.selectOnly(this,
|
||||
distinct: distinct,
|
||||
includeJoinedTableColumns: includeJoinedTableColumns);
|
||||
JoinedSelectStatement<Tbl, Row> selectOnly({bool distinct = false}) {
|
||||
return attachedDatabase.selectOnly(this, distinct: distinct);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ void main() {
|
|||
|
||||
test('updates stream queries', () async {
|
||||
await db.batch((b) {
|
||||
b.insert(db.todosTable, TodoEntry(id: 3, content: 'content'));
|
||||
b.insert(db.todosTable, const TodoEntry(id: 3, content: 'content'));
|
||||
|
||||
b.update(db.users, const UsersCompanion(name: Value('new user name')));
|
||||
b.replace(
|
||||
|
|
|
@ -77,7 +77,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('generated data classes can be converted to companions', () {
|
||||
final entry = Category(
|
||||
const entry = Category(
|
||||
id: 3,
|
||||
description: 'description',
|
||||
priority: CategoryPriority.low,
|
||||
|
@ -97,7 +97,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('data classes can be converted to companions with null to absent', () {
|
||||
final entry = PureDefault(txt: null);
|
||||
const entry = PureDefault(txt: null);
|
||||
|
||||
expect(entry.toCompanion(false),
|
||||
const PureDefaultsCompanion(txt: Value(null)));
|
||||
|
|
|
@ -33,14 +33,18 @@ void main() {
|
|||
.go();
|
||||
|
||||
verify(executor.runDelete(
|
||||
'DELETE FROM users WHERE NOT is_awesome OR id < ?;', [100]));
|
||||
'DELETE FROM users WHERE NOT is_awesome OR id < ?;', const [100]));
|
||||
});
|
||||
|
||||
test('to delete an entity via a dataclasss', () async {
|
||||
await db.delete(db.sharedTodos).delete(SharedTodo(todo: 3, user: 2));
|
||||
await db
|
||||
.delete(db.sharedTodos)
|
||||
.delete(const SharedTodo(todo: 3, user: 2));
|
||||
|
||||
verify(executor.runDelete(
|
||||
'DELETE FROM shared_todos WHERE todo = ? AND user = ?;', [3, 2]));
|
||||
'DELETE FROM shared_todos WHERE todo = ? AND user = ?;',
|
||||
const [3, 2],
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -75,19 +79,19 @@ void main() {
|
|||
test('delete()', () async {
|
||||
await db.users.delete().go();
|
||||
|
||||
verify(executor.runDelete('DELETE FROM users;', []));
|
||||
verify(executor.runDelete('DELETE FROM users;', const []));
|
||||
});
|
||||
|
||||
test('deleteOne()', () async {
|
||||
await db.users.deleteOne(const UsersCompanion(id: Value(3)));
|
||||
|
||||
verify(executor.runDelete('DELETE FROM users WHERE id = ?;', [3]));
|
||||
verify(executor.runDelete('DELETE FROM users WHERE id = ?;', const [3]));
|
||||
});
|
||||
|
||||
test('deleteWhere', () async {
|
||||
await db.users.deleteWhere((tbl) => tbl.id.isSmallerThanValue(3));
|
||||
|
||||
verify(executor.runDelete('DELETE FROM users WHERE id < ?;', [3]));
|
||||
verify(executor.runDelete('DELETE FROM users WHERE id < ?;', const [3]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ void main() {
|
|||
|
||||
test('generates insert or replace statements', () async {
|
||||
await db.into(db.todosTable).insert(
|
||||
TodoEntry(
|
||||
const TodoEntry(
|
||||
id: 113,
|
||||
content: 'Done',
|
||||
),
|
||||
|
@ -452,7 +452,7 @@ void main() {
|
|||
CategoriesCompanion.insert(description: 'description'));
|
||||
expect(
|
||||
row,
|
||||
Category(
|
||||
const Category(
|
||||
id: 1,
|
||||
description: 'description',
|
||||
descriptionInUpperCase: 'DESCRIPTION',
|
||||
|
@ -503,7 +503,7 @@ void main() {
|
|||
CategoriesCompanion.insert(description: 'description'));
|
||||
expect(
|
||||
row,
|
||||
Category(
|
||||
const Category(
|
||||
id: 1,
|
||||
description: 'description',
|
||||
descriptionInUpperCase: 'DESCRIPTION',
|
||||
|
|
|
@ -72,7 +72,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
row.readTable(categories),
|
||||
Category(
|
||||
const Category(
|
||||
id: 3,
|
||||
description: 'description',
|
||||
priority: CategoryPriority.high,
|
||||
|
@ -107,7 +107,7 @@ void main() {
|
|||
expect(() => row.readTable(db.categories), throwsArgumentError);
|
||||
expect(
|
||||
row.readTable(db.todosTable),
|
||||
TodoEntry(
|
||||
const TodoEntry(
|
||||
id: 5,
|
||||
title: 'title',
|
||||
content: 'content',
|
||||
|
@ -224,7 +224,7 @@ void main() {
|
|||
expect(
|
||||
result.readTable(categories),
|
||||
equals(
|
||||
Category(
|
||||
const Category(
|
||||
id: 3,
|
||||
description: 'Description',
|
||||
descriptionInUpperCase: 'DESCRIPTION',
|
||||
|
@ -274,7 +274,7 @@ void main() {
|
|||
expect(
|
||||
result.readTable(categories),
|
||||
equals(
|
||||
Category(
|
||||
const Category(
|
||||
id: 3,
|
||||
description: 'Description',
|
||||
descriptionInUpperCase: 'DESCRIPTION',
|
||||
|
@ -330,7 +330,7 @@ void main() {
|
|||
expect(result.readTableOrNull(todos), isNull);
|
||||
expect(
|
||||
result.readTable(categories),
|
||||
Category(
|
||||
const Category(
|
||||
id: 3,
|
||||
description: 'desc',
|
||||
descriptionInUpperCase: 'DESC',
|
||||
|
@ -406,8 +406,7 @@ void main() {
|
|||
final categories = db.categories;
|
||||
final todos = db.todosTable;
|
||||
|
||||
final query =
|
||||
db.selectOnly(categories, includeJoinedTableColumns: false).join([
|
||||
final query = db.selectOnly(categories).join([
|
||||
innerJoin(
|
||||
todos,
|
||||
todos.category.equalsExp(categories.id),
|
||||
|
|
|
@ -187,6 +187,28 @@ void main() {
|
|||
verify(executor.transactions.runCustom(any, any));
|
||||
verifyNever(executor.runCustom(any, any));
|
||||
});
|
||||
|
||||
test('removes variables in `CREATE TABLE` statements', () async {
|
||||
final executor = MockExecutor();
|
||||
final db = _DefaultDb(executor);
|
||||
|
||||
late GeneratedColumn<int> column;
|
||||
column = GeneratedColumn<int>(
|
||||
'foo',
|
||||
'foo',
|
||||
true,
|
||||
type: DriftSqlType.int,
|
||||
check: () => column.isSmallerThan(const Variable(3)),
|
||||
);
|
||||
final table = CustomTable('foo', db, [column]);
|
||||
|
||||
await db.createMigrator().createTable(table);
|
||||
await db.close();
|
||||
|
||||
// This should not attempt to generate a parameter (`?`)
|
||||
// https://github.com/simolus3/drift/discussions/1936
|
||||
verify(executor.runCustom(argThat(contains('CHECK(foo < 3)')), []));
|
||||
});
|
||||
}
|
||||
|
||||
class _DefaultDb extends GeneratedDatabase {
|
||||
|
|
|
@ -14,7 +14,7 @@ final _dataOfTodoEntry = {
|
|||
'category': 3
|
||||
};
|
||||
|
||||
final _todoEntry = TodoEntry(
|
||||
const _todoEntry = TodoEntry(
|
||||
id: 10,
|
||||
title: 'A todo title',
|
||||
content: 'Content',
|
||||
|
@ -123,7 +123,7 @@ void main() {
|
|||
'category': null,
|
||||
}
|
||||
];
|
||||
final resolved = TodoEntry(
|
||||
const resolved = TodoEntry(
|
||||
id: 10,
|
||||
title: null,
|
||||
content: 'Content',
|
||||
|
@ -193,7 +193,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
category,
|
||||
Category(
|
||||
const Category(
|
||||
id: 1,
|
||||
description: 'description',
|
||||
descriptionInUpperCase: 'DESCRIPTION',
|
||||
|
|
|
@ -52,7 +52,7 @@ void main() {
|
|||
|
||||
group('generates replace statements', () {
|
||||
test('regular', () async {
|
||||
await db.update(db.todosTable).replace(TodoEntry(
|
||||
await db.update(db.todosTable).replace(const TodoEntry(
|
||||
id: 3,
|
||||
title: 'Title',
|
||||
content: 'Updated content',
|
||||
|
|
|
@ -43,7 +43,7 @@ void main() {
|
|||
final user = db.sharedTodos.mapFromCompanion(companion, db);
|
||||
expect(
|
||||
user,
|
||||
SharedTodo(todo: 3, user: 4),
|
||||
const SharedTodo(todo: 3, user: 4),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -58,7 +58,7 @@ void main() {
|
|||
final todo = db.todosTable.mapFromRowOrNull(QueryRow(rowData, db));
|
||||
expect(
|
||||
todo,
|
||||
TodoEntry(
|
||||
const TodoEntry(
|
||||
id: 1,
|
||||
title: 'some title',
|
||||
content: 'do this',
|
||||
|
|
|
@ -12,7 +12,7 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
final String? configValue;
|
||||
final SyncType? syncState;
|
||||
final SyncType? syncStateImplicit;
|
||||
Config(
|
||||
const Config(
|
||||
{required this.configKey,
|
||||
this.configValue,
|
||||
this.syncState,
|
||||
|
@ -292,7 +292,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
|||
class WithDefault extends DataClass implements Insertable<WithDefault> {
|
||||
final String? a;
|
||||
final int? b;
|
||||
WithDefault({this.a, this.b});
|
||||
const WithDefault({this.a, this.b});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
|
@ -565,7 +565,7 @@ class WithConstraint extends DataClass implements Insertable<WithConstraint> {
|
|||
final String? a;
|
||||
final int b;
|
||||
final double? c;
|
||||
WithConstraint({this.a, required this.b, this.c});
|
||||
const WithConstraint({this.a, required this.b, this.c});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
|
@ -783,7 +783,7 @@ class MytableData extends DataClass implements Insertable<MytableData> {
|
|||
final String? sometext;
|
||||
final bool? isInserting;
|
||||
final DateTime? somedate;
|
||||
MytableData(
|
||||
const MytableData(
|
||||
{required this.someid, this.sometext, this.isInserting, this.somedate});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
|
@ -1046,7 +1046,7 @@ class EMail extends DataClass implements Insertable<EMail> {
|
|||
final String sender;
|
||||
final String title;
|
||||
final String body;
|
||||
EMail({required this.sender, required this.title, required this.body});
|
||||
const EMail({required this.sender, required this.title, required this.body});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
|
@ -1261,7 +1261,7 @@ class Email extends Table
|
|||
class WeirdData extends DataClass implements Insertable<WeirdData> {
|
||||
final int sqlClass;
|
||||
final String textColumn;
|
||||
WeirdData({required this.sqlClass, required this.textColumn});
|
||||
const WeirdData({required this.sqlClass, required this.textColumn});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
|
@ -1444,7 +1444,7 @@ class MyViewData extends DataClass {
|
|||
final String? configValue;
|
||||
final SyncType? syncState;
|
||||
final SyncType? syncStateImplicit;
|
||||
MyViewData(
|
||||
const MyViewData(
|
||||
{required this.configKey,
|
||||
this.configValue,
|
||||
this.syncState,
|
||||
|
|
|
@ -12,7 +12,7 @@ class Category extends DataClass implements Insertable<Category> {
|
|||
final String description;
|
||||
final CategoryPriority priority;
|
||||
final String descriptionInUpperCase;
|
||||
Category(
|
||||
const Category(
|
||||
{required this.id,
|
||||
required this.description,
|
||||
required this.priority,
|
||||
|
@ -267,7 +267,7 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
|||
final String content;
|
||||
final DateTime? targetDate;
|
||||
final int? category;
|
||||
TodoEntry(
|
||||
const TodoEntry(
|
||||
{required this.id,
|
||||
this.title,
|
||||
required this.content,
|
||||
|
@ -570,7 +570,7 @@ class User extends DataClass implements Insertable<User> {
|
|||
final bool isAwesome;
|
||||
final Uint8List profilePicture;
|
||||
final DateTime creationTime;
|
||||
User(
|
||||
const User(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.isAwesome,
|
||||
|
@ -863,7 +863,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
|||
class SharedTodo extends DataClass implements Insertable<SharedTodo> {
|
||||
final int todo;
|
||||
final int user;
|
||||
SharedTodo({required this.todo, required this.user});
|
||||
const SharedTodo({required this.todo, required this.user});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
|
@ -1229,7 +1229,7 @@ class $TableWithoutPKTable extends TableWithoutPK
|
|||
|
||||
class PureDefault extends DataClass implements Insertable<PureDefault> {
|
||||
final MyCustomObject? txt;
|
||||
PureDefault({this.txt});
|
||||
const PureDefault({this.txt});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
|
@ -1381,7 +1381,7 @@ class $PureDefaultsTable extends PureDefaults
|
|||
class CategoryTodoCountViewData extends DataClass {
|
||||
final String description;
|
||||
final int itemCount;
|
||||
CategoryTodoCountViewData(
|
||||
const CategoryTodoCountViewData(
|
||||
{required this.description, required this.itemCount});
|
||||
factory CategoryTodoCountViewData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
|
@ -1477,8 +1477,7 @@ class $CategoryTodoCountViewView
|
|||
|
||||
@override
|
||||
Query? get query =>
|
||||
(attachedDatabase.selectOnly(categories, includeJoinedTableColumns: false)
|
||||
..addColumns($columns))
|
||||
(attachedDatabase.selectOnly(categories)..addColumns($columns))
|
||||
.join([innerJoin(todos, todos.category.equalsExp(categories.id))])
|
||||
..groupBy([categories.id]);
|
||||
@override
|
||||
|
@ -1488,7 +1487,7 @@ class $CategoryTodoCountViewView
|
|||
class TodoWithCategoryViewData extends DataClass {
|
||||
final String? title;
|
||||
final String description;
|
||||
TodoWithCategoryViewData({this.title, required this.description});
|
||||
const TodoWithCategoryViewData({this.title, required this.description});
|
||||
factory TodoWithCategoryViewData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
|
@ -1580,9 +1579,7 @@ class $TodoWithCategoryViewView
|
|||
}
|
||||
|
||||
@override
|
||||
Query? get query => (attachedDatabase.selectOnly(todos,
|
||||
includeJoinedTableColumns: false)
|
||||
..addColumns($columns))
|
||||
Query? get query => (attachedDatabase.selectOnly(todos)..addColumns($columns))
|
||||
.join([innerJoin(categories, categories.id.equalsExp(todos.category))]);
|
||||
@override
|
||||
Set<String> get readTables => const {'todos', 'categories'};
|
||||
|
|
|
@ -69,7 +69,7 @@ void main() {
|
|||
|
||||
test('can be used in a query stream', () async {
|
||||
final stream = db.readView().watch();
|
||||
final entry = Config(
|
||||
const entry = Config(
|
||||
configKey: 'another_key',
|
||||
configValue: 'value',
|
||||
syncState: SyncType.synchronized,
|
||||
|
@ -141,7 +141,7 @@ void main() {
|
|||
expect(result, hasLength(1));
|
||||
expect(
|
||||
result.single,
|
||||
Config(
|
||||
const Config(
|
||||
configKey: 'key2',
|
||||
configValue: 'val',
|
||||
syncState: SyncType.locallyCreated,
|
||||
|
|
|
@ -124,7 +124,7 @@ void main() {
|
|||
|
||||
verify(
|
||||
mock.runSelect('SELECT * FROM config WHERE config_key = ?1', ['key']));
|
||||
expect(parsed, Config(configKey: 'key', configValue: 'value'));
|
||||
expect(parsed, const Config(configKey: 'key', configValue: 'value'));
|
||||
});
|
||||
|
||||
test('applies default parameter expressions when not set', () async {
|
||||
|
@ -167,7 +167,7 @@ void main() {
|
|||
row: QueryRow(row, db),
|
||||
a: 'text for a',
|
||||
b: 42,
|
||||
c: WithConstraint(a: 'text', b: 1337, c: 18.7),
|
||||
c: const WithConstraint(a: 'text', b: 1337, c: 18.7),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
@ -216,7 +216,7 @@ void main() {
|
|||
final entry = await db.readConfig('key').getSingle();
|
||||
expect(
|
||||
entry,
|
||||
Config(
|
||||
const Config(
|
||||
configKey: 'key',
|
||||
configValue: 'value',
|
||||
syncState: SyncType.locallyUpdated,
|
||||
|
|
|
@ -104,7 +104,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
entry,
|
||||
Category(
|
||||
const Category(
|
||||
id: 1,
|
||||
description: 'Description',
|
||||
priority: CategoryPriority.low,
|
||||
|
|
|
@ -14,7 +14,12 @@ void main() {
|
|||
db.select(db.myView).watch(),
|
||||
emitsInOrder([
|
||||
isEmpty,
|
||||
[MyViewData(configKey: 'another', syncState: SyncType.synchronized)]
|
||||
[
|
||||
const MyViewData(
|
||||
configKey: 'another',
|
||||
syncState: SyncType.synchronized,
|
||||
),
|
||||
]
|
||||
]),
|
||||
);
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ void main() {
|
|||
final result = await db.todoWithCategoryView.select().getSingle();
|
||||
expect(
|
||||
result,
|
||||
TodoWithCategoryViewData(
|
||||
const TodoWithCategoryViewData(
|
||||
description: 'category description', title: 'title'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -171,7 +171,10 @@ void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,
|
|||
|
||||
await expectLater(stream, emits(null));
|
||||
await database.into(database.todosTable).insert(initialCompanion);
|
||||
await expectLater(stream, emits(TodoEntry(id: 1, content: 'my content')));
|
||||
await expectLater(
|
||||
stream,
|
||||
emits(const TodoEntry(id: 1, content: 'my content')),
|
||||
);
|
||||
});
|
||||
|
||||
test('can start transactions', () async {
|
||||
|
|
|
@ -29,3 +29,29 @@ class _NullExecutor extends Fake implements QueryExecutor {
|
|||
@override
|
||||
SqlDialect get dialect => SqlDialect.sqlite;
|
||||
}
|
||||
|
||||
class CustomTable extends Table with TableInfo<CustomTable, Null> {
|
||||
@override
|
||||
final String actualTableName;
|
||||
@override
|
||||
final DatabaseConnectionUser attachedDatabase;
|
||||
final List<GeneratedColumn<Object>> columns;
|
||||
final String? _alias;
|
||||
|
||||
CustomTable(this.actualTableName, this.attachedDatabase, this.columns,
|
||||
[this._alias]);
|
||||
|
||||
@override
|
||||
List<GeneratedColumn<Object>> get $columns => columns;
|
||||
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
|
||||
@override
|
||||
CustomTable createAlias(String alias) {
|
||||
return CustomTable(actualTableName, attachedDatabase, columns, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
Null map(Map<String, dynamic> data, {String? tablePrefix}) => null;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@ class DataClassWriter {
|
|||
}
|
||||
|
||||
// write constructor with named optional fields
|
||||
|
||||
if (!scope.options.generateMutableClasses) {
|
||||
_buffer.write('const ');
|
||||
}
|
||||
_buffer
|
||||
..write(table.dartTypeName)
|
||||
..write('({')
|
||||
|
|
|
@ -132,8 +132,8 @@ class ViewWriter extends TableOrViewWriter {
|
|||
buffer.write('@override\nQuery? get query => ');
|
||||
final query = view.viewQuery;
|
||||
if (query != null) {
|
||||
buffer.write('(attachedDatabase.selectOnly(${query.from}, '
|
||||
'includeJoinedTableColumns: false)..addColumns(\$columns))'
|
||||
buffer.write('(attachedDatabase.selectOnly(${query.from})'
|
||||
'..addColumns(\$columns))'
|
||||
'${query.query};');
|
||||
} else {
|
||||
buffer.write('null;\n');
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:drift_dev/src/model/model.dart';
|
|||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('removes leaading numbers', () {
|
||||
test('removes leading numbers', () {
|
||||
expect(dartNameForSqlColumn('foo'), 'foo');
|
||||
expect(dartNameForSqlColumn('123a'), 'a');
|
||||
});
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/file_system/memory_file_system.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:build_test/build_test.dart';
|
||||
import 'package:drift_dev/src/backends/build/drift_builder.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const _testInput = r'''
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
part 'main.moor.dart';
|
||||
|
||||
class Users extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text()();
|
||||
}
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [Users],
|
||||
)
|
||||
class Database extends _$Database {}
|
||||
''';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'generates const constructor for data classes can companion classes',
|
||||
() async {
|
||||
await testBuilder(
|
||||
DriftPartBuilder(const BuilderOptions({})),
|
||||
const {'a|lib/main.dart': _testInput},
|
||||
reader: await PackageAssetReader.currentIsolate(),
|
||||
outputs: const {
|
||||
'a|lib/main.moor.dart': _GeneratesConstDataClasses(
|
||||
{'User', 'UsersCompanion'},
|
||||
),
|
||||
},
|
||||
);
|
||||
},
|
||||
tags: 'analyzer',
|
||||
);
|
||||
}
|
||||
|
||||
class _GeneratesConstDataClasses extends Matcher {
|
||||
final Set<String> expectedWithConstConstructor;
|
||||
|
||||
const _GeneratesConstDataClasses(this.expectedWithConstConstructor);
|
||||
|
||||
@override
|
||||
Description describe(Description description) {
|
||||
return description.add('generates classes $expectedWithConstConstructor '
|
||||
'const constructor.');
|
||||
}
|
||||
|
||||
@override
|
||||
bool matches(dynamic desc, Map matchState) {
|
||||
// Parse the file, assure we don't have final fields in data classes.
|
||||
final resourceProvider = MemoryResourceProvider();
|
||||
if (desc is List<int>) {
|
||||
resourceProvider.newFileWithBytes('/foo.dart', desc);
|
||||
} else if (desc is String) {
|
||||
resourceProvider.newFile('/foo.dart', desc);
|
||||
} else {
|
||||
desc['desc'] = 'Neither a List<int> or String - cannot be parsed';
|
||||
return false;
|
||||
}
|
||||
|
||||
final parsed = parseFile(
|
||||
path: '/foo.dart',
|
||||
featureSet: FeatureSet.fromEnableFlags2(
|
||||
sdkLanguageVersion: Version(2, 12, 0),
|
||||
flags: const [],
|
||||
),
|
||||
resourceProvider: resourceProvider,
|
||||
throwIfDiagnostics: true,
|
||||
).unit;
|
||||
|
||||
final remaining = expectedWithConstConstructor.toSet();
|
||||
|
||||
final definedClasses = parsed.declarations.whereType<ClassDeclaration>();
|
||||
for (final definedClass in definedClasses) {
|
||||
if (expectedWithConstConstructor.contains(definedClass.name.name)) {
|
||||
final constructor = definedClass.getConstructor(null);
|
||||
if (constructor?.constKeyword == null) {
|
||||
matchState['desc'] = 'Constructor ${definedClass.name.name} is not '
|
||||
'const.';
|
||||
return false;
|
||||
}
|
||||
|
||||
remaining.remove(definedClass.name.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Also ensure that all expected classes were generated.
|
||||
if (remaining.isNotEmpty) {
|
||||
matchState['desc'] = 'Did not generate $remaining classes';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Description describeMismatch(dynamic item, Description mismatchDescription,
|
||||
Map matchState, bool verbose) {
|
||||
return mismatchDescription
|
||||
.add((matchState['desc'] as String?) ?? 'Had syntax errors');
|
||||
}
|
||||
}
|
|
@ -76,8 +76,9 @@ class AppDatabase extends _$AppDatabase {
|
|||
/// Returns an auto-updating stream of all todo entries in a given category
|
||||
/// id.
|
||||
Stream<List<TodoEntryWithCategory>> entriesInCategory(int? categoryId) {
|
||||
final query = select(todoEntries).join(
|
||||
[leftOuterJoin(categories, categories.id.equalsExp(todoEntries.id))]);
|
||||
final query = select(todoEntries).join([
|
||||
leftOuterJoin(categories, categories.id.equalsExp(todoEntries.category))
|
||||
]);
|
||||
|
||||
if (categoryId != null) {
|
||||
query.where(categories.id.equals(categoryId));
|
||||
|
|
|
@ -7,14 +7,14 @@ Thank you for understanding!
|
|||
|
||||
__To start using Drift, read our detailed [docs](https://drift.simonbinder.eu/docs/getting-started/).__
|
||||
|
||||
## 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)
|
||||
## Proudly Sponsored by [Stream 💙](https://getstream.io/chat/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22)
|
||||
|
||||
<p align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<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>
|
||||
<a href="https://getstream.io/chat/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22" 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/sdk/android/?utm_source=Moor&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Moor_July2022_AndroidChatSDK_klmh22" target="_blank">Try the Flutter Chat Tutorial  💬</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
Loading…
Reference in New Issue