Merge pull request #2212 from North101/develop

Allow join on views
This commit is contained in:
Simon Binder 2022-12-16 01:13:33 +01:00 committed by GitHub
commit dec5769dcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 22 deletions

View File

@ -27,7 +27,7 @@ class Join<T extends HasResultSet, D> extends Component {
final _JoinType _type;
/// The [TableInfo] that will be added to the query
final Table table;
final HasResultSet table;
/// For joins that aren't [_JoinType.cross], contains an additional predicate
/// that must be matched for the join.
@ -79,7 +79,7 @@ class Join<T extends HasResultSet, D> extends Component {
/// See also:
/// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins
/// - http://www.sqlitetutorial.net/sqlite-inner-join/
Join innerJoin(Table other, Expression<bool> on, {bool? useColumns}) {
Join innerJoin(HasResultSet other, Expression<bool> on, {bool? useColumns}) {
return Join._(_JoinType.inner, other, on, includeInResult: useColumns);
}
@ -91,7 +91,8 @@ Join innerJoin(Table other, Expression<bool> on, {bool? useColumns}) {
/// See also:
/// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins
/// - http://www.sqlitetutorial.net/sqlite-left-join/
Join leftOuterJoin(Table other, Expression<bool> on, {bool? useColumns}) {
Join leftOuterJoin(HasResultSet other, Expression<bool> on,
{bool? useColumns}) {
return Join._(_JoinType.leftOuter, other, on, includeInResult: useColumns);
}
@ -103,6 +104,6 @@ Join leftOuterJoin(Table other, Expression<bool> on, {bool? useColumns}) {
/// See also:
/// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins
/// - http://www.sqlitetutorial.net/sqlite-cross-join/
Join crossJoin(Table other, {bool? useColumns}) {
Join crossJoin(HasResultSet other, {bool? useColumns}) {
return Join._(_JoinType.cross, other, null, includeInResult: useColumns);
}

View File

@ -18,18 +18,34 @@ void main() {
test('generates join statements', () async {
final todos = db.alias(db.todosTable, 't');
final categories = db.alias(db.categories, 'c');
final categoryTodoCountView = db.alias(db.categoryTodoCountView, 'ct');
await db.select(todos).join([
leftOuterJoin(categories, categories.id.equalsExp(todos.category))
leftOuterJoin(categories, categories.id.equalsExp(todos.category)),
leftOuterJoin(categoryTodoCountView,
categoryTodoCountView.categoryId.equalsExp(categories.id)),
]).get();
verify(executor.runSelect(
'SELECT "t"."id" AS "t.id", "t"."title" AS "t.title", '
'"t"."content" AS "t.content", "t"."target_date" AS "t.target_date", '
'"t"."category" AS "t.category", "t"."status" AS "t.status", "c"."id" AS "c.id", '
'"c"."desc" AS "c.desc", "c"."priority" AS "c.priority", '
'"c"."description_in_upper_case" AS "c.description_in_upper_case" '
'FROM "todos" "t" LEFT OUTER JOIN "categories" "c" ON "c"."id" = "t"."category";',
'SELECT '
'"t"."id" AS "t.id", '
'"t"."title" AS "t.title", '
'"t"."content" AS "t.content", '
'"t"."target_date" AS "t.target_date", '
'"t"."category" AS "t.category", '
'"t"."status" AS "t.status", '
'"c"."id" AS "c.id", '
'"c"."desc" AS "c.desc", '
'"c"."priority" AS "c.priority", '
'"c"."description_in_upper_case" AS "c.description_in_upper_case", '
'"ct"."category_id" AS "ct.category_id", '
'"ct"."description" AS "ct.description", '
'"ct"."item_count" AS "ct.item_count" '
'FROM "todos" "t" '
'LEFT OUTER JOIN "categories" "c" '
'ON "c"."id" = "t"."category" '
'LEFT OUTER JOIN "category_todo_count_view" "ct" '
'ON "ct"."category_id" = "c"."id";',
argThat(isEmpty)));
});

View File

@ -71,7 +71,8 @@ void main() {
verify(mockExecutor.runCustom(
'CREATE VIEW IF NOT EXISTS "category_todo_count_view" '
'("description", "item_count") AS SELECT '
'("category_id", "description", "item_count") AS SELECT '
'"t1"."id" AS "category_id", '
'"t1"."desc" || \'!\' AS "description", '
'COUNT("t0"."id") AS "item_count" '
'FROM "categories" "t1" '
@ -112,7 +113,8 @@ void main() {
verify(mockExecutor.runCustom(
'CREATE VIEW IF NOT EXISTS "category_todo_count_view" '
'("description", "item_count") AS SELECT '
'("category_id", "description", "item_count") AS SELECT '
'"t1"."id" AS "category_id", '
'"t1"."desc" || \'!\' AS "description", '
'COUNT("t0"."id") AS "item_count" '
'FROM "categories" "t1" '

View File

@ -157,12 +157,13 @@ abstract class CategoryTodoCountView extends View {
TodosTable get todos;
Categories get categories;
Expression<int> get categoryId => categories.id;
Expression<String> get description =>
categories.description + const Variable('!');
Expression<int> get itemCount => todos.id.count();
@override
Query as() => select([description, itemCount])
Query as() => select([categoryId, description, itemCount])
.from(categories)
.join([innerJoin(todos, todos.category.equalsExp(categories.id))])
..groupBy([categories.id]);

View File

@ -1440,13 +1440,16 @@ class $PureDefaultsTable extends PureDefaults
}
class CategoryTodoCountViewData extends DataClass {
final int? categoryId;
final String? description;
final int? itemCount;
const CategoryTodoCountViewData({this.description, this.itemCount});
const CategoryTodoCountViewData(
{this.categoryId, this.description, this.itemCount});
factory CategoryTodoCountViewData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return CategoryTodoCountViewData(
categoryId: serializer.fromJson<int?>(json['categoryId']),
description: serializer.fromJson<String?>(json['description']),
itemCount: serializer.fromJson<int?>(json['itemCount']),
);
@ -1460,21 +1463,25 @@ class CategoryTodoCountViewData extends DataClass {
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'categoryId': serializer.toJson<int?>(categoryId),
'description': serializer.toJson<String?>(description),
'itemCount': serializer.toJson<int?>(itemCount),
};
}
CategoryTodoCountViewData copyWith(
{Value<String?> description = const Value.absent(),
{Value<int?> categoryId = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<int?> itemCount = const Value.absent()}) =>
CategoryTodoCountViewData(
categoryId: categoryId.present ? categoryId.value : this.categoryId,
description: description.present ? description.value : this.description,
itemCount: itemCount.present ? itemCount.value : this.itemCount,
);
@override
String toString() {
return (StringBuffer('CategoryTodoCountViewData(')
..write('categoryId: $categoryId, ')
..write('description: $description, ')
..write('itemCount: $itemCount')
..write(')'))
@ -1482,11 +1489,12 @@ class CategoryTodoCountViewData extends DataClass {
}
@override
int get hashCode => Object.hash(description, itemCount);
int get hashCode => Object.hash(categoryId, description, itemCount);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is CategoryTodoCountViewData &&
other.categoryId == this.categoryId &&
other.description == this.description &&
other.itemCount == this.itemCount);
}
@ -1502,7 +1510,7 @@ class $CategoryTodoCountViewView
$CategoriesTable get categories =>
attachedDatabase.categories.createAlias('t1');
@override
List<GeneratedColumn> get $columns => [description, itemCount];
List<GeneratedColumn> get $columns => [categoryId, description, itemCount];
@override
String get aliasedName => _alias ?? entityName;
@override
@ -1516,6 +1524,8 @@ class $CategoryTodoCountViewView
{String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return CategoryTodoCountViewData(
categoryId: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}category_id']),
description: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}description']),
itemCount: attachedDatabase.typeMapping
@ -1523,6 +1533,9 @@ class $CategoryTodoCountViewView
);
}
late final GeneratedColumn<int> categoryId = GeneratedColumn<int>(
'category_id', aliasedName, true,
generatedAs: GeneratedAs(categories.id, false), type: DriftSqlType.int);
late final GeneratedColumn<String> description = GeneratedColumn<String>(
'description', aliasedName, true,
generatedAs:

View File

@ -562,7 +562,7 @@ abstract class _$Database extends GeneratedDatabase {
],
readsFrom: {
friendships,
}).map((row) => row.read<int>('_c0'));
}).map((QueryRow row) => row.read<int>('_c0'));
}
Selectable<FriendshipsOfResult> friendshipsOf(int user) {
@ -574,7 +574,7 @@ abstract class _$Database extends GeneratedDatabase {
readsFrom: {
friendships,
users,
}).asyncMap((row) async {
}).asyncMap((QueryRow row) async {
return FriendshipsOfResult(
reallyGoodFriends: row.read<bool>('really_good_friends'),
user: await users.mapFromRow(row, tablePrefix: 'nested_0'),
@ -587,7 +587,7 @@ abstract class _$Database extends GeneratedDatabase {
variables: [],
readsFrom: {
users,
}).map((row) => row.read<int>('_c0'));
}).map((QueryRow row) => row.read<int>('_c0'));
}
Selectable<Preferences?> settingsFor(int user) {
@ -597,7 +597,7 @@ abstract class _$Database extends GeneratedDatabase {
],
readsFrom: {
users,
}).map((row) => $UsersTable.$converterpreferences
}).map((QueryRow row) => $UsersTable.$converterpreferences
.fromSql(row.readNullable<String>('preferences')));
}