Make drift's tests pass with new analyzer

This commit is contained in:
Simon Binder 2022-11-04 13:42:06 +01:00
parent c2b319bc8e
commit c5504237d5
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
14 changed files with 728 additions and 623 deletions

View File

@ -6,6 +6,178 @@ part of 'main.dart';
// DriftElementId(asset:drift/example/main.dart, todo_items)
// DriftElementId(asset:drift/example/main.dart, todo_categories)
// DriftElementId(asset:drift/example/main.dart, customViewName)
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
final int id;
final String name;
const TodoCategory({required this.id, required this.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> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$TodoCategoriesTable(this.attachedDatabase, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string, 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}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TodoCategory(
id: attachedDatabase.options.types
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
name: attachedDatabase.options.types
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
);
}
@override
$TodoCategoriesTable createAlias(String alias) {
return $TodoCategoriesTable(attachedDatabase, alias);
}
}
class TodoItem extends DataClass implements Insertable<TodoItem> {
final int id;
final String title;
@ -290,178 +462,6 @@ class $TodoItemsTable extends TodoItems
}
}
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
final int id;
final String name;
const TodoCategory({required this.id, required this.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> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$TodoCategoriesTable(this.attachedDatabase, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string, 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}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TodoCategory(
id: attachedDatabase.options.types
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
name: attachedDatabase.options.types
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
);
}
@override
$TodoCategoriesTable createAlias(String alias) {
return $TodoCategoriesTable(attachedDatabase, alias);
}
}
class TodoCategoryItemCountData extends DataClass {
final String name;
final int? itemCount;
@ -680,8 +680,8 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo<
abstract class _$Database extends GeneratedDatabase {
_$Database(QueryExecutor e) : super(e);
_$Database.connect(DatabaseConnection c) : super.connect(c);
late final $TodoItemsTable todoItems = $TodoItemsTable(this);
late final $TodoCategoriesTable todoCategories = $TodoCategoriesTable(this);
late final $TodoItemsTable todoItems = $TodoItemsTable(this);
late final $TodoCategoryItemCountView todoCategoryItemCount =
$TodoCategoryItemCountView(this);
late final $TodoItemWithCategoryNameViewView customViewName =
@ -691,5 +691,5 @@ abstract class _$Database extends GeneratedDatabase {
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[todoItems, todoCategories, todoCategoryItemCount, customViewName];
[todoCategories, todoItems, todoCategoryItemCount, customViewName];
}

View File

@ -23,7 +23,7 @@ void main() {
'CREATE TABLE IF NOT EXISTS "todos" '
'("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "title" TEXT NULL, '
'"content" TEXT NOT NULL, "target_date" INTEGER NULL UNIQUE, '
'"category" INTEGER NULL REFERENCES "categories" ("id"), '
'"category" INTEGER NULL REFERENCES categories (id), '
'UNIQUE ("title", "category"), UNIQUE ("title", "target_date"));',
[]));
@ -41,7 +41,7 @@ void main() {
'CREATE TABLE IF NOT EXISTS "users" ('
'"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'"name" TEXT NOT NULL UNIQUE, '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 CHECK ("is_awesome" IN (0, 1)), '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 CHECK (is_awesome IN (0, 1)), '
'"profile_picture" BLOB NOT NULL, '
'"creation_time" INTEGER NOT NULL '
"DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)) "
@ -98,7 +98,7 @@ void main() {
'CREATE TABLE IF NOT EXISTS "users" '
'("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'"name" TEXT NOT NULL UNIQUE, '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 CHECK ("is_awesome" IN (0, 1)), '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 CHECK (is_awesome IN (0, 1)), '
'"profile_picture" BLOB NOT NULL, '
'"creation_time" INTEGER NOT NULL '
"DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)) "
@ -145,7 +145,7 @@ void main() {
verify(mockExecutor.runCustom('ALTER TABLE "users" ADD COLUMN '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 '
'CHECK ("is_awesome" IN (0, 1));'));
'CHECK (is_awesome IN (0, 1));'));
});
test('renames columns', () async {

View File

@ -52,7 +52,7 @@ class $NoIdsTable extends Table with TableInfo<$NoIdsTable, NoIdRow> {
'payload', aliasedName, false,
type: DriftSqlType.blob,
requiredDuringInsert: true,
defaultConstraints: 'PRIMARY KEY');
$customConstraints: 'NOT NULL PRIMARY KEY');
@override
List<GeneratedColumn> get $columns => [payload];
@override
@ -92,6 +92,8 @@ class $NoIdsTable extends Table with TableInfo<$NoIdsTable, NoIdRow> {
@override
bool get withoutRowId => true;
@override
List<String> get customConstraints => const [];
@override
bool get dontWriteConstraints => true;
}
@ -225,13 +227,14 @@ class $WithDefaultsTable extends Table
'a', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT \'something\'',
defaultValue: const CustomExpression('\'something\''));
final VerificationMeta _bMeta = const VerificationMeta('b');
late final GeneratedColumn<int> b = GeneratedColumn<int>(
'b', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'UNIQUE');
$customConstraints: 'UNIQUE');
@override
List<GeneratedColumn> get $columns => [a, b];
@override
@ -270,6 +273,8 @@ class $WithDefaultsTable extends Table
return $WithDefaultsTable(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const [];
@override
bool get dontWriteConstraints => true;
}
@ -424,15 +429,21 @@ class $WithConstraintsTable extends Table
final VerificationMeta _aMeta = const VerificationMeta('a');
late final GeneratedColumn<String> a = GeneratedColumn<String>(
'a', aliasedName, true,
type: DriftSqlType.string, requiredDuringInsert: false);
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '');
final VerificationMeta _bMeta = const VerificationMeta('b');
late final GeneratedColumn<int> b = GeneratedColumn<int>(
'b', aliasedName, false,
type: DriftSqlType.int, requiredDuringInsert: true);
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
final VerificationMeta _cMeta = const VerificationMeta('c');
late final GeneratedColumn<double> c = GeneratedColumn<double>(
'c', aliasedName, true,
type: DriftSqlType.double, requiredDuringInsert: false);
type: DriftSqlType.double,
requiredDuringInsert: false,
$customConstraints: '');
@override
List<GeneratedColumn> get $columns => [a, b, c];
@override
@ -478,6 +489,9 @@ class $WithConstraintsTable extends Table
return $WithConstraintsTable(attachedDatabase, alias);
}
@override
List<String> get customConstraints =>
const ['FOREIGN KEY(a, b)REFERENCES with_defaults(a, b)'];
@override
bool get dontWriteConstraints => true;
}
@ -676,23 +690,29 @@ class $ConfigTable extends Table with TableInfo<$ConfigTable, Config> {
'config_key', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
defaultConstraints: 'PRIMARY KEY');
$customConstraints: 'NOT NULL PRIMARY KEY');
final VerificationMeta _configValueMeta =
const VerificationMeta('configValue');
late final GeneratedColumn<String> configValue = GeneratedColumn<String>(
'config_value', aliasedName, true,
type: DriftSqlType.string, requiredDuringInsert: false);
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '');
final VerificationMeta _syncStateMeta = const VerificationMeta('syncState');
late final GeneratedColumnWithTypeConverter<SyncType?, int> syncState =
GeneratedColumn<int>('sync_state', aliasedName, true,
type: DriftSqlType.int, requiredDuringInsert: false)
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: '')
.withConverter<SyncType?>($ConfigTable.$convertersyncStaten);
final VerificationMeta _syncStateImplicitMeta =
const VerificationMeta('syncStateImplicit');
late final GeneratedColumnWithTypeConverter<SyncType?, int>
syncStateImplicit = GeneratedColumn<int>(
'sync_state_implicit', aliasedName, true,
type: DriftSqlType.int, requiredDuringInsert: false)
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: '')
.withConverter<SyncType?>($ConfigTable.$convertersyncStateImplicitn);
@override
List<GeneratedColumn> get $columns =>
@ -758,6 +778,8 @@ class $ConfigTable extends Table with TableInfo<$ConfigTable, Config> {
@override
bool get isStrict => true;
@override
List<String> get customConstraints => const [];
@override
bool get dontWriteConstraints => true;
}
@ -941,22 +963,28 @@ class $MytableTable extends Table with TableInfo<$MytableTable, MytableData> {
final VerificationMeta _someidMeta = const VerificationMeta('someid');
late final GeneratedColumn<int> someid = GeneratedColumn<int>(
'someid', aliasedName, false,
type: DriftSqlType.int, requiredDuringInsert: false);
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
final VerificationMeta _sometextMeta = const VerificationMeta('sometext');
late final GeneratedColumn<String> sometext = GeneratedColumn<String>(
'sometext', aliasedName, true,
type: DriftSqlType.string, requiredDuringInsert: false);
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '');
final VerificationMeta _isInsertingMeta =
const VerificationMeta('isInserting');
late final GeneratedColumn<bool> isInserting = GeneratedColumn<bool>(
'is_inserting', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: 'CHECK (is_inserting IN (0, 1))');
$customConstraints: '');
final VerificationMeta _somedateMeta = const VerificationMeta('somedate');
late final GeneratedColumn<DateTime> somedate = GeneratedColumn<DateTime>(
'somedate', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
$customConstraints: '');
@override
List<GeneratedColumn> get $columns =>
[someid, sometext, isInserting, somedate];
@ -1013,6 +1041,8 @@ class $MytableTable extends Table with TableInfo<$MytableTable, MytableData> {
return $MytableTable(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY(someid DESC)'];
@override
bool get dontWriteConstraints => true;
}
@ -1160,15 +1190,21 @@ class $EmailTable extends Table
final VerificationMeta _senderMeta = const VerificationMeta('sender');
late final GeneratedColumn<String> sender = GeneratedColumn<String>(
'sender', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
final VerificationMeta _titleMeta = const VerificationMeta('title');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
final VerificationMeta _bodyMeta = const VerificationMeta('body');
late final GeneratedColumn<String> body = GeneratedColumn<String>(
'body', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
@override
List<GeneratedColumn> get $columns => [sender, title, body];
@override
@ -1221,6 +1257,8 @@ class $EmailTable extends Table
return $EmailTable(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const [];
@override
bool get dontWriteConstraints => true;
@override
@ -1352,11 +1390,15 @@ class $WeirdTableTable extends Table
final VerificationMeta _sqlClassMeta = const VerificationMeta('sqlClass');
late final GeneratedColumn<int> sqlClass = GeneratedColumn<int>(
'class', aliasedName, false,
type: DriftSqlType.int, requiredDuringInsert: true);
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
final VerificationMeta _textColumnMeta = const VerificationMeta('textColumn');
late final GeneratedColumn<String> textColumn = GeneratedColumn<String>(
'text', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
@override
List<GeneratedColumn> get $columns => [sqlClass, textColumn];
@override
@ -1401,6 +1443,8 @@ class $WeirdTableTable extends Table
return $WeirdTableTable(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const [];
@override
bool get dontWriteConstraints => true;
}
@ -1815,6 +1859,18 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
'INSERT INTO config (config_key, config_value) VALUES (\'key\', \'values\')')
];
@override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
[
WritePropagation(
on: TableUpdateQuery.onTableName('config',
limitUpdateKind: UpdateKind.insert),
result: [
TableUpdate('with_defaults', kind: UpdateKind.insert),
],
),
],
);
@override
DriftDatabaseOptions get options =>
const DriftDatabaseOptions(storeDateTimeAsText: true);
}

View File

@ -6,6 +6,260 @@ part of 'todos.dart';
// DriftElementId(asset:drift/test/generated/todos.dart, todos)
// DriftElementId(asset:drift/test/generated/todos.dart, categories)
// DriftElementId(asset:drift/test/generated/todos.dart, todo_with_category_view)
class Category extends DataClass implements Insertable<Category> {
final int id;
final String description;
final CategoryPriority priority;
final String descriptionInUpperCase;
const Category(
{required this.id,
required this.description,
required this.priority,
required this.descriptionInUpperCase});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['desc'] = Variable<String>(description);
{
final converter = $CategoriesTable.$converterpriority;
map['priority'] = Variable<int>(converter.toSql(priority));
}
return map;
}
CategoriesCompanion toCompanion(bool nullToAbsent) {
return CategoriesCompanion(
id: Value(id),
description: Value(description),
priority: Value(priority),
);
}
factory Category.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return Category(
id: serializer.fromJson<int>(json['id']),
description: serializer.fromJson<String>(json['description']),
priority: serializer.fromJson<CategoryPriority>(json['priority']),
descriptionInUpperCase:
serializer.fromJson<String>(json['descriptionInUpperCase']),
);
}
factory Category.fromJsonString(String encodedJson,
{ValueSerializer? serializer}) =>
Category.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),
'description': serializer.toJson<String>(description),
'priority': serializer.toJson<CategoryPriority>(priority),
'descriptionInUpperCase':
serializer.toJson<String>(descriptionInUpperCase),
};
}
Category copyWith(
{int? id,
String? description,
CategoryPriority? priority,
String? descriptionInUpperCase}) =>
Category(
id: id ?? this.id,
description: description ?? this.description,
priority: priority ?? this.priority,
descriptionInUpperCase:
descriptionInUpperCase ?? this.descriptionInUpperCase,
);
@override
String toString() {
return (StringBuffer('Category(')
..write('id: $id, ')
..write('description: $description, ')
..write('priority: $priority, ')
..write('descriptionInUpperCase: $descriptionInUpperCase')
..write(')'))
.toString();
}
@override
int get hashCode =>
Object.hash(id, description, priority, descriptionInUpperCase);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is Category &&
other.id == this.id &&
other.description == this.description &&
other.priority == this.priority &&
other.descriptionInUpperCase == this.descriptionInUpperCase);
}
class CategoriesCompanion extends UpdateCompanion<Category> {
final Value<int> id;
final Value<String> description;
final Value<CategoryPriority> priority;
const CategoriesCompanion({
this.id = const Value.absent(),
this.description = const Value.absent(),
this.priority = const Value.absent(),
});
CategoriesCompanion.insert({
this.id = const Value.absent(),
required String description,
this.priority = const Value.absent(),
}) : description = Value(description);
static Insertable<Category> custom({
Expression<int>? id,
Expression<String>? description,
Expression<int>? priority,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (description != null) 'desc': description,
if (priority != null) 'priority': priority,
});
}
CategoriesCompanion copyWith(
{Value<int>? id,
Value<String>? description,
Value<CategoryPriority>? priority}) {
return CategoriesCompanion(
id: id ?? this.id,
description: description ?? this.description,
priority: priority ?? this.priority,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (description.present) {
map['desc'] = Variable<String>(description.value);
}
if (priority.present) {
final converter = $CategoriesTable.$converterpriority;
map['priority'] = Variable<int>(converter.toSql(priority.value));
}
return map;
}
@override
String toString() {
return (StringBuffer('CategoriesCompanion(')
..write('id: $id, ')
..write('description: $description, ')
..write('priority: $priority')
..write(')'))
.toString();
}
}
class $CategoriesTable extends Categories
with TableInfo<$CategoriesTable, Category> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$CategoriesTable(this.attachedDatabase, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _descriptionMeta =
const VerificationMeta('description');
@override
late final GeneratedColumn<String> description = GeneratedColumn<String>(
'desc', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL UNIQUE');
final VerificationMeta _priorityMeta = const VerificationMeta('priority');
@override
late final GeneratedColumnWithTypeConverter<CategoryPriority, int> priority =
GeneratedColumn<int>('priority', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0))
.withConverter<CategoryPriority>($CategoriesTable.$converterpriority);
final VerificationMeta _descriptionInUpperCaseMeta =
const VerificationMeta('descriptionInUpperCase');
@override
late final GeneratedColumn<String> descriptionInUpperCase =
GeneratedColumn<String>('description_in_upper_case', aliasedName, false,
generatedAs: GeneratedAs(description.upper(), false),
type: DriftSqlType.string,
requiredDuringInsert: false);
@override
List<GeneratedColumn> get $columns =>
[id, description, priority, descriptionInUpperCase];
@override
String get aliasedName => _alias ?? 'categories';
@override
String get actualTableName => 'categories';
@override
VerificationContext validateIntegrity(Insertable<Category> 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('desc')) {
context.handle(_descriptionMeta,
description.isAcceptableOrUnknown(data['desc']!, _descriptionMeta));
} else if (isInserting) {
context.missing(_descriptionMeta);
}
context.handle(_priorityMeta, const VerificationResult.success());
if (data.containsKey('description_in_upper_case')) {
context.handle(
_descriptionInUpperCaseMeta,
descriptionInUpperCase.isAcceptableOrUnknown(
data['description_in_upper_case']!, _descriptionInUpperCaseMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Category map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return Category(
id: attachedDatabase.options.types
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
description: attachedDatabase.options.types
.read(DriftSqlType.string, data['${effectivePrefix}desc'])!,
priority: $CategoriesTable.$converterpriority.fromSql(attachedDatabase
.options.types
.read(DriftSqlType.int, data['${effectivePrefix}priority'])!),
descriptionInUpperCase: attachedDatabase.options.types.read(
DriftSqlType.string,
data['${effectivePrefix}description_in_upper_case'])!,
);
}
@override
$CategoriesTable createAlias(String alias) {
return $CategoriesTable(attachedDatabase, alias);
}
static TypeConverter<CategoryPriority, int> $converterpriority =
const EnumIndexConverter<CategoryPriority>(CategoryPriority.values);
}
class TodoEntry extends DataClass implements Insertable<TodoEntry> {
final int id;
final String? title;
@ -309,260 +563,6 @@ class $TodosTableTable extends TodosTable
}
}
class Category extends DataClass implements Insertable<Category> {
final int id;
final String description;
final CategoryPriority priority;
final String descriptionInUpperCase;
const Category(
{required this.id,
required this.description,
required this.priority,
required this.descriptionInUpperCase});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['desc'] = Variable<String>(description);
{
final converter = $CategoriesTable.$converterpriority;
map['priority'] = Variable<int>(converter.toSql(priority));
}
return map;
}
CategoriesCompanion toCompanion(bool nullToAbsent) {
return CategoriesCompanion(
id: Value(id),
description: Value(description),
priority: Value(priority),
);
}
factory Category.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return Category(
id: serializer.fromJson<int>(json['id']),
description: serializer.fromJson<String>(json['description']),
priority: serializer.fromJson<CategoryPriority>(json['priority']),
descriptionInUpperCase:
serializer.fromJson<String>(json['descriptionInUpperCase']),
);
}
factory Category.fromJsonString(String encodedJson,
{ValueSerializer? serializer}) =>
Category.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),
'description': serializer.toJson<String>(description),
'priority': serializer.toJson<CategoryPriority>(priority),
'descriptionInUpperCase':
serializer.toJson<String>(descriptionInUpperCase),
};
}
Category copyWith(
{int? id,
String? description,
CategoryPriority? priority,
String? descriptionInUpperCase}) =>
Category(
id: id ?? this.id,
description: description ?? this.description,
priority: priority ?? this.priority,
descriptionInUpperCase:
descriptionInUpperCase ?? this.descriptionInUpperCase,
);
@override
String toString() {
return (StringBuffer('Category(')
..write('id: $id, ')
..write('description: $description, ')
..write('priority: $priority, ')
..write('descriptionInUpperCase: $descriptionInUpperCase')
..write(')'))
.toString();
}
@override
int get hashCode =>
Object.hash(id, description, priority, descriptionInUpperCase);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is Category &&
other.id == this.id &&
other.description == this.description &&
other.priority == this.priority &&
other.descriptionInUpperCase == this.descriptionInUpperCase);
}
class CategoriesCompanion extends UpdateCompanion<Category> {
final Value<int> id;
final Value<String> description;
final Value<CategoryPriority> priority;
const CategoriesCompanion({
this.id = const Value.absent(),
this.description = const Value.absent(),
this.priority = const Value.absent(),
});
CategoriesCompanion.insert({
this.id = const Value.absent(),
required String description,
this.priority = const Value.absent(),
}) : description = Value(description);
static Insertable<Category> custom({
Expression<int>? id,
Expression<String>? description,
Expression<int>? priority,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (description != null) 'desc': description,
if (priority != null) 'priority': priority,
});
}
CategoriesCompanion copyWith(
{Value<int>? id,
Value<String>? description,
Value<CategoryPriority>? priority}) {
return CategoriesCompanion(
id: id ?? this.id,
description: description ?? this.description,
priority: priority ?? this.priority,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (description.present) {
map['desc'] = Variable<String>(description.value);
}
if (priority.present) {
final converter = $CategoriesTable.$converterpriority;
map['priority'] = Variable<int>(converter.toSql(priority.value));
}
return map;
}
@override
String toString() {
return (StringBuffer('CategoriesCompanion(')
..write('id: $id, ')
..write('description: $description, ')
..write('priority: $priority')
..write(')'))
.toString();
}
}
class $CategoriesTable extends Categories
with TableInfo<$CategoriesTable, Category> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$CategoriesTable(this.attachedDatabase, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _descriptionMeta =
const VerificationMeta('description');
@override
late final GeneratedColumn<String> description = GeneratedColumn<String>(
'desc', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL UNIQUE');
final VerificationMeta _priorityMeta = const VerificationMeta('priority');
@override
late final GeneratedColumnWithTypeConverter<CategoryPriority, int> priority =
GeneratedColumn<int>('priority', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0))
.withConverter<CategoryPriority>($CategoriesTable.$converterpriority);
final VerificationMeta _descriptionInUpperCaseMeta =
const VerificationMeta('descriptionInUpperCase');
@override
late final GeneratedColumn<String> descriptionInUpperCase =
GeneratedColumn<String>('description_in_upper_case', aliasedName, false,
generatedAs: GeneratedAs(description.upper(), false),
type: DriftSqlType.string,
requiredDuringInsert: false);
@override
List<GeneratedColumn> get $columns =>
[id, description, priority, descriptionInUpperCase];
@override
String get aliasedName => _alias ?? 'categories';
@override
String get actualTableName => 'categories';
@override
VerificationContext validateIntegrity(Insertable<Category> 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('desc')) {
context.handle(_descriptionMeta,
description.isAcceptableOrUnknown(data['desc']!, _descriptionMeta));
} else if (isInserting) {
context.missing(_descriptionMeta);
}
context.handle(_priorityMeta, const VerificationResult.success());
if (data.containsKey('description_in_upper_case')) {
context.handle(
_descriptionInUpperCaseMeta,
descriptionInUpperCase.isAcceptableOrUnknown(
data['description_in_upper_case']!, _descriptionInUpperCaseMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Category map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return Category(
id: attachedDatabase.options.types
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
description: attachedDatabase.options.types
.read(DriftSqlType.string, data['${effectivePrefix}desc'])!,
priority: $CategoriesTable.$converterpriority.fromSql(attachedDatabase
.options.types
.read(DriftSqlType.int, data['${effectivePrefix}priority'])!),
descriptionInUpperCase: attachedDatabase.options.types.read(
DriftSqlType.string,
data['${effectivePrefix}description_in_upper_case'])!,
);
}
@override
$CategoriesTable createAlias(String alias) {
return $CategoriesTable(attachedDatabase, alias);
}
static TypeConverter<CategoryPriority, int> $converterpriority =
const EnumIndexConverter<CategoryPriority>(CategoryPriority.values);
}
class User extends DataClass implements Insertable<User> {
final int id;
final String name;
@ -1590,8 +1590,8 @@ class $TodoWithCategoryViewView
abstract class _$TodoDb extends GeneratedDatabase {
_$TodoDb(QueryExecutor e) : super(e);
_$TodoDb.connect(DatabaseConnection c) : super.connect(c);
late final $TodosTableTable todosTable = $TodosTableTable(this);
late final $CategoriesTable categories = $CategoriesTable(this);
late final $TodosTableTable todosTable = $TodosTableTable(this);
late final $UsersTable users = $UsersTable(this);
late final $SharedTodosTable sharedTodos = $SharedTodosTable(this);
late final $TableWithoutPKTable tableWithoutPK = $TableWithoutPKTable(this);
@ -1673,8 +1673,8 @@ abstract class _$TodoDb extends GeneratedDatabase {
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [
todosTable,
categories,
todosTable,
users,
sharedTodos,
tableWithoutPK,
@ -1738,10 +1738,10 @@ class AllTodosWithCategoryResult extends CustomResultSet {
mixin _$SomeDaoMixin on DatabaseAccessor<TodoDb> {
$UsersTable get users => attachedDatabase.users;
$SharedTodosTable get sharedTodos => attachedDatabase.sharedTodos;
$CategoriesTable get categories => attachedDatabase.categories;
$TodosTableTable get todosTable => attachedDatabase.todosTable;
$TodoWithCategoryViewView get todoWithCategoryView =>
attachedDatabase.todoWithCategoryView;
$CategoriesTable get categories => attachedDatabase.categories;
Selectable<TodoEntry> todosForUser({required int user}) {
return customSelect(
'SELECT t.* FROM todos AS t INNER JOIN shared_todos AS st ON st.todo = t.id INNER JOIN users AS u ON u.id = st.user WHERE u.id = ?1',

View File

@ -15,11 +15,11 @@ const _createWithDefaults = 'CREATE TABLE IF NOT EXISTS "with_defaults" ('
const _createWithConstraints = 'CREATE TABLE IF NOT EXISTS "with_constraints" ('
'"a" TEXT, "b" INTEGER NOT NULL, "c" REAL, '
'FOREIGN KEY (a, b) REFERENCES with_defaults (a, b)'
'FOREIGN KEY(a, b)REFERENCES with_defaults(a, b)'
');';
const _createConfig = 'CREATE TABLE IF NOT EXISTS "config" ('
'"config_key" TEXT not null primary key, '
'"config_key" TEXT NOT NULL PRIMARY KEY, '
'"config_value" TEXT, '
'"sync_state" INTEGER, '
'"sync_state_implicit" INTEGER) STRICT;';
@ -29,7 +29,7 @@ const _createMyTable = 'CREATE TABLE IF NOT EXISTS "mytable" ('
'"sometext" TEXT, '
'"is_inserting" INTEGER, '
'"somedate" TEXT, '
'PRIMARY KEY (someid DESC)'
'PRIMARY KEY(someid DESC)'
');';
const _createEmail = 'CREATE VIRTUAL TABLE IF NOT EXISTS "email" USING '

View File

@ -0,0 +1,113 @@
import 'results/results.dart';
/// Transforms queries given in [inputs] so that their result sets respect
/// custom result class names specified by the user.
///
/// The "custom result class name" feature can be used to change the name of a
/// result class and to generate the same result class for multiple custom
/// queries.
///
/// Merging result classes of queries will always happen from the point of a
/// database class or dao. This means that incompatible queries can have the
/// same result class name as long as they're not imported into the same moor
/// accessor.
///
/// This feature doesn't work when we apply other simplifications to query, so
/// we report an error if the query returns a single column or if it has a
/// matching table. This restriction might be lifted in the future, but it makes
/// the implementation easier.
Map<SqlSelectQuery, SqlSelectQuery> transformCustomResultClasses(
Iterable<SqlQuery> inputs,
void Function(String) reportError,
) {
// A group of queries sharing a common result class name.
final queryGroups = <String, List<SqlSelectQuery>>{};
// Find and group queries with the same result class name
for (final query in inputs) {
if (query is! SqlSelectQuery) continue;
final selectQuery = query;
// Doesn't use a custom result class, so it's not affected by this
if (selectQuery.requestedResultClass == null) continue;
// Alright, the query wants a custom result class, but is it allowed to
// have one?
if (selectQuery.resultSet.singleColumn) {
reportError("The query ${selectQuery.name} can't have a custom name as "
'it only returns one column.');
continue;
}
if (selectQuery.resultSet.matchingTable != null) {
reportError("The query ${selectQuery.name} can't have a custom name as "
'it returns a single table data class.');
continue;
}
if (selectQuery.requestedResultClass != null) {
queryGroups
.putIfAbsent(selectQuery.requestedResultClass!, () => [])
.add(selectQuery);
}
}
final replacements = <SqlSelectQuery, SqlSelectQuery>{};
for (final group in queryGroups.entries) {
final resultSetName = group.key;
final queries = group.value;
if (!_resultSetsCompatible(queries.map((e) => e.resultSet))) {
reportError(
'Could not merge result sets to $resultSetName: The queries '
'have different columns and types.',
);
continue;
}
final referenceResult = queries.first.resultSet;
final dartNames = {
for (final column in referenceResult.columns)
column: referenceResult.dartNameFor(column),
};
var isFirst = true;
for (final query in queries) {
final newResultSet = InferredResultSet(
null,
query.resultSet.columns,
resultClassName: resultSetName,
nestedResults: query.resultSet.nestedResults,
// Only generate a result class for the first query in the group
dontGenerateResultClass: !isFirst,
);
// Make sure compatible columns in the two result sets have the same
// Dart name.
newResultSet.forceDartNames({
for (final entry in dartNames.entries)
newResultSet.columns.singleWhere((e) => e.compatibleTo(entry.key)):
entry.value,
});
final newQuery = query.replaceResultSet(newResultSet);
replacements[query] = newQuery;
isFirst = false;
}
}
return replacements;
}
bool _resultSetsCompatible(Iterable<InferredResultSet> resultSets) {
InferredResultSet? last;
for (final current in resultSets) {
if (last != null && !last.isCompatibleTo(current)) {
return false;
}
last = current;
}
return true;
}

View File

@ -84,10 +84,16 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
// can't read the constraints.
final sqlConstraints =
column.hasDefinition ? column.constraints : const <Never>[];
final customConstraintsForDrift = StringBuffer();
for (final constraint in sqlConstraints) {
var writeIntoTable = true;
if (constraint is DriftDartName) {
overriddenDartName = constraint.dartName;
writeIntoTable = false;
} else if (constraint is MappedBy) {
writeIntoTable = false;
if (converter != null) {
reportError(DriftAnalysisError.inDriftFile(
constraint,
@ -150,6 +156,13 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
} else if (constraint is sql.UniqueColumn) {
constraints.add(UniqueColumn());
}
if (writeIntoTable) {
if (customConstraintsForDrift.isNotEmpty) {
customConstraintsForDrift.write(' ');
}
customConstraintsForDrift.write(constraint.toSql());
}
}
columns.add(DriftColumn(
@ -160,6 +173,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
constraints: constraints,
typeConverter: converter,
defaultArgument: defaultArgument,
customConstraints: customConstraintsForDrift.toString(),
declaration: DriftDeclaration.driftFile(
column.definition?.nameToken ?? stmt,
state.ownId.libraryUri,
@ -168,9 +182,12 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
}
VirtualTableData? virtualTableData;
final sqlTableConstraints = <String>[];
if (stmt is CreateTableStatement) {
for (final constraint in stmt.tableConstraints) {
sqlTableConstraints.add(constraint.toSql());
if (constraint is ForeignKeyTableConstraint) {
final otherTable = await resolveSqlReferenceOrReportError<DriftTable>(
constraint.clause.foreignTable.tableName,
@ -325,6 +342,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
tableConstraints: tableConstraints,
virtualTableData: virtualTableData,
writeDefaultConstraints: false,
overrideTableConstraints: sqlTableConstraints,
);
}

View File

@ -1,5 +1,6 @@
import 'package:sqlparser/sqlparser.dart';
import '../../utils/entity_reference_sorter.dart';
import '../driver/driver.dart';
import '../driver/error.dart';
import '../driver/state.dart';
@ -45,7 +46,7 @@ class FileAnalyzer {
.whereType<DriftElement>()
.followedBy(element.references)
.transitiveClosureUnderReferences()
.toList();
.sortTopologicallyOrElse(driver.backend.log.severe);
for (final query in element.declaredQueries) {
final engine =

View File

@ -11,7 +11,7 @@ class FileAnalysisResult {
}
class ResolvedDatabaseAccessor {
final Map<String, SqlQuery> definedQueries;
Map<String, SqlQuery> definedQueries;
final List<FileState> knownImports;
final List<DriftElement> availableElements;

View File

@ -1,127 +0,0 @@
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/errors.dart';
/// Transforms queries accessible to the [accessor] so that they use custom
/// result names.
///
/// The "custom result class name" feature can be used to change the name of a
/// result class and to generate the same result class for multiple custom
/// queries.
///
/// Merging result classes of queries will always happen from the point of a
/// database class or dao. This means that incompatible queries can have the
/// same result class name as long as they're not imported into the same moor
/// accessor.
///
/// This feature doesn't work when we apply other simplifications to query, so
/// we report an error if the query returns a single column or if it has a
/// matching table. This restriction might be lifted in the future, but it makes
/// the implementation easier.
class CustomResultClassTransformer {
final BaseDriftAccessor accessor;
CustomResultClassTransformer(this.accessor);
void transform(ErrorSink errors) {
// For efficient replacing later on
final indexOfOldQueries = <SqlSelectQuery, int>{};
final queryGroups = <String, List<SqlSelectQuery>>{};
// Find and group queries with the same result class name
var index = 0;
for (final query in accessor.queries ?? const <Never>[]) {
final indexOfQuery = index++;
if (query is! SqlSelectQuery) continue;
final selectQuery = query;
// Doesn't use a custom result class, so it's not affected by this
if (selectQuery.requestedResultClass == null) continue;
// Alright, the query wants a custom result class, but is it allowed to
// have one?
if (selectQuery.resultSet.singleColumn) {
errors.report(ErrorInDartCode(
message: "The query ${selectQuery.name} can't have a custom name as "
'it only returns one column.',
affectedElement: accessor.declaration?.element,
));
continue;
}
if (selectQuery.resultSet.matchingTable != null) {
errors.report(ErrorInDartCode(
message: "The query ${selectQuery.name} can't have a custom name as "
'it returns a single table data class.',
affectedElement: accessor.declaration?.element,
));
continue;
}
// query will be replaced, save index for fast replacement later on
indexOfOldQueries[selectQuery] = indexOfQuery;
if (selectQuery.requestedResultClass != null) {
queryGroups
.putIfAbsent(selectQuery.requestedResultClass!, () => [])
.add(selectQuery);
}
}
for (final group in queryGroups.entries) {
final resultSetName = group.key;
final queries = group.value;
if (!_resultSetsCompatible(queries.map((e) => e.resultSet))) {
errors.report(ErrorInDartCode(
message: 'Could not merge result sets to $resultSetName: The queries '
'have different columns and types.',
affectedElement: accessor.declaration?.element,
));
continue;
}
final referenceResult = queries.first.resultSet;
final dartNames = {
for (final column in referenceResult.columns)
column: referenceResult.dartNameFor(column),
};
var isFirst = true;
for (final query in queries) {
final newResultSet = InferredResultSet(
null,
query.resultSet.columns,
resultClassName: resultSetName,
nestedResults: query.resultSet.nestedResults,
// Only generate a result class for the first query in the group
dontGenerateResultClass: !isFirst,
);
// Make sure compatible columns in the two result sets have the same
// Dart name.
newResultSet.forceDartNames({
for (final entry in dartNames.entries)
newResultSet.columns.singleWhere((e) => e.compatibleTo(entry.key)):
entry.value,
});
final newQuery = query.replaceResultSet(newResultSet);
accessor.queries![indexOfOldQueries[query]!] = newQuery;
isFirst = false;
}
}
}
bool _resultSetsCompatible(Iterable<InferredResultSet> resultSets) {
InferredResultSet? last;
for (final current in resultSets) {
if (last != null && !last.isCompatibleTo(current)) {
return false;
}
last = current;
}
return true;
}
}

View File

@ -1,7 +1,9 @@
import 'package:build/build.dart';
import 'package:dart_style/dart_style.dart';
import '../../analysis/custom_result_class.dart';
import '../../analysis/driver/driver.dart';
import '../../analysis/driver/error.dart';
import '../../analysis/driver/state.dart';
import '../../analysis/results/results.dart';
import '../../analyzer/options.dart';
@ -107,7 +109,7 @@ class DriftBuilder extends Builder {
if (result is BaseDriftAccessor) {
final resolved = fileResult.fileAnalysis!.resolvedDatabases[result.id]!;
final importedQueries = <DefinedSqlQuery, SqlQuery>{};
var importedQueries = <DefinedSqlQuery, SqlQuery>{};
for (final query
in resolved.availableElements.whereType<DefinedSqlQuery>()) {
@ -120,6 +122,16 @@ class DriftBuilder extends Builder {
}
}
// Apply custom result classes
final mappedQueries = transformCustomResultClasses(
resolved.definedQueries.values.followedBy(importedQueries.values),
(message) => log.warning('For accessor ${result.id.name}: $message'),
);
importedQueries =
importedQueries.map((k, v) => MapEntry(k, mappedQueries[v] ?? v));
resolved.definedQueries = resolved.definedQueries
.map((k, v) => MapEntry(k, mappedQueries[v] ?? v));
if (result is DriftDatabase) {
final input =
DatabaseGenerationInput(result, resolved, importedQueries);

View File

@ -1,17 +1,18 @@
import 'package:drift/drift.dart' hide DriftDatabase;
import 'package:sqlparser/sqlparser.dart';
import '../analysis/results/file_results.dart';
import '../analysis/results/results.dart';
class FindStreamUpdateRules {
final DriftDatabase db;
final ResolvedDatabaseAccessor db;
FindStreamUpdateRules(this.db);
StreamQueryUpdateRules identifyRules() {
final rules = <UpdateRule>[];
for (final entity in db.references) {
for (final entity in db.availableElements) {
if (entity is DriftTrigger) {
_writeRulesForTrigger(entity, rules);
} else if (entity is DriftTable) {

View File

@ -1,42 +1,71 @@
import '../analysis/results/results.dart';
/// Topologically sorts a list of [DriftElement]s by their
/// [DriftElement.references] relationship: Tables appearing first in the
/// output have to be created first so the table creation script doesn't crash
/// because of tables not existing.
///
/// If there is a circular reference between [DriftTable]s, an error will
/// be added that contains the name of the tables in question. Self-references
/// in tables are allowed.
List<DriftElement> sortEntitiesTopologically(Iterable<DriftElement> tables) {
final run = _SortRun();
extension SortTopologically on Iterable<DriftElement> {
/// Topologically sorts a list of [DriftElement]s by their
/// [DriftElement.references] relationship: Tables appearing first in the
/// output have to be created first so the table creation script doesn't crash
/// because of tables not existing.
///
/// If there is a circular reference between [DriftTable]s, an error will
/// be added that contains the name of the tables in question.
///
/// Note that self-references (e.g. a foreign column in a table referencing
/// itself or another column in the same table) are _not_ included in
/// [DriftElement.references]. For example, an element created for the
/// statement `CREATE TABLE pairs (id INTEGER PRIMARY KEY, partner INTEGER
/// REFERENCES pairs (id))` has no references in the drift element model.
List<DriftElement> sortTopologically() {
final run = _SortRun();
for (final entity in tables) {
if (!run.didVisitAlready(entity)) {
run.previous[entity] = null;
_visit(entity, run);
for (final entity in this) {
if (!run.didVisitAlready(entity)) {
run.previous[entity] = null;
_visit(entity, run);
}
}
return run.result;
}
/// Sorts elements topologically (like [sortTopologically]).
///
/// Unlike throwing an exception for circular references, the [reportError]
/// callback is invoked and the elements are returned unchanged.
List<DriftElement> sortTopologicallyOrElse(
void Function(String) reportError) {
try {
return sortTopologically();
} on CircularReferenceException catch (e) {
final joined = e.affected.map((e) => e.id.name).join('->');
final last = e.affected.last.id.name;
final message =
'Illegal circular reference. This is likely a bug in drift, '
'generated code may be invalid. Invalid cycle from $joined->$last.';
reportError(message);
return toList();
}
}
return run.result;
}
static void _visit(DriftElement entity, _SortRun run) {
for (final reference in entity.references) {
assert(reference != entity, 'Illegal self-reference in $entity');
void _visit(DriftElement entity, _SortRun run) {
for (final reference in entity.references) {
if (run.result.contains(reference) || reference == entity) {
// When the target entity has already been added there's nothing to do.
// We also ignore self-references
} else if (run.previous.containsKey(reference)) {
// that's a circular reference, report
run.throwCircularException(entity, reference);
} else {
run.previous[reference] = entity;
_visit(reference, run);
if (run.result.contains(reference)) {
// When the target entity has already been added there's nothing to do.
// We also ignore self-references
} else if (run.previous.containsKey(reference)) {
// that's a circular reference, report
run.throwCircularException(entity, reference);
} else {
run.previous[reference] = entity;
_visit(reference, run);
}
}
}
// now that everything this table references is written, add the table itself
run.result.add(entity);
// now that everything this table references is written, add the table itself
run.result.add(entity);
}
}
class _SortRun {
@ -73,8 +102,8 @@ class _SortRun {
}
}
/// Thrown by [sortEntitiesTopologically] when the graph formed by
/// [DriftElement.references] is not acyclic except for self-references.
/// Thrown by [SortTopologically] when the graph formed by
/// [DriftElement.references] is not acyclic.
class CircularReferenceException implements Exception {
/// The list of entities forming a circular reference, so that the first
/// entity in this list references the second one and so on. The last entity

View File

@ -158,7 +158,8 @@ class DatabaseWriter {
// close list literal and allSchemaEntities getter
..write('];\n');
final updateRules = FindStreamUpdateRules(db).identifyRules();
final updateRules =
FindStreamUpdateRules(input.resolvedAccessor).identifyRules();
if (updateRules.rules.isNotEmpty) {
schemaScope
..write('@override\nStreamQueryUpdateRules get streamUpdateRules => ')
@ -240,13 +241,14 @@ extension on drift.TableUpdate {
void writeConstructor(TextEmitter emitter) {
emitter
..writeDriftRef('TableUpdate')
..write('(${asDartLiteral(table)})');
..write('(${asDartLiteral(table)}');
if (kind == null) {
emitter.write(')');
} else {
emitter.write(', kind: ');
kind!.write(emitter);
emitter.write(')');
}
}
}
@ -262,7 +264,7 @@ extension on drift.TableUpdateQuery {
emitter.write('.onTableName(${asDartLiteral(query.table)} ');
if (query.limitUpdateKind != null) {
emitter.write(', ');
emitter.write(', limitUpdateKind: ');
query.limitUpdateKind!.write(emitter);
}
emitter.write(')');