Fix hashcodes, document joins

This commit is contained in:
Simon Binder 2019-04-06 13:08:54 +02:00
parent ab5ac57778
commit f28eb13b4c
No known key found for this signature in database
GPG Key ID: B807FDF954BA00CF
10 changed files with 149 additions and 37 deletions

View File

@ -2,6 +2,7 @@
layout: feature
title: Custom queries
parent: Writing queries
permalink: /queries/custom
---
# Custom statements

View File

@ -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
```

View File

@ -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) =>

View File

@ -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';

View File

@ -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.

View File

@ -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;
}
}

View File

@ -14,4 +14,4 @@ int $mrjf(int hash) {
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}

View File

@ -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) ||

View File

@ -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];
}

View File

@ -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)';
}
}
}