mirror of https://github.com/AMT-Cheif/drift.git
Fix hashcodes, document joins
This commit is contained in:
parent
ab5ac57778
commit
f28eb13b4c
|
@ -2,6 +2,7 @@
|
|||
layout: feature
|
||||
title: Custom queries
|
||||
parent: Writing queries
|
||||
permalink: /queries/custom
|
||||
---
|
||||
|
||||
# Custom statements
|
||||
|
|
|
@ -7,4 +7,111 @@ permalink: /queries/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
|
||||
int get hashCode =>
|
||||
$moorjf($mrjf($mrjc(0, id.hashCode), description.hashCode));
|
||||
int get hashCode => $mrjf($mrjc($mrjc(0, id.hashCode), description.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
|
@ -180,9 +179,9 @@ class Recipe {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => $moorjf($mrjf(
|
||||
$mrjf(
|
||||
$mrjf($mrjc(0, id.hashCode), title.hashCode), instructions.hashCode),
|
||||
int get hashCode => $mrjf($mrjc(
|
||||
$mrjc(
|
||||
$mrjc($mrjc(0, id.hashCode), title.hashCode), instructions.hashCode),
|
||||
category.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
|
@ -337,8 +336,8 @@ class Ingredient {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => $moorjf($mrjf(
|
||||
$mrjf($mrjc(0, id.hashCode), name.hashCode), caloriesPer100g.hashCode));
|
||||
int get hashCode => $mrjf($mrjc(
|
||||
$mrjc($mrjc(0, id.hashCode), name.hashCode), caloriesPer100g.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
|
@ -482,8 +481,8 @@ class IngredientInRecipe {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => $moorjf($mrjf(
|
||||
$mrjf($mrjc(0, recipe.hashCode), ingredient.hashCode),
|
||||
int get hashCode => $mrjf($mrjc(
|
||||
$mrjc($mrjc(0, recipe.hashCode), ingredient.hashCode),
|
||||
amountInGrams.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
|
|
|
@ -26,4 +26,4 @@ export 'package:moor/src/runtime/database.dart';
|
|||
export 'package:moor/src/types/sql_types.dart';
|
||||
export 'package:moor/src/runtime/migration.dart';
|
||||
export 'package:moor/src/runtime/exceptions.dart';
|
||||
export 'package:moor/src/utils/hash.dart';
|
||||
export 'package:moor/src/utils/hash.dart';
|
||||
|
|
|
@ -80,6 +80,11 @@ class ColumnBuilder<
|
|||
/// The column will use this expression when a row is inserted and no value
|
||||
/// 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:
|
||||
/// - [Constant], which can be used to model literals that appear in CREATE
|
||||
/// TABLE statements.
|
||||
|
|
|
@ -118,7 +118,7 @@ class JoinedSelectStatement<FirstT, FirstD> extends Query<FirstT, FirstD>
|
|||
}
|
||||
}
|
||||
|
||||
return TypedResult(map);
|
||||
return TypedResult(map, QueryRow(row, database));
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
@ -262,13 +262,16 @@ class CustomSelectStatement {
|
|||
/// entities.
|
||||
class TypedResult {
|
||||
/// 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.
|
||||
D readTable<T, D>(TableInfo<T, D> table) {
|
||||
return _data[table] as D;
|
||||
return _parsedData[table] as D;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,4 +14,4 @@ int $mrjf(int hash) {
|
|||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,9 +78,9 @@ class TodoEntry {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => $moorjf($mrjf(
|
||||
$mrjf(
|
||||
$mrjf($mrjf($mrjc(0, id.hashCode), title.hashCode), content.hashCode),
|
||||
int get hashCode => $mrjf($mrjc(
|
||||
$mrjc(
|
||||
$mrjc($mrjc($mrjc(0, id.hashCode), title.hashCode), content.hashCode),
|
||||
targetDate.hashCode),
|
||||
category.hashCode));
|
||||
@override
|
||||
|
@ -251,8 +251,7 @@ class Category {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
$moorjf($mrjf($mrjc(0, id.hashCode), description.hashCode));
|
||||
int get hashCode => $mrjf($mrjc($mrjc(0, id.hashCode), description.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
|
@ -398,10 +397,10 @@ class User {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => $moorjf($mrjf(
|
||||
$mrjf(
|
||||
$mrjf(
|
||||
$mrjf($mrjc(0, id.hashCode), name.hashCode), isAwesome.hashCode),
|
||||
int get hashCode => $mrjf($mrjc(
|
||||
$mrjc(
|
||||
$mrjc(
|
||||
$mrjc($mrjc(0, id.hashCode), name.hashCode), isAwesome.hashCode),
|
||||
profilePicture.hashCode),
|
||||
creationTime.hashCode));
|
||||
@override
|
||||
|
@ -567,7 +566,7 @@ class SharedTodo {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => $moorjf($mrjf($mrjc(0, todo.hashCode), user.hashCode));
|
||||
int get hashCode => $mrjf($mrjc($mrjc(0, todo.hashCode), user.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
|
|
|
@ -66,10 +66,10 @@ class TodoEntry {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
(((id.hashCode) * 31 + content.hashCode) * 31 + targetDate.hashCode) *
|
||||
31 +
|
||||
category.hashCode;
|
||||
int get hashCode => $mrjf($mrjc(
|
||||
$mrjc(
|
||||
$mrjc($mrjc(0, id.hashCode), content.hashCode), targetDate.hashCode),
|
||||
category.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
|
@ -221,7 +221,7 @@ class Category {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => (id.hashCode) * 31 + description.hashCode;
|
||||
int get hashCode => $mrjf($mrjc($mrjc(0, id.hashCode), description.hashCode));
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
|
@ -300,8 +300,6 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
$TodosTable get todos => _todos ??= $TodosTable(this);
|
||||
$CategoriesTable _categories;
|
||||
$CategoriesTable get categories => _categories ??= $CategoriesTable(this);
|
||||
TodosDao _todosDao;
|
||||
TodosDao get todosDao => _todosDao ??= TodosDao(this as Database);
|
||||
@override
|
||||
List<TableInfo> get allTables => [todos, categories];
|
||||
}
|
||||
|
|
|
@ -38,8 +38,6 @@ class DataClassWriter {
|
|||
|
||||
_writeToString(buffer);
|
||||
|
||||
buffer.write('@override\n int get hashCode => ');
|
||||
|
||||
_writeHashCode(buffer);
|
||||
|
||||
// override ==
|
||||
|
@ -183,12 +181,14 @@ class DataClassWriter {
|
|||
}
|
||||
|
||||
void _writeHashCode(StringBuffer buffer) {
|
||||
buffer.write('@override\n int get hashCode => ');
|
||||
|
||||
if (table.columns.isEmpty) {
|
||||
buffer.write('identityHashCode(this); \n');
|
||||
} else {
|
||||
final fields = table.columns.map((c) => c.dartGetterName).toList();
|
||||
buffer
|
||||
..write('\$moorjf(')
|
||||
..write('$_hashFinish(')
|
||||
..write(_calculateHashCode(fields))
|
||||
..write(')')
|
||||
..write('; \n');
|
||||
|
@ -206,7 +206,7 @@ class DataClassWriter {
|
|||
final last = fields.removeLast();
|
||||
final innerHash = _calculateHashCode(fields);
|
||||
|
||||
return '$_hashFinish($innerHash, $last.hashCode)';
|
||||
return '$_hashCombine($innerHash, $last.hashCode)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue