Merge pull request #1541 from westito/views

Dsl views
This commit is contained in:
Simon Binder 2021-12-02 22:47:32 +01:00 committed by GitHub
commit 18206c8510
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1682 additions and 120 deletions

View File

@ -167,3 +167,66 @@ Applying a `customConstraint` will override all other constraints that would be
particular, that means that we need to also include the `NOT NULL` constraint again. particular, that means that we need to also include the `NOT NULL` constraint again.
You can also add table-wide constraints by overriding the `customConstraints` getter in your table class. You can also add table-wide constraints by overriding the `customConstraints` getter in your table class.
## References
[Foreign key references](https://www.sqlite.org/foreignkeys.html) can be expressed
in Dart tables with the `references()` method when building a column:
```dart
class Todos extends Table {
// ...
IntColumn get category => integer().nullable().references(Categories, #id)();
}
@DataClassName("Category")
class Categories extends Table {
IntColumn get id => integer().autoIncrement()();
// and more columns...
}
```
The first parameter to `references` points to the table on which a reference should be created.
The second parameter is a [symbol](https://dart.dev/guides/language/language-tour#symbols) of the column to use for the reference.
Optionally, the `onUpdate` and `onDelete` parameters can be used to describe what
should happen when the target row gets updated or deleted.
Be aware that, in sqlite3, foreign key references aren't enabled by default.
They need to be enabled with `PRAGMA foreign_keys = ON`.
A suitable place to issue that pragma with drift is in a [post-migration callback]({{ '../Advanced Features/migrations.md#post-migration-callbacks' | pageUrl }}).
## Views
It is also possible to define [SQL views](https://www.sqlite.org/lang_createview.html)
as Dart classes.
To do so, write an abstract class extending `View`. This example declares a view reading
the amount of todo-items added to a category in the schema from [the example]({{ 'index.md' | pageUrl }}):
```dart
abstract class CategoryTodoCount extends View {
TodosTable get todos;
Categories get categories;
Expression<int> get itemCount => todos.id.count();
@override
Query as() => select([categories.description, itemCount])
.from(categories)
.join([innerJoin(todos, todos.category.equalsExp(categories.id))]);
}
```
Inside a Dart view, use
- abstract getters to declare tables that you'll read from (e.g. `TodosTable get todos`)
- `Expression` getters to add columns: (e.g. `itemCount => todos.id.count()`);
- the overridden `as` method to define the select statement backing the view
Finally, a view needs to be added to a database or accessor by including it in the
`views` parameter:
```dart
@DriftDatabase(tables: [Todos, Categories], views: [CategoryTodoCount])
class MyDatabase extends _$MyDatabase {
```

View File

@ -5,18 +5,67 @@ import 'package:drift/native.dart';
part 'main.g.dart'; part 'main.g.dart';
@DataClassName('TodoCategory')
class TodoCategories extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
}
class TodoItems extends Table { class TodoItems extends Table {
IntColumn get id => integer().autoIncrement()(); IntColumn get id => integer().autoIncrement()();
TextColumn get title => text()(); TextColumn get title => text()();
TextColumn get content => text().nullable()(); TextColumn get content => text().nullable()();
IntColumn get categoryId => integer().references(TodoCategories, #id)();
TextColumn get generatedText => text().nullable().generatedAs(
title + const Constant(' (') + content + const Constant(')'))();
} }
@DriftDatabase(tables: [TodoItems]) abstract class TodoCategoryItemCount extends View {
TodoItems get todoItems;
TodoCategories get todoCategories;
Expression<int> get itemCount => todoItems.id.count();
@override
Query as() => select([
todoCategories.name,
itemCount,
]).from(todoCategories).join([
innerJoin(todoItems, todoItems.categoryId.equalsExp(todoCategories.id))
]);
}
@DriftView(name: 'customViewName')
abstract class TodoItemWithCategoryNameView extends View {
TodoItems get todoItems;
TodoCategories get todoCategories;
Expression<String> get title =>
todoItems.title +
const Constant('(') +
todoCategories.name +
const Constant(')');
@override
Query as() => select([todoItems.id, title]).from(todoItems).join([
innerJoin(
todoCategories, todoCategories.id.equalsExp(todoItems.categoryId))
]);
}
@DriftDatabase(tables: [
TodoItems,
TodoCategories,
], views: [
TodoCategoryItemCount,
TodoItemWithCategoryNameView,
])
class Database extends _$Database { class Database extends _$Database {
Database(QueryExecutor e) : super(e); Database(QueryExecutor e) : super(e);
@override @override
int get schemaVersion => 1; int get schemaVersion => 2;
@override @override
MigrationStrategy get migration { MigrationStrategy get migration {
@ -27,11 +76,12 @@ class Database extends _$Database {
// Add a bunch of default items in a batch // Add a bunch of default items in a batch
await batch((b) { await batch((b) {
b.insertAll(todoItems, [ b.insertAll(todoItems, [
TodoItemsCompanion.insert(title: 'A first entry'), TodoItemsCompanion.insert(title: 'A first entry', categoryId: 0),
TodoItemsCompanion.insert( TodoItemsCompanion.insert(
title: 'Todo: Checkout drift', title: 'Todo: Checkout drift',
content: const Value('Drift is a persistence library for Dart ' content: const Value('Drift is a persistence library for Dart '
'and Flutter applications.'), 'and Flutter applications.'),
categoryId: 0,
), ),
]); ]);
}); });
@ -55,10 +105,17 @@ Future<void> main() async {
print('Todo-item in database: $event'); print('Todo-item in database: $event');
}); });
// Add category
final categoryId = await db
.into(db.todoCategories)
.insert(TodoCategoriesCompanion.insert(name: 'Category'));
// Add another entry // Add another entry
await db await db.into(db.todoItems).insert(TodoItemsCompanion.insert(
.into(db.todoItems) title: 'Another entry added later', categoryId: categoryId));
.insert(TodoItemsCompanion.insert(title: 'Another entry added later'));
(await db.select(db.customViewName).get()).forEach(print);
(await db.select(db.todoCategoryItemCount).get()).forEach(print);
// Delete all todo items // Delete all todo items
await db.delete(db.todoItems).go(); await db.delete(db.todoItems).go();

View File

@ -7,11 +7,193 @@ part of 'main.dart';
// ************************************************************************** // **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this // ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
final int id;
final String name;
TodoCategory({required this.id, required this.name});
factory TodoCategory.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return TodoCategory(
id: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
name: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}name'])!,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['name'] = Variable<String>(name);
return map;
}
TodoCategoriesCompanion toCompanion(bool nullToAbsent) {
return TodoCategoriesCompanion(
id: Value(id),
name: Value(name),
);
}
factory TodoCategory.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodoCategory(
id: serializer.fromJson<int>(json['id']),
name: serializer.fromJson<String>(json['name']),
);
}
factory TodoCategory.fromJsonString(String encodedJson,
{ValueSerializer? serializer}) =>
TodoCategory.fromJson(
DataClass.parseJson(encodedJson) as Map<String, dynamic>,
serializer: serializer);
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'name': serializer.toJson<String>(name),
};
}
TodoCategory copyWith({int? id, String? name}) => TodoCategory(
id: id ?? this.id,
name: name ?? this.name,
);
@override
String toString() {
return (StringBuffer('TodoCategory(')
..write('id: $id, ')
..write('name: $name')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, name);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TodoCategory && other.id == this.id && other.name == this.name);
}
class TodoCategoriesCompanion extends UpdateCompanion<TodoCategory> {
final Value<int> id;
final Value<String> name;
const TodoCategoriesCompanion({
this.id = const Value.absent(),
this.name = const Value.absent(),
});
TodoCategoriesCompanion.insert({
this.id = const Value.absent(),
required String name,
}) : name = Value(name);
static Insertable<TodoCategory> custom({
Expression<int>? id,
Expression<String>? name,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (name != null) 'name': name,
});
}
TodoCategoriesCompanion copyWith({Value<int>? id, Value<String>? name}) {
return TodoCategoriesCompanion(
id: id ?? this.id,
name: name ?? this.name,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TodoCategoriesCompanion(')
..write('id: $id, ')
..write('name: $name')
..write(')'))
.toString();
}
}
class $TodoCategoriesTable extends TodoCategories
with TableInfo<$TodoCategoriesTable, TodoCategory> {
final GeneratedDatabase _db;
final String? _alias;
$TodoCategoriesTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
@override
List<GeneratedColumn> get $columns => [id, name];
@override
String get aliasedName => _alias ?? 'todo_categories';
@override
String get actualTableName => 'todo_categories';
@override
VerificationContext validateIntegrity(Insertable<TodoCategory> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('name')) {
context.handle(
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
} else if (isInserting) {
context.missing(_nameMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
TodoCategory map(Map<String, dynamic> data, {String? tablePrefix}) {
return TodoCategory.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
$TodoCategoriesTable createAlias(String alias) {
return $TodoCategoriesTable(_db, alias);
}
}
class TodoItem extends DataClass implements Insertable<TodoItem> { class TodoItem extends DataClass implements Insertable<TodoItem> {
final int id; final int id;
final String title; final String title;
final String? content; final String? content;
TodoItem({required this.id, required this.title, this.content}); final int categoryId;
final String? generatedText;
TodoItem(
{required this.id,
required this.title,
this.content,
required this.categoryId,
this.generatedText});
factory TodoItem.fromData(Map<String, dynamic> data, {String? prefix}) { factory TodoItem.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? ''; final effectivePrefix = prefix ?? '';
return TodoItem( return TodoItem(
@ -21,6 +203,10 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
.mapFromDatabaseResponse(data['${effectivePrefix}title'])!, .mapFromDatabaseResponse(data['${effectivePrefix}title'])!,
content: const StringType() content: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}content']), .mapFromDatabaseResponse(data['${effectivePrefix}content']),
categoryId: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}category_id'])!,
generatedText: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}generated_text']),
); );
} }
@override @override
@ -31,6 +217,7 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
if (!nullToAbsent || content != null) { if (!nullToAbsent || content != null) {
map['content'] = Variable<String?>(content); map['content'] = Variable<String?>(content);
} }
map['category_id'] = Variable<int>(categoryId);
return map; return map;
} }
@ -41,6 +228,7 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
content: content == null && nullToAbsent content: content == null && nullToAbsent
? const Value.absent() ? const Value.absent()
: Value(content), : Value(content),
categoryId: Value(categoryId),
); );
} }
@ -51,6 +239,8 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
id: serializer.fromJson<int>(json['id']), id: serializer.fromJson<int>(json['id']),
title: serializer.fromJson<String>(json['title']), title: serializer.fromJson<String>(json['title']),
content: serializer.fromJson<String?>(json['content']), content: serializer.fromJson<String?>(json['content']),
categoryId: serializer.fromJson<int>(json['categoryId']),
generatedText: serializer.fromJson<String?>(json['generatedText']),
); );
} }
factory TodoItem.fromJsonString(String encodedJson, factory TodoItem.fromJsonString(String encodedJson,
@ -65,71 +255,93 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
'id': serializer.toJson<int>(id), 'id': serializer.toJson<int>(id),
'title': serializer.toJson<String>(title), 'title': serializer.toJson<String>(title),
'content': serializer.toJson<String?>(content), 'content': serializer.toJson<String?>(content),
'categoryId': serializer.toJson<int>(categoryId),
'generatedText': serializer.toJson<String?>(generatedText),
}; };
} }
TodoItem copyWith( TodoItem copyWith(
{int? id, {int? id,
String? title, String? title,
Value<String?> content = const Value.absent()}) => Value<String?> content = const Value.absent(),
int? categoryId,
Value<String?> generatedText = const Value.absent()}) =>
TodoItem( TodoItem(
id: id ?? this.id, id: id ?? this.id,
title: title ?? this.title, title: title ?? this.title,
content: content.present ? content.value : this.content, content: content.present ? content.value : this.content,
categoryId: categoryId ?? this.categoryId,
generatedText:
generatedText.present ? generatedText.value : this.generatedText,
); );
@override @override
String toString() { String toString() {
return (StringBuffer('TodoItem(') return (StringBuffer('TodoItem(')
..write('id: $id, ') ..write('id: $id, ')
..write('title: $title, ') ..write('title: $title, ')
..write('content: $content') ..write('content: $content, ')
..write('categoryId: $categoryId, ')
..write('generatedText: $generatedText')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@override @override
int get hashCode => Object.hash(id, title, content); int get hashCode =>
Object.hash(id, title, content, categoryId, generatedText);
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
(other is TodoItem && (other is TodoItem &&
other.id == this.id && other.id == this.id &&
other.title == this.title && other.title == this.title &&
other.content == this.content); other.content == this.content &&
other.categoryId == this.categoryId &&
other.generatedText == this.generatedText);
} }
class TodoItemsCompanion extends UpdateCompanion<TodoItem> { class TodoItemsCompanion extends UpdateCompanion<TodoItem> {
final Value<int> id; final Value<int> id;
final Value<String> title; final Value<String> title;
final Value<String?> content; final Value<String?> content;
final Value<int> categoryId;
const TodoItemsCompanion({ const TodoItemsCompanion({
this.id = const Value.absent(), this.id = const Value.absent(),
this.title = const Value.absent(), this.title = const Value.absent(),
this.content = const Value.absent(), this.content = const Value.absent(),
this.categoryId = const Value.absent(),
}); });
TodoItemsCompanion.insert({ TodoItemsCompanion.insert({
this.id = const Value.absent(), this.id = const Value.absent(),
required String title, required String title,
this.content = const Value.absent(), this.content = const Value.absent(),
}) : title = Value(title); required int categoryId,
}) : title = Value(title),
categoryId = Value(categoryId);
static Insertable<TodoItem> custom({ static Insertable<TodoItem> custom({
Expression<int>? id, Expression<int>? id,
Expression<String>? title, Expression<String>? title,
Expression<String?>? content, Expression<String?>? content,
Expression<int>? categoryId,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
if (id != null) 'id': id, if (id != null) 'id': id,
if (title != null) 'title': title, if (title != null) 'title': title,
if (content != null) 'content': content, if (content != null) 'content': content,
if (categoryId != null) 'category_id': categoryId,
}); });
} }
TodoItemsCompanion copyWith( TodoItemsCompanion copyWith(
{Value<int>? id, Value<String>? title, Value<String?>? content}) { {Value<int>? id,
Value<String>? title,
Value<String?>? content,
Value<int>? categoryId}) {
return TodoItemsCompanion( return TodoItemsCompanion(
id: id ?? this.id, id: id ?? this.id,
title: title ?? this.title, title: title ?? this.title,
content: content ?? this.content, content: content ?? this.content,
categoryId: categoryId ?? this.categoryId,
); );
} }
@ -145,6 +357,9 @@ class TodoItemsCompanion extends UpdateCompanion<TodoItem> {
if (content.present) { if (content.present) {
map['content'] = Variable<String?>(content.value); map['content'] = Variable<String?>(content.value);
} }
if (categoryId.present) {
map['category_id'] = Variable<int>(categoryId.value);
}
return map; return map;
} }
@ -153,7 +368,8 @@ class TodoItemsCompanion extends UpdateCompanion<TodoItem> {
return (StringBuffer('TodoItemsCompanion(') return (StringBuffer('TodoItemsCompanion(')
..write('id: $id, ') ..write('id: $id, ')
..write('title: $title, ') ..write('title: $title, ')
..write('content: $content') ..write('content: $content, ')
..write('categoryId: $categoryId')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -165,21 +381,41 @@ class $TodoItemsTable extends TodoItems
final String? _alias; final String? _alias;
$TodoItemsTable(this._db, [this._alias]); $TodoItemsTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id'); final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>( late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false, 'id', aliasedName, false,
type: const IntType(), type: const IntType(),
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _titleMeta = const VerificationMeta('title'); final VerificationMeta _titleMeta = const VerificationMeta('title');
@override
late final GeneratedColumn<String?> title = GeneratedColumn<String?>( late final GeneratedColumn<String?> title = GeneratedColumn<String?>(
'title', aliasedName, false, 'title', aliasedName, false,
type: const StringType(), requiredDuringInsert: true); type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _contentMeta = const VerificationMeta('content'); final VerificationMeta _contentMeta = const VerificationMeta('content');
@override
late final GeneratedColumn<String?> content = GeneratedColumn<String?>( late final GeneratedColumn<String?> content = GeneratedColumn<String?>(
'content', aliasedName, true, 'content', aliasedName, true,
type: const StringType(), requiredDuringInsert: false); type: const StringType(), requiredDuringInsert: false);
final VerificationMeta _categoryIdMeta = const VerificationMeta('categoryId');
@override @override
List<GeneratedColumn> get $columns => [id, title, content]; late final GeneratedColumn<int?> categoryId = GeneratedColumn<int?>(
'category_id', aliasedName, false,
type: const IntType(),
requiredDuringInsert: true,
defaultConstraints: 'REFERENCES todo_categories (id)');
final VerificationMeta _generatedTextMeta =
const VerificationMeta('generatedText');
@override
late final GeneratedColumn<String?> generatedText = GeneratedColumn<String?>(
'generated_text', aliasedName, true,
type: const StringType(),
requiredDuringInsert: false,
generatedAs: GeneratedAs(
title + const Constant(' (') + content + const Constant(')'), false));
@override
List<GeneratedColumn> get $columns =>
[id, title, content, categoryId, generatedText];
@override @override
String get aliasedName => _alias ?? 'todo_items'; String get aliasedName => _alias ?? 'todo_items';
@override @override
@ -202,6 +438,20 @@ class $TodoItemsTable extends TodoItems
context.handle(_contentMeta, context.handle(_contentMeta,
content.isAcceptableOrUnknown(data['content']!, _contentMeta)); content.isAcceptableOrUnknown(data['content']!, _contentMeta));
} }
if (data.containsKey('category_id')) {
context.handle(
_categoryIdMeta,
categoryId.isAcceptableOrUnknown(
data['category_id']!, _categoryIdMeta));
} else if (isInserting) {
context.missing(_categoryIdMeta);
}
if (data.containsKey('generated_text')) {
context.handle(
_generatedTextMeta,
generatedText.isAcceptableOrUnknown(
data['generated_text']!, _generatedTextMeta));
}
return context; return context;
} }
@ -219,12 +469,236 @@ class $TodoItemsTable extends TodoItems
} }
} }
class TodoCategoryItemCountData extends DataClass {
final String name;
final int itemCount;
TodoCategoryItemCountData({required this.name, required this.itemCount});
factory TodoCategoryItemCountData.fromData(Map<String, dynamic> data,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return TodoCategoryItemCountData(
name: const StringType().mapFromDatabaseResponse(
data['${effectivePrefix}todo_categories.name'])!,
itemCount: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}item_count'])!,
);
}
factory TodoCategoryItemCountData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodoCategoryItemCountData(
name: serializer.fromJson<String>(json['name']),
itemCount: serializer.fromJson<int>(json['itemCount']),
);
}
factory TodoCategoryItemCountData.fromJsonString(String encodedJson,
{ValueSerializer? serializer}) =>
TodoCategoryItemCountData.fromJson(
DataClass.parseJson(encodedJson) as Map<String, dynamic>,
serializer: serializer);
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'name': serializer.toJson<String>(name),
'itemCount': serializer.toJson<int>(itemCount),
};
}
TodoCategoryItemCountData copyWith({String? name, int? itemCount}) =>
TodoCategoryItemCountData(
name: name ?? this.name,
itemCount: itemCount ?? this.itemCount,
);
@override
String toString() {
return (StringBuffer('TodoCategoryItemCountData(')
..write('name: $name, ')
..write('itemCount: $itemCount')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(name, itemCount);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TodoCategoryItemCountData &&
other.name == this.name &&
other.itemCount == this.itemCount);
}
class $TodoCategoryItemCountView
extends ViewInfo<$TodoCategoryItemCountView, TodoCategoryItemCountData>
implements HasResultSet {
final _$Database _db;
final String? _alias;
$TodoCategoryItemCountView(this._db, [this._alias]);
$TodoItemsTable get todoItems => _db.todoItems;
$TodoCategoriesTable get todoCategories => _db.todoCategories;
@override
List<GeneratedColumn> get $columns => [todoCategories.name, itemCount];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'todo_category_item_count';
@override
String? get createViewStmt => null;
@override
$TodoCategoryItemCountView get asDslTable => this;
@override
TodoCategoryItemCountData map(Map<String, dynamic> data,
{String? tablePrefix}) {
return TodoCategoryItemCountData.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false,
type: const StringType());
late final GeneratedColumn<int?> itemCount = GeneratedColumn<int?>(
'item_count', aliasedName, false,
type: const IntType(),
generatedAs: GeneratedAs(todoItems.id.count(), false));
@override
$TodoCategoryItemCountView createAlias(String alias) {
return $TodoCategoryItemCountView(_db, alias);
}
@override
Query? get query =>
(_db.selectOnly(todoCategories, includeJoinedTableColumns: false)
..addColumns($columns))
.join([
innerJoin(todoItems, todoItems.categoryId.equalsExp(todoCategories.id))
]);
}
class TodoItemWithCategoryNameViewData extends DataClass {
final int id;
final String title;
TodoItemWithCategoryNameViewData({required this.id, required this.title});
factory TodoItemWithCategoryNameViewData.fromData(Map<String, dynamic> data,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return TodoItemWithCategoryNameViewData(
id: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}todo_items.id'])!,
title: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}title'])!,
);
}
factory TodoItemWithCategoryNameViewData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodoItemWithCategoryNameViewData(
id: serializer.fromJson<int>(json['id']),
title: serializer.fromJson<String>(json['title']),
);
}
factory TodoItemWithCategoryNameViewData.fromJsonString(String encodedJson,
{ValueSerializer? serializer}) =>
TodoItemWithCategoryNameViewData.fromJson(
DataClass.parseJson(encodedJson) as Map<String, dynamic>,
serializer: serializer);
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'title': serializer.toJson<String>(title),
};
}
TodoItemWithCategoryNameViewData copyWith({int? id, String? title}) =>
TodoItemWithCategoryNameViewData(
id: id ?? this.id,
title: title ?? this.title,
);
@override
String toString() {
return (StringBuffer('TodoItemWithCategoryNameViewData(')
..write('id: $id, ')
..write('title: $title')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, title);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TodoItemWithCategoryNameViewData &&
other.id == this.id &&
other.title == this.title);
}
class $TodoItemWithCategoryNameViewView extends ViewInfo<
$TodoItemWithCategoryNameViewView,
TodoItemWithCategoryNameViewData> implements HasResultSet {
final _$Database _db;
final String? _alias;
$TodoItemWithCategoryNameViewView(this._db, [this._alias]);
$TodoItemsTable get todoItems => _db.todoItems;
$TodoCategoriesTable get todoCategories => _db.todoCategories;
@override
List<GeneratedColumn> get $columns => [todoItems.id, title];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'customViewName';
@override
String? get createViewStmt => null;
@override
$TodoItemWithCategoryNameViewView get asDslTable => this;
@override
TodoItemWithCategoryNameViewData map(Map<String, dynamic> data,
{String? tablePrefix}) {
return TodoItemWithCategoryNameViewData.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false,
type: const IntType(), defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
late final GeneratedColumn<String?> title = GeneratedColumn<String?>(
'title', aliasedName, false,
type: const StringType(),
generatedAs: GeneratedAs(
todoItems.title +
const Constant('(') +
todoCategories.name +
const Constant(')'),
false));
@override
$TodoItemWithCategoryNameViewView createAlias(String alias) {
return $TodoItemWithCategoryNameViewView(_db, alias);
}
@override
Query? get query =>
(_db.selectOnly(todoItems, includeJoinedTableColumns: false)
..addColumns($columns))
.join([
innerJoin(
todoCategories, todoCategories.id.equalsExp(todoItems.categoryId))
]);
}
abstract class _$Database extends GeneratedDatabase { abstract class _$Database extends GeneratedDatabase {
_$Database(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); _$Database(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
_$Database.connect(DatabaseConnection c) : super.connect(c); _$Database.connect(DatabaseConnection c) : super.connect(c);
late final $TodoCategoriesTable todoCategories = $TodoCategoriesTable(this);
late final $TodoItemsTable todoItems = $TodoItemsTable(this); late final $TodoItemsTable todoItems = $TodoItemsTable(this);
late final $TodoCategoryItemCountView todoCategoryItemCount =
$TodoCategoryItemCountView(this);
late final $TodoItemWithCategoryNameViewView customViewName =
$TodoItemWithCategoryNameViewView(this);
@override @override
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>(); Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
@override @override
List<DatabaseSchemaEntity> get allSchemaEntities => [todoItems]; List<DatabaseSchemaEntity> get allSchemaEntities =>
[todoCategories, todoItems, todoCategoryItemCount, customViewName];
} }

View File

@ -19,6 +19,9 @@ class DriftDatabase {
/// The tables to include in the database /// The tables to include in the database
final List<Type> tables; final List<Type> tables;
/// The views to include in the database
final List<Type> views;
/// Optionally, the list of daos to use. A dao can also make queries like a /// Optionally, the list of daos to use. A dao can also make queries like a
/// regular database class, making is suitable to extract parts of your /// regular database class, making is suitable to extract parts of your
/// database logic into smaller components. /// database logic into smaller components.
@ -58,6 +61,7 @@ class DriftDatabase {
/// class should be generated using the specified [DriftDatabase.tables]. /// class should be generated using the specified [DriftDatabase.tables].
const DriftDatabase({ const DriftDatabase({
this.tables = const [], this.tables = const [],
this.views = const [],
this.daos = const [], this.daos = const [],
this.queries = const {}, this.queries = const {},
this.include = const {}, this.include = const {},
@ -91,6 +95,9 @@ class DriftAccessor {
/// The tables accessed by this DAO. /// The tables accessed by this DAO.
final List<Type> tables; final List<Type> tables;
/// The views to make accessible in this DAO.
final List<Type> views;
/// {@macro drift_compile_queries_param} /// {@macro drift_compile_queries_param}
final Map<String, String> queries; final Map<String, String> queries;
@ -101,6 +108,7 @@ class DriftAccessor {
/// the referenced documentation on how to use daos with drift. /// the referenced documentation on how to use daos with drift.
const DriftAccessor({ const DriftAccessor({
this.tables = const [], this.tables = const [],
this.views = const [],
this.queries = const {}, this.queries = const {},
this.include = const {}, this.include = const {},
}); });

View File

@ -7,6 +7,8 @@ abstract class HasResultSet {
} }
/// Subclasses represent a table in a database generated by drift. /// Subclasses represent a table in a database generated by drift.
///
/// For more information on how to write tables, see [the documentation](https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/)
abstract class Table extends HasResultSet { abstract class Table extends HasResultSet {
/// Defines a table to be used with drift. /// Defines a table to be used with drift.
const Table(); const Table();
@ -118,6 +120,46 @@ abstract class Table extends HasResultSet {
ColumnBuilder<double> real() => _isGenerated(); ColumnBuilder<double> real() => _isGenerated();
} }
/// Subclasses represent a view in a database generated by drift.
///
/// For more information on how to define views in Dart, see
/// [the documentation](https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#views)
abstract class View extends HasResultSet {
/// Defines a view to be used with drift.
const View();
/// The select method can be used in [as] to define the select query backing
/// this view.
///
/// The select statement should select all columns defined on this view.
@protected
View select(List<Expression> columns) => _isGenerated();
/// This method should be called on [select] to define the main table of this
/// view:
///
/// ```dart
/// abstract class CategoryTodoCount extends View {
/// TodosTable get todos;
/// Categories get categories;
///
/// Expression<int> get itemCount => todos.id.count();
///
/// @override
/// Query as() => select([categories.description, itemCount])
/// .from(categories)
/// .join([innerJoin(todos, todos.category.equalsExp(categories.id))]);
/// }
/// ```
@protected
SimpleSelectStatement from(Table table) => _isGenerated();
/// This method is overridden by Dart-defined views to declare the right
/// query to run.
@visibleForOverriding
Query as();
}
/// A class to be used as an annotation on [Table] classes to customize the /// A class to be used as an annotation on [Table] classes to customize the
/// name for the data class that will be generated for the table class. The data /// name for the data class that will be generated for the table class. The data
/// class is a dart object that will be used to represent a row in the table. /// class is a dart object that will be used to represent a row in the table.
@ -169,3 +211,26 @@ class UseRowClass {
const UseRowClass(this.type, const UseRowClass(this.type,
{this.constructor = '', this.generateInsertable = false}); {this.constructor = '', this.generateInsertable = false});
} }
/// An annotation specifying view properties
@Target({TargetKind.classType})
class DriftView {
/// The sql view name to be used. By default, drift will use the snake_case
/// representation of your class name as the sql view name. For instance, a
/// [View] class named `UserView` will be called `user_view` by
/// default.
final String? name;
/// The name for the data class that will be generated for the view class.
/// The data class is a dart object that will be used to represent a result of
/// the view.
/// {@template drift_custom_data_class}
/// By default, drift will attempt to use the view name followed by "Data"
/// when naming data classes (e.g. a view named "UserView" will generate a
/// data class called "UserViewData").
/// {@macro drift_custom_data_class}
final String? dataClassName;
/// Customize view name and data class name
const DriftView({this.name, this.dataClassName});
}

View File

@ -223,17 +223,24 @@ abstract class DatabaseConnectionUser {
/// The [distinct] parameter (defaults to false) can be used to remove /// The [distinct] parameter (defaults to false) can be used to remove
/// duplicate rows from the result set. /// duplicate rows from the result set.
/// ///
/// The [includeJoinedTableColumns] parameter (defaults to true) can be used
/// to determinate join statement's `useColumns` parameter default value. Set
/// it to false if you don't want to include joined table columns by default.
/// If you leave it on true and don't set `useColumns` parameter to false in
/// join declarations, all columns of joined table will be included in query
/// by default.
///
/// For simple queries, use [select]. /// For simple queries, use [select].
/// ///
/// See also: /// See also:
/// - the documentation on [aggregate expressions](https://drift.simonbinder.eu/docs/getting-started/expressions/#aggregate) /// - the documentation on [aggregate expressions](https://drift.simonbinder.eu/docs/getting-started/expressions/#aggregate)
/// - the documentation on [group by](https://drift.simonbinder.eu/docs/advanced-features/joins/#group-by) /// - the documentation on [group by](https://drift.simonbinder.eu/docs/advanced-features/joins/#group-by)
JoinedSelectStatement<T, R> selectOnly<T extends HasResultSet, R>( JoinedSelectStatement<T, R> selectOnly<T extends HasResultSet, R>(
ResultSetImplementation<T, R> table, { ResultSetImplementation<T, R> table,
bool distinct = false, {bool distinct = false,
}) { bool includeJoinedTableColumns = true}) {
return JoinedSelectStatement<T, R>( return JoinedSelectStatement<T, R>(
resolvedEngine, table, [], distinct, false); resolvedEngine, table, [], distinct, false, includeJoinedTableColumns);
} }
/// Starts a [DeleteStatement] that can be used to delete rows from a table. /// Starts a [DeleteStatement] that can be used to delete rows from a table.

View File

@ -27,30 +27,39 @@ class Join<T extends HasResultSet, D> extends Component {
final _JoinType type; final _JoinType type;
/// The [TableInfo] that will be added to the query /// The [TableInfo] that will be added to the query
final ResultSetImplementation<T, D> table; final Table table;
/// For joins that aren't [_JoinType.cross], contains an additional predicate /// For joins that aren't [_JoinType.cross], contains an additional predicate
/// that must be matched for the join. /// that must be matched for the join.
final Expression<bool?>? on; final Expression<bool?>? on;
/// Whether [table] should appear in the result set (defaults to true). /// Whether [table] should appear in the result set (defaults to true).
/// Default value can be changed by `includeJoinedTableColumns` in
/// `selectOnly` statements.
/// ///
/// It can be useful to exclude some tables. Sometimes, tables are used in a /// It can be useful to exclude some tables. Sometimes, tables are used in a
/// join only to run aggregate functions on them. /// join only to run aggregate functions on them.
final bool includeInResult; final bool? includeInResult;
/// Constructs a [Join] by providing the relevant fields. [on] is optional for /// Constructs a [Join] by providing the relevant fields. [on] is optional for
/// [_JoinType.cross]. /// [_JoinType.cross].
Join._(this.type, this.table, this.on, {bool? includeInResult}) Join._(this.type, this.table, this.on, {this.includeInResult}) {
: includeInResult = includeInResult ?? true; if (table is! ResultSetImplementation<T, D>) {
throw ArgumentError(
'Invalid table parameter. You must provide the table reference from '
'generated database object.',
'table');
}
}
@override @override
void writeInto(GenerationContext context) { void writeInto(GenerationContext context) {
context.buffer.write(_joinKeywords[type]); context.buffer.write(_joinKeywords[type]);
context.buffer.write(' JOIN '); context.buffer.write(' JOIN ');
context.buffer.write(table.tableWithAlias); final resultSet = table as ResultSetImplementation<T, D>;
context.watchedTables.add(table); context.buffer.write(resultSet.tableWithAlias);
context.watchedTables.add(resultSet);
if (type != _JoinType.cross) { if (type != _JoinType.cross) {
context.buffer.write(' ON '); context.buffer.write(' ON ');
@ -70,9 +79,7 @@ class Join<T extends HasResultSet, D> extends Component {
/// See also: /// See also:
/// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins /// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins
/// - http://www.sqlitetutorial.net/sqlite-inner-join/ /// - http://www.sqlitetutorial.net/sqlite-inner-join/
Join innerJoin<T extends HasResultSet, D>( Join innerJoin(Table other, Expression<bool?> on, {bool? useColumns}) {
ResultSetImplementation<T, D> other, Expression<bool?> on,
{bool? useColumns}) {
return Join._(_JoinType.inner, other, on, includeInResult: useColumns); return Join._(_JoinType.inner, other, on, includeInResult: useColumns);
} }
@ -84,9 +91,7 @@ Join innerJoin<T extends HasResultSet, D>(
/// See also: /// See also:
/// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins /// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins
/// - http://www.sqlitetutorial.net/sqlite-left-join/ /// - http://www.sqlitetutorial.net/sqlite-left-join/
Join leftOuterJoin<T extends HasResultSet, D>( Join leftOuterJoin(Table other, Expression<bool?> on, {bool? useColumns}) {
ResultSetImplementation<T, D> other, Expression<bool?> on,
{bool? useColumns}) {
return Join._(_JoinType.leftOuter, other, on, includeInResult: useColumns); return Join._(_JoinType.leftOuter, other, on, includeInResult: useColumns);
} }
@ -98,7 +103,6 @@ Join leftOuterJoin<T extends HasResultSet, D>(
/// See also: /// See also:
/// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins /// - https://drift.simonbinder.eu/docs/advanced-features/joins/#joins
/// - http://www.sqlitetutorial.net/sqlite-cross-join/ /// - http://www.sqlitetutorial.net/sqlite-cross-join/
Join crossJoin<T extends HasResultSet, D>(ResultSetImplementation<T, D> other, Join crossJoin(Table other, {bool? useColumns}) {
{bool? useColumns}) {
return Join._(_JoinType.cross, other, null, includeInResult: useColumns); return Join._(_JoinType.cross, other, null, includeInResult: useColumns);
} }

View File

@ -12,6 +12,10 @@ class GenerationContext {
/// explicit indices starting at [explicitVariableIndex]. /// explicit indices starting at [explicitVariableIndex].
int? explicitVariableIndex; int? explicitVariableIndex;
/// When set to an entity name (view or table), generated column in that
/// entity definition will written into query as expression
String? generatingForView;
/// All tables that the generated query reads from. /// All tables that the generated query reads from.
final List<ResultSetImplementation> watchedTables = []; final List<ResultSetImplementation> watchedTables = [];

View File

@ -73,7 +73,7 @@ class Migrator {
await createIndex(entity); await createIndex(entity);
} else if (entity is OnCreateQuery) { } else if (entity is OnCreateQuery) {
await _issueCustomQuery(entity.sql, const []); await _issueCustomQuery(entity.sql, const []);
} else if (entity is View) { } else if (entity is ViewInfo) {
await createView(entity); await createView(entity);
} else { } else {
throw AssertionError('Unknown entity: $entity'); throw AssertionError('Unknown entity: $entity');
@ -81,6 +81,17 @@ class Migrator {
} }
} }
/// Drop and recreate all views. You should call it on every upgrade
Future<void> recreateAllViews() async {
for (final entity in _db.allSchemaEntities) {
if (entity is ViewInfo) {
await _issueCustomQuery(
'DROP VIEW IF EXISTS ${entity.entityName}', const []);
await createView(entity);
}
}
}
GenerationContext _createContext() { GenerationContext _createContext() {
return GenerationContext.fromDb(_db); return GenerationContext.fromDb(_db);
} }
@ -305,8 +316,17 @@ class Migrator {
} }
/// Executes a `CREATE VIEW` statement to create the [view]. /// Executes a `CREATE VIEW` statement to create the [view].
Future<void> createView(View view) { Future<void> createView(ViewInfo view) async {
return _issueCustomQuery(view.createViewStmt, const []); final stmt = view.createViewStmt;
if (stmt != null) {
await _issueCustomQuery(stmt, const []);
} else if (view.query != null) {
final context = GenerationContext.fromDb(_db);
context.generatingForView = view.entityName;
context.buffer.write('CREATE VIEW ${view.entityName} AS ');
view.query!.writeInto(context);
await _issueCustomQuery(context.sql, const []);
}
} }
/// Drops a table, trigger or index. /// Drops a table, trigger or index.

View File

@ -33,6 +33,7 @@ part 'expressions/variables.dart';
part 'schema/column_impl.dart'; part 'schema/column_impl.dart';
part 'schema/entities.dart'; part 'schema/entities.dart';
part 'schema/table_info.dart'; part 'schema/table_info.dart';
part 'schema/view_info.dart';
part 'statements/select/custom_select.dart'; part 'statements/select/custom_select.dart';
part 'statements/select/select.dart'; part 'statements/select/select.dart';

View File

@ -153,6 +153,9 @@ class GeneratedColumn<T> extends Column<T> {
@override @override
void writeInto(GenerationContext context, {bool ignoreEscape = false}) { void writeInto(GenerationContext context, {bool ignoreEscape = false}) {
if (generatedAs != null && context.generatingForView == tableName) {
generatedAs!.generatedAs.writeInto(context);
} else {
if (context.hasMultipleTables) { if (context.hasMultipleTables) {
context.buffer context.buffer
..write(tableName) ..write(tableName)
@ -160,6 +163,7 @@ class GeneratedColumn<T> extends Column<T> {
} }
context.buffer.write(ignoreEscape ? $name : escapedName); context.buffer.write(ignoreEscape ? $name : escapedName);
} }
}
/// Checks whether the given value fits into this column. The default /// Checks whether the given value fits into this column. The default
/// implementation only checks for nullability, but subclasses might enforce /// implementation only checks for nullability, but subclasses might enforce

View File

@ -47,28 +47,6 @@ class Index extends DatabaseSchemaEntity {
Index(this.entityName, this.createIndexStmt); Index(this.entityName, this.createIndexStmt);
} }
/// A sqlite view.
///
/// In drift, views can only be declared in `.drift` files.
///
/// For more information on views, see the [CREATE VIEW][sqlite-docs]
/// documentation from sqlite, or the [entry on sqlitetutorial.net][sql-tut].
///
/// [sqlite-docs]: https://www.sqlite.org/lang_createview.html
/// [sql-tut]: https://www.sqlitetutorial.net/sqlite-create-view/
abstract class View<Self, Row> extends ResultSetImplementation<Self, Row>
implements HasResultSet {
@override
final String entityName;
/// The `CREATE VIEW` sql statement that can be used to create this view.
final String createViewStmt;
/// Creates an view model by the [createViewStmt] and its [entityName].
/// Mainly used by generated code.
View(this.entityName, this.createViewStmt);
}
/// An internal schema entity to run an sql statement when the database is /// An internal schema entity to run an sql statement when the database is
/// created. /// created.
/// ///

View File

@ -0,0 +1,22 @@
part of '../query_builder.dart';
/// A sqlite view.
///
/// In drift, views can only be declared in `.drift` files.
///
/// For more information on views, see the [CREATE VIEW][sqlite-docs]
/// documentation from sqlite, or the [entry on sqlitetutorial.net][sql-tut].
///
/// [sqlite-docs]: https://www.sqlite.org/lang_createview.html
/// [sql-tut]: https://www.sqlitetutorial.net/sqlite-create-view/
abstract class ViewInfo<Self extends HasResultSet, Row>
implements ResultSetImplementation<Self, Row> {
@override
String get entityName;
/// The `CREATE VIEW` sql statement that can be used to create this view.
String? get createViewStmt;
/// Predefined query from `View.as()`
Query? get query;
}

View File

@ -12,13 +12,16 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
/// instead. /// instead.
JoinedSelectStatement(DatabaseConnectionUser database, JoinedSelectStatement(DatabaseConnectionUser database,
ResultSetImplementation<FirstT, FirstD> table, this._joins, ResultSetImplementation<FirstT, FirstD> table, this._joins,
[this.distinct = false, this._includeMainTableInResult = true]) [this.distinct = false,
this._includeMainTableInResult = true,
this._includeJoinedTablesInResult = true])
: super(database, table); : super(database, table);
/// Whether to generate a `SELECT DISTINCT` query that will remove duplicate /// Whether to generate a `SELECT DISTINCT` query that will remove duplicate
/// rows from the result set. /// rows from the result set.
final bool distinct; final bool distinct;
final bool _includeMainTableInResult; final bool _includeMainTableInResult;
final bool _includeJoinedTablesInResult;
final List<Join> _joins; final List<Join> _joins;
/// All columns that we're selecting from. /// All columns that we're selecting from.
@ -43,8 +46,8 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
@override @override
int get _returnedColumnCount { int get _returnedColumnCount {
return _joins.fold(_selectedColumns.length, (prev, join) { return _joins.fold(_selectedColumns.length, (prev, join) {
if (join.includeInResult) { if (join.includeInResult ?? _includeJoinedTablesInResult) {
return prev + join.table.$columns.length; return prev + (join.table as ResultSetImplementation).$columns.length;
} }
return prev; return prev;
}); });
@ -61,9 +64,10 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
} }
for (final join in _joins) { for (final join in _joins) {
if (onlyResults && !join.includeInResult) continue; if (onlyResults &&
!(join.includeInResult ?? _includeJoinedTablesInResult)) continue;
yield join.table; yield join.table as ResultSetImplementation;
} }
} }
@ -86,7 +90,11 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
final column = _selectedColumns[i]; final column = _selectedColumns[i];
String chosenAlias; String chosenAlias;
if (column is GeneratedColumn) { if (column is GeneratedColumn) {
if (ctx.generatingForView == column.tableName) {
chosenAlias = '${column.$name}';
} else {
chosenAlias = '${column.tableName}.${column.$name}'; chosenAlias = '${column.tableName}.${column.$name}';
}
} else { } else {
chosenAlias = 'c$i'; chosenAlias = 'c$i';
} }

View File

@ -1536,14 +1536,21 @@ class MyViewData extends DataClass {
other.syncStateImplicit == this.syncStateImplicit); other.syncStateImplicit == this.syncStateImplicit);
} }
class MyView extends View<MyView, MyViewData> { class MyView extends ViewInfo<MyView, MyViewData> implements HasResultSet {
MyView() final _$CustomTablesDb _db;
: super('my_view', final String? _alias;
'CREATE VIEW my_view AS SELECT * FROM config WHERE sync_state = 2'); MyView(this._db, [this._alias]);
@override @override
List<GeneratedColumn> get $columns => List<GeneratedColumn> get $columns =>
[configKey, configValue, syncState, syncStateImplicit]; [configKey, configValue, syncState, syncStateImplicit];
@override @override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'my_view';
@override
String get createViewStmt =>
'CREATE VIEW my_view AS SELECT * FROM config WHERE sync_state = 2';
@override
MyView get asDslTable => this; MyView get asDslTable => this;
@override @override
MyViewData map(Map<String, dynamic> data, {String? tablePrefix}) { MyViewData map(Map<String, dynamic> data, {String? tablePrefix}) {
@ -1566,6 +1573,13 @@ class MyView extends View<MyView, MyViewData> {
'sync_state_implicit', aliasedName, true, 'sync_state_implicit', aliasedName, true,
type: const IntType()) type: const IntType())
.withConverter<SyncType?>(ConfigTable.$converter1); .withConverter<SyncType?>(ConfigTable.$converter1);
@override
MyView createAlias(String alias) {
return MyView(_db, alias);
}
@override
Query? get query => null;
} }
abstract class _$CustomTablesDb extends GeneratedDatabase { abstract class _$CustomTablesDb extends GeneratedDatabase {
@ -1578,7 +1592,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
late final Trigger myTrigger = Trigger( late final Trigger myTrigger = Trigger(
'CREATE TRIGGER my_trigger AFTER INSERT ON config BEGIN INSERT INTO with_defaults VALUES (new.config_key, LENGTH(new.config_value));END', 'CREATE TRIGGER my_trigger AFTER INSERT ON config BEGIN INSERT INTO with_defaults VALUES (new.config_key, LENGTH(new.config_value));END',
'my_trigger'); 'my_trigger');
late final MyView myView = MyView(); late final MyView myView = MyView(this);
late final NoIds noIds = NoIds(this); late final NoIds noIds = NoIds(this);
late final WithConstraints withConstraints = WithConstraints(this); late final WithConstraints withConstraints = WithConstraints(this);
late final Mytable mytable = Mytable(this); late final Mytable mytable = Mytable(this);

View File

@ -117,6 +117,28 @@ class CustomConverter extends TypeConverter<MyCustomObject, String> {
} }
} }
abstract class CategoryTodoCountView extends View {
TodosTable get todos;
Categories get categories;
Expression<int> get itemCount => todos.id.count();
@override
Query as() => select([categories.description, itemCount])
.from(categories)
.join([innerJoin(todos, todos.category.equalsExp(categories.id))]);
}
abstract class TodoWithCategoryView extends View {
TodosTable get todos;
Categories get categories;
@override
Query as() => select([todos.title, categories.description])
.from(todos)
.join([innerJoin(categories, categories.id.equalsExp(todos.category))]);
}
@DriftDatabase( @DriftDatabase(
tables: [ tables: [
TodosTable, TodosTable,
@ -126,6 +148,10 @@ class CustomConverter extends TypeConverter<MyCustomObject, String> {
TableWithoutPK, TableWithoutPK,
PureDefaults, PureDefaults,
], ],
views: [
CategoryTodoCountView,
TodoWithCategoryView,
],
daos: [SomeDao], daos: [SomeDao],
queries: { queries: {
'allTodosWithCategory': 'SELECT t.*, c.id as catId, c."desc" as catDesc ' 'allTodosWithCategory': 'SELECT t.*, c.id as catId, c."desc" as catDesc '

View File

@ -184,6 +184,7 @@ class $CategoriesTable extends Categories
final String? _alias; final String? _alias;
$CategoriesTable(this._db, [this._alias]); $CategoriesTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id'); final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>( late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false, 'id', aliasedName, false,
type: const IntType(), type: const IntType(),
@ -191,12 +192,14 @@ class $CategoriesTable extends Categories
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _descriptionMeta = final VerificationMeta _descriptionMeta =
const VerificationMeta('description'); const VerificationMeta('description');
@override
late final GeneratedColumn<String?> description = GeneratedColumn<String?>( late final GeneratedColumn<String?> description = GeneratedColumn<String?>(
'desc', aliasedName, false, 'desc', aliasedName, false,
type: const StringType(), type: const StringType(),
requiredDuringInsert: true, requiredDuringInsert: true,
$customConstraints: 'NOT NULL UNIQUE'); $customConstraints: 'NOT NULL UNIQUE');
final VerificationMeta _priorityMeta = const VerificationMeta('priority'); final VerificationMeta _priorityMeta = const VerificationMeta('priority');
@override
late final GeneratedColumnWithTypeConverter<CategoryPriority, int?> priority = late final GeneratedColumnWithTypeConverter<CategoryPriority, int?> priority =
GeneratedColumn<int?>('priority', aliasedName, false, GeneratedColumn<int?>('priority', aliasedName, false,
type: const IntType(), type: const IntType(),
@ -205,6 +208,7 @@ class $CategoriesTable extends Categories
.withConverter<CategoryPriority>($CategoriesTable.$converter0); .withConverter<CategoryPriority>($CategoriesTable.$converter0);
final VerificationMeta _descriptionInUpperCaseMeta = final VerificationMeta _descriptionInUpperCaseMeta =
const VerificationMeta('descriptionInUpperCase'); const VerificationMeta('descriptionInUpperCase');
@override
late final GeneratedColumn<String?> descriptionInUpperCase = late final GeneratedColumn<String?> descriptionInUpperCase =
GeneratedColumn<String?>('description_in_upper_case', aliasedName, false, GeneratedColumn<String?>('description_in_upper_case', aliasedName, false,
type: const StringType(), type: const StringType(),
@ -474,12 +478,14 @@ class $TodosTableTable extends TodosTable
final String? _alias; final String? _alias;
$TodosTableTable(this._db, [this._alias]); $TodosTableTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id'); final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>( late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false, 'id', aliasedName, false,
type: const IntType(), type: const IntType(),
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _titleMeta = const VerificationMeta('title'); final VerificationMeta _titleMeta = const VerificationMeta('title');
@override
late final GeneratedColumn<String?> title = GeneratedColumn<String?>( late final GeneratedColumn<String?> title = GeneratedColumn<String?>(
'title', aliasedName, true, 'title', aliasedName, true,
additionalChecks: additionalChecks:
@ -487,14 +493,17 @@ class $TodosTableTable extends TodosTable
type: const StringType(), type: const StringType(),
requiredDuringInsert: false); requiredDuringInsert: false);
final VerificationMeta _contentMeta = const VerificationMeta('content'); final VerificationMeta _contentMeta = const VerificationMeta('content');
@override
late final GeneratedColumn<String?> content = GeneratedColumn<String?>( late final GeneratedColumn<String?> content = GeneratedColumn<String?>(
'content', aliasedName, false, 'content', aliasedName, false,
type: const StringType(), requiredDuringInsert: true); type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _targetDateMeta = const VerificationMeta('targetDate'); final VerificationMeta _targetDateMeta = const VerificationMeta('targetDate');
@override
late final GeneratedColumn<DateTime?> targetDate = GeneratedColumn<DateTime?>( late final GeneratedColumn<DateTime?> targetDate = GeneratedColumn<DateTime?>(
'target_date', aliasedName, true, 'target_date', aliasedName, true,
type: const IntType(), requiredDuringInsert: false); type: const IntType(), requiredDuringInsert: false);
final VerificationMeta _categoryMeta = const VerificationMeta('category'); final VerificationMeta _categoryMeta = const VerificationMeta('category');
@override
late final GeneratedColumn<int?> category = GeneratedColumn<int?>( late final GeneratedColumn<int?> category = GeneratedColumn<int?>(
'category', aliasedName, true, 'category', aliasedName, true,
type: const IntType(), type: const IntType(),
@ -757,12 +766,14 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
final String? _alias; final String? _alias;
$UsersTable(this._db, [this._alias]); $UsersTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id'); final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>( late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false, 'id', aliasedName, false,
type: const IntType(), type: const IntType(),
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name'); final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String?> name = GeneratedColumn<String?>( late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false, 'name', aliasedName, false,
additionalChecks: additionalChecks:
@ -770,6 +781,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
type: const StringType(), type: const StringType(),
requiredDuringInsert: true); requiredDuringInsert: true);
final VerificationMeta _isAwesomeMeta = const VerificationMeta('isAwesome'); final VerificationMeta _isAwesomeMeta = const VerificationMeta('isAwesome');
@override
late final GeneratedColumn<bool?> isAwesome = GeneratedColumn<bool?>( late final GeneratedColumn<bool?> isAwesome = GeneratedColumn<bool?>(
'is_awesome', aliasedName, false, 'is_awesome', aliasedName, false,
type: const BoolType(), type: const BoolType(),
@ -778,11 +790,13 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
defaultValue: const Constant(true)); defaultValue: const Constant(true));
final VerificationMeta _profilePictureMeta = final VerificationMeta _profilePictureMeta =
const VerificationMeta('profilePicture'); const VerificationMeta('profilePicture');
@override
late final GeneratedColumn<Uint8List?> profilePicture = late final GeneratedColumn<Uint8List?> profilePicture =
GeneratedColumn<Uint8List?>('profile_picture', aliasedName, false, GeneratedColumn<Uint8List?>('profile_picture', aliasedName, false,
type: const BlobType(), requiredDuringInsert: true); type: const BlobType(), requiredDuringInsert: true);
final VerificationMeta _creationTimeMeta = final VerificationMeta _creationTimeMeta =
const VerificationMeta('creationTime'); const VerificationMeta('creationTime');
@override
late final GeneratedColumn<DateTime?> creationTime = late final GeneratedColumn<DateTime?> creationTime =
GeneratedColumn<DateTime?>('creation_time', aliasedName, false, GeneratedColumn<DateTime?>('creation_time', aliasedName, false,
type: const IntType(), type: const IntType(),
@ -974,10 +988,12 @@ class $SharedTodosTable extends SharedTodos
final String? _alias; final String? _alias;
$SharedTodosTable(this._db, [this._alias]); $SharedTodosTable(this._db, [this._alias]);
final VerificationMeta _todoMeta = const VerificationMeta('todo'); final VerificationMeta _todoMeta = const VerificationMeta('todo');
@override
late final GeneratedColumn<int?> todo = GeneratedColumn<int?>( late final GeneratedColumn<int?> todo = GeneratedColumn<int?>(
'todo', aliasedName, false, 'todo', aliasedName, false,
type: const IntType(), requiredDuringInsert: true); type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _userMeta = const VerificationMeta('user'); final VerificationMeta _userMeta = const VerificationMeta('user');
@override
late final GeneratedColumn<int?> user = GeneratedColumn<int?>( late final GeneratedColumn<int?> user = GeneratedColumn<int?>(
'user', aliasedName, false, 'user', aliasedName, false,
type: const IntType(), requiredDuringInsert: true); type: const IntType(), requiredDuringInsert: true);
@ -1114,14 +1130,17 @@ class $TableWithoutPKTable extends TableWithoutPK
$TableWithoutPKTable(this._db, [this._alias]); $TableWithoutPKTable(this._db, [this._alias]);
final VerificationMeta _notReallyAnIdMeta = final VerificationMeta _notReallyAnIdMeta =
const VerificationMeta('notReallyAnId'); const VerificationMeta('notReallyAnId');
@override
late final GeneratedColumn<int?> notReallyAnId = GeneratedColumn<int?>( late final GeneratedColumn<int?> notReallyAnId = GeneratedColumn<int?>(
'not_really_an_id', aliasedName, false, 'not_really_an_id', aliasedName, false,
type: const IntType(), requiredDuringInsert: true); type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _someFloatMeta = const VerificationMeta('someFloat'); final VerificationMeta _someFloatMeta = const VerificationMeta('someFloat');
@override
late final GeneratedColumn<double?> someFloat = GeneratedColumn<double?>( late final GeneratedColumn<double?> someFloat = GeneratedColumn<double?>(
'some_float', aliasedName, false, 'some_float', aliasedName, false,
type: const RealType(), requiredDuringInsert: true); type: const RealType(), requiredDuringInsert: true);
final VerificationMeta _customMeta = const VerificationMeta('custom'); final VerificationMeta _customMeta = const VerificationMeta('custom');
@override
late final GeneratedColumnWithTypeConverter<MyCustomObject, String?> custom = late final GeneratedColumnWithTypeConverter<MyCustomObject, String?> custom =
GeneratedColumn<String?>('custom', aliasedName, false, GeneratedColumn<String?>('custom', aliasedName, false,
type: const StringType(), type: const StringType(),
@ -1291,6 +1310,7 @@ class $PureDefaultsTable extends PureDefaults
final String? _alias; final String? _alias;
$PureDefaultsTable(this._db, [this._alias]); $PureDefaultsTable(this._db, [this._alias]);
final VerificationMeta _txtMeta = const VerificationMeta('txt'); final VerificationMeta _txtMeta = const VerificationMeta('txt');
@override
late final GeneratedColumn<String?> txt = GeneratedColumn<String?>( late final GeneratedColumn<String?> txt = GeneratedColumn<String?>(
'insert', aliasedName, true, 'insert', aliasedName, true,
type: const StringType(), requiredDuringInsert: false); type: const StringType(), requiredDuringInsert: false);
@ -1326,6 +1346,215 @@ class $PureDefaultsTable extends PureDefaults
} }
} }
class CategoryTodoCountViewData extends DataClass {
final String description;
final int itemCount;
CategoryTodoCountViewData(
{required this.description, required this.itemCount});
factory CategoryTodoCountViewData.fromData(Map<String, dynamic> data,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return CategoryTodoCountViewData(
description: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}categories.desc'])!,
itemCount: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}item_count'])!,
);
}
factory CategoryTodoCountViewData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return CategoryTodoCountViewData(
description: serializer.fromJson<String>(json['description']),
itemCount: serializer.fromJson<int>(json['itemCount']),
);
}
factory CategoryTodoCountViewData.fromJsonString(String encodedJson,
{ValueSerializer? serializer}) =>
CategoryTodoCountViewData.fromJson(
DataClass.parseJson(encodedJson) as Map<String, dynamic>,
serializer: serializer);
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'description': serializer.toJson<String>(description),
'itemCount': serializer.toJson<int>(itemCount),
};
}
CategoryTodoCountViewData copyWith({String? description, int? itemCount}) =>
CategoryTodoCountViewData(
description: description ?? this.description,
itemCount: itemCount ?? this.itemCount,
);
@override
String toString() {
return (StringBuffer('CategoryTodoCountViewData(')
..write('description: $description, ')
..write('itemCount: $itemCount')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(description, itemCount);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is CategoryTodoCountViewData &&
other.description == this.description &&
other.itemCount == this.itemCount);
}
class $CategoryTodoCountViewView
extends ViewInfo<$CategoryTodoCountViewView, CategoryTodoCountViewData>
implements HasResultSet {
final _$TodoDb _db;
final String? _alias;
$CategoryTodoCountViewView(this._db, [this._alias]);
$TodosTableTable get todos => _db.todosTable;
$CategoriesTable get categories => _db.categories;
@override
List<GeneratedColumn> get $columns => [categories.description, itemCount];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'category_todo_count_view';
@override
String? get createViewStmt => null;
@override
$CategoryTodoCountViewView get asDslTable => this;
@override
CategoryTodoCountViewData map(Map<String, dynamic> data,
{String? tablePrefix}) {
return CategoryTodoCountViewData.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
late final GeneratedColumn<String?> description = GeneratedColumn<String?>(
'desc', aliasedName, false,
type: const StringType(), $customConstraints: 'NOT NULL UNIQUE');
late final GeneratedColumn<int?> itemCount = GeneratedColumn<int?>(
'item_count', aliasedName, false,
type: const IntType(), generatedAs: GeneratedAs(todos.id.count(), false));
@override
$CategoryTodoCountViewView createAlias(String alias) {
return $CategoryTodoCountViewView(_db, alias);
}
@override
Query? get query =>
(_db.selectOnly(categories, includeJoinedTableColumns: false)
..addColumns($columns))
.join([innerJoin(todos, todos.category.equalsExp(categories.id))]);
}
class TodoWithCategoryViewData extends DataClass {
final String? title;
final String description;
TodoWithCategoryViewData({this.title, required this.description});
factory TodoWithCategoryViewData.fromData(Map<String, dynamic> data,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return TodoWithCategoryViewData(
title: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}todos.title']),
description: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}categories.desc'])!,
);
}
factory TodoWithCategoryViewData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodoWithCategoryViewData(
title: serializer.fromJson<String?>(json['title']),
description: serializer.fromJson<String>(json['description']),
);
}
factory TodoWithCategoryViewData.fromJsonString(String encodedJson,
{ValueSerializer? serializer}) =>
TodoWithCategoryViewData.fromJson(
DataClass.parseJson(encodedJson) as Map<String, dynamic>,
serializer: serializer);
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'title': serializer.toJson<String?>(title),
'description': serializer.toJson<String>(description),
};
}
TodoWithCategoryViewData copyWith(
{Value<String?> title = const Value.absent(), String? description}) =>
TodoWithCategoryViewData(
title: title.present ? title.value : this.title,
description: description ?? this.description,
);
@override
String toString() {
return (StringBuffer('TodoWithCategoryViewData(')
..write('title: $title, ')
..write('description: $description')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(title, description);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TodoWithCategoryViewData &&
other.title == this.title &&
other.description == this.description);
}
class $TodoWithCategoryViewView
extends ViewInfo<$TodoWithCategoryViewView, TodoWithCategoryViewData>
implements HasResultSet {
final _$TodoDb _db;
final String? _alias;
$TodoWithCategoryViewView(this._db, [this._alias]);
$TodosTableTable get todos => _db.todosTable;
$CategoriesTable get categories => _db.categories;
@override
List<GeneratedColumn> get $columns => [todos.title, categories.description];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'todo_with_category_view';
@override
String? get createViewStmt => null;
@override
$TodoWithCategoryViewView get asDslTable => this;
@override
TodoWithCategoryViewData map(Map<String, dynamic> data,
{String? tablePrefix}) {
return TodoWithCategoryViewData.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
late final GeneratedColumn<String?> title = GeneratedColumn<String?>(
'title', aliasedName, true,
additionalChecks:
GeneratedColumn.checkTextLength(minTextLength: 4, maxTextLength: 16),
type: const StringType());
late final GeneratedColumn<String?> description = GeneratedColumn<String?>(
'desc', aliasedName, false,
type: const StringType(), $customConstraints: 'NOT NULL UNIQUE');
@override
$TodoWithCategoryViewView createAlias(String alias) {
return $TodoWithCategoryViewView(_db, alias);
}
@override
Query? get query => (_db.selectOnly(todos, includeJoinedTableColumns: false)
..addColumns($columns))
.join([innerJoin(categories, categories.id.equalsExp(todos.category))]);
}
abstract class _$TodoDb extends GeneratedDatabase { abstract class _$TodoDb extends GeneratedDatabase {
_$TodoDb(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); _$TodoDb(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
_$TodoDb.connect(DatabaseConnection c) : super.connect(c); _$TodoDb.connect(DatabaseConnection c) : super.connect(c);
@ -1335,6 +1564,10 @@ abstract class _$TodoDb extends GeneratedDatabase {
late final $SharedTodosTable sharedTodos = $SharedTodosTable(this); late final $SharedTodosTable sharedTodos = $SharedTodosTable(this);
late final $TableWithoutPKTable tableWithoutPK = $TableWithoutPKTable(this); late final $TableWithoutPKTable tableWithoutPK = $TableWithoutPKTable(this);
late final $PureDefaultsTable pureDefaults = $PureDefaultsTable(this); late final $PureDefaultsTable pureDefaults = $PureDefaultsTable(this);
late final $CategoryTodoCountViewView categoryTodoCountView =
$CategoryTodoCountViewView(this);
late final $TodoWithCategoryViewView todoWithCategoryView =
$TodoWithCategoryViewView(this);
late final SomeDao someDao = SomeDao(this as TodoDb); late final SomeDao someDao = SomeDao(this as TodoDb);
Selectable<AllTodosWithCategoryResult> allTodosWithCategory() { Selectable<AllTodosWithCategoryResult> allTodosWithCategory() {
return customSelect( return customSelect(
@ -1412,7 +1645,9 @@ abstract class _$TodoDb extends GeneratedDatabase {
users, users,
sharedTodos, sharedTodos,
tableWithoutPK, tableWithoutPK,
pureDefaults pureDefaults,
categoryTodoCountView,
todoWithCategoryView
]; ];
} }

View File

@ -105,7 +105,7 @@ void main() {
for (var i = 0; i < 4; i++) { for (var i = 0; i < 4; i++) {
filter.add(i); filter.add(i);
await pumpEventQueue(times: 10); await pumpEventQueue();
} }
final values = await db final values = await db
@ -113,6 +113,6 @@ void main() {
.map((row) => row.read<String>('r')) .map((row) => row.read<String>('r'))
.getSingle(); .getSingle();
expect(values, anyOf('0,3', '3')); expect(values, anyOf('0,3', '3', '1,3'));
}); });
} }

View File

@ -401,6 +401,43 @@ void main() {
expect(result.read(todos.id.count()), equals(10)); expect(result.read(todos.id.count()), equals(10));
}); });
test('use selectOnly(includeJoinedTableColumns) instead of useColumns',
() async {
final categories = db.categories;
final todos = db.todosTable;
final query =
db.selectOnly(categories, includeJoinedTableColumns: false).join([
innerJoin(
todos,
todos.category.equalsExp(categories.id),
)
]);
query
..addColumns([categories.id, todos.id.count()])
..groupBy([categories.id]);
when(executor.runSelect(any, any)).thenAnswer((_) async {
return [
{
'categories.id': 2,
'c1': 10,
}
];
});
final result = await query.getSingle();
verify(executor.runSelect(
'SELECT categories.id AS "categories.id", COUNT(todos.id) AS "c1" '
'FROM categories INNER JOIN todos ON todos.category = categories.id '
'GROUP BY categories.id;',
[]));
expect(result.read(categories.id), equals(2));
expect(result.read(todos.id.count()), equals(10));
});
test('injects custom error message when a table is used multiple times', test('injects custom error message when a table is used multiple times',
() async { () async {
when(executor.runSelect(any, any)).thenAnswer((_) => Future.error('nah')); when(executor.runSelect(any, any)).thenAnswer((_) => Future.error('nah'));

View File

@ -64,6 +64,24 @@ void main() {
'custom TEXT NOT NULL' 'custom TEXT NOT NULL'
');', ');',
[])); []));
verify(mockExecutor.runCustom(
'CREATE VIEW category_todo_count_view AS SELECT '
'categories."desc" AS "categories.desc", '
'COUNT(todos.id) AS "item_count" '
'FROM categories '
'INNER JOIN todos '
'ON todos.category = categories.id',
[]));
verify(mockExecutor.runCustom(
'CREATE VIEW 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',
[]));
}); });
test('creates individual tables', () async { test('creates individual tables', () async {

View File

@ -1,6 +1,7 @@
import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:drift/sqlite_keywords.dart'; import 'package:drift/sqlite_keywords.dart';
@ -9,6 +10,7 @@ import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:drift_dev/src/analyzer/runner/steps.dart'; import 'package:drift_dev/src/analyzer/runner/steps.dart';
import 'package:drift_dev/src/model/declarations/declaration.dart'; import 'package:drift_dev/src/model/declarations/declaration.dart';
import 'package:drift_dev/src/model/used_type_converter.dart'; import 'package:drift_dev/src/model/used_type_converter.dart';
import 'package:drift_dev/src/utils/exception.dart';
import 'package:drift_dev/src/utils/names.dart'; import 'package:drift_dev/src/utils/names.dart';
import 'package:drift_dev/src/utils/type_utils.dart'; import 'package:drift_dev/src/utils/type_utils.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
@ -20,6 +22,7 @@ import '../custom_row_class.dart';
part 'column_parser.dart'; part 'column_parser.dart';
part 'table_parser.dart'; part 'table_parser.dart';
part 'view_parser.dart';
part 'use_dao_parser.dart'; part 'use_dao_parser.dart';
part 'use_moor_parser.dart'; part 'use_moor_parser.dart';
@ -28,16 +31,23 @@ class MoorDartParser {
late ColumnParser _columnParser; late ColumnParser _columnParser;
late TableParser _tableParser; late TableParser _tableParser;
late ViewParser _viewParser;
MoorDartParser(this.step) { MoorDartParser(this.step) {
_columnParser = ColumnParser(this); _columnParser = ColumnParser(this);
_tableParser = TableParser(this); _tableParser = TableParser(this);
_viewParser = ViewParser(this);
} }
Future<MoorTable?> parseTable(ClassElement classElement) { Future<MoorTable?> parseTable(ClassElement classElement) {
return _tableParser.parseTable(classElement); return _tableParser.parseTable(classElement);
} }
Future<MoorView?> parseView(
ClassElement classElement, List<MoorTable> tables) {
return _viewParser.parseView(classElement, tables);
}
/// Attempts to parse the column created from the Dart getter. /// Attempts to parse the column created from the Dart getter.
/// ///
/// When the column is invalid, an error will be logged and `null` is /// When the column is invalid, an error will be logged and `null` is

View File

@ -40,6 +40,13 @@ class UseDaoParser {
const []; const [];
final queryStrings = annotation.peek('queries')?.mapValue ?? {}; final queryStrings = annotation.peek('queries')?.mapValue ?? {};
final viewTypes = annotation
.peek('views')
?.listValue
.map((obj) => obj.toTypeValue())
.whereType<DartType>() ??
const [];
final includes = annotation final includes = annotation
.read('include') .read('include')
.objectValue .objectValue
@ -50,12 +57,14 @@ class UseDaoParser {
[]; [];
final parsedTables = await step.parseTables(tableTypes, element); final parsedTables = await step.parseTables(tableTypes, element);
final parsedViews = await step.parseViews(viewTypes, element, parsedTables);
final parsedQueries = step.readDeclaredQueries(queryStrings.cast()); final parsedQueries = step.readDeclaredQueries(queryStrings.cast());
return Dao( return Dao(
declaration: DatabaseOrDaoDeclaration(element, step.file), declaration: DatabaseOrDaoDeclaration(element, step.file),
dbClass: dbImpl, dbClass: dbImpl,
declaredTables: parsedTables, declaredTables: parsedTables,
declaredViews: parsedViews,
declaredIncludes: includes, declaredIncludes: includes,
declaredQueries: parsedQueries, declaredQueries: parsedQueries,
); );

View File

@ -23,6 +23,13 @@ class UseMoorParser {
)); ));
} }
final viewTypes = annotation
.peek('views')
?.listValue
.map((obj) => obj.toTypeValue())
.whereType<DartType>() ??
const [];
final tableTypes = tablesOrNull ?? []; final tableTypes = tablesOrNull ?? [];
final queryStrings = annotation.peek('queries')?.mapValue ?? {}; final queryStrings = annotation.peek('queries')?.mapValue ?? {};
final includes = annotation final includes = annotation
@ -34,13 +41,14 @@ class UseMoorParser {
[]; [];
final parsedTables = await step.parseTables(tableTypes, element); final parsedTables = await step.parseTables(tableTypes, element);
final parsedViews = await step.parseViews(viewTypes, element, parsedTables);
final parsedQueries = step.readDeclaredQueries(queryStrings.cast()); final parsedQueries = step.readDeclaredQueries(queryStrings.cast());
final daoTypes = _readDaoTypes(annotation); final daoTypes = _readDaoTypes(annotation);
return Database( return Database(
declaration: DatabaseOrDaoDeclaration(element, step.file), declaration: DatabaseOrDaoDeclaration(element, step.file),
declaredTables: parsedTables, declaredTables: parsedTables,
declaredViews: parsedViews,
daos: daoTypes, daos: daoTypes,
declaredIncludes: includes, declaredIncludes: includes,
declaredQueries: parsedQueries, declaredQueries: parsedQueries,

View File

@ -0,0 +1,285 @@
part of 'parser.dart';
/// Parses a [MoorView] from a Dart class.
class ViewParser {
final MoorDartParser base;
ViewParser(this.base);
Future<MoorView?> parseView(
ClassElement element, List<MoorTable> tables) async {
final name = await _parseViewName(element);
final columns = (await _parseColumns(element)).toList();
final staticReferences =
(await _parseStaticReferences(element, tables)).toList();
final dataClassInfo = _readDataClassInformation(columns, element);
final query = await _parseQuery(element, staticReferences, columns);
final view = MoorView(
declaration: DartViewDeclaration(element, base.step.file),
name: name,
dartTypeName: dataClassInfo.enforcedName,
existingRowClass: dataClassInfo.existingClass,
entityInfoName: '\$${element.name}View',
staticReferences: staticReferences.map((ref) => ref.declaration).toList(),
viewQuery: query,
);
view.columns = columns;
return view;
}
_DataClassInformation _readDataClassInformation(
List<MoorColumn> columns, ClassElement element) {
DartObject? useRowClass;
String? dataClassName;
for (final annotation in element.metadata) {
final computed = annotation.computeConstantValue();
final annotationClass = computed!.type!.element!.name;
if (annotationClass == 'DriftView') {
dataClassName = computed.getField('dataClassName')?.toStringValue();
} else if (annotationClass == 'UseRowClass') {
useRowClass = computed;
}
}
if (dataClassName != null && useRowClass != null) {
base.step.reportError(ErrorInDartCode(
message: "A table can't be annotated with both @DataClassName and "
'@UseRowClass',
affectedElement: element,
));
}
FoundDartClass? existingClass;
String? constructorInExistingClass;
bool? generateInsertable;
var name = dataClassName ?? dataClassNameForClassName(element.name);
if (useRowClass != null) {
final type = useRowClass.getField('type')!.toTypeValue();
constructorInExistingClass =
useRowClass.getField('constructor')!.toStringValue()!;
generateInsertable =
useRowClass.getField('generateInsertable')!.toBoolValue()!;
if (type is InterfaceType) {
existingClass = FoundDartClass(type.element, type.typeArguments);
name = type.element.name;
} else {
base.step.reportError(ErrorInDartCode(
message: 'The @UseRowClass annotation must be used with a class',
affectedElement: element,
));
}
}
final verified = existingClass == null
? null
: validateExistingClass(columns, existingClass,
constructorInExistingClass!, generateInsertable!, base.step);
return _DataClassInformation(name, verified);
}
Future<String> _parseViewName(ClassElement element) async {
for (final annotation in element.metadata) {
final computed = annotation.computeConstantValue();
final annotationClass = computed!.type!.element!.name;
if (annotationClass == 'DriftView') {
final name = computed.getField('name')?.toStringValue();
if (name != null) {
return name;
}
break;
}
}
return element.name.snakeCase;
}
Future<Iterable<MoorColumn>> _parseColumns(ClassElement element) async {
final columnNames = element.allSupertypes
.map((t) => t.element)
.followedBy([element])
.expand((e) => e.fields)
.where((field) =>
isExpression(field.type) &&
field.getter != null &&
!field.getter!.isSynthetic)
.map((field) => field.name)
.toSet();
final fields = columnNames.map((name) {
final getter = element.getGetter(name) ??
element.lookUpInheritedConcreteGetter(name, element.library);
return getter!.variable;
}).toList();
final results = await Future.wait(fields.map((field) async {
final dartType = (field.type as InterfaceType).typeArguments[0];
final typeName = dartType.element!.name!;
final sqlType = _dartTypeToColumnType(typeName);
if (sqlType == null) {
final String errorMessage;
if (typeName == 'dynamic') {
errorMessage = 'You must specify Expression<?> type argument';
} else {
errorMessage =
'Invalid Expression<?> type argument `$typeName` found. '
'Must be one of: '
'bool, String, int, DateTime, Uint8List, double';
}
throw analysisError(base.step, field, errorMessage);
}
final node =
await base.loadElementDeclaration(field.getter!) as MethodDeclaration;
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));
}).toList());
return results.whereType();
}
ColumnType? _dartTypeToColumnType(String name) {
return const {
'bool': ColumnType.boolean,
'String': ColumnType.text,
'int': ColumnType.integer,
'DateTime': ColumnType.datetime,
'Uint8List': ColumnType.blob,
'double': ColumnType.real,
}[name];
}
Future<List<_TableReference>> _parseStaticReferences(
ClassElement element, List<MoorTable> tables) async {
return await Stream.fromIterable(element.allSupertypes
.map((t) => t.element)
.followedBy([element]).expand((e) => e.fields))
.asyncMap((field) => _getStaticReference(field, tables))
.where((ref) => ref != null)
.cast<_TableReference>()
.toList();
}
Future<_TableReference?> _getStaticReference(
FieldElement field, List<MoorTable> tables) async {
if (field.getter != null) {
try {
final node = await base.loadElementDeclaration(field.getter!);
if (node is MethodDeclaration && node.body is EmptyFunctionBody) {
final type = tables.firstWhereOrNull(
(tbl) => tbl.fromClass!.name == node.returnType.toString());
if (type != null) {
final name = node.name.toString();
final declaration = '${type.entityInfoName} get $name => '
'_db.${type.dbGetterName};';
return _TableReference(type, name, declaration);
}
}
} catch (_) {}
}
return null;
}
Future<ViewQueryInformation> _parseQuery(ClassElement element,
List<_TableReference> references, List<MoorColumn> columns) async {
final as =
element.methods.where((method) => method.name == 'as').firstOrNull;
if (as != null) {
try {
final node = await base.loadElementDeclaration(as);
var target =
((node as MethodDeclaration).body as ExpressionFunctionBody)
.expression as MethodInvocation;
for (;;) {
if (target.target == null) break;
target = target.target as MethodInvocation;
}
if (target.methodName.toString() != 'select') {
throw analysisError(
base.step,
element,
'The `as()` query declaration must be started '
'with `select(columns).from(table)');
}
final columnListLiteral =
target.argumentList.arguments[0] as ListLiteral;
final columnList =
columnListLiteral.elements.map((col) => col.toString()).map((col) {
final parts = col.split('.');
if (parts.length > 1) {
final reference =
references.firstWhereOrNull((ref) => ref.name == parts[0]);
if (reference == null) {
throw analysisError(
base.step,
element,
'Table named `${parts[0]}` not found! Maybe not included in '
'@DriftDatabase or not belongs to this database');
}
final column = reference.table.columns
.firstWhere((col) => col.dartGetterName == parts[1]);
column.table = reference.table;
return MapEntry(
'${reference.name}.${column.dartGetterName}', column);
}
final column =
columns.firstWhere((col) => col.dartGetterName == parts[0]);
return MapEntry('${column.dartGetterName}', column);
});
final columnMap = Map.fromEntries(columnList);
target = target.parent as MethodInvocation;
if (target.methodName.toString() != 'from') {
throw analysisError(
base.step,
element,
'The `as()` query declaration must be started '
'with `select(columns).from(table)');
}
final from = target.argumentList.arguments[0].toString();
var query = '';
if (target.parent is MethodInvocation) {
target = target.parent as MethodInvocation;
query = target.toString().substring(target.target!.toString().length);
}
return ViewQueryInformation(columnMap, from, query);
} catch (e) {
print(e);
throw analysisError(
base.step, element, 'Failed to parse view `as()` query');
}
}
throw analysisError(base.step, element, 'Missing `as()` query declaration');
}
}
class _TableReference {
MoorTable table;
String name;
String declaration;
_TableReference(this.table, this.name, this.declaration);
}

View File

@ -18,6 +18,12 @@ class AnalyzeDartStep extends AnalyzingStep {
(entry.declaration as DartTableDeclaration).element: entry (entry.declaration as DartTableDeclaration).element: entry
}; };
final viewDartClasses = {
for (final entry in unsortedEntities)
if (entry.declaration is DartViewDeclaration)
(entry.declaration as DartViewDeclaration).element: entry
};
for (final declaredHere in accessor.declaredTables) { for (final declaredHere in accessor.declaredTables) {
// See issue #447: The table added to an accessor might already be // See issue #447: The table added to an accessor might already be
// included through a transitive moor file. In that case, we just ignore // included through a transitive moor file. In that case, we just ignore
@ -39,6 +45,23 @@ class AnalyzeDartStep extends AnalyzingStep {
_resolveDartColumnReferences(tableDartClasses); _resolveDartColumnReferences(tableDartClasses);
} }
for (final declaredHere in accessor.declaredViews) {
// See issue #447: The view added to an accessor might already be
// included through a transitive moor file. In that case, we just ignore
// it to avoid duplicates.
final declaration = declaredHere.declaration;
if (declaration is DartViewDeclaration &&
viewDartClasses.containsKey(declaration.element)) {
continue;
}
// Not a Dart view that we already included - add it now
unsortedEntities.add(declaredHere);
if (declaration is DartViewDeclaration) {
viewDartClasses[declaration.element] = declaredHere;
}
}
List<MoorSchemaEntity>? availableEntities; List<MoorSchemaEntity>? availableEntities;
try { try {

View File

@ -8,6 +8,7 @@ part of '../steps.dart';
/// Notably, this step does not analyze defined queries. /// Notably, this step does not analyze defined queries.
class ParseDartStep extends Step { class ParseDartStep extends Step {
static const _tableTypeChecker = TypeChecker.fromRuntime(Table); static const _tableTypeChecker = TypeChecker.fromRuntime(Table);
static const _viewTypeChecker = TypeChecker.fromRuntime(View);
static const _generatedInfoChecker = TypeChecker.fromRuntime(TableInfo); static const _generatedInfoChecker = TypeChecker.fromRuntime(TableInfo);
static const _useMoorChecker = TypeChecker.fromRuntime(DriftDatabase); static const _useMoorChecker = TypeChecker.fromRuntime(DriftDatabase);
static const _useDaoChecker = TypeChecker.fromRuntime(DriftAccessor); static const _useDaoChecker = TypeChecker.fromRuntime(DriftAccessor);
@ -18,6 +19,7 @@ class ParseDartStep extends Step {
MoorDartParser get parser => _parser; MoorDartParser get parser => _parser;
final Map<ClassElement, MoorTable> _tables = {}; final Map<ClassElement, MoorTable> _tables = {};
final Map<ClassElement, MoorView> _views = {};
ParseDartStep(Task task, FoundFile file, this.library) : super(task, file) { ParseDartStep(Task task, FoundFile file, this.library) : super(task, file) {
_parser = MoorDartParser(this); _parser = MoorDartParser(this);
@ -66,6 +68,18 @@ class ParseDartStep extends Step {
return _tables[element]; return _tables[element];
} }
Future<MoorView?> _parseView(
ClassElement element, List<MoorTable> tables) async {
if (!_views.containsKey(element)) {
final view = await parser.parseView(element, tables);
if (view != null) {
_views[element] = view;
}
}
return _views[element];
}
void _lintDartTable(MoorTable table, ClassElement from) { void _lintDartTable(MoorTable table, ClassElement from) {
if (table.primaryKey != null) { if (table.primaryKey != null) {
final hasAdditional = table.columns.any((c) { final hasAdditional = table.columns.any((c) {
@ -101,8 +115,8 @@ class ParseDartStep extends Step {
/// Resolves a [MoorTable] for the class of each [DartType] in [types]. /// Resolves a [MoorTable] for the class of each [DartType] in [types].
/// The [initializedBy] element should be the piece of code that caused the /// The [initializedBy] element should be the piece of code that caused the
/// parsing (e.g. the database class that is annotated with `@UseMoor`). This /// parsing (e.g. the database class that is annotated with `@DriftDatabase`).
/// will allow for more descriptive error messages. /// This will allow for more descriptive error messages.
Future<List<MoorTable>> parseTables( Future<List<MoorTable>> parseTables(
Iterable<DartType> types, Element initializedBy) { Iterable<DartType> types, Element initializedBy) {
return Future.wait(types.map((type) { return Future.wait(types.map((type) {
@ -122,6 +136,29 @@ class ParseDartStep extends Step {
}); });
} }
/// Resolves a [MoorView] for the class of each [DartType] in [types].
/// The [initializedBy] element should be the piece of code that caused the
/// parsing (e.g. the database class that is annotated with `@DriftDatabase`).
/// This will allow for more descriptive error messages.
Future<List<MoorView>> parseViews(
Iterable<DartType> types, Element initializedBy, List<MoorTable> tables) {
return Future.wait(types.map((type) {
if (!_viewTypeChecker.isAssignableFrom(type.element!)) {
reportError(ErrorInDartCode(
severity: Severity.criticalError,
message: 'The type $type is not a drift view',
affectedElement: initializedBy,
));
return Future.value(null);
} else {
return _parseView(type.element as ClassElement, tables);
}
})).then((list) {
// only keep tables that were resolved successfully
return List.from(list.where((t) => t != null));
});
}
List<DeclaredQuery> readDeclaredQueries(Map<DartObject, DartObject> obj) { List<DeclaredQuery> readDeclaredQueries(Map<DartObject, DartObject> obj) {
return obj.entries.map((entry) { return obj.entries.map((entry) {
final key = entry.key.toStringValue()!; final key = entry.key.toStringValue()!;

View File

@ -22,10 +22,13 @@ class ViewAnalyzer extends BaseAnalyzer {
Future<void> resolve(Iterable<MoorView> viewsToAnalyze) async { Future<void> resolve(Iterable<MoorView> viewsToAnalyze) async {
// Going through the topologically sorted list and analyzing each view. // Going through the topologically sorted list and analyzing each view.
for (final view in viewsToAnalyze) { for (final view in viewsToAnalyze) {
final ctx = engine.analyzeNode( if (view.declaration is! MoorViewDeclaration) continue;
view.declaration!.node, view.file!.parseResult.sql); final viewDeclaration = view.declaration as MoorViewDeclaration;
final ctx =
engine.analyzeNode(viewDeclaration.node, view.file!.parseResult.sql);
lintContext(ctx, view.name); lintContext(ctx, view.name);
final declaration = view.declaration!.creatingStatement; final declaration = viewDeclaration.creatingStatement;
final parserView = view.parserView = final parserView = view.parserView =
const SchemaFromCreateTable(moorExtensions: true) const SchemaFromCreateTable(moorExtensions: true)
@ -76,7 +79,7 @@ class ViewAnalyzer extends BaseAnalyzer {
} }
engine.registerView(mapper.extractView(view)); engine.registerView(mapper.extractView(view));
view.references = findReferences(view.declaration!.node).toList(); view.references = findReferences(viewDeclaration.node).toList();
} }
} }
} }

View File

@ -111,6 +111,9 @@ class MoorColumn implements HasDeclaration, HasType {
bool get isGenerated => generatedAs != null; bool get isGenerated => generatedAs != null;
/// Parent table
MoorTable? table;
/// The column type from the dsl library. For instance, if a table has /// The column type from the dsl library. For instance, if a table has
/// declared an `IntColumn`, the matching dsl column name would also be an /// declared an `IntColumn`, the matching dsl column name would also be an
/// `IntColumn`. /// `IntColumn`.

View File

@ -22,6 +22,13 @@ abstract class BaseMoorAccessor implements HasDeclaration {
/// that. /// that.
final List<MoorTable> declaredTables; final List<MoorTable> declaredTables;
/// All views that have been declared on this accessor directly.
///
/// This contains the `views` field from a `DriftDatabase` or `UseDao`
/// annotation, but not tables that are declared in imported moor files.
/// Use [views] for that.
final List<MoorView> declaredViews;
/// The `includes` field from the `UseMoor` or `UseDao` annotation. /// The `includes` field from the `UseMoor` or `UseDao` annotation.
final List<String> declaredIncludes; final List<String> declaredIncludes;
@ -48,7 +55,7 @@ abstract class BaseMoorAccessor implements HasDeclaration {
/// Resolved imports from this file. /// Resolved imports from this file.
List<FoundFile>? imports = []; List<FoundFile>? imports = [];
BaseMoorAccessor._(this.declaration, this.declaredTables, BaseMoorAccessor._(this.declaration, this.declaredTables, this.declaredViews,
this.declaredIncludes, this.declaredQueries); this.declaredIncludes, this.declaredQueries);
} }
@ -60,9 +67,11 @@ class Database extends BaseMoorAccessor {
this.daos = const [], this.daos = const [],
DatabaseOrDaoDeclaration? declaration, DatabaseOrDaoDeclaration? declaration,
List<MoorTable> declaredTables = const [], List<MoorTable> declaredTables = const [],
List<MoorView> declaredViews = const [],
List<String> declaredIncludes = const [], List<String> declaredIncludes = const [],
List<DeclaredQuery> declaredQueries = const [], List<DeclaredQuery> declaredQueries = const [],
}) : super._(declaration, declaredTables, declaredIncludes, declaredQueries); }) : super._(declaration, declaredTables, declaredViews, declaredIncludes,
declaredQueries);
} }
/// A dao, declared via an `UseDao` annotation on a Dart class. /// A dao, declared via an `UseDao` annotation on a Dart class.
@ -74,7 +83,9 @@ class Dao extends BaseMoorAccessor {
required this.dbClass, required this.dbClass,
DatabaseOrDaoDeclaration? declaration, DatabaseOrDaoDeclaration? declaration,
required List<MoorTable> declaredTables, required List<MoorTable> declaredTables,
List<MoorView> declaredViews = const [],
required List<String> declaredIncludes, required List<String> declaredIncludes,
required List<DeclaredQuery> declaredQueries, required List<DeclaredQuery> declaredQueries,
}) : super._(declaration, declaredTables, declaredIncludes, declaredQueries); }) : super._(declaration, declaredTables, declaredViews, declaredIncludes,
declaredQueries);
} }

View File

@ -10,6 +10,23 @@ abstract class ViewDeclarationWithSql implements ViewDeclaration {
CreateViewStatement get creatingStatement; CreateViewStatement get creatingStatement;
} }
class DartViewDeclaration implements ViewDeclaration, DartDeclaration {
@override
final SourceRange declaration;
@override
final ClassElement element;
DartViewDeclaration._(this.declaration, this.element);
factory DartViewDeclaration(ClassElement element, FoundFile file) {
return DartViewDeclaration._(
SourceRange.fromElementAndFile(element, file),
element,
);
}
}
class MoorViewDeclaration class MoorViewDeclaration
implements ViewDeclaration, MoorDeclaration, ViewDeclarationWithSql { implements ViewDeclaration, MoorDeclaration, ViewDeclarationWithSql {
@override @override

View File

@ -13,7 +13,7 @@ import 'model.dart';
/// A parsed view /// A parsed view
class MoorView extends MoorEntityWithResultSet { class MoorView extends MoorEntityWithResultSet {
@override @override
final MoorViewDeclaration? declaration; final ViewDeclaration? declaration;
/// The associated view to use for the sqlparser package when analyzing /// The associated view to use for the sqlparser package when analyzing
/// sql queries. Note that this field is set lazily. /// sql queries. Note that this field is set lazily.
@ -38,12 +38,18 @@ class MoorView extends MoorEntityWithResultSet {
@override @override
ExistingRowClass? existingRowClass; ExistingRowClass? existingRowClass;
final List<String> staticReferences;
final ViewQueryInformation? viewQuery;
MoorView({ MoorView({
this.declaration, this.declaration,
required this.name, required this.name,
required this.dartTypeName, required this.dartTypeName,
required this.entityInfoName, required this.entityInfoName,
this.existingRowClass, this.existingRowClass,
this.staticReferences = const [],
this.viewQuery,
}); });
@override @override
@ -80,7 +86,7 @@ class MoorView extends MoorEntityWithResultSet {
/// The `CREATE VIEW` statement that can be used to create this view. /// The `CREATE VIEW` statement that can be used to create this view.
String createSql(MoorOptions options) { String createSql(MoorOptions options) {
final decl = declaration; final decl = declaration as MoorViewDeclaration?;
if (decl == null) { if (decl == null) {
throw StateError('Cannot show SQL for views without a declaration'); throw StateError('Cannot show SQL for views without a declaration');
} }
@ -94,3 +100,11 @@ class MoorView extends MoorEntityWithResultSet {
@override @override
String get displayName => name; String get displayName => name;
} }
class ViewQueryInformation {
final Map<String, MoorColumn> columns;
final String from;
final String query;
ViewQueryInformation(this.columns, this.from, this.query);
}

View File

@ -0,0 +1,14 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:drift_dev/src/analyzer/runner/steps.dart';
Exception analysisError(Step step, Element element, String message) {
final error = ErrorInDartCode(
message: message,
severity: Severity.criticalError,
affectedElement: element,
);
step.reportError(error);
return AnalysisException(error.toString());
}

View File

@ -18,6 +18,12 @@ bool isColumn(DartType type) {
!name.contains('Builder'); !name.contains('Builder');
} }
bool isExpression(DartType type) {
final name = type.element?.name ?? '';
return isFromMoor(type) && name.startsWith('Expression');
}
extension TypeUtils on DartType { extension TypeUtils on DartType {
String get userVisibleName => getDisplayString(withNullability: true); String get userVisibleName => getDisplayString(withNullability: true);

View File

@ -18,7 +18,7 @@ class DatabaseWriter {
DatabaseWriter(this.db, this.scope); DatabaseWriter(this.db, this.scope);
String get _dbClassName { String get dbClassName {
if (scope.generationOptions.isGeneratingForSchema) { if (scope.generationOptions.isGeneratingForSchema) {
return 'DatabaseAtV${scope.generationOptions.forSchema}'; return 'DatabaseAtV${scope.generationOptions.forSchema}';
} }
@ -32,13 +32,13 @@ class DatabaseWriter {
TableWriter(table, scope.child()).writeInto(); TableWriter(table, scope.child()).writeInto();
} }
for (final view in db.views) { for (final view in db.views) {
ViewWriter(view, scope.child()).write(); ViewWriter(view, scope.child(), this).write();
} }
// Write the database class // Write the database class
final dbScope = scope.child(); final dbScope = scope.child();
final className = _dbClassName; final className = dbClassName;
final firstLeaf = dbScope.leaf(); final firstLeaf = dbScope.leaf();
final isAbstract = !scope.generationOptions.isGeneratingForSchema; final isAbstract = !scope.generationOptions.isGeneratingForSchema;
if (isAbstract) { if (isAbstract) {
@ -95,7 +95,7 @@ class DatabaseWriter {
buffer: dbScope.leaf(), buffer: dbScope.leaf(),
getterName: entity.dbGetterName, getterName: entity.dbGetterName,
returnType: entity.entityInfoName, returnType: entity.entityInfoName,
code: '${entity.entityInfoName}()', code: '${entity.entityInfoName}(this)',
options: scope.generationOptions, options: scope.generationOptions,
); );
} }

View File

@ -7,6 +7,7 @@ import 'package:drift_dev/writer.dart';
class DataClassWriter { class DataClassWriter {
final MoorEntityWithResultSet table; final MoorEntityWithResultSet table;
final Scope scope; final Scope scope;
final columns = <MoorColumn>[];
bool get isInsertable => table is MoorTable; bool get isInsertable => table is MoorTable;
@ -32,8 +33,16 @@ class DataClassWriter {
_buffer.writeln('{'); _buffer.writeln('{');
} }
// write view columns
final view = table;
if (view is MoorView && view.viewQuery != null) {
columns.addAll(view.viewQuery!.columns.values);
} else {
columns.addAll(table.columns);
}
// write individual fields // write individual fields
for (final column in table.columns) { for (final column in columns) {
if (column.documentationComment != null) { if (column.documentationComment != null) {
_buffer.write('${column.documentationComment}\n'); _buffer.write('${column.documentationComment}\n');
} }
@ -46,7 +55,7 @@ class DataClassWriter {
_buffer _buffer
..write(table.dartTypeName) ..write(table.dartTypeName)
..write('({') ..write('({')
..write(table.columns.map((column) { ..write(columns.map((column) {
final nullableDartType = column.typeConverter != null && final nullableDartType = column.typeConverter != null &&
scope.options.nullAwareTypeConverters scope.options.nullAwareTypeConverters
? column.typeConverter!.hasNullableDartType ? column.typeConverter!.hasNullableDartType
@ -80,8 +89,8 @@ class DataClassWriter {
_writeToString(); _writeToString();
_writeHashCode(); _writeHashCode();
overrideEquals(table.columns.map((c) => c.dartGetterName), overrideEquals(
table.dartTypeName, _buffer); columns.map((c) => c.dartGetterName), table.dartTypeName, _buffer);
// finish class declaration // finish class declaration
_buffer.write('}'); _buffer.write('}');
@ -103,7 +112,7 @@ class DataClassWriter {
final writer = RowMappingWriter( final writer = RowMappingWriter(
const [], const [],
{for (final column in table.columns) column: column.dartGetterName}, {for (final column in columns) column: column.dartGetterName},
table, table,
scope.generationOptions, scope.generationOptions,
scope.options, scope.options,
@ -124,7 +133,7 @@ class DataClassWriter {
..write('serializer ??= $_runtimeOptions.defaultSerializer;\n') ..write('serializer ??= $_runtimeOptions.defaultSerializer;\n')
..write('return $dataClassName('); ..write('return $dataClassName(');
for (final column in table.columns) { for (final column in columns) {
final getter = column.dartGetterName; final getter = column.dartGetterName;
final jsonKey = column.getJsonKey(scope.options); final jsonKey = column.getJsonKey(scope.options);
final type = column.dartTypeCode(scope.generationOptions); final type = column.dartTypeCode(scope.generationOptions);
@ -150,7 +159,7 @@ class DataClassWriter {
'serializer ??= $_runtimeOptions.defaultSerializer;\n' 'serializer ??= $_runtimeOptions.defaultSerializer;\n'
'return <String, dynamic>{\n'); 'return <String, dynamic>{\n');
for (final column in table.columns) { for (final column in columns) {
final name = column.getJsonKey(scope.options); final name = column.getJsonKey(scope.options);
final getter = column.dartGetterName; final getter = column.dartGetterName;
final needsThis = getter == 'serializer'; final needsThis = getter == 'serializer';
@ -168,9 +177,9 @@ class DataClassWriter {
final wrapNullableInValue = scope.options.generateValuesInCopyWith; final wrapNullableInValue = scope.options.generateValuesInCopyWith;
_buffer.write('$dataClassName copyWith({'); _buffer.write('$dataClassName copyWith({');
for (var i = 0; i < table.columns.length; i++) { for (var i = 0; i < columns.length; i++) {
final column = table.columns[i]; final column = columns[i];
final last = i == table.columns.length - 1; final last = i == columns.length - 1;
final isNullable = column.nullableInDart; final isNullable = column.nullableInDart;
final typeName = column.dartTypeCode(scope.generationOptions); final typeName = column.dartTypeCode(scope.generationOptions);
@ -194,7 +203,7 @@ class DataClassWriter {
_buffer.write('}) => $dataClassName('); _buffer.write('}) => $dataClassName(');
for (final column in table.columns) { for (final column in columns) {
// We also have a method parameter called like the getter, so we can use // We also have a method parameter called like the getter, so we can use
// field: field ?? this.field. If we wrapped the parameter in a `Value`, // field: field ?? this.field. If we wrapped the parameter in a `Value`,
// we can use field.present ? field.value : this.field // we can use field.present ? field.value : this.field
@ -217,7 +226,7 @@ class DataClassWriter {
'(bool nullToAbsent) {\n') '(bool nullToAbsent) {\n')
..write('final map = <String, Expression> {};'); ..write('final map = <String, Expression> {};');
for (final column in table.columns) { for (final column in columns) {
// Generated column - cannot be used for inserts or updates // Generated column - cannot be used for inserts or updates
if (column.isGenerated) continue; if (column.isGenerated) continue;
@ -275,7 +284,7 @@ class DataClassWriter {
..write(asTable.getNameForCompanionClass(scope.options)) ..write(asTable.getNameForCompanionClass(scope.options))
..write('('); ..write('(');
for (final column in table.columns) { for (final column in columns) {
// Generated columns are not parts of companions. // Generated columns are not parts of companions.
if (column.isGenerated) continue; if (column.isGenerated) continue;
@ -304,7 +313,7 @@ class DataClassWriter {
void _writeToString() { void _writeToString() {
overrideToString( overrideToString(
table.dartTypeName, table.dartTypeName,
[for (final column in table.columns) column.dartGetterName], [for (final column in columns) column.dartGetterName],
_buffer, _buffer,
); );
} }
@ -312,7 +321,7 @@ class DataClassWriter {
void _writeHashCode() { void _writeHashCode() {
_buffer.write('@override\n int get hashCode => '); _buffer.write('@override\n int get hashCode => ');
final fields = table.columns.map((c) => c.dartGetterName).toList(); final fields = columns.map((c) => c.dartGetterName).toList();
const HashCodeWriter().writeHashCode(fields, _buffer); const HashCodeWriter().writeHashCode(fields, _buffer);
_buffer.write(';'); _buffer.write(';');
} }
@ -332,7 +341,11 @@ class RowMappingWriter {
void writeArguments(StringBuffer buffer) { void writeArguments(StringBuffer buffer) {
String readAndMap(MoorColumn column) { String readAndMap(MoorColumn column) {
final rawData = "data['\${effectivePrefix}${column.name.name}']"; var columnName = column.name.name;
if (column.table != null && column.table != table) {
columnName = '${column.table!.sqlName}.${column.name.name}';
}
final rawData = "data['\${effectivePrefix}$columnName']";
final sqlType = 'const ${sqlTypes[column.type]}()'; final sqlType = 'const ${sqlTypes[column.type]}()';
var loadType = '$sqlType.mapFromDatabaseResponse($rawData)'; var loadType = '$sqlType.mapFromDatabaseResponse($rawData)';

View File

@ -1,6 +1,7 @@
import 'package:drift_dev/moor_generator.dart'; import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/utils/string_escaper.dart'; import 'package:drift_dev/src/utils/string_escaper.dart';
import '../database_writer.dart';
import '../writer.dart'; import '../writer.dart';
import 'data_class_writer.dart'; import 'data_class_writer.dart';
import 'table_writer.dart'; import 'table_writer.dart';
@ -8,6 +9,7 @@ import 'table_writer.dart';
class ViewWriter extends TableOrViewWriter { class ViewWriter extends TableOrViewWriter {
final MoorView view; final MoorView view;
final Scope scope; final Scope scope;
final DatabaseWriter databaseWriter;
@override @override
late StringBuffer buffer; late StringBuffer buffer;
@ -15,7 +17,7 @@ class ViewWriter extends TableOrViewWriter {
@override @override
MoorView get tableOrView => view; MoorView get tableOrView => view;
ViewWriter(this.view, this.scope); ViewWriter(this.view, this.scope, this.databaseWriter);
void write() { void write() {
if (scope.generationOptions.writeDataClasses && if (scope.generationOptions.writeDataClasses &&
@ -29,29 +31,79 @@ class ViewWriter extends TableOrViewWriter {
void _writeViewInfoClass() { void _writeViewInfoClass() {
buffer = scope.leaf(); buffer = scope.leaf();
buffer.write('class ${view.entityInfoName} extends View'); buffer.write('class ${view.entityInfoName} extends ViewInfo');
if (scope.generationOptions.writeDataClasses) { if (scope.generationOptions.writeDataClasses) {
buffer.write('<${view.entityInfoName}, ' buffer.write('<${view.entityInfoName}, '
'${view.dartTypeCode(scope.generationOptions)}>'); '${view.dartTypeCode(scope.generationOptions)}>');
} else { } else {
buffer.write('<${view.entityInfoName}, Never>'); buffer.write('<${view.entityInfoName}, Never>');
} }
buffer.write(' implements HasResultSet');
buffer buffer
..write('{\n') ..write('{\n')
..write('${view.entityInfoName}(): super(') // write the generated database reference that is set in the constructor
..write(asDartLiteral(view.name)) ..write('final ${databaseWriter.dbClassName} _db;\n')
..write(',') ..write('final ${scope.nullableType('String')} _alias;\n')
..write(asDartLiteral(view.createSql(scope.options))) ..write('${view.entityInfoName}(this._db, [this._alias]);\n');
..write(');');
for (final ref in view.staticReferences) {
buffer.write('$ref\n');
}
if (view.viewQuery == null) {
writeGetColumnsOverride(); writeGetColumnsOverride();
} else {
final columns = view.viewQuery!.columns.keys.join(', ');
buffer.write('@override\nList<GeneratedColumn> get \$columns => '
'[$columns];\n');
}
buffer
..write('@override\nString get aliasedName => '
'_alias ?? entityName;\n')
..write('@override\n String get entityName=>'
' ${asDartLiteral(view.name)};\n');
if (view.declaration is MoorViewDeclaration) {
buffer.write('@override\n String get createViewStmt =>'
' ${asDartLiteral(view.createSql(scope.options))};\n');
} else {
buffer.write('@override\n String? get createViewStmt => null;\n');
}
writeAsDslTable(); writeAsDslTable();
writeMappingMethod(scope); writeMappingMethod(scope);
for (final column in view.columns) { for (final column in view.viewQuery?.columns.values ?? view.columns) {
writeColumnGetter(column, scope.generationOptions, false); writeColumnGetter(column, scope.generationOptions, false);
} }
_writeAliasGenerator();
_writeQuery();
buffer.writeln('}'); buffer.writeln('}');
} }
void _writeAliasGenerator() {
final typeName = view.entityInfoName;
buffer
..write('@override\n')
..write('$typeName createAlias(String alias) {\n')
..write('return $typeName(_db, alias);')
..write('}');
}
void _writeQuery() {
buffer.write('@override\nQuery? get query => ');
final query = view.viewQuery;
if (query != null) {
buffer.write('(_db.selectOnly(${query.from}, '
'includeJoinedTableColumns: false)..addColumns(\$columns))'
'${query.query};');
} else {
buffer.write('null;\n');
}
}
} }

View File

@ -25,7 +25,7 @@ dependencies:
io: ^1.0.3 io: ^1.0.3
# Moor-specific analysis and apis # Moor-specific analysis and apis
drift: ^1.1.0-dev drift: '>=1.1.0-dev <1.2.0'
sqlite3: '>=0.1.6 <2.0.0' sqlite3: '>=0.1.6 <2.0.0'
sqlparser: ^0.18.0 sqlparser: ^0.18.0

View File

@ -130,10 +130,12 @@ class $KeyValuesTable extends KeyValues
final String? _alias; final String? _alias;
$KeyValuesTable(this._db, [this._alias]); $KeyValuesTable(this._db, [this._alias]);
final VerificationMeta _keyMeta = const VerificationMeta('key'); final VerificationMeta _keyMeta = const VerificationMeta('key');
@override
late final GeneratedColumn<String?> key = GeneratedColumn<String?>( late final GeneratedColumn<String?> key = GeneratedColumn<String?>(
'key', aliasedName, false, 'key', aliasedName, false,
type: const StringType(), requiredDuringInsert: true); type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _valueMeta = const VerificationMeta('value'); final VerificationMeta _valueMeta = const VerificationMeta('value');
@override
late final GeneratedColumn<String?> value = GeneratedColumn<String?>( late final GeneratedColumn<String?> value = GeneratedColumn<String?>(
'value', aliasedName, false, 'value', aliasedName, false,
type: const StringType(), requiredDuringInsert: true); type: const StringType(), requiredDuringInsert: true);

View File

@ -237,26 +237,31 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
final String? _alias; final String? _alias;
$UsersTable(this._db, [this._alias]); $UsersTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id'); final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>( late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false, 'id', aliasedName, false,
type: const IntType(), type: const IntType(),
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name'); final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String?> name = GeneratedColumn<String?>( late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false, 'name', aliasedName, false,
type: const StringType(), requiredDuringInsert: true); type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _birthDateMeta = const VerificationMeta('birthDate'); final VerificationMeta _birthDateMeta = const VerificationMeta('birthDate');
@override
late final GeneratedColumn<DateTime?> birthDate = GeneratedColumn<DateTime?>( late final GeneratedColumn<DateTime?> birthDate = GeneratedColumn<DateTime?>(
'birth_date', aliasedName, false, 'birth_date', aliasedName, false,
type: const IntType(), requiredDuringInsert: true); type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _profilePictureMeta = final VerificationMeta _profilePictureMeta =
const VerificationMeta('profilePicture'); const VerificationMeta('profilePicture');
@override
late final GeneratedColumn<Uint8List?> profilePicture = late final GeneratedColumn<Uint8List?> profilePicture =
GeneratedColumn<Uint8List?>('profile_picture', aliasedName, true, GeneratedColumn<Uint8List?>('profile_picture', aliasedName, true,
type: const BlobType(), requiredDuringInsert: false); type: const BlobType(), requiredDuringInsert: false);
final VerificationMeta _preferencesMeta = final VerificationMeta _preferencesMeta =
const VerificationMeta('preferences'); const VerificationMeta('preferences');
@override
late final GeneratedColumnWithTypeConverter<Preferences, String?> late final GeneratedColumnWithTypeConverter<Preferences, String?>
preferences = GeneratedColumn<String?>('preferences', aliasedName, true, preferences = GeneratedColumn<String?>('preferences', aliasedName, true,
type: const StringType(), requiredDuringInsert: false) type: const StringType(), requiredDuringInsert: false)
@ -468,15 +473,18 @@ class $FriendshipsTable extends Friendships
final String? _alias; final String? _alias;
$FriendshipsTable(this._db, [this._alias]); $FriendshipsTable(this._db, [this._alias]);
final VerificationMeta _firstUserMeta = const VerificationMeta('firstUser'); final VerificationMeta _firstUserMeta = const VerificationMeta('firstUser');
@override
late final GeneratedColumn<int?> firstUser = GeneratedColumn<int?>( late final GeneratedColumn<int?> firstUser = GeneratedColumn<int?>(
'first_user', aliasedName, false, 'first_user', aliasedName, false,
type: const IntType(), requiredDuringInsert: true); type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _secondUserMeta = const VerificationMeta('secondUser'); final VerificationMeta _secondUserMeta = const VerificationMeta('secondUser');
@override
late final GeneratedColumn<int?> secondUser = GeneratedColumn<int?>( late final GeneratedColumn<int?> secondUser = GeneratedColumn<int?>(
'second_user', aliasedName, false, 'second_user', aliasedName, false,
type: const IntType(), requiredDuringInsert: true); type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _reallyGoodFriendsMeta = final VerificationMeta _reallyGoodFriendsMeta =
const VerificationMeta('reallyGoodFriends'); const VerificationMeta('reallyGoodFriends');
@override
late final GeneratedColumn<bool?> reallyGoodFriends = GeneratedColumn<bool?>( late final GeneratedColumn<bool?> reallyGoodFriends = GeneratedColumn<bool?>(
'really_good_friends', aliasedName, false, 'really_good_friends', aliasedName, false,
type: const BoolType(), type: const BoolType(),

View File

@ -128,12 +128,14 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
final String? _alias; final String? _alias;
$UsersTable(this._db, [this._alias]); $UsersTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id'); final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>( late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false, 'id', aliasedName, false,
type: const IntType(), type: const IntType(),
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name'); final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String?> name = GeneratedColumn<String?>( late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false, 'name', aliasedName, false,
type: const StringType(), type: const StringType(),