Fix hashcodes, document joins

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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