diff --git a/drift/example/main.dart b/drift/example/main.dart index f26af054..27f00d7b 100644 --- a/drift/example/main.dart +++ b/drift/example/main.dart @@ -27,6 +27,7 @@ abstract class TodoCategoryItemCount extends View { TodoCategories get todoCategories; Expression get itemCount => todoItems.id.count(); + // @override Query as() => select([ diff --git a/drift/example/main.g.dart b/drift/example/main.g.dart index 4c21a041..8bfa1c39 100644 --- a/drift/example/main.g.dart +++ b/drift/example/main.g.dart @@ -538,8 +538,9 @@ class $TodoCategoryItemCountView @override final _$Database attachedDatabase; $TodoCategoryItemCountView(this.attachedDatabase, [this._alias]); - $TodoItemsTable get todoItems => attachedDatabase.todoItems; - $TodoCategoriesTable get todoCategories => attachedDatabase.todoCategories; + $TodoItemsTable get todoItems => attachedDatabase.todoItems.createAlias('t0'); + $TodoCategoriesTable get todoCategories => + attachedDatabase.todoCategories.createAlias('t1'); @override List get $columns => [todoCategories.name, itemCount]; @override @@ -647,8 +648,9 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo< @override final _$Database attachedDatabase; $TodoItemWithCategoryNameViewView(this.attachedDatabase, [this._alias]); - $TodoItemsTable get todoItems => attachedDatabase.todoItems; - $TodoCategoriesTable get todoCategories => attachedDatabase.todoCategories; + $TodoItemsTable get todoItems => attachedDatabase.todoItems.createAlias('t0'); + $TodoCategoriesTable get todoCategories => + attachedDatabase.todoCategories.createAlias('t1'); @override List get $columns => [todoItems.id, title]; @override diff --git a/drift/lib/src/runtime/query_builder/migration.dart b/drift/lib/src/runtime/query_builder/migration.dart index 974a1b59..d87a8259 100644 --- a/drift/lib/src/runtime/query_builder/migration.dart +++ b/drift/lib/src/runtime/query_builder/migration.dart @@ -350,8 +350,11 @@ class Migrator { await _issueCustomQuery(stmt, const []); } else if (view.query != null) { final context = GenerationContext.fromDb(_db, supportsVariables: false); + final columnNames = view.$columns.map((e) => e.escapedName).join(', '); + context.generatingForView = view.entityName; - context.buffer.write('CREATE VIEW IF NOT EXISTS ${view.entityName} AS '); + context.buffer.write( + 'CREATE VIEW IF NOT EXISTS ${view.entityName} ($columnNames) AS '); view.query!.writeInto(context); await _issueCustomQuery(context.sql, const []); } diff --git a/drift/test/database/statements/schema_test.dart b/drift/test/database/statements/schema_test.dart index 8dc84cd2..af72af41 100644 --- a/drift/test/database/statements/schema_test.dart +++ b/drift/test/database/statements/schema_test.dart @@ -70,22 +70,24 @@ void main() { [])); verify(mockExecutor.runCustom( - 'CREATE VIEW IF NOT EXISTS category_todo_count_view AS SELECT ' - 'categories."desc" || \'!\' AS "description", ' - 'COUNT(todos.id) AS "item_count" ' - 'FROM categories ' - 'INNER JOIN todos ' - 'ON todos.category = categories.id ' - 'GROUP BY categories.id', + 'CREATE VIEW IF NOT EXISTS category_todo_count_view ' + '(description, item_count) AS SELECT ' + 't1."desc" || \'!\' AS "description", ' + 'COUNT(t0.id) AS "item_count" ' + 'FROM categories t1 ' + 'INNER JOIN todos t0 ' + 'ON t0.category = t1.id ' + 'GROUP BY t1.id', [])); verify(mockExecutor.runCustom( - 'CREATE VIEW IF NOT EXISTS todo_with_category_view AS SELECT ' - 'todos.title AS "todos.title", ' - 'categories."desc" AS "categories.desc" ' - 'FROM todos ' - 'INNER JOIN categories ' - 'ON categories.id = todos.category', + 'CREATE VIEW IF NOT EXISTS todo_with_category_view ' + '(title, "desc") AS SELECT ' + 't0.title AS "t0.title", ' + 't1."desc" AS "t1.desc" ' + 'FROM todos t0 ' + 'INNER JOIN categories t1 ' + 'ON t1.id = t0.category', [])); }); @@ -105,17 +107,18 @@ void main() { [])); }); - test('creates views through `create()`', () async { + test('creates views through create()', () async { await db.createMigrator().create(db.categoryTodoCountView); verify(mockExecutor.runCustom( - 'CREATE VIEW IF NOT EXISTS category_todo_count_view AS SELECT ' - 'categories."desc" || \'!\' AS "description", ' - 'COUNT(todos.id) AS "item_count" ' - 'FROM categories ' - 'INNER JOIN todos ' - 'ON todos.category = categories.id ' - 'GROUP BY categories.id', + 'CREATE VIEW IF NOT EXISTS category_todo_count_view ' + '(description, item_count) AS SELECT ' + 't1."desc" || \'!\' AS "description", ' + 'COUNT(t0.id) AS "item_count" ' + 'FROM categories t1 ' + 'INNER JOIN todos t0 ' + 'ON t0.category = t1.id ' + 'GROUP BY t1.id', [])); }); diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index 38856fe2..13f4332c 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -1460,8 +1460,9 @@ class $CategoryTodoCountViewView @override final _$TodoDb attachedDatabase; $CategoryTodoCountViewView(this.attachedDatabase, [this._alias]); - $TodosTableTable get todos => attachedDatabase.todosTable; - $CategoriesTable get categories => attachedDatabase.categories; + $TodosTableTable get todos => attachedDatabase.todosTable.createAlias('t0'); + $CategoriesTable get categories => + attachedDatabase.categories.createAlias('t1'); @override List get $columns => [description, itemCount]; @override @@ -1570,8 +1571,9 @@ class $TodoWithCategoryViewView @override final _$TodoDb attachedDatabase; $TodoWithCategoryViewView(this.attachedDatabase, [this._alias]); - $TodosTableTable get todos => attachedDatabase.todosTable; - $CategoriesTable get categories => attachedDatabase.categories; + $TodosTableTable get todos => attachedDatabase.todosTable.createAlias('t0'); + $CategoriesTable get categories => + attachedDatabase.categories.createAlias('t1'); @override List get $columns => [todos.title, categories.description]; @override diff --git a/drift_dev/lib/src/analyzer/dart/view_parser.dart b/drift_dev/lib/src/analyzer/dart/view_parser.dart index 4aebc043..e8283ef2 100644 --- a/drift_dev/lib/src/analyzer/dart/view_parser.dart +++ b/drift_dev/lib/src/analyzer/dart/view_parser.dart @@ -155,11 +155,12 @@ class ViewParser { final expression = (node.body as ExpressionFunctionBody).expression; return MoorColumn( - type: sqlType, - dartGetterName: field.name, - name: ColumnName.implicitly(ReCase(field.name).snakeCase), - nullable: dartType.nullabilitySuffix == NullabilitySuffix.question, - generatedAs: ColumnGeneratedAs(expression.toString(), false)); + type: sqlType, + dartGetterName: field.name, + name: ColumnName.implicitly(ReCase(field.name).snakeCase), + nullable: dartType.nullabilitySuffix == NullabilitySuffix.question, + generatedAs: ColumnGeneratedAs(expression.toString(), false), + ); }).toList()); return results.whereType(); @@ -275,8 +276,7 @@ class ViewParser { final column = columns.firstWhere((col) => col.dartGetterName == parts[0]); return MapEntry('${column.dartGetterName}', column); - }); - final columnMap = Map.fromEntries(columnList); + }).toList(); target = target.parent as MethodInvocation; if (target.methodName.toString() != 'from') { @@ -291,7 +291,7 @@ class ViewParser { final query = body.expression.toString().substring(target.toString().length); - return ViewQueryInformation(columnMap, from, query); + return ViewQueryInformation(columnList, from, query); } catch (e) { print(e); throw analysisError( diff --git a/drift_dev/lib/src/model/view.dart b/drift_dev/lib/src/model/view.dart index 0e299d9e..06086d3d 100644 --- a/drift_dev/lib/src/model/view.dart +++ b/drift_dev/lib/src/model/view.dart @@ -101,7 +101,9 @@ class MoorView extends MoorEntityWithResultSet { } class ViewQueryInformation { - final Map columns; + /// All columns from this Dart-defined view, in the order in which they were + /// added to the `query` getter. + final List> columns; final String from; final String query; diff --git a/drift_dev/lib/src/writer/tables/data_class_writer.dart b/drift_dev/lib/src/writer/tables/data_class_writer.dart index 1cb51b7c..fe8e7d9c 100644 --- a/drift_dev/lib/src/writer/tables/data_class_writer.dart +++ b/drift_dev/lib/src/writer/tables/data_class_writer.dart @@ -38,7 +38,7 @@ class DataClassWriter { // write view columns final view = table; if (view is MoorView && view.viewQuery != null) { - columns.addAll(view.viewQuery!.columns.values); + columns.addAll(view.viewQuery!.columns.map((e) => e.value)); } else { columns.addAll(table.columns); } diff --git a/drift_dev/lib/src/writer/tables/view_writer.dart b/drift_dev/lib/src/writer/tables/view_writer.dart index 71f33196..3c549306 100644 --- a/drift_dev/lib/src/writer/tables/view_writer.dart +++ b/drift_dev/lib/src/writer/tables/view_writer.dart @@ -49,9 +49,16 @@ class ViewWriter extends TableOrViewWriter { final declaration = view.declaration; if (declaration is DartViewDeclaration) { + // A view may read from the same table more than once, so we implicitly + // introduce aliases for tables. + var tableCounter = 0; + for (final ref in declaration.staticReferences) { - final declaration = '${ref.table.entityInfoName} get ${ref.name} => ' - 'attachedDatabase.${ref.table.dbGetterName};'; + final table = ref.table; + final alias = asDartLiteral('t${tableCounter++}'); + + final declaration = '${table.entityInfoName} get ${ref.name} => ' + 'attachedDatabase.${table.dbGetterName}.createAlias($alias);'; buffer.writeln(declaration); } } @@ -59,7 +66,7 @@ class ViewWriter extends TableOrViewWriter { if (view.viewQuery == null) { writeGetColumnsOverride(); } else { - final columns = view.viewQuery!.columns.keys.join(', '); + final columns = view.viewQuery!.columns.map((e) => e.key).join(', '); buffer.write('@override\nList get \$columns => ' '[$columns];\n'); } @@ -80,7 +87,8 @@ class ViewWriter extends TableOrViewWriter { writeAsDslTable(); writeMappingMethod(scope); - for (final column in view.viewQuery?.columns.values ?? view.columns) { + final columns = view.viewQuery?.columns.map((e) => e.value) ?? view.columns; + for (final column in columns) { writeColumnGetter(column, scope.generationOptions, false); }