diff --git a/docs/docs/writing_queries/custom_queries.md b/docs/docs/writing_queries/custom_queries.md index 53d47c32..68226234 100644 --- a/docs/docs/writing_queries/custom_queries.md +++ b/docs/docs/writing_queries/custom_queries.md @@ -2,6 +2,7 @@ layout: feature title: Custom queries parent: Writing queries +permalink: /queries/custom --- # Custom statements diff --git a/docs/docs/writing_queries/joins.md b/docs/docs/writing_queries/joins.md index 9aafc451..2a3a7948 100644 --- a/docs/docs/writing_queries/joins.md +++ b/docs/docs/writing_queries/joins.md @@ -7,4 +7,111 @@ permalink: /queries/joins --- # Joins -TBD \ No newline at end of file +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> 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` 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> 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 +``` \ No newline at end of file diff --git a/moor/example/example.g.dart b/moor/example/example.g.dart index d4f20ac8..6c247967 100644 --- a/moor/example/example.g.dart +++ b/moor/example/example.g.dart @@ -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) => diff --git a/moor/lib/src/dsl/columns.dart b/moor/lib/src/dsl/columns.dart index 84de2e62..687d4715 100644 --- a/moor/lib/src/dsl/columns.dart +++ b/moor/lib/src/dsl/columns.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. diff --git a/moor/lib/src/runtime/statements/select.dart b/moor/lib/src/runtime/statements/select.dart index 25a4f8d3..687c14c8 100644 --- a/moor/lib/src/runtime/statements/select.dart +++ b/moor/lib/src/runtime/statements/select.dart @@ -118,7 +118,7 @@ class JoinedSelectStatement extends Query } } - return TypedResult(map); + return TypedResult(map, QueryRow(row, database)); }).toList(); } } @@ -262,13 +262,15 @@ class CustomSelectStatement { /// entities. class TypedResult { /// Creates the result from the parsed table data. - TypedResult(this._data); + TypedResult(this._parsedData, this.rawData); - final Map _data; + final Map _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(TableInfo table) { - return _data[table] as D; + return _parsedData[table] as D; } } diff --git a/moor/test/data/tables/todos.g.dart b/moor/test/data/tables/todos.g.dart index 4c970047..60da42b5 100644 --- a/moor/test/data/tables/todos.g.dart +++ b/moor/test/data/tables/todos.g.dart @@ -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) || diff --git a/moor_flutter/example/lib/database/database.g.dart b/moor_flutter/example/lib/database/database.g.dart index ad96897e..9684368c 100644 --- a/moor_flutter/example/lib/database/database.g.dart +++ b/moor_flutter/example/lib/database/database.g.dart @@ -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 get allTables => [todos, categories]; } diff --git a/moor_generator/lib/src/writer/data_class_writer.dart b/moor_generator/lib/src/writer/data_class_writer.dart index fedfac75..85ec70ae 100644 --- a/moor_generator/lib/src/writer/data_class_writer.dart +++ b/moor_generator/lib/src/writer/data_class_writer.dart @@ -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)'; } } }