mirror of https://github.com/AMT-Cheif/drift.git
Fix hashcodes, document joins
This commit is contained in:
parent
ab5ac57778
commit
438ba0e2cf
|
@ -2,6 +2,7 @@
|
||||||
layout: feature
|
layout: feature
|
||||||
title: Custom queries
|
title: Custom queries
|
||||||
parent: Writing queries
|
parent: Writing queries
|
||||||
|
permalink: /queries/custom
|
||||||
---
|
---
|
||||||
|
|
||||||
# Custom statements
|
# Custom statements
|
||||||
|
|
|
@ -7,4 +7,111 @@ permalink: /queries/joins
|
||||||
---
|
---
|
||||||
|
|
||||||
# Joins
|
# Joins
|
||||||
TBD
|
Moor supports sql joins to write queries that operate on more than one table. To use that feature, start
|
||||||
|
a select regular select statement with `select(table)` and then add a list of joins using `.join()`. For
|
||||||
|
inner and left outer joins, a `ON` expression needs to be specified. Here's an example using the tables
|
||||||
|
defined in the [example]({{ site.common_links.getting_started | absolute_url }}).
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// we define a data class to contain both a todo entry and the associated category
|
||||||
|
class EntryWithCategory {
|
||||||
|
EntryWithCategory(this.entry, this.category);
|
||||||
|
|
||||||
|
final TodoEntry entry;
|
||||||
|
final Category category;
|
||||||
|
}
|
||||||
|
|
||||||
|
// in the database class, we can then load the category for each entry
|
||||||
|
Stream<List<EntryWithCategory>> entriesWithCategory() {
|
||||||
|
final query = select(todos).join([
|
||||||
|
leftOuterJoin(categories, categories.id.equalsExp(todos.category)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// see next section on how to parse the result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parsing results
|
||||||
|
Calling `get()` or `watch` on a select statement with join returns a `Future` or `Stream` of
|
||||||
|
`List<TypedResult>` respectively. Each `TypedResult` represents a row from which data can be
|
||||||
|
read. It contains a `rawData` getter to obtain the raw row. But more importantly, the
|
||||||
|
`readTable` method can be used to read a data class from a table.
|
||||||
|
|
||||||
|
In the example query above, we can read the todo entry and the category from each row like this:
|
||||||
|
```dart
|
||||||
|
return query.watch().map((rows) {
|
||||||
|
return rows.map((row) {
|
||||||
|
return EntryWithCategory(
|
||||||
|
row.readTable(todos),
|
||||||
|
row.readTable(categories),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
_Note_: `readTable` returns `null` when an entity is not present in the row. For instance, todo entries
|
||||||
|
might not be in any category. In that case, `row.readTable(categories)` returns `null`.
|
||||||
|
## Aliases
|
||||||
|
Sometimes, a query references a table more than once. Consider the following example to store saved routes for a
|
||||||
|
navigation system:
|
||||||
|
```dart
|
||||||
|
class GeoPoints extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
TextColumn get latitude => text()();
|
||||||
|
TextColumn get longitude => text()();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Routes extends Table {
|
||||||
|
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
|
||||||
|
// contains the id for the start and destination geopoint.
|
||||||
|
IntColumn get start => integer()();
|
||||||
|
IntColumn get destination => integer()();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, let's say we wanted to also load the start and destination `GeoPoint` object for each route. We'd have to use
|
||||||
|
a join on the `geo-points` table twice: For the start and destination point. To express that in a query, aliases
|
||||||
|
can be used:
|
||||||
|
```dart
|
||||||
|
class RouteWithPoints {
|
||||||
|
final Route route;
|
||||||
|
final GeoPoint start;
|
||||||
|
final GeoPoint destination;
|
||||||
|
|
||||||
|
RouteWithPoints({this.route, this.start, this.destination});
|
||||||
|
}
|
||||||
|
|
||||||
|
// inside the database class:
|
||||||
|
Future<List<RouteWithPoints>> loadRoutes() async {
|
||||||
|
// create aliases for the geoPoints table so that we can reference it twice
|
||||||
|
final start = alias(geoPoints, 's');
|
||||||
|
final destination = alias(geoPoints, 'd');
|
||||||
|
|
||||||
|
final rows = await select(routes).join([
|
||||||
|
innerJoin(start, start.id.equalsExp(routes.start)),
|
||||||
|
innerJoin(destination, destination.id.equalsExp(routes.destination)),
|
||||||
|
]).get();
|
||||||
|
|
||||||
|
return rows.map((resultRow) {
|
||||||
|
return RouteWithPoints(
|
||||||
|
route: resultRow.readTable(routes),
|
||||||
|
start: resultRow.readTable(start),
|
||||||
|
destination: resultRow.readTable(destination),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The generated statement then looks like this:
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
routes.id, routes.name, routes.start, routes.destination,
|
||||||
|
s.id, s.name, s.latitude, s.longitude,
|
||||||
|
d.id, d.name, d.latitude, d.longitude
|
||||||
|
FROM routes
|
||||||
|
INNER JOIN geo_points s ON s.id = routes.start
|
||||||
|
INNER JOIN geo_points d ON d.id = routes.destination
|
||||||
|
```
|
|
@ -49,8 +49,7 @@ class Category {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => $mrjf($mrjc($mrjc(0, id.hashCode), description.hashCode));
|
||||||
$moorjf($mrjf($mrjc(0, id.hashCode), description.hashCode));
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
@ -180,9 +179,9 @@ class Recipe {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => $moorjf($mrjf(
|
int get hashCode => $mrjf($mrjc(
|
||||||
$mrjf(
|
$mrjc(
|
||||||
$mrjf($mrjc(0, id.hashCode), title.hashCode), instructions.hashCode),
|
$mrjc($mrjc(0, id.hashCode), title.hashCode), instructions.hashCode),
|
||||||
category.hashCode));
|
category.hashCode));
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
|
@ -337,8 +336,8 @@ class Ingredient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => $moorjf($mrjf(
|
int get hashCode => $mrjf($mrjc(
|
||||||
$mrjf($mrjc(0, id.hashCode), name.hashCode), caloriesPer100g.hashCode));
|
$mrjc($mrjc(0, id.hashCode), name.hashCode), caloriesPer100g.hashCode));
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
@ -482,8 +481,8 @@ class IngredientInRecipe {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => $moorjf($mrjf(
|
int get hashCode => $mrjf($mrjc(
|
||||||
$mrjf($mrjc(0, recipe.hashCode), ingredient.hashCode),
|
$mrjc($mrjc(0, recipe.hashCode), ingredient.hashCode),
|
||||||
amountInGrams.hashCode));
|
amountInGrams.hashCode));
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
|
|
|
@ -80,6 +80,11 @@ class ColumnBuilder<
|
||||||
/// The column will use this expression when a row is inserted and no value
|
/// The column will use this expression when a row is inserted and no value
|
||||||
/// has been specified.
|
/// has been specified.
|
||||||
///
|
///
|
||||||
|
/// Note: Unless most other methods used to declare tables, the parameter
|
||||||
|
/// [e] which denotes the default expression, doesn't have to be constant.
|
||||||
|
/// Particularly, you can use methods like [and], [or] and [not] to form
|
||||||
|
/// expressions here.
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// - [Constant], which can be used to model literals that appear in CREATE
|
/// - [Constant], which can be used to model literals that appear in CREATE
|
||||||
/// TABLE statements.
|
/// TABLE statements.
|
||||||
|
|
|
@ -118,7 +118,7 @@ class JoinedSelectStatement<FirstT, FirstD> extends Query<FirstT, FirstD>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypedResult(map);
|
return TypedResult(map, QueryRow(row, database));
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,13 +262,15 @@ class CustomSelectStatement {
|
||||||
/// entities.
|
/// entities.
|
||||||
class TypedResult {
|
class TypedResult {
|
||||||
/// Creates the result from the parsed table data.
|
/// Creates the result from the parsed table data.
|
||||||
TypedResult(this._data);
|
TypedResult(this._parsedData, this.rawData);
|
||||||
|
|
||||||
final Map<TableInfo, dynamic> _data;
|
final Map<TableInfo, dynamic> _parsedData;
|
||||||
|
/// The raw data contained in this row.
|
||||||
|
final QueryRow rawData;
|
||||||
|
|
||||||
/// Reads all data that belongs to the given [table] from this row.
|
/// Reads all data that belongs to the given [table] from this row.
|
||||||
D readTable<T, D>(TableInfo<T, D> table) {
|
D readTable<T, D>(TableInfo<T, D> table) {
|
||||||
return _data[table] as D;
|
return _parsedData[table] as D;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,9 +78,9 @@ class TodoEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => $moorjf($mrjf(
|
int get hashCode => $mrjf($mrjc(
|
||||||
$mrjf(
|
$mrjc(
|
||||||
$mrjf($mrjf($mrjc(0, id.hashCode), title.hashCode), content.hashCode),
|
$mrjc($mrjc($mrjc(0, id.hashCode), title.hashCode), content.hashCode),
|
||||||
targetDate.hashCode),
|
targetDate.hashCode),
|
||||||
category.hashCode));
|
category.hashCode));
|
||||||
@override
|
@override
|
||||||
|
@ -251,8 +251,7 @@ class Category {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => $mrjf($mrjc($mrjc(0, id.hashCode), description.hashCode));
|
||||||
$moorjf($mrjf($mrjc(0, id.hashCode), description.hashCode));
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
@ -398,10 +397,10 @@ class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => $moorjf($mrjf(
|
int get hashCode => $mrjf($mrjc(
|
||||||
$mrjf(
|
$mrjc(
|
||||||
$mrjf(
|
$mrjc(
|
||||||
$mrjf($mrjc(0, id.hashCode), name.hashCode), isAwesome.hashCode),
|
$mrjc($mrjc(0, id.hashCode), name.hashCode), isAwesome.hashCode),
|
||||||
profilePicture.hashCode),
|
profilePicture.hashCode),
|
||||||
creationTime.hashCode));
|
creationTime.hashCode));
|
||||||
@override
|
@override
|
||||||
|
@ -567,7 +566,7 @@ class SharedTodo {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => $moorjf($mrjf($mrjc(0, todo.hashCode), user.hashCode));
|
int get hashCode => $mrjf($mrjc($mrjc(0, todo.hashCode), user.hashCode));
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
|
|
@ -66,10 +66,10 @@ class TodoEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => $mrjf($mrjc(
|
||||||
(((id.hashCode) * 31 + content.hashCode) * 31 + targetDate.hashCode) *
|
$mrjc(
|
||||||
31 +
|
$mrjc($mrjc(0, id.hashCode), content.hashCode), targetDate.hashCode),
|
||||||
category.hashCode;
|
category.hashCode));
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
@ -221,7 +221,7 @@ class Category {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => (id.hashCode) * 31 + description.hashCode;
|
int get hashCode => $mrjf($mrjc($mrjc(0, id.hashCode), description.hashCode));
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
@ -300,8 +300,6 @@ abstract class _$Database extends GeneratedDatabase {
|
||||||
$TodosTable get todos => _todos ??= $TodosTable(this);
|
$TodosTable get todos => _todos ??= $TodosTable(this);
|
||||||
$CategoriesTable _categories;
|
$CategoriesTable _categories;
|
||||||
$CategoriesTable get categories => _categories ??= $CategoriesTable(this);
|
$CategoriesTable get categories => _categories ??= $CategoriesTable(this);
|
||||||
TodosDao _todosDao;
|
|
||||||
TodosDao get todosDao => _todosDao ??= TodosDao(this as Database);
|
|
||||||
@override
|
@override
|
||||||
List<TableInfo> get allTables => [todos, categories];
|
List<TableInfo> get allTables => [todos, categories];
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,6 @@ class DataClassWriter {
|
||||||
|
|
||||||
_writeToString(buffer);
|
_writeToString(buffer);
|
||||||
|
|
||||||
buffer.write('@override\n int get hashCode => ');
|
|
||||||
|
|
||||||
_writeHashCode(buffer);
|
_writeHashCode(buffer);
|
||||||
|
|
||||||
// override ==
|
// override ==
|
||||||
|
@ -183,12 +181,14 @@ class DataClassWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeHashCode(StringBuffer buffer) {
|
void _writeHashCode(StringBuffer buffer) {
|
||||||
|
buffer.write('@override\n int get hashCode => ');
|
||||||
|
|
||||||
if (table.columns.isEmpty) {
|
if (table.columns.isEmpty) {
|
||||||
buffer.write('identityHashCode(this); \n');
|
buffer.write('identityHashCode(this); \n');
|
||||||
} else {
|
} else {
|
||||||
final fields = table.columns.map((c) => c.dartGetterName).toList();
|
final fields = table.columns.map((c) => c.dartGetterName).toList();
|
||||||
buffer
|
buffer
|
||||||
..write('\$moorjf(')
|
..write('$_hashFinish(')
|
||||||
..write(_calculateHashCode(fields))
|
..write(_calculateHashCode(fields))
|
||||||
..write(')')
|
..write(')')
|
||||||
..write('; \n');
|
..write('; \n');
|
||||||
|
@ -206,7 +206,7 @@ class DataClassWriter {
|
||||||
final last = fields.removeLast();
|
final last = fields.removeLast();
|
||||||
final innerHash = _calculateHashCode(fields);
|
final innerHash = _calculateHashCode(fields);
|
||||||
|
|
||||||
return '$_hashFinish($innerHash, $last.hashCode)';
|
return '$_hashCombine($innerHash, $last.hashCode)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue