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
|
.DS_Store
|
||||||
|
|
||||||
docs/**/*.g.dart
|
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)
|
[![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)
|
[![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">
|
<p align="center">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</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,17 +2,25 @@
|
||||||
data:
|
data:
|
||||||
title: "Custom queries"
|
title: "Custom queries"
|
||||||
weight: 10
|
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:
|
aliases:
|
||||||
- /queries/custom
|
- /queries/custom
|
||||||
template: layouts/docs/single
|
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
|
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
|
features like `WITH` clauses or some subqueries aren't supported yet.
|
||||||
use these features with custom statements. You don't have to miss out on other benefits
|
However, you can use methods like `customSelect` and `customStatement` to run advanced
|
||||||
drift brings, though: Drift helps you parse the result rows and custom queries also
|
statements on the database by writing the SQL manually.
|
||||||
support auto-updating streams.
|
|
||||||
|
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
|
## Statements with a generated api
|
||||||
|
|
||||||
|
@ -21,26 +29,29 @@ API for your select, update and delete statements. Of course, you can still writ
|
||||||
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:
|
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,
|
{% include "blocks/snippet" snippets = snippets name = "setup" %}
|
||||||
drift will figure out an appropriate type for them and include them in the generated methods. For instance,
|
|
||||||
|
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)`.
|
`'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" %}
|
{% 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
|
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
|
the underlying data changes. Using the todo example introduced in the
|
||||||
[getting started guide]({{ "../Getting started/index.md" | pageUrl }}), we can
|
[getting started guide]({{ "../Getting started/index.md" | pageUrl }}), we can
|
||||||
write this query which will load the amount of todo entries in each category:
|
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
|
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
|
reading. When using a `Stream`, drift will be able to know after which updates the stream should emit
|
||||||
items.
|
items.
|
||||||
|
|
|
@ -9,6 +9,14 @@
|
||||||
- To describe the type a column has, use the `DriftSqlType` enum
|
- 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`,
|
- To map a value from Dart to SQL and vice-versa, use an instance of `SqlTypes`,
|
||||||
reachable via `database.options.types`.
|
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`
|
- Consistently handle transaction errors like a failing `BEGIN` or `COMMIT`
|
||||||
across database implementations.
|
across database implementations.
|
||||||
- Support nested transactions.
|
- Support nested transactions.
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# Drift
|
# 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">
|
<p align="center">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -10,7 +10,7 @@ part of 'main.dart';
|
||||||
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
|
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
|
||||||
final int id;
|
final int id;
|
||||||
final String name;
|
final String name;
|
||||||
TodoCategory({required this.id, required this.name});
|
const TodoCategory({required this.id, required this.name});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
|
@ -185,7 +185,7 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
|
||||||
final String? content;
|
final String? content;
|
||||||
final int categoryId;
|
final int categoryId;
|
||||||
final String? generatedText;
|
final String? generatedText;
|
||||||
TodoItem(
|
const TodoItem(
|
||||||
{required this.id,
|
{required this.id,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.content,
|
this.content,
|
||||||
|
@ -466,7 +466,8 @@ class $TodoItemsTable extends TodoItems
|
||||||
class TodoCategoryItemCountData extends DataClass {
|
class TodoCategoryItemCountData extends DataClass {
|
||||||
final String name;
|
final String name;
|
||||||
final int itemCount;
|
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,
|
factory TodoCategoryItemCountData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
@ -558,10 +559,8 @@ class $TodoCategoryItemCountView
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Query? get query => (attachedDatabase.selectOnly(todoCategories,
|
Query? get query =>
|
||||||
includeJoinedTableColumns: false)
|
(attachedDatabase.selectOnly(todoCategories)..addColumns($columns)).join([
|
||||||
..addColumns($columns))
|
|
||||||
.join([
|
|
||||||
innerJoin(todoItems, todoItems.categoryId.equalsExp(todoCategories.id))
|
innerJoin(todoItems, todoItems.categoryId.equalsExp(todoCategories.id))
|
||||||
]);
|
]);
|
||||||
@override
|
@override
|
||||||
|
@ -571,7 +570,8 @@ class $TodoCategoryItemCountView
|
||||||
class TodoItemWithCategoryNameViewData extends DataClass {
|
class TodoItemWithCategoryNameViewData extends DataClass {
|
||||||
final int id;
|
final int id;
|
||||||
final String title;
|
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,
|
factory TodoItemWithCategoryNameViewData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
@ -668,9 +668,7 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Query? get query =>
|
Query? get query =>
|
||||||
(attachedDatabase.selectOnly(todoItems, includeJoinedTableColumns: false)
|
(attachedDatabase.selectOnly(todoItems)..addColumns($columns)).join([
|
||||||
..addColumns($columns))
|
|
||||||
.join([
|
|
||||||
innerJoin(
|
innerJoin(
|
||||||
todoCategories, todoCategories.id.equalsExp(todoItems.categoryId))
|
todoCategories, todoCategories.id.equalsExp(todoItems.categoryId))
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -242,13 +242,6 @@ abstract class DatabaseConnectionUser {
|
||||||
/// The [distinct] parameter (defaults to false) can be used to remove
|
/// The [distinct] parameter (defaults to false) can be used to remove
|
||||||
/// duplicate rows from the result set.
|
/// 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].
|
/// For simple queries, use [select].
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
|
@ -256,10 +249,9 @@ abstract class DatabaseConnectionUser {
|
||||||
/// - the documentation on [group by](https://drift.simonbinder.eu/docs/advanced-features/joins/#group-by)
|
/// - the documentation on [group by](https://drift.simonbinder.eu/docs/advanced-features/joins/#group-by)
|
||||||
JoinedSelectStatement<T, R> selectOnly<T extends HasResultSet, R>(
|
JoinedSelectStatement<T, R> selectOnly<T extends HasResultSet, R>(
|
||||||
ResultSetImplementation<T, R> table,
|
ResultSetImplementation<T, R> table,
|
||||||
{bool distinct = false,
|
{bool distinct = false}) {
|
||||||
bool includeJoinedTableColumns = true}) {
|
|
||||||
return JoinedSelectStatement<T, R>(
|
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.
|
/// 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.
|
/// Composes a `SELECT` statement only selecting a subset of columns.
|
||||||
///
|
///
|
||||||
/// This is equivalent to calling [DatabaseConnectionUser.selectOnly].
|
/// This is equivalent to calling [DatabaseConnectionUser.selectOnly].
|
||||||
JoinedSelectStatement<Tbl, Row> selectOnly(
|
JoinedSelectStatement<Tbl, Row> selectOnly({bool distinct = false}) {
|
||||||
{bool distinct = false, bool includeJoinedTableColumns = true}) {
|
return _user.selectOnly(_sourceSet, distinct: distinct);
|
||||||
return _user.selectOnly(_sourceSet,
|
|
||||||
distinct: distinct,
|
|
||||||
includeJoinedTableColumns: includeJoinedTableColumns);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,8 +102,8 @@ class Migrator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerationContext _createContext() {
|
GenerationContext _createContext({bool supportsVariables = false}) {
|
||||||
return GenerationContext.fromDb(_db);
|
return GenerationContext.fromDb(_db, supportsVariables: supportsVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the given table if it doesn't exist
|
/// Creates the given table if it doesn't exist
|
||||||
|
@ -197,7 +197,7 @@ class Migrator {
|
||||||
await createTable(temporaryTable);
|
await createTable(temporaryTable);
|
||||||
|
|
||||||
// Step 5: Transfer old content into the new table
|
// Step 5: Transfer old content into the new table
|
||||||
final context = _createContext();
|
final context = _createContext(supportsVariables: true);
|
||||||
final expressionsForSelect = <Expression>[];
|
final expressionsForSelect = <Expression>[];
|
||||||
|
|
||||||
context.buffer.write('INSERT INTO $temporaryName (');
|
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.
|
/// Composes a `SELECT` statement only selecting a subset of columns.
|
||||||
///
|
///
|
||||||
/// This is equivalent to calling [DatabaseConnectionUser.selectOnly].
|
/// This is equivalent to calling [DatabaseConnectionUser.selectOnly].
|
||||||
JoinedSelectStatement<Tbl, Row> selectOnly(
|
JoinedSelectStatement<Tbl, Row> selectOnly({bool distinct = false}) {
|
||||||
{bool distinct = false, bool includeJoinedTableColumns = true}) {
|
return attachedDatabase.selectOnly(this, distinct: distinct);
|
||||||
return attachedDatabase.selectOnly(this,
|
|
||||||
distinct: distinct,
|
|
||||||
includeJoinedTableColumns: includeJoinedTableColumns);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,7 +189,7 @@ void main() {
|
||||||
|
|
||||||
test('updates stream queries', () async {
|
test('updates stream queries', () async {
|
||||||
await db.batch((b) {
|
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.update(db.users, const UsersCompanion(name: Value('new user name')));
|
||||||
b.replace(
|
b.replace(
|
||||||
|
|
|
@ -77,7 +77,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generated data classes can be converted to companions', () {
|
test('generated data classes can be converted to companions', () {
|
||||||
final entry = Category(
|
const entry = Category(
|
||||||
id: 3,
|
id: 3,
|
||||||
description: 'description',
|
description: 'description',
|
||||||
priority: CategoryPriority.low,
|
priority: CategoryPriority.low,
|
||||||
|
@ -97,7 +97,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('data classes can be converted to companions with null to absent', () {
|
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),
|
expect(entry.toCompanion(false),
|
||||||
const PureDefaultsCompanion(txt: Value(null)));
|
const PureDefaultsCompanion(txt: Value(null)));
|
||||||
|
|
|
@ -33,14 +33,18 @@ void main() {
|
||||||
.go();
|
.go();
|
||||||
|
|
||||||
verify(executor.runDelete(
|
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 {
|
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(
|
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 {
|
test('delete()', () async {
|
||||||
await db.users.delete().go();
|
await db.users.delete().go();
|
||||||
|
|
||||||
verify(executor.runDelete('DELETE FROM users;', []));
|
verify(executor.runDelete('DELETE FROM users;', const []));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deleteOne()', () async {
|
test('deleteOne()', () async {
|
||||||
await db.users.deleteOne(const UsersCompanion(id: Value(3)));
|
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 {
|
test('deleteWhere', () async {
|
||||||
await db.users.deleteWhere((tbl) => tbl.id.isSmallerThanValue(3));
|
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 {
|
test('generates insert or replace statements', () async {
|
||||||
await db.into(db.todosTable).insert(
|
await db.into(db.todosTable).insert(
|
||||||
TodoEntry(
|
const TodoEntry(
|
||||||
id: 113,
|
id: 113,
|
||||||
content: 'Done',
|
content: 'Done',
|
||||||
),
|
),
|
||||||
|
@ -452,7 +452,7 @@ void main() {
|
||||||
CategoriesCompanion.insert(description: 'description'));
|
CategoriesCompanion.insert(description: 'description'));
|
||||||
expect(
|
expect(
|
||||||
row,
|
row,
|
||||||
Category(
|
const Category(
|
||||||
id: 1,
|
id: 1,
|
||||||
description: 'description',
|
description: 'description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
|
@ -503,7 +503,7 @@ void main() {
|
||||||
CategoriesCompanion.insert(description: 'description'));
|
CategoriesCompanion.insert(description: 'description'));
|
||||||
expect(
|
expect(
|
||||||
row,
|
row,
|
||||||
Category(
|
const Category(
|
||||||
id: 1,
|
id: 1,
|
||||||
description: 'description',
|
description: 'description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
|
|
|
@ -72,7 +72,7 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
row.readTable(categories),
|
row.readTable(categories),
|
||||||
Category(
|
const Category(
|
||||||
id: 3,
|
id: 3,
|
||||||
description: 'description',
|
description: 'description',
|
||||||
priority: CategoryPriority.high,
|
priority: CategoryPriority.high,
|
||||||
|
@ -107,7 +107,7 @@ void main() {
|
||||||
expect(() => row.readTable(db.categories), throwsArgumentError);
|
expect(() => row.readTable(db.categories), throwsArgumentError);
|
||||||
expect(
|
expect(
|
||||||
row.readTable(db.todosTable),
|
row.readTable(db.todosTable),
|
||||||
TodoEntry(
|
const TodoEntry(
|
||||||
id: 5,
|
id: 5,
|
||||||
title: 'title',
|
title: 'title',
|
||||||
content: 'content',
|
content: 'content',
|
||||||
|
@ -224,7 +224,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
result.readTable(categories),
|
result.readTable(categories),
|
||||||
equals(
|
equals(
|
||||||
Category(
|
const Category(
|
||||||
id: 3,
|
id: 3,
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
|
@ -274,7 +274,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
result.readTable(categories),
|
result.readTable(categories),
|
||||||
equals(
|
equals(
|
||||||
Category(
|
const Category(
|
||||||
id: 3,
|
id: 3,
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
|
@ -330,7 +330,7 @@ void main() {
|
||||||
expect(result.readTableOrNull(todos), isNull);
|
expect(result.readTableOrNull(todos), isNull);
|
||||||
expect(
|
expect(
|
||||||
result.readTable(categories),
|
result.readTable(categories),
|
||||||
Category(
|
const Category(
|
||||||
id: 3,
|
id: 3,
|
||||||
description: 'desc',
|
description: 'desc',
|
||||||
descriptionInUpperCase: 'DESC',
|
descriptionInUpperCase: 'DESC',
|
||||||
|
@ -406,8 +406,7 @@ void main() {
|
||||||
final categories = db.categories;
|
final categories = db.categories;
|
||||||
final todos = db.todosTable;
|
final todos = db.todosTable;
|
||||||
|
|
||||||
final query =
|
final query = db.selectOnly(categories).join([
|
||||||
db.selectOnly(categories, includeJoinedTableColumns: false).join([
|
|
||||||
innerJoin(
|
innerJoin(
|
||||||
todos,
|
todos,
|
||||||
todos.category.equalsExp(categories.id),
|
todos.category.equalsExp(categories.id),
|
||||||
|
|
|
@ -187,6 +187,28 @@ void main() {
|
||||||
verify(executor.transactions.runCustom(any, any));
|
verify(executor.transactions.runCustom(any, any));
|
||||||
verifyNever(executor.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 {
|
class _DefaultDb extends GeneratedDatabase {
|
||||||
|
|
|
@ -14,7 +14,7 @@ final _dataOfTodoEntry = {
|
||||||
'category': 3
|
'category': 3
|
||||||
};
|
};
|
||||||
|
|
||||||
final _todoEntry = TodoEntry(
|
const _todoEntry = TodoEntry(
|
||||||
id: 10,
|
id: 10,
|
||||||
title: 'A todo title',
|
title: 'A todo title',
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
|
@ -123,7 +123,7 @@ void main() {
|
||||||
'category': null,
|
'category': null,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
final resolved = TodoEntry(
|
const resolved = TodoEntry(
|
||||||
id: 10,
|
id: 10,
|
||||||
title: null,
|
title: null,
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
|
@ -193,7 +193,7 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
category,
|
category,
|
||||||
Category(
|
const Category(
|
||||||
id: 1,
|
id: 1,
|
||||||
description: 'description',
|
description: 'description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
|
|
|
@ -52,7 +52,7 @@ void main() {
|
||||||
|
|
||||||
group('generates replace statements', () {
|
group('generates replace statements', () {
|
||||||
test('regular', () async {
|
test('regular', () async {
|
||||||
await db.update(db.todosTable).replace(TodoEntry(
|
await db.update(db.todosTable).replace(const TodoEntry(
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
content: 'Updated content',
|
content: 'Updated content',
|
||||||
|
|
|
@ -43,7 +43,7 @@ void main() {
|
||||||
final user = db.sharedTodos.mapFromCompanion(companion, db);
|
final user = db.sharedTodos.mapFromCompanion(companion, db);
|
||||||
expect(
|
expect(
|
||||||
user,
|
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));
|
final todo = db.todosTable.mapFromRowOrNull(QueryRow(rowData, db));
|
||||||
expect(
|
expect(
|
||||||
todo,
|
todo,
|
||||||
TodoEntry(
|
const TodoEntry(
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'some title',
|
title: 'some title',
|
||||||
content: 'do this',
|
content: 'do this',
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Config extends DataClass implements Insertable<Config> {
|
||||||
final String? configValue;
|
final String? configValue;
|
||||||
final SyncType? syncState;
|
final SyncType? syncState;
|
||||||
final SyncType? syncStateImplicit;
|
final SyncType? syncStateImplicit;
|
||||||
Config(
|
const Config(
|
||||||
{required this.configKey,
|
{required this.configKey,
|
||||||
this.configValue,
|
this.configValue,
|
||||||
this.syncState,
|
this.syncState,
|
||||||
|
@ -292,7 +292,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
||||||
class WithDefault extends DataClass implements Insertable<WithDefault> {
|
class WithDefault extends DataClass implements Insertable<WithDefault> {
|
||||||
final String? a;
|
final String? a;
|
||||||
final int? b;
|
final int? b;
|
||||||
WithDefault({this.a, this.b});
|
const WithDefault({this.a, this.b});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
|
@ -565,7 +565,7 @@ class WithConstraint extends DataClass implements Insertable<WithConstraint> {
|
||||||
final String? a;
|
final String? a;
|
||||||
final int b;
|
final int b;
|
||||||
final double? c;
|
final double? c;
|
||||||
WithConstraint({this.a, required this.b, this.c});
|
const WithConstraint({this.a, required this.b, this.c});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
|
@ -783,7 +783,7 @@ class MytableData extends DataClass implements Insertable<MytableData> {
|
||||||
final String? sometext;
|
final String? sometext;
|
||||||
final bool? isInserting;
|
final bool? isInserting;
|
||||||
final DateTime? somedate;
|
final DateTime? somedate;
|
||||||
MytableData(
|
const MytableData(
|
||||||
{required this.someid, this.sometext, this.isInserting, this.somedate});
|
{required this.someid, this.sometext, this.isInserting, this.somedate});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
@ -1046,7 +1046,7 @@ class EMail extends DataClass implements Insertable<EMail> {
|
||||||
final String sender;
|
final String sender;
|
||||||
final String title;
|
final String title;
|
||||||
final String body;
|
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
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
|
@ -1261,7 +1261,7 @@ class Email extends Table
|
||||||
class WeirdData extends DataClass implements Insertable<WeirdData> {
|
class WeirdData extends DataClass implements Insertable<WeirdData> {
|
||||||
final int sqlClass;
|
final int sqlClass;
|
||||||
final String textColumn;
|
final String textColumn;
|
||||||
WeirdData({required this.sqlClass, required this.textColumn});
|
const WeirdData({required this.sqlClass, required this.textColumn});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
|
@ -1444,7 +1444,7 @@ class MyViewData extends DataClass {
|
||||||
final String? configValue;
|
final String? configValue;
|
||||||
final SyncType? syncState;
|
final SyncType? syncState;
|
||||||
final SyncType? syncStateImplicit;
|
final SyncType? syncStateImplicit;
|
||||||
MyViewData(
|
const MyViewData(
|
||||||
{required this.configKey,
|
{required this.configKey,
|
||||||
this.configValue,
|
this.configValue,
|
||||||
this.syncState,
|
this.syncState,
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
final String description;
|
final String description;
|
||||||
final CategoryPriority priority;
|
final CategoryPriority priority;
|
||||||
final String descriptionInUpperCase;
|
final String descriptionInUpperCase;
|
||||||
Category(
|
const Category(
|
||||||
{required this.id,
|
{required this.id,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.priority,
|
required this.priority,
|
||||||
|
@ -267,7 +267,7 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
||||||
final String content;
|
final String content;
|
||||||
final DateTime? targetDate;
|
final DateTime? targetDate;
|
||||||
final int? category;
|
final int? category;
|
||||||
TodoEntry(
|
const TodoEntry(
|
||||||
{required this.id,
|
{required this.id,
|
||||||
this.title,
|
this.title,
|
||||||
required this.content,
|
required this.content,
|
||||||
|
@ -570,7 +570,7 @@ class User extends DataClass implements Insertable<User> {
|
||||||
final bool isAwesome;
|
final bool isAwesome;
|
||||||
final Uint8List profilePicture;
|
final Uint8List profilePicture;
|
||||||
final DateTime creationTime;
|
final DateTime creationTime;
|
||||||
User(
|
const User(
|
||||||
{required this.id,
|
{required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.isAwesome,
|
required this.isAwesome,
|
||||||
|
@ -863,7 +863,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
||||||
class SharedTodo extends DataClass implements Insertable<SharedTodo> {
|
class SharedTodo extends DataClass implements Insertable<SharedTodo> {
|
||||||
final int todo;
|
final int todo;
|
||||||
final int user;
|
final int user;
|
||||||
SharedTodo({required this.todo, required this.user});
|
const SharedTodo({required this.todo, required this.user});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
|
@ -1229,7 +1229,7 @@ class $TableWithoutPKTable extends TableWithoutPK
|
||||||
|
|
||||||
class PureDefault extends DataClass implements Insertable<PureDefault> {
|
class PureDefault extends DataClass implements Insertable<PureDefault> {
|
||||||
final MyCustomObject? txt;
|
final MyCustomObject? txt;
|
||||||
PureDefault({this.txt});
|
const PureDefault({this.txt});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
|
@ -1381,7 +1381,7 @@ class $PureDefaultsTable extends PureDefaults
|
||||||
class CategoryTodoCountViewData extends DataClass {
|
class CategoryTodoCountViewData extends DataClass {
|
||||||
final String description;
|
final String description;
|
||||||
final int itemCount;
|
final int itemCount;
|
||||||
CategoryTodoCountViewData(
|
const CategoryTodoCountViewData(
|
||||||
{required this.description, required this.itemCount});
|
{required this.description, required this.itemCount});
|
||||||
factory CategoryTodoCountViewData.fromJson(Map<String, dynamic> json,
|
factory CategoryTodoCountViewData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
|
@ -1477,8 +1477,7 @@ class $CategoryTodoCountViewView
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Query? get query =>
|
Query? get query =>
|
||||||
(attachedDatabase.selectOnly(categories, includeJoinedTableColumns: false)
|
(attachedDatabase.selectOnly(categories)..addColumns($columns))
|
||||||
..addColumns($columns))
|
|
||||||
.join([innerJoin(todos, todos.category.equalsExp(categories.id))])
|
.join([innerJoin(todos, todos.category.equalsExp(categories.id))])
|
||||||
..groupBy([categories.id]);
|
..groupBy([categories.id]);
|
||||||
@override
|
@override
|
||||||
|
@ -1488,7 +1487,7 @@ class $CategoryTodoCountViewView
|
||||||
class TodoWithCategoryViewData extends DataClass {
|
class TodoWithCategoryViewData extends DataClass {
|
||||||
final String? title;
|
final String? title;
|
||||||
final String description;
|
final String description;
|
||||||
TodoWithCategoryViewData({this.title, required this.description});
|
const TodoWithCategoryViewData({this.title, required this.description});
|
||||||
factory TodoWithCategoryViewData.fromJson(Map<String, dynamic> json,
|
factory TodoWithCategoryViewData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
@ -1580,9 +1579,7 @@ class $TodoWithCategoryViewView
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Query? get query => (attachedDatabase.selectOnly(todos,
|
Query? get query => (attachedDatabase.selectOnly(todos)..addColumns($columns))
|
||||||
includeJoinedTableColumns: false)
|
|
||||||
..addColumns($columns))
|
|
||||||
.join([innerJoin(categories, categories.id.equalsExp(todos.category))]);
|
.join([innerJoin(categories, categories.id.equalsExp(todos.category))]);
|
||||||
@override
|
@override
|
||||||
Set<String> get readTables => const {'todos', 'categories'};
|
Set<String> get readTables => const {'todos', 'categories'};
|
||||||
|
|
|
@ -69,7 +69,7 @@ void main() {
|
||||||
|
|
||||||
test('can be used in a query stream', () async {
|
test('can be used in a query stream', () async {
|
||||||
final stream = db.readView().watch();
|
final stream = db.readView().watch();
|
||||||
final entry = Config(
|
const entry = Config(
|
||||||
configKey: 'another_key',
|
configKey: 'another_key',
|
||||||
configValue: 'value',
|
configValue: 'value',
|
||||||
syncState: SyncType.synchronized,
|
syncState: SyncType.synchronized,
|
||||||
|
@ -141,7 +141,7 @@ void main() {
|
||||||
expect(result, hasLength(1));
|
expect(result, hasLength(1));
|
||||||
expect(
|
expect(
|
||||||
result.single,
|
result.single,
|
||||||
Config(
|
const Config(
|
||||||
configKey: 'key2',
|
configKey: 'key2',
|
||||||
configValue: 'val',
|
configValue: 'val',
|
||||||
syncState: SyncType.locallyCreated,
|
syncState: SyncType.locallyCreated,
|
||||||
|
|
|
@ -124,7 +124,7 @@ void main() {
|
||||||
|
|
||||||
verify(
|
verify(
|
||||||
mock.runSelect('SELECT * FROM config WHERE config_key = ?1', ['key']));
|
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 {
|
test('applies default parameter expressions when not set', () async {
|
||||||
|
@ -167,7 +167,7 @@ void main() {
|
||||||
row: QueryRow(row, db),
|
row: QueryRow(row, db),
|
||||||
a: 'text for a',
|
a: 'text for a',
|
||||||
b: 42,
|
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();
|
final entry = await db.readConfig('key').getSingle();
|
||||||
expect(
|
expect(
|
||||||
entry,
|
entry,
|
||||||
Config(
|
const Config(
|
||||||
configKey: 'key',
|
configKey: 'key',
|
||||||
configValue: 'value',
|
configValue: 'value',
|
||||||
syncState: SyncType.locallyUpdated,
|
syncState: SyncType.locallyUpdated,
|
||||||
|
|
|
@ -104,7 +104,7 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
entry,
|
entry,
|
||||||
Category(
|
const Category(
|
||||||
id: 1,
|
id: 1,
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
priority: CategoryPriority.low,
|
priority: CategoryPriority.low,
|
||||||
|
|
|
@ -14,7 +14,12 @@ void main() {
|
||||||
db.select(db.myView).watch(),
|
db.select(db.myView).watch(),
|
||||||
emitsInOrder([
|
emitsInOrder([
|
||||||
isEmpty,
|
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();
|
final result = await db.todoWithCategoryView.select().getSingle();
|
||||||
expect(
|
expect(
|
||||||
result,
|
result,
|
||||||
TodoWithCategoryViewData(
|
const TodoWithCategoryViewData(
|
||||||
description: 'category description', title: 'title'));
|
description: 'category description', title: 'title'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,10 @@ void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,
|
||||||
|
|
||||||
await expectLater(stream, emits(null));
|
await expectLater(stream, emits(null));
|
||||||
await database.into(database.todosTable).insert(initialCompanion);
|
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 {
|
test('can start transactions', () async {
|
||||||
|
|
|
@ -29,3 +29,29 @@ class _NullExecutor extends Fake implements QueryExecutor {
|
||||||
@override
|
@override
|
||||||
SqlDialect get dialect => SqlDialect.sqlite;
|
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
|
// write constructor with named optional fields
|
||||||
|
|
||||||
|
if (!scope.options.generateMutableClasses) {
|
||||||
|
_buffer.write('const ');
|
||||||
|
}
|
||||||
_buffer
|
_buffer
|
||||||
..write(table.dartTypeName)
|
..write(table.dartTypeName)
|
||||||
..write('({')
|
..write('({')
|
||||||
|
|
|
@ -132,8 +132,8 @@ class ViewWriter extends TableOrViewWriter {
|
||||||
buffer.write('@override\nQuery? get query => ');
|
buffer.write('@override\nQuery? get query => ');
|
||||||
final query = view.viewQuery;
|
final query = view.viewQuery;
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
buffer.write('(attachedDatabase.selectOnly(${query.from}, '
|
buffer.write('(attachedDatabase.selectOnly(${query.from})'
|
||||||
'includeJoinedTableColumns: false)..addColumns(\$columns))'
|
'..addColumns(\$columns))'
|
||||||
'${query.query};');
|
'${query.query};');
|
||||||
} else {
|
} else {
|
||||||
buffer.write('null;\n');
|
buffer.write('null;\n');
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:drift_dev/src/model/model.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('removes leaading numbers', () {
|
test('removes leading numbers', () {
|
||||||
expect(dartNameForSqlColumn('foo'), 'foo');
|
expect(dartNameForSqlColumn('foo'), 'foo');
|
||||||
expect(dartNameForSqlColumn('123a'), 'a');
|
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
|
/// Returns an auto-updating stream of all todo entries in a given category
|
||||||
/// id.
|
/// id.
|
||||||
Stream<List<TodoEntryWithCategory>> entriesInCategory(int? categoryId) {
|
Stream<List<TodoEntryWithCategory>> entriesInCategory(int? categoryId) {
|
||||||
final query = select(todoEntries).join(
|
final query = select(todoEntries).join([
|
||||||
[leftOuterJoin(categories, categories.id.equalsExp(todoEntries.id))]);
|
leftOuterJoin(categories, categories.id.equalsExp(todoEntries.category))
|
||||||
|
]);
|
||||||
|
|
||||||
if (categoryId != null) {
|
if (categoryId != null) {
|
||||||
query.where(categories.id.equals(categoryId));
|
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/).__
|
__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">
|
<p align="center">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
Loading…
Reference in New Issue