diff --git a/docs/lib/snippets/_shared/todo_tables.dart b/docs/lib/snippets/_shared/todo_tables.dart new file mode 100644 index 000000000..8a0da5bae --- /dev/null +++ b/docs/lib/snippets/_shared/todo_tables.dart @@ -0,0 +1,35 @@ +import 'package:drift/drift.dart'; +import 'package:drift/internal/modular.dart'; + +import 'todo_tables.drift.dart'; + +// #docregion tables +class TodoItems extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get title => text().withLength(min: 6, max: 32)(); + TextColumn get content => text().named('body')(); + IntColumn get category => integer().nullable().references(Categories, #id)(); + // #enddocregion tables + DateTimeColumn get dueDate => dateTime().nullable()(); + // #docregion tables +} + +@DataClassName('Category') +class Categories extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); +} +// #enddocregion tables + +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + DateTimeColumn get birthDate => dateTime()(); +} + +class CanUseCommonTables extends ModularAccessor { + CanUseCommonTables(super.attachedDatabase); + + $TodoItemsTable get todoItems => resultSet('todo_items'); + $CategoriesTable get categories => resultSet('categories'); + $UsersTable get users => resultSet('users'); +} diff --git a/docs/lib/snippets/_shared/todo_tables.drift.dart b/docs/lib/snippets/_shared/todo_tables.drift.dart new file mode 100644 index 000000000..52ae385db --- /dev/null +++ b/docs/lib/snippets/_shared/todo_tables.drift.dart @@ -0,0 +1,1181 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/_shared/todo_tables.drift.dart' as i1; +import 'package:drift_docs/snippets/_shared/todo_tables.dart' as i2; +import 'package:drift/internal/modular.dart' as i3; + +typedef $$TodoItemsTableCreateCompanionBuilder = i1.TodoItemsCompanion + Function({ + i0.Value id, + required String title, + required String content, + i0.Value category, + i0.Value dueDate, +}); +typedef $$TodoItemsTableUpdateCompanionBuilder = i1.TodoItemsCompanion + Function({ + i0.Value id, + i0.Value title, + i0.Value content, + i0.Value category, + i0.Value dueDate, +}); + +class $$TodoItemsTableFilterComposer + extends i0.Composer { + $$TodoItemsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get content => $composableBuilder( + column: $table.content, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get dueDate => $composableBuilder( + column: $table.dueDate, builder: (column) => i0.ColumnFilters(column)); + + i1.$$CategoriesTableFilterComposer get category { + final i1.$$CategoriesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.category, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('categories'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$CategoriesTableFilterComposer( + $db: $db, + $table: i3.ReadDatabaseContainer($db) + .resultSet('categories'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$TodoItemsTableOrderingComposer + extends i0.Composer { + $$TodoItemsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get content => $composableBuilder( + column: $table.content, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get dueDate => $composableBuilder( + column: $table.dueDate, builder: (column) => i0.ColumnOrderings(column)); + + i1.$$CategoriesTableOrderingComposer get category { + final i1.$$CategoriesTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.category, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('categories'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$CategoriesTableOrderingComposer( + $db: $db, + $table: i3.ReadDatabaseContainer($db) + .resultSet('categories'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$TodoItemsTableAnnotationComposer + extends i0.Composer { + $$TodoItemsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + i0.GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); + + i0.GeneratedColumn get dueDate => + $composableBuilder(column: $table.dueDate, builder: (column) => column); + + i1.$$CategoriesTableAnnotationComposer get category { + final i1.$$CategoriesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.category, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('categories'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$CategoriesTableAnnotationComposer( + $db: $db, + $table: i3.ReadDatabaseContainer($db) + .resultSet('categories'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$TodoItemsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$TodoItemsTable, + i1.TodoItem, + i1.$$TodoItemsTableFilterComposer, + i1.$$TodoItemsTableOrderingComposer, + i1.$$TodoItemsTableAnnotationComposer, + $$TodoItemsTableCreateCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder, + ( + i1.TodoItem, + i0.BaseReferences + ), + i1.TodoItem, + i0.PrefetchHooks Function({bool category})> { + $$TodoItemsTableTableManager( + i0.GeneratedDatabase db, i1.$TodoItemsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$TodoItemsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$TodoItemsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$TodoItemsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value title = const i0.Value.absent(), + i0.Value content = const i0.Value.absent(), + i0.Value category = const i0.Value.absent(), + i0.Value dueDate = const i0.Value.absent(), + }) => + i1.TodoItemsCompanion( + id: id, + title: title, + content: content, + category: category, + dueDate: dueDate, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String title, + required String content, + i0.Value category = const i0.Value.absent(), + i0.Value dueDate = const i0.Value.absent(), + }) => + i1.TodoItemsCompanion.insert( + id: id, + title: title, + content: content, + category: category, + dueDate: dueDate, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$TodoItemsTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$TodoItemsTable, + i1.TodoItem, + i1.$$TodoItemsTableFilterComposer, + i1.$$TodoItemsTableOrderingComposer, + i1.$$TodoItemsTableAnnotationComposer, + $$TodoItemsTableCreateCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder, + ( + i1.TodoItem, + i0.BaseReferences + ), + i1.TodoItem, + i0.PrefetchHooks Function({bool category})>; +typedef $$CategoriesTableCreateCompanionBuilder = i1.CategoriesCompanion + Function({ + i0.Value id, + required String name, +}); +typedef $$CategoriesTableUpdateCompanionBuilder = i1.CategoriesCompanion + Function({ + i0.Value id, + i0.Value name, +}); + +class $$CategoriesTableFilterComposer + extends i0.Composer { + $$CategoriesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => i0.ColumnFilters(column)); + + i0.Expression todoItemsRefs( + i0.Expression Function(i1.$$TodoItemsTableFilterComposer f) f) { + final i1.$$TodoItemsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('todo_items'), + getReferencedColumn: (t) => t.category, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$TodoItemsTableFilterComposer( + $db: $db, + $table: i3.ReadDatabaseContainer($db) + .resultSet('todo_items'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $$CategoriesTableOrderingComposer + extends i0.Composer { + $$CategoriesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$CategoriesTableAnnotationComposer + extends i0.Composer { + $$CategoriesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.Expression todoItemsRefs( + i0.Expression Function(i1.$$TodoItemsTableAnnotationComposer a) f) { + final i1.$$TodoItemsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('todo_items'), + getReferencedColumn: (t) => t.category, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$TodoItemsTableAnnotationComposer( + $db: $db, + $table: i3.ReadDatabaseContainer($db) + .resultSet('todo_items'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $$CategoriesTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$CategoriesTable, + i1.Category, + i1.$$CategoriesTableFilterComposer, + i1.$$CategoriesTableOrderingComposer, + i1.$$CategoriesTableAnnotationComposer, + $$CategoriesTableCreateCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder, + ( + i1.Category, + i0.BaseReferences + ), + i1.Category, + i0.PrefetchHooks Function({bool todoItemsRefs})> { + $$CategoriesTableTableManager( + i0.GeneratedDatabase db, i1.$CategoriesTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$CategoriesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$CategoriesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$CategoriesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + }) => + i1.CategoriesCompanion( + id: id, + name: name, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String name, + }) => + i1.CategoriesCompanion.insert( + id: id, + name: name, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$CategoriesTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$CategoriesTable, + i1.Category, + i1.$$CategoriesTableFilterComposer, + i1.$$CategoriesTableOrderingComposer, + i1.$$CategoriesTableAnnotationComposer, + $$CategoriesTableCreateCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder, + ( + i1.Category, + i0.BaseReferences + ), + i1.Category, + i0.PrefetchHooks Function({bool todoItemsRefs})>; +typedef $$UsersTableCreateCompanionBuilder = i1.UsersCompanion Function({ + i0.Value id, + required DateTime birthDate, +}); +typedef $$UsersTableUpdateCompanionBuilder = i1.UsersCompanion Function({ + i0.Value id, + i0.Value birthDate, +}); + +class $$UsersTableFilterComposer + extends i0.Composer { + $$UsersTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get birthDate => $composableBuilder( + column: $table.birthDate, builder: (column) => i0.ColumnFilters(column)); +} + +class $$UsersTableOrderingComposer + extends i0.Composer { + $$UsersTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get birthDate => $composableBuilder( + column: $table.birthDate, + builder: (column) => i0.ColumnOrderings(column)); +} + +class $$UsersTableAnnotationComposer + extends i0.Composer { + $$UsersTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get birthDate => + $composableBuilder(column: $table.birthDate, builder: (column) => column); +} + +class $$UsersTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$UsersTable, + i1.User, + i1.$$UsersTableFilterComposer, + i1.$$UsersTableOrderingComposer, + i1.$$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + (i1.User, i0.BaseReferences), + i1.User, + i0.PrefetchHooks Function()> { + $$UsersTableTableManager(i0.GeneratedDatabase db, i1.$UsersTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$UsersTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$UsersTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$UsersTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value birthDate = const i0.Value.absent(), + }) => + i1.UsersCompanion( + id: id, + birthDate: birthDate, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required DateTime birthDate, + }) => + i1.UsersCompanion.insert( + id: id, + birthDate: birthDate, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$UsersTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$UsersTable, + i1.User, + i1.$$UsersTableFilterComposer, + i1.$$UsersTableOrderingComposer, + i1.$$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + (i1.User, i0.BaseReferences), + i1.User, + i0.PrefetchHooks Function()>; + +class $TodoItemsTable extends i2.TodoItems + with i0.TableInfo<$TodoItemsTable, i1.TodoItem> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $TodoItemsTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _titleMeta = + const i0.VerificationMeta('title'); + @override + late final i0.GeneratedColumn title = i0.GeneratedColumn( + 'title', aliasedName, false, + additionalChecks: i0.GeneratedColumn.checkTextLength( + minTextLength: 6, maxTextLength: 32), + type: i0.DriftSqlType.string, + requiredDuringInsert: true); + static const i0.VerificationMeta _contentMeta = + const i0.VerificationMeta('content'); + @override + late final i0.GeneratedColumn content = i0.GeneratedColumn( + 'body', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _categoryMeta = + const i0.VerificationMeta('category'); + @override + late final i0.GeneratedColumn category = i0.GeneratedColumn( + 'category', aliasedName, true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('REFERENCES categories (id)')); + static const i0.VerificationMeta _dueDateMeta = + const i0.VerificationMeta('dueDate'); + @override + late final i0.GeneratedColumn dueDate = + i0.GeneratedColumn('due_date', aliasedName, true, + type: i0.DriftSqlType.dateTime, requiredDuringInsert: false); + @override + List get $columns => + [id, title, content, category, dueDate]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'todo_items'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + } else if (isInserting) { + context.missing(_titleMeta); + } + if (data.containsKey('body')) { + context.handle(_contentMeta, + content.isAcceptableOrUnknown(data['body']!, _contentMeta)); + } else if (isInserting) { + context.missing(_contentMeta); + } + if (data.containsKey('category')) { + context.handle(_categoryMeta, + category.isAcceptableOrUnknown(data['category']!, _categoryMeta)); + } + if (data.containsKey('due_date')) { + context.handle(_dueDateMeta, + dueDate.isAcceptableOrUnknown(data['due_date']!, _dueDateMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.TodoItem map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.TodoItem( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}title'])!, + content: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}body'])!, + category: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}category']), + dueDate: attachedDatabase.typeMapping + .read(i0.DriftSqlType.dateTime, data['${effectivePrefix}due_date']), + ); + } + + @override + $TodoItemsTable createAlias(String alias) { + return $TodoItemsTable(attachedDatabase, alias); + } +} + +class TodoItem extends i0.DataClass implements i0.Insertable { + final int id; + final String title; + final String content; + final int? category; + final DateTime? dueDate; + const TodoItem( + {required this.id, + required this.title, + required this.content, + this.category, + this.dueDate}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['title'] = i0.Variable(title); + map['body'] = i0.Variable(content); + if (!nullToAbsent || category != null) { + map['category'] = i0.Variable(category); + } + if (!nullToAbsent || dueDate != null) { + map['due_date'] = i0.Variable(dueDate); + } + return map; + } + + i1.TodoItemsCompanion toCompanion(bool nullToAbsent) { + return i1.TodoItemsCompanion( + id: i0.Value(id), + title: i0.Value(title), + content: i0.Value(content), + category: category == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(category), + dueDate: dueDate == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(dueDate), + ); + } + + factory TodoItem.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return TodoItem( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + content: serializer.fromJson(json['content']), + category: serializer.fromJson(json['category']), + dueDate: serializer.fromJson(json['dueDate']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'content': serializer.toJson(content), + 'category': serializer.toJson(category), + 'dueDate': serializer.toJson(dueDate), + }; + } + + i1.TodoItem copyWith( + {int? id, + String? title, + String? content, + i0.Value category = const i0.Value.absent(), + i0.Value dueDate = const i0.Value.absent()}) => + i1.TodoItem( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category.present ? category.value : this.category, + dueDate: dueDate.present ? dueDate.value : this.dueDate, + ); + TodoItem copyWithCompanion(i1.TodoItemsCompanion data) { + return TodoItem( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + content: data.content.present ? data.content.value : this.content, + category: data.category.present ? data.category.value : this.category, + dueDate: data.dueDate.present ? data.dueDate.value : this.dueDate, + ); + } + + @override + String toString() { + return (StringBuffer('TodoItem(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category, ') + ..write('dueDate: $dueDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, title, content, category, dueDate); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.TodoItem && + other.id == this.id && + other.title == this.title && + other.content == this.content && + other.category == this.category && + other.dueDate == this.dueDate); +} + +class TodoItemsCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value title; + final i0.Value content; + final i0.Value category; + final i0.Value dueDate; + const TodoItemsCompanion({ + this.id = const i0.Value.absent(), + this.title = const i0.Value.absent(), + this.content = const i0.Value.absent(), + this.category = const i0.Value.absent(), + this.dueDate = const i0.Value.absent(), + }); + TodoItemsCompanion.insert({ + this.id = const i0.Value.absent(), + required String title, + required String content, + this.category = const i0.Value.absent(), + this.dueDate = const i0.Value.absent(), + }) : title = i0.Value(title), + content = i0.Value(content); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? title, + i0.Expression? content, + i0.Expression? category, + i0.Expression? dueDate, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (content != null) 'body': content, + if (category != null) 'category': category, + if (dueDate != null) 'due_date': dueDate, + }); + } + + i1.TodoItemsCompanion copyWith( + {i0.Value? id, + i0.Value? title, + i0.Value? content, + i0.Value? category, + i0.Value? dueDate}) { + return i1.TodoItemsCompanion( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category ?? this.category, + dueDate: dueDate ?? this.dueDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (title.present) { + map['title'] = i0.Variable(title.value); + } + if (content.present) { + map['body'] = i0.Variable(content.value); + } + if (category.present) { + map['category'] = i0.Variable(category.value); + } + if (dueDate.present) { + map['due_date'] = i0.Variable(dueDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TodoItemsCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category, ') + ..write('dueDate: $dueDate') + ..write(')')) + .toString(); + } +} + +class $CategoriesTable extends i2.Categories + with i0.TableInfo<$CategoriesTable, i1.Category> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $CategoriesTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _nameMeta = + const i0.VerificationMeta('name'); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'categories'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.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 get $primaryKey => {id}; + @override + i1.Category map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.Category( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!, + ); + } + + @override + $CategoriesTable createAlias(String alias) { + return $CategoriesTable(attachedDatabase, alias); + } +} + +class Category extends i0.DataClass implements i0.Insertable { + final int id; + final String name; + const Category({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['name'] = i0.Variable(name); + return map; + } + + i1.CategoriesCompanion toCompanion(bool nullToAbsent) { + return i1.CategoriesCompanion( + id: i0.Value(id), + name: i0.Value(name), + ); + } + + factory Category.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Category( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + i1.Category copyWith({int? id, String? name}) => i1.Category( + id: id ?? this.id, + name: name ?? this.name, + ); + Category copyWithCompanion(i1.CategoriesCompanion data) { + return Category( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('Category(') + ..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 i1.Category && other.id == this.id && other.name == this.name); +} + +class CategoriesCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value name; + const CategoriesCompanion({ + this.id = const i0.Value.absent(), + this.name = const i0.Value.absent(), + }); + CategoriesCompanion.insert({ + this.id = const i0.Value.absent(), + required String name, + }) : name = i0.Value(name); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? name, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + }); + } + + i1.CategoriesCompanion copyWith({i0.Value? id, i0.Value? name}) { + return i1.CategoriesCompanion( + id: id ?? this.id, + name: name ?? this.name, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (name.present) { + map['name'] = i0.Variable(name.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CategoriesCompanion(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } +} + +class $UsersTable extends i2.Users with i0.TableInfo<$UsersTable, i1.User> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $UsersTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _birthDateMeta = + const i0.VerificationMeta('birthDate'); + @override + late final i0.GeneratedColumn birthDate = + i0.GeneratedColumn('birth_date', aliasedName, false, + type: i0.DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [id, birthDate]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'users'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('birth_date')) { + context.handle(_birthDateMeta, + birthDate.isAcceptableOrUnknown(data['birth_date']!, _birthDateMeta)); + } else if (isInserting) { + context.missing(_birthDateMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.User map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.User( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + birthDate: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, data['${effectivePrefix}birth_date'])!, + ); + } + + @override + $UsersTable createAlias(String alias) { + return $UsersTable(attachedDatabase, alias); + } +} + +class User extends i0.DataClass implements i0.Insertable { + final int id; + final DateTime birthDate; + const User({required this.id, required this.birthDate}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['birth_date'] = i0.Variable(birthDate); + return map; + } + + i1.UsersCompanion toCompanion(bool nullToAbsent) { + return i1.UsersCompanion( + id: i0.Value(id), + birthDate: i0.Value(birthDate), + ); + } + + factory User.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return User( + id: serializer.fromJson(json['id']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'birthDate': serializer.toJson(birthDate), + }; + } + + i1.User copyWith({int? id, DateTime? birthDate}) => i1.User( + id: id ?? this.id, + birthDate: birthDate ?? this.birthDate, + ); + User copyWithCompanion(i1.UsersCompanion data) { + return User( + id: data.id.present ? data.id.value : this.id, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('User(') + ..write('id: $id, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, birthDate); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.User && + other.id == this.id && + other.birthDate == this.birthDate); +} + +class UsersCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value birthDate; + const UsersCompanion({ + this.id = const i0.Value.absent(), + this.birthDate = const i0.Value.absent(), + }); + UsersCompanion.insert({ + this.id = const i0.Value.absent(), + required DateTime birthDate, + }) : birthDate = i0.Value(birthDate); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? birthDate, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + i1.UsersCompanion copyWith( + {i0.Value? id, i0.Value? birthDate}) { + return i1.UsersCompanion( + id: id ?? this.id, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (birthDate.present) { + map['birth_date'] = i0.Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UsersCompanion(') + ..write('id: $id, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} diff --git a/docs/lib/snippets/custom_row_classes/default.dart b/docs/lib/snippets/custom_row_classes/default.dart new file mode 100644 index 000000000..1fa01cd85 --- /dev/null +++ b/docs/lib/snippets/custom_row_classes/default.dart @@ -0,0 +1,18 @@ +import 'package:drift/drift.dart'; + +// #docregion start +@UseRowClass(User) +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + DateTimeColumn get birthday => dateTime()(); +} + +class User { + final int id; + final String name; + final DateTime birthday; + + User({required this.id, required this.name, required this.birthday}); +} +// #enddocregion start diff --git a/docs/lib/snippets/custom_row_classes/employee.dart b/docs/lib/snippets/custom_row_classes/employee.dart new file mode 100644 index 000000000..a9c5aa37b --- /dev/null +++ b/docs/lib/snippets/custom_row_classes/employee.dart @@ -0,0 +1 @@ +class EmployeeWithStaff {} diff --git a/docs/lib/snippets/custom_row_classes/employees_sql.drift b/docs/lib/snippets/custom_row_classes/employees_sql.drift new file mode 100644 index 000000000..10350b48c --- /dev/null +++ b/docs/lib/snippets/custom_row_classes/employees_sql.drift @@ -0,0 +1,17 @@ +import 'employee.dart'; + +/* #docregion example */ +CREATE TABLE employees( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + supervisor INTEGER REFERENCES employees(id) +); + +employeeWithStaff WITH EmployeeWithStaff: SELECT + self.**, + supervisor.name, + LIST(SELECT * FROM employees WHERE supervisor = self.id) AS staff + FROM employees AS self + INNER JOIN employees supervisor ON supervisor.id = self.supervisor + WHERE id = ?; +/* #enddocregion example */ diff --git a/docs/lib/snippets/custom_row_classes/named.dart b/docs/lib/snippets/custom_row_classes/named.dart new file mode 100644 index 000000000..5a7e4f7fc --- /dev/null +++ b/docs/lib/snippets/custom_row_classes/named.dart @@ -0,0 +1,21 @@ +import 'package:drift/drift.dart'; + +// #docregion named +@UseRowClass(User, constructor: 'fromDb') +class Users extends Table { + // ... + // #enddocregion named + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + DateTimeColumn get birthday => dateTime()(); + // #docregion named +} + +class User { + final int id; + final String name; + final DateTime birthday; + + User.fromDb({required this.id, required this.name, required this.birthday}); +} +// #enddocregion named diff --git a/docs/lib/snippets/dart_api/datetime_conversion.dart b/docs/lib/snippets/dart_api/datetime_conversion.dart new file mode 100644 index 000000000..617999064 --- /dev/null +++ b/docs/lib/snippets/dart_api/datetime_conversion.dart @@ -0,0 +1,74 @@ +import 'package:drift/drift.dart'; + +extension MigrateToTextDateTimes on GeneratedDatabase { + // #docregion unix-to-text + Future migrateFromUnixTimestampsToText(Migrator m) async { + for (final table in allTables) { + final dateTimeColumns = + table.$columns.where((c) => c.type == DriftSqlType.dateTime); + + if (dateTimeColumns.isNotEmpty) { + // This table has dateTime columns which need to be migrated. + await m.alterTable(TableMigration( + table, + columnTransformer: { + for (final column in dateTimeColumns) + // We assume that the column in the database is an int (unix + // timestamp), use `fromUnixEpoch` to convert it to a date time. + // Note that the resulting value in the database is in UTC. + column: DateTimeExpressions.fromUnixEpoch(column.dartCast()), + }, + )); + } + } + } + // #enddocregion unix-to-text +} + +extension MigrateToTimestamps on GeneratedDatabase { + // #docregion text-to-unix + Future migrateFromTextDateTimesToUnixTimestamps(Migrator m) async { + for (final table in allTables) { + final dateTimeColumns = + table.$columns.where((c) => c.type == DriftSqlType.dateTime); + + if (dateTimeColumns.isNotEmpty) { + // This table has dateTime columns which need to be migrated. + await m.alterTable(TableMigration( + table, + columnTransformer: { + for (final column in dateTimeColumns) + // We assume that the column in the database is a string. We want + // to parse it to a date in SQL and then get the unix timestamp of + // it. + // Note that this requires sqlite version 3.38 or above. + column: FunctionCallExpression('unixepoch', [column]), + }, + )); + } + } + } + // #enddocregion text-to-unix + + Future migrateFromTextDateTimesToUnixTimestampsPre338( + Migrator m) async { + for (final table in allTables) { + final dateTimeColumns = + table.$columns.where((c) => c.type == DriftSqlType.dateTime); + + if (dateTimeColumns.isNotEmpty) { + await m.alterTable(TableMigration( + table, + // #docregion text-to-unix-old + columnTransformer: { + for (final column in dateTimeColumns) + // Use this as an alternative to `unixepoch`: + column: FunctionCallExpression( + 'strftime', [const Constant('%s'), column]).cast(), + }, + // #enddocregion text-to-unix-old + )); + } + } + } +} diff --git a/docs/lib/snippets/dart_api/expressions.dart b/docs/lib/snippets/dart_api/expressions.dart new file mode 100644 index 000000000..67ad178fa --- /dev/null +++ b/docs/lib/snippets/dart_api/expressions.dart @@ -0,0 +1,44 @@ +import 'package:drift/drift.dart'; + +import '../_shared/todo_tables.dart'; +import '../_shared/todo_tables.drift.dart'; + +extension Snippets on CanUseCommonTables { + // #docregion emptyCategories + Future> emptyCategories() { + final hasNoTodo = notExistsQuery(select(todoItems) + ..where((row) => row.category.equalsExp(categories.id))); + return (select(categories)..where((row) => hasNoTodo)).get(); + } + // #enddocregion emptyCategories + + void queries() { + // #docregion date1 + select(users).where((u) => u.birthDate.year.isSmallerThanValue(1950)); + // #enddocregion date1 + } + + // #docregion date2 + Future increaseDueDates() async { + final change = TodoItemsCompanion.custom( + dueDate: todoItems.dueDate + Duration(days: 1)); + await update(todoItems).write(change); + } + // #enddocregion date2 + + // #docregion date3 + Future moveDueDateToNextMonday() async { + final change = TodoItemsCompanion.custom( + dueDate: todoItems.dueDate + .modify(DateTimeModifier.weekday(DateTime.monday))); + await update(todoItems).write(change); + } + // #enddocregion date3 +} + +// #docregion bitwise +Expression bitwiseMagic(Expression a, Expression b) { + // Generates `~(a & b)` in SQL. + return ~(a.bitwiseAnd(b)); +} +// #enddocregion bitwise diff --git a/docs/lib/snippets/dart_api/json.dart b/docs/lib/snippets/dart_api/json.dart new file mode 100644 index 000000000..9a86fae5e --- /dev/null +++ b/docs/lib/snippets/dart_api/json.dart @@ -0,0 +1,80 @@ +// #docregion existing +import 'dart:convert'; + +import 'package:drift/drift.dart'; +import 'package:drift/extensions/json1.dart'; +import 'package:json_annotation/json_annotation.dart'; + +// #enddocregion existing +import 'package:drift/native.dart'; + +part 'json.g.dart'; + +// #docregion existing +@JsonSerializable() +class ContactData { + final String name; + final List phoneNumbers; + + ContactData(this.name, this.phoneNumbers); + + factory ContactData.fromJson(Map json) => + _$ContactDataFromJson(json); + + Map toJson() => _$ContactDataToJson(this); +} +// #enddocregion existing + +// #docregion contacts +class _ContactsConverter extends TypeConverter { + @override + ContactData fromSql(String fromDb) { + return ContactData.fromJson(json.decode(fromDb) as Map); + } + + @override + String toSql(ContactData value) { + return json.encode(value.toJson()); + } +} + +class Contacts extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get data => text().map(_ContactsConverter())(); + + TextColumn get name => text().generatedAs(data.jsonExtract(r'$.name'))(); +} +// #enddocregion contacts + +// #docregion calls +class Calls extends Table { + IntColumn get id => integer().autoIncrement()(); + BoolColumn get incoming => boolean()(); + TextColumn get phoneNumber => text()(); + DateTimeColumn get callTime => dateTime()(); +} +// #enddocregion calls + +@DriftDatabase(tables: [Contacts, Calls]) +class MyDatabase extends _$MyDatabase { + MyDatabase() : super(NativeDatabase.memory()); + + @override + int get schemaVersion => 1; + + // #docregion calls-with-contacts + Future> callsWithContact() async { + final phoneNumbersForContact = + contacts.data.jsonEach(this, r'$.phoneNumbers'); + final phoneNumberQuery = selectOnly(phoneNumbersForContact) + ..addColumns([phoneNumbersForContact.value]); + + final query = select(calls).join( + [innerJoin(contacts, calls.phoneNumber.isInQuery(phoneNumberQuery))]); + + return query + .map((row) => (row.readTable(calls), row.readTable(contacts))) + .get(); + } + // #enddocregion calls-with-contacts +} diff --git a/docs/lib/snippets/dart_api/old_name.dart b/docs/lib/snippets/dart_api/old_name.dart new file mode 100644 index 000000000..a078ce274 --- /dev/null +++ b/docs/lib/snippets/dart_api/old_name.dart @@ -0,0 +1,5 @@ +import 'package:drift/drift.dart'; + +class EnabledCategories extends Table { + IntColumn get parentCategory => integer()(); +} diff --git a/docs/lib/snippets/dart_api/select.dart b/docs/lib/snippets/dart_api/select.dart new file mode 100644 index 000000000..f1c5a370c --- /dev/null +++ b/docs/lib/snippets/dart_api/select.dart @@ -0,0 +1,236 @@ +import 'package:drift/drift.dart'; + +import '../_shared/todo_tables.dart'; +import '../_shared/todo_tables.drift.dart'; + +// #docregion joinIntro +// We define a data class to contain both a todo entry and the associated +// category. +class EntryWithCategory { + EntryWithCategory(this.entry, this.category); + + // The classes are generated by drift for each of the tables involved in the + // join. + final TodoItem entry; + final Category? category; +} + +// #enddocregion joinIntro + +extension SelectExamples on CanUseCommonTables { + // #docregion limit + Future> limitTodos(int limit, {int? offset}) { + return (select(todoItems)..limit(limit, offset: offset)).get(); + } + // #enddocregion limit + + // #docregion order-by + Future> sortEntriesAlphabetically() { + return (select(todoItems) + ..orderBy([(t) => OrderingTerm(expression: t.title)])) + .get(); + } + // #enddocregion order-by + + // #docregion single + Stream entryById(int id) { + return (select(todoItems)..where((t) => t.id.equals(id))).watchSingle(); + } + // #enddocregion single + + // #docregion mapping + Stream> contentWithLongTitles() { + final query = select(todoItems) + ..where((t) => t.title.length.isBiggerOrEqualValue(16)); + + return query.map((row) => row.content).watch(); + } + // #enddocregion mapping + + // #docregion selectable + // Exposes `get` and `watch` + MultiSelectable pageOfTodos(int page, {int pageSize = 10}) { + return select(todoItems)..limit(pageSize, offset: page); + } + + // Exposes `getSingle` and `watchSingle` + SingleSelectable selectableEntryById(int id) { + return select(todoItems)..where((t) => t.id.equals(id)); + } + + // Exposes `getSingleOrNull` and `watchSingleOrNull` + SingleOrNullSelectable entryFromExternalLink(int id) { + return select(todoItems)..where((t) => t.id.equals(id)); + } + // #enddocregion selectable + + // #docregion joinIntro + // in the database class, we can then load the category for each entry + Stream> entriesWithCategory() { + final query = select(todoItems).join([ + leftOuterJoin(categories, categories.id.equalsExp(todoItems.category)), + ]); + + // #docregion results + return query.watch().map((rows) { + return rows.map((row) { + return EntryWithCategory( + row.readTable(todoItems), + row.readTableOrNull(categories), + ); + }).toList(); + }); + // #enddocregion results + } +// #enddocregion joinIntro + + // #docregion otherTodosInSameCategory + /// Searches for todo entries in the same category as the ones having + /// `titleQuery` in their titles. + Future> otherTodosInSameCategory(String titleQuery) async { + // Since we're adding the same table twice (once to filter for the title, + // and once to find other todos in same category), we need a way to + // distinguish the two tables. So, we're giving one of them a special name: + final otherTodos = alias(todoItems, 'inCategory'); + + final query = select(otherTodos).join([ + // In joins, `useColumns: false` tells drift to not add columns of the + // joined table to the result set. This is useful here, since we only join + // the tables so that we can refer to them in the where clause. + innerJoin(categories, categories.id.equalsExp(otherTodos.category), + useColumns: false), + innerJoin(todoItems, todoItems.category.equalsExp(categories.id), + useColumns: false), + ]) + ..where(todoItems.title.contains(titleQuery)); + + return query.map((row) => row.readTable(otherTodos)).get(); + } + // #enddocregion otherTodosInSameCategory + + // #docregion countTodosInCategories + Future countTodosInCategories() async { + final amountOfTodos = todoItems.id.count(); + + final query = select(categories).join([ + innerJoin( + todoItems, + todoItems.category.equalsExp(categories.id), + useColumns: false, + ) + ]); + query + ..addColumns([amountOfTodos]) + ..groupBy([categories.id]); + + final result = await query.get(); + + for (final row in result) { + print('there are ${row.read(amountOfTodos)} entries in' + '${row.readTable(categories)}'); + } + } + // #enddocregion countTodosInCategories + + // #docregion averageItemLength + Stream averageItemLength() { + final avgLength = todoItems.content.length.avg(); + final query = selectOnly(todoItems)..addColumns([avgLength]); + + return query.map((row) => row.read(avgLength)!).watchSingle(); + } + // #enddocregion averageItemLength + + // #docregion createCategoryForUnassignedTodoEntries + Future createCategoryForUnassignedTodoEntries() async { + final newDescription = Variable('category for: ') + todoItems.title; + final query = selectOnly(todoItems) + ..where(todoItems.category.isNull()) + ..addColumns([newDescription]); + + await into(categories).insertFromSelect(query, columns: { + categories.name: newDescription, + }); + } + // #enddocregion createCategoryForUnassignedTodoEntries + + // #docregion subquery + Future> amountOfLengthyTodoItemsPerCategory() async { + final longestTodos = Subquery( + select(todoItems) + ..orderBy([(row) => OrderingTerm.desc(row.title.length)]) + ..limit(10), + 's', + ); + + // In the main query, we want to count how many entries in longestTodos were + // found for each category. But we can't access todos.title directly since + // we're not selecting from `todos`. Instead, we'll use Subquery.ref to read + // from a column in a subquery. + final itemCount = longestTodos.ref(todoItems.title).count(); + final query = select(categories).join( + [ + innerJoin( + longestTodos, + // Again using .ref() here to access the category in the outer select + // statement. + longestTodos.ref(todoItems.category).equalsExp(categories.id), + useColumns: false, + ) + ], + ) + ..addColumns([itemCount]) + ..groupBy([categories.id]); + + final rows = await query.get(); + + return [ + for (final row in rows) (row.readTable(categories), row.read(itemCount)!), + ]; + } + // #enddocregion subquery + + // #docregion custom-columns + Future> loadEntries() { + // assume that an entry is important if it has the string "important" somewhere in its content + final isImportant = todoItems.content.like('%important%'); + + return select(todoItems).addColumns([isImportant]).map((row) { + final entry = row.readTable(todoItems); + final entryIsImportant = row.read(isImportant)!; + + return (entry, entryIsImportant); + }).get(); + } + // #enddocregion custom-columns + + // #docregion hasTodoItem + Future hasTodoItem() async { + final todoItemExists = existsQuery(select(todoItems)); + final row = await selectExpressions([todoItemExists]).getSingle(); + return row.read(todoItemExists)!; + } + // #enddocregion hasTodoItem + + // #docregion compound + Future> todoItemsInCategory() async { + final countWithCategory = subqueryExpression(selectOnly(todoItems) + ..addColumns([countAll()]) + ..where(todoItems.category.equalsExp(categories.id))); + + final countWithoutCategory = subqueryExpression(selectOnly(todoItems) + ..addColumns([countAll()]) + ..where(todoItems.category.isNull())); + + final query = db.selectOnly(categories) + ..addColumns([categories.name, countWithoutCategory]) + ..groupBy([categories.id]); + query.unionAll(db.selectExpressions( + [const Constant(null), countWithoutCategory])); + + return query + .map((row) => (row.read(categories.name), row.read(countWithCategory)!)) + .get(); + } + // #enddocregion compound +} diff --git a/docs/lib/snippets/dart_api/tables.dart b/docs/lib/snippets/dart_api/tables.dart new file mode 100644 index 000000000..b12d98b05 --- /dev/null +++ b/docs/lib/snippets/dart_api/tables.dart @@ -0,0 +1,241 @@ +// ignore_for_file: unused_local_variable, unused_element + +import 'package:drift/drift.dart'; + +part 'tables.g.dart'; + +// #docregion simple_schema +class TodoCategories extends Table { + IntColumn get id => integer().autoIncrement()(); // (1)! + TextColumn get description => text()(); +} + +class TodoItems extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get title => text()(); + DateTimeColumn get createdAt => dateTime().nullable()(); // (2)! + + @ReferenceName('categories') + IntColumn get category => integer().references(TodoCategories, #id)(); +} +// #enddocregion simple_schema + +// #docregion simple_schema_db +@DriftDatabase(tables: [TodoItems, TodoCategories]) +class Database extends _$Database { + Database(super.e); + + @override + int get schemaVersion => 1; +} +// #enddocregion simple_schema_db + +// #docregion references +class Albums extends Table { + late final id = integer().autoIncrement()(); + late final name = text()(); + late final artist = integer().references(Artists, #id)(); +} + +class Artists extends Table { + late final id = integer().autoIncrement()(); + late final name = text()(); +} +// #enddocregion references + +bool isInDarkMode() => false; + +class Table1 extends Table { + // #docregion client_default + late final useDarkMode = boolean().clientDefault(() => false)(); + // #enddocregion client_default + // #docregion db_default + late final creationTime = dateTime().withDefault(currentDateAndTime)(); + // #enddocregion db_default + // #docregion optional_columns + late final age = integer().nullable()(); + // #enddocregion optional_columns + // #docregion unique_columns + late final username = text().unique()(); + // #enddocregion unique_columns + // #docregion withLength + late final name = text().withLength(min: 1, max: 50)(); + // #enddocregion withLength + // #docregion named_column + late final isAdmin = boolean().named('admin')(); + // #enddocregion named_column +} + +class Table2 extends Table { + // #docregion check_column + late final Column age = integer().check(age.isBiggerOrEqualValue(0))(); + // #enddocregion check_column +} + +// #docregion generated_column +class Squares extends Table { + late final length = integer()(); + late final width = integer()(); + late final area = integer().generatedAs(length * width)(); +} +// #enddocregion generated_column + +// #docregion generated_column_stored +class Boxes extends Table { + late final length = integer()(); + late final width = integer()(); + late final area = integer().generatedAs(length * width, stored: true)(); +} +// #enddocregion generated_column_stored + +// #docregion pk-example +// #enddocregion pk-example + +// #docregion custom_table_name +class Products extends Table { + @override + String get tableName => 'product_table'; +} +// #enddocregion custom_table_name + +// #docregion custom-constraint-table +class TableWithCustomConstraints extends Table { + late final foo = integer()(); + late final bar = integer()(); + + @override + List get customConstraints => [ + 'FOREIGN KEY (foo, bar) REFERENCES group_memberships ("group", user)', + ]; +} +// #enddocregion custom-constraint-table + +class GroupMemberships extends Table { + late final group = integer()(); + late final user = integer()(); +} + +// #docregion table_mixin +mixin TableMixin on Table { + // Primary key column + late final id = integer().autoIncrement()(); + + // Column for created at timestamp + late final createdAt = dateTime().withDefault(currentDateAndTime)(); +} + +class Posts extends Table with TableMixin { + late final content = text()(); +} +// #enddocregion table_mixin + +class ColumnConstraint extends Table { + // #docregion custom_column_constraint + late final name = text().nullable().customConstraint('COLLATE BINARY')(); + // #enddocregion custom_column_constraint + + // #docregion custom_column_constraint_not_nullable + late final username = text().customConstraint('NOT NULL COLLATE BINARY')(); + // #enddocregion custom_column_constraint_not_nullable +} + +// #docregion custom_pk +class Profiles extends Table { + late final email = text()(); + + @override + Set> get primaryKey => {email}; +} +// #enddocregion custom_pk + +// #docregion unique-table +class Reservations extends Table { + late final reservationId = integer().autoIncrement()(); + + late final room = text()(); + late final onDay = dateTime()(); + + @override + List> get uniqueKeys => [ + {room, onDay} + ]; +} +// #enddocregion unique-table + +// #docregion autoIncrement +class Items extends Table { + late final id = integer().autoIncrement()(); + late final title = text()(); +} +// #enddocregion autoIncrement + +Future insertWithAutoIncrement(CatDatabase database) async { + // #docregion autoIncrementUse + await database.items.insertAll([ + // Only the title is required here + ItemsCompanion.insert(title: 'First entry'), + ItemsCompanion.insert(title: 'Another item'), + ]); + + final items = await database.items.all().get(); + // This prints [(id: 1, title: First entry), (id: 2, title: Another item)]. + // The id has been chosen by the database. + print(items); + // #enddocregion autoIncrementUse +} + +Future insertWithAutoIncrementManager(CatDatabase database) async { + // #docregion autoIncrementUseManager + await database.managers.items.bulkCreate((c) => [ + c(title: 'First entry'), + c(title: 'Another item'), + ]); + + final items = await database.managers.items.get(); + // This prints [(id: 1, title: First entry), (id: 2, title: Another item)]. + // The id has been chosen by the database. + print(items); + // #enddocregion autoIncrementUseManager +} + +@DriftDatabase(tables: [Reservations, Items]) +class CatDatabase extends _$CatDatabase { + CatDatabase(super.e); + + @override + int get schemaVersion => 1; +} + +// #docregion index +@TableIndex(name: 'user_name', columns: {#name}) +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); +} +// #enddocregion index + +// #docregion indexsql +@TableIndex.sql(''' + CREATE INDEX pending_orders ON orders (creation_time) + WHERE status == 'pending'; +''') +class Orders extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get totalAmount => integer()(); + DateTimeColumn get creationTime => dateTime()(); + TextColumn get status => text()(); +} +// #enddocregion indexsql + +// #docregion strict +class Preferences extends Table { + TextColumn get key => text()(); + AnyColumn get value => sqliteAny().nullable()(); + + @override + Set>? get primaryKey => {key}; + + @override + bool get isStrict => true; +} +// #enddocregion strict diff --git a/docs/lib/snippets/dart_api/transactions.dart b/docs/lib/snippets/dart_api/transactions.dart new file mode 100644 index 000000000..ab0d1aa70 --- /dev/null +++ b/docs/lib/snippets/dart_api/transactions.dart @@ -0,0 +1,58 @@ +import 'package:drift/drift.dart'; + +import '../_shared/todo_tables.dart'; +import '../_shared/todo_tables.drift.dart'; + +extension Snippets on CanUseCommonTables { + // #docregion deleteCategory + Future deleteCategory(Category category) { + return transaction(() async { + // first, move the affected todo entries back to the default category + await (update(todoItems) + ..where((row) => row.category.equals(category.id))) + .write(const TodoItemsCompanion(category: Value(null))); + + // then, delete the category + await delete(categories).delete(category); + }); + } + // #enddocregion deleteCategory + + // #docregion nested + Future nestedTransactions() async { + await transaction(() async { + await into(categories).insert(CategoriesCompanion.insert(name: 'first')); + + // this is a nested transaction: + await transaction(() async { + // At this point, the first category is visible + await into(categories) + .insert(CategoriesCompanion.insert(name: 'second')); + // Here, the second category is only visible inside this nested + // transaction. + }); + + // At this point, the second category is visible here as well. + + try { + await transaction(() async { + // At this point, both categories are visible + await into(categories) + .insert(CategoriesCompanion.insert(name: 'third')); + // The third category is only visible here. + throw Exception('Abort in the second nested transaction'); + }); + } on Exception { + // We're catching the exception so that this transaction isn't reverted + // as well. + } + + // At this point, the third category is NOT visible, but the other two + // are. The transaction is in the same state as before the second nested + // `transaction()` call. + }); + + // After the transaction, two categories are visible. + } + // #enddocregion nested +} diff --git a/docs/lib/snippets/dart_api/views.dart b/docs/lib/snippets/dart_api/views.dart new file mode 100644 index 000000000..720bb8198 --- /dev/null +++ b/docs/lib/snippets/dart_api/views.dart @@ -0,0 +1,33 @@ +import 'package:drift/drift.dart'; + +class Todos extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get title => text().withLength(min: 6, max: 32)(); + TextColumn get content => text().named('body')(); + IntColumn get category => integer().nullable()(); +} + +@DataClassName('Category') +class Categories extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get description => text()(); +} + +// #docregion view +abstract class CategoryTodoCount extends View { + // Getters define the tables that this view is reading from. + Todos get todos; + Categories get categories; + + // Custom expressions can be given a name by defining them as a getter:. + Expression get itemCount => todos.id.count(); + + @override + Query as() => + // Views can select columns defined as expression getters on the class, or + // they can reference columns from other tables. + select([categories.description, itemCount]) + .from(categories) + .join([innerJoin(todos, todos.category.equalsExp(categories.id))]); +} +// #enddocregion view diff --git a/docs/lib/snippets/drift_files/custom_queries.dart b/docs/lib/snippets/drift_files/custom_queries.dart new file mode 100644 index 000000000..1ac5b7309 --- /dev/null +++ b/docs/lib/snippets/drift_files/custom_queries.dart @@ -0,0 +1,84 @@ +import 'package:drift/drift.dart'; + +import '../_shared/todo_tables.dart'; +import '../_shared/todo_tables.drift.dart'; +import 'custom_queries.drift.dart'; + +// #docregion manual +class CategoryWithCount { + final Category category; + final int count; // amount of entries in this category + + CategoryWithCount({required this.category, required this.count}); +} + +// #enddocregion manual + +// #docregion setup +@DriftDatabase( + tables: [TodoItems, Categories], + queries: { + 'categoriesWithCount': 'SELECT *, ' + '(SELECT COUNT(*) FROM todo_items WHERE category = c.id) AS "amount" ' + 'FROM categories c;' + }, +) +class MyDatabase extends $MyDatabase { + // rest of class stays the same + // #enddocregion setup + @override + int get schemaVersion => 1; + + MyDatabase(super.e); + + // #docregion amountOfTodosInCategory + Stream amountOfTodosInCategory(int id) { + return customSelect( + 'SELECT COUNT(*) AS c FROM todo_items WHERE category = ?', + variables: [Variable.withInt(id)], + readsFrom: {todoItems}, + ).map((row) => row.read('c')).watchSingle(); + } + // #enddocregion amountOfTodosInCategory + + // #docregion run + Future useGeneratedQuery() async { + // The generated query can be run once as a future: + await categoriesWithCount().get(); + + // Or multiple times as a stream + await for (final snapshot in categoriesWithCount().watch()) { + print('Found ${snapshot.length} category results'); + } + } + + // #enddocregion run + // #docregion manual + // then, in the database class: + Stream> allCategoriesWithCount() { + // select all categories and load how many associated entries there are for + // each category + return customSelect( + 'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount"' + ' FROM categories c;', + // used for the stream: the stream will update when either table changes + readsFrom: {todoItems, categories}, + ).watch().map((rows) { + // we get list of rows here. We just have to turn the raw data from the + // row into a CategoryWithCount instnace. As we defined the Category table + // earlier, drift knows how to parse a category. The only thing left to do + // manually is extracting the amount. + return rows + .map((row) => CategoryWithCount( + category: categories.map(row.data), + count: row.read('amount'), + )) + .toList(); + }); + } + +// #enddocregion manual + + // #docregion setup +} +// #enddocregion setup diff --git a/docs/lib/snippets/drift_files/custom_queries.drift.dart b/docs/lib/snippets/drift_files/custom_queries.drift.dart new file mode 100644 index 000000000..45b40b246 --- /dev/null +++ b/docs/lib/snippets/drift_files/custom_queries.drift.dart @@ -0,0 +1,51 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/_shared/todo_tables.drift.dart' as i1; + +abstract class $MyDatabase extends i0.GeneratedDatabase { + $MyDatabase(i0.QueryExecutor e) : super(e); + $MyDatabaseManager get managers => $MyDatabaseManager(this); + late final i1.$CategoriesTable categories = i1.$CategoriesTable(this); + late final i1.$TodoItemsTable todoItems = i1.$TodoItemsTable(this); + i0.Selectable categoriesWithCount() { + return customSelect( + 'SELECT *, (SELECT COUNT(*) FROM todo_items WHERE category = c.id) AS amount FROM categories AS c', + variables: [], + readsFrom: { + todoItems, + categories, + }).map((i0.QueryRow row) => CategoriesWithCountResult( + id: row.read('id'), + name: row.read('name'), + amount: row.read('amount'), + )); + } + + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => + [categories, todoItems]; +} + +class $MyDatabaseManager { + final $MyDatabase _db; + $MyDatabaseManager(this._db); + i1.$$CategoriesTableTableManager get categories => + i1.$$CategoriesTableTableManager(_db, _db.categories); + i1.$$TodoItemsTableTableManager get todoItems => + i1.$$TodoItemsTableTableManager(_db, _db.todoItems); +} + +class CategoriesWithCountResult { + final int id; + final String name; + final int amount; + CategoriesWithCountResult({ + required this.id, + required this.name, + required this.amount, + }); +} diff --git a/docs/lib/snippets/drift_files/database.dart b/docs/lib/snippets/drift_files/database.dart new file mode 100644 index 000000000..4de57e081 --- /dev/null +++ b/docs/lib/snippets/drift_files/database.dart @@ -0,0 +1,27 @@ +// #docregion overview +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; + +part 'database.g.dart'; + +@DriftDatabase( + include: {'tables.drift'}, +) +class MyDb extends _$MyDb { + // This example creates a simple in-memory database (without actual + // persistence). + // To store data, see the database setups from other "Getting started" guides. + MyDb() : super(NativeDatabase.memory()); + + @override + int get schemaVersion => 1; +} +// #enddocregion overview + +extension MoreSnippets on MyDb { + // #docregion dart_interop_insert + Future insert(TodosCompanion companion) async { + await into(todos).insert(companion); + } + // #enddocregion dart_interop_insert +} diff --git a/docs/lib/snippets/drift_files/getting_started/database.dart b/docs/lib/snippets/drift_files/getting_started/database.dart new file mode 100644 index 000000000..45c4b4cd1 --- /dev/null +++ b/docs/lib/snippets/drift_files/getting_started/database.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +// These imports are used to open the database +import 'package:drift/native.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; + +part 'database.g.dart'; + +@DriftDatabase( + // relative import for the drift file. Drift also supports `package:` + // imports + include: {'tables.drift'}, +) +class AppDb extends _$AppDb { + AppDb() : super(_openConnection()); + + @override + int get schemaVersion => 1; +} + +LazyDatabase _openConnection() { + // the LazyDatabase util lets us find the right location for the file async. + return LazyDatabase(() async { + // put the database file, called db.sqlite here, into the documents folder + // for your app. + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, 'db.sqlite')); + + return NativeDatabase.createInBackground(file); + }); +} diff --git a/docs/lib/snippets/drift_files/getting_started/tables.drift b/docs/lib/snippets/drift_files/getting_started/tables.drift new file mode 100644 index 000000000..cc7160b4d --- /dev/null +++ b/docs/lib/snippets/drift_files/getting_started/tables.drift @@ -0,0 +1,26 @@ +-- this is the tables.drift file +CREATE TABLE todos ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + title TEXT, + body TEXT, + category INT REFERENCES categories (id) +); + +CREATE TABLE categories ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + description TEXT +) AS Category; -- see the explanation on "AS Category" below + +/* after declaring your tables, you can put queries in here. Just + write the name of the query, a colon (:) and the SQL: */ +todosInCategory: SELECT * FROM todos WHERE category = ?; + +/* Here's a more complex query: It counts the amount of entries per +category, including those entries which aren't in any category at all. */ +countEntries: + SELECT + c.description, + (SELECT COUNT(*) FROM todos WHERE category = c.id) AS amount + FROM categories c + UNION ALL + SELECT null, (SELECT COUNT(*) FROM todos WHERE category IS NULL); diff --git a/docs/lib/snippets/drift_files/nested.drift b/docs/lib/snippets/drift_files/nested.drift new file mode 100644 index 000000000..64521b9c6 --- /dev/null +++ b/docs/lib/snippets/drift_files/nested.drift @@ -0,0 +1,44 @@ +/* #docregion overview */ +CREATE TABLE coordinates ( + id INTEGER NOT NULL PRIMARY KEY, + lat REAL NOT NULL, + long REAL NOT NULL +); + +CREATE TABLE saved_routes ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + "from" INTEGER NOT NULL REFERENCES coordinates (id), + "to" INTEGER NOT NULL REFERENCES coordinates (id) +); + +/* #enddocregion overview */ +/* #docregion route_points*/ +CREATE TABLE route_points ( + route INTEGER NOT NULL REFERENCES saved_routes (id), + point INTEGER NOT NULL REFERENCES coordinates (id), + index_on_route INTEGER, + PRIMARY KEY (route, point) +); +/* #enddocregion route_points */ + +/* #docregion overview */ +routesWithPoints: SELECT r.id, r.name, f.*, t.* FROM saved_routes r + INNER JOIN coordinates f ON f.id = r."from" + INNER JOIN coordinates t ON t.id = r."to"; +/* #enddocregion overview */ +/* #docregion nested */ +routesWithNestedPoints: SELECT r.id, r.name, f.** AS "from", t.** AS "to" FROM saved_routes r + INNER JOIN coordinates f ON f.id = r."from" + INNER JOIN coordinates t ON t.id = r."to"; +/* #enddocregion nested */ +/* #docregion list */ +routeWithPoints: SELECT + route.**, + LIST(SELECT coordinates.* FROM route_points + INNER JOIN coordinates ON id = point + WHERE route = route.id + ORDER BY index_on_route + ) AS points + FROM saved_routes route; +/* #enddocregion list */ \ No newline at end of file diff --git a/docs/lib/snippets/drift_files/small_snippets.drift b/docs/lib/snippets/drift_files/small_snippets.drift new file mode 100644 index 000000000..c7de7c0e2 --- /dev/null +++ b/docs/lib/snippets/drift_files/small_snippets.drift @@ -0,0 +1,19 @@ +/* #docregion import */ +import 'tables.drift'; -- single quotes are required for imports +/* #enddocregion import */ + +/* #docregion q1 */ +myQuery(:variable AS TEXT): SELECT :variable; +/* #enddocregion q1 */ +/* #docregion q2 */ +myNullableQuery(:variable AS TEXT OR NULL): SELECT :variable; +/* #enddocregion q2 */ +/* #docregion q3 */ +myRequiredQuery(REQUIRED :variable AS TEXT OR NULL): SELECT :variable; +/* #enddocregion q3 */ +/* #docregion entries */ +entriesWithId: SELECT * FROM todos WHERE id IN ?; +/* #enddocregion entries */ +/* #docregion filter */ +_filterTodos: SELECT * FROM todos WHERE $predicate; +/* #enddocregion filter */ diff --git a/docs/lib/snippets/drift_files/tables.drift b/docs/lib/snippets/drift_files/tables.drift new file mode 100644 index 000000000..c3de6d494 --- /dev/null +++ b/docs/lib/snippets/drift_files/tables.drift @@ -0,0 +1,19 @@ +CREATE TABLE todos ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + category INTEGER REFERENCES categories(id) +); + +CREATE TABLE categories ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + description TEXT NOT NULL +) AS Category; -- the AS xyz after the table defines the data class name + +-- You can also create an index or triggers with drift files +CREATE INDEX categories_description ON categories(description); + +-- we can put named SQL queries in here as well: +createEntry: INSERT INTO todos (title, content) VALUES (:title, :content); +deleteById: DELETE FROM todos WHERE id = :id; +allTodos: SELECT * FROM todos; diff --git a/docs/lib/snippets/isolates.dart b/docs/lib/snippets/isolates.dart new file mode 100644 index 000000000..56e99153d --- /dev/null +++ b/docs/lib/snippets/isolates.dart @@ -0,0 +1,253 @@ +import 'dart:io'; +import 'dart:isolate'; + +import 'package:drift/drift.dart'; +// #docregion isolate +import 'package:drift/isolate.dart'; +// #enddocregion isolate +import 'package:drift/native.dart'; +// #docregion initialization +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +// #enddocregion initialization + +part 'isolates.g.dart'; + +QueryExecutor _openConnection() { + return NativeDatabase.memory(); +} + +class SomeTable extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get content => text()(); +} + +// Copying the definitions here because we can't import Flutter in documentation +// snippets. +class RootIsolateToken { + static RootIsolateToken? instance; +} + +class BackgroundIsolateBinaryMessenger { + static void ensureInitialized(RootIsolateToken token) {} +} + +// #docregion isolate, database-definition + +@DriftDatabase(tables: [SomeTable] /* ... */) +class MyDatabase extends _$MyDatabase { + // A constructor like this can use the default connection as described in the + // getting started guide, but also allows overriding the connection. + MyDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); + + @override + int get schemaVersion => 1; +} +// #enddocregion isolate, database-definition + +// #docregion driftisolate-spawn +Future createIsolateWithSpawn() async { + final token = RootIsolateToken.instance!; + return await DriftIsolate.spawn(() { + // This function runs in a new isolate, so we must first initialize the + // messenger to use platform channels. + BackgroundIsolateBinaryMessenger.ensureInitialized(token); + + // The callback to DriftIsolate.spawn() must return the database connection + // to use. + return LazyDatabase(() async { + // Note that this runs on a background isolate, which only started to + // support platform channels in Flutter 3.7. For earlier Flutter versions, + // a workaround is described later in this article. + final dbFolder = await getApplicationDocumentsDirectory(); + final path = p.join(dbFolder.path, 'app.db'); + + return NativeDatabase(File(path)); + }); + }); +} +// #enddocregion driftisolate-spawn + +// #docregion custom-spawn +Future createIsolateManually() async { + final receiveIsolate = ReceivePort('receive drift isolate handle'); + await Isolate.spawn((message) async { + final server = DriftIsolate.inCurrent(() { + // Again, this needs to return the LazyDatabase or the connection to use. + // #enddocregion custom-spawn + throw 'stub'; + // #docregion custom-spawn + }); + + // Now, inform the original isolate about the created server: + message.send(server); + }, receiveIsolate.sendPort); + + final server = await receiveIsolate.first as DriftIsolate; + receiveIsolate.close(); + return server; +} +// #enddocregion custom-spawn + +Future createIsolate() => createIsolateWithSpawn(); + +// #docregion isolate +void main() async { + final isolate = await createIsolate(); + + // After creating the isolate, calling connect() will return a connection + // which can be used to create a database. + // As long as the isolate is used by only one database (it is here), we can + // use `singleClientMode` to dispose the isolate after closing the connection. + final database = MyDatabase(await isolate.connect(singleClientMode: true)); + + // you can now use your database exactly like you regularly would, it + // transparently uses a background isolate to execute queries. + // #enddocregion isolate + // Just using the db to avoid an analyzer error, this isn't part of the docs. + database.customSelect('SELECT 1'); + // #docregion isolate +} +// #enddocregion isolate + +void connectSynchronously() { + // #docregion delayed + MyDatabase( + DatabaseConnection.delayed(Future.sync(() async { + final isolate = await createIsolate(); + return isolate.connect(singleClientMode: true); + })), + ); + // #enddocregion delayed +} + +// #docregion initialization + +Future _createDriftIsolate() async { + // this method is called from the main isolate. Since we can't use + // getApplicationDocumentsDirectory on a background isolate, we calculate + // the database path in the foreground isolate and then inform the + // background isolate about the path. + final dir = await getApplicationDocumentsDirectory(); + final path = p.join(dir.path, 'db.sqlite'); + final receivePort = ReceivePort(); + + await Isolate.spawn( + _startBackground, + _IsolateStartRequest(receivePort.sendPort, path), + ); + + // _startBackground will send the DriftIsolate to this ReceivePort + return await receivePort.first as DriftIsolate; +} + +void _startBackground(_IsolateStartRequest request) { + // this is the entry point from the background isolate! Let's create + // the database from the path we received + final executor = NativeDatabase(File(request.targetPath)); + // we're using DriftIsolate.inCurrent here as this method already runs on a + // background isolate. If we used DriftIsolate.spawn, a third isolate would be + // started which is not what we want! + final driftIsolate = DriftIsolate.inCurrent( + () => DatabaseConnection(executor), + ); + // inform the starting isolate about this, so that it can call .connect() + request.sendDriftIsolate.send(driftIsolate); +} + +// used to bundle the SendPort and the target path, since isolate entry point +// functions can only take one parameter. +class _IsolateStartRequest { + final SendPort sendDriftIsolate; + final String targetPath; + + _IsolateStartRequest(this.sendDriftIsolate, this.targetPath); +} +// #enddocregion initialization + +// #docregion init_connect +DatabaseConnection createDriftIsolateAndConnect() { + return DatabaseConnection.delayed(Future.sync(() async { + final isolate = await _createDriftIsolate(); + return await isolate.connect(singleClientMode: true); + })); +} +// #enddocregion init_connect + +// #docregion simple +QueryExecutor createSimple() { + return LazyDatabase(() async { + final dir = await getApplicationDocumentsDirectory(); + final file = File(p.join(dir.path, 'db.sqlite')); + + // Using createInBackground creates a drift isolate with the recommended + // options behind the scenes. + return NativeDatabase.createInBackground(file); + }); +} +// #enddocregion simple + +// #docregion invalid +Future invalidIsolateUsage() async { + final database = MyDatabase(NativeDatabase.memory()); + + // Unfortunately, this doesn't work: Drift databases contain references to + // async primitives like streams and futures that can't be serialized across + // isolates like this. + await Isolate.run(() async { + await database.batch((batch) { + // ... + }); + }); +} +// #enddocregion invalid + +Future> _complexAndExpensiveOperationToFetchRows() async { + throw 'stub'; +} + +// #docregion compute +Future insertBulkData(MyDatabase database) async { + // computeWithDatabase is an extension provided by package:drift/isolate.dart + await database.computeWithDatabase( + computation: (database) async { + // Expensive computation that runs on its own isolate but talks to the + // main database. + final rows = await _complexAndExpensiveOperationToFetchRows(); + await database.batch((batch) { + batch.insertAll(database.someTable, rows); + }); + }, + connect: (connection) { + // This function is responsible for creating a second instance of your + // database class with a short-lived [connection]. + // For this to work, your database class needs to have a constructor that + // allows taking a connection as described above. + return MyDatabase(connection); + }, + ); +} +// #enddocregion compute + +// #docregion custom-compute +Future customIsolateUsage(MyDatabase database) async { + final connection = await database.serializableConnection(); + + await Isolate.run( + () async { + // We can't share the [database] object across isolates, but the connection + // is fine! + final databaseForIsolate = MyDatabase(await connection.connect()); + + try { + await databaseForIsolate.batch((batch) { + // (...) + }); + } finally { + databaseForIsolate.close(); + } + }, + debugName: 'My custom database task', + ); +} +// #enddocregion custom-compute diff --git a/docs/lib/snippets/log_interceptor.dart b/docs/lib/snippets/log_interceptor.dart new file mode 100644 index 000000000..979c8aa4e --- /dev/null +++ b/docs/lib/snippets/log_interceptor.dart @@ -0,0 +1,92 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; + +// #docregion class +class LogInterceptor extends QueryInterceptor { + Future _run( + String description, FutureOr Function() operation) async { + final stopwatch = Stopwatch()..start(); + print('Running $description'); + + try { + final result = await operation(); + print(' => succeeded after ${stopwatch.elapsedMilliseconds}ms'); + return result; + } on Object catch (e) { + print(' => failed after ${stopwatch.elapsedMilliseconds}ms ($e)'); + rethrow; + } + } + + @override + TransactionExecutor beginTransaction(QueryExecutor parent) { + print('begin'); + return super.beginTransaction(parent); + } + + @override + Future commitTransaction(TransactionExecutor inner) { + return _run('commit', () => inner.send()); + } + + @override + Future rollbackTransaction(TransactionExecutor inner) { + return _run('rollback', () => inner.rollback()); + } + + @override + Future runBatched( + QueryExecutor executor, BatchedStatements statements) { + return _run( + 'batch with $statements', () => executor.runBatched(statements)); + } + + @override + Future runInsert( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runInsert(statement, args)); + } + + @override + Future runUpdate( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runUpdate(statement, args)); + } + + @override + Future runDelete( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runDelete(statement, args)); + } + + @override + Future runCustom( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runCustom(statement, args)); + } + + @override + Future>> runSelect( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runSelect(statement, args)); + } +} +// #enddocregion class + +void use() { + final myDatabaseFile = File('/dev/null'); + + // #docregion use + NativeDatabase.createInBackground( + myDatabaseFile, + ).interceptWith(LogInterceptor()); + // #enddocregion use +} diff --git a/docs/lib/snippets/migrations/exported_eschema/drift_schema_v1.json b/docs/lib/snippets/migrations/exported_eschema/drift_schema_v1.json new file mode 100644 index 000000000..c0910adfe --- /dev/null +++ b/docs/lib/snippets/migrations/exported_eschema/drift_schema_v1.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.0.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"todos","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"title","getter_name":"title","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":6,"max":10}}]},{"name":"body","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/docs/lib/snippets/migrations/exported_eschema/drift_schema_v2.json b/docs/lib/snippets/migrations/exported_eschema/drift_schema_v2.json new file mode 100644 index 000000000..08aa3fa4f --- /dev/null +++ b/docs/lib/snippets/migrations/exported_eschema/drift_schema_v2.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.0.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"todos","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"title","getter_name":"title","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":6,"max":10}}]},{"name":"body","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"due_date","getter_name":"dueDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/docs/lib/snippets/migrations/exported_eschema/drift_schema_v3.json b/docs/lib/snippets/migrations/exported_eschema/drift_schema_v3.json new file mode 100644 index 000000000..1cd239c3f --- /dev/null +++ b/docs/lib/snippets/migrations/exported_eschema/drift_schema_v3.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.0.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"todos","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"title","getter_name":"title","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":6,"max":10}}]},{"name":"body","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"due_date","getter_name":"dueDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"priority","getter_name":"priority","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/docs/lib/snippets/migrations/migrations.dart b/docs/lib/snippets/migrations/migrations.dart new file mode 100644 index 000000000..d025c2134 --- /dev/null +++ b/docs/lib/snippets/migrations/migrations.dart @@ -0,0 +1,93 @@ +import 'package:drift/drift.dart'; + +part 'migrations.g.dart'; + +const kDebugMode = false; + +// #docregion table +class Todos extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get title => text().withLength(min: 6, max: 10)(); + TextColumn get content => text().named('body')(); + IntColumn get category => integer().nullable()(); + DateTimeColumn get dueDate => + dateTime().nullable()(); // new, added column in v2 + IntColumn get priority => integer().nullable()(); // new, added column in v3 +} +// #enddocregion table + +@DriftDatabase(tables: [Todos]) +class MyDatabase extends _$MyDatabase { + MyDatabase(super.e); + + // #docregion start + @override + int get schemaVersion => 3; // bump because the tables have changed. + + @override + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (Migrator m) async { + await m.createAll(); + }, + onUpgrade: (Migrator m, int from, int to) async { + if (from < 2) { + // we added the dueDate property in the change from version 1 to + // version 2 + await m.addColumn(todos, todos.dueDate); + } + if (from < 3) { + // we added the priority property in the change from version 1 or 2 + // to version 3 + await m.addColumn(todos, todos.priority); + } + }, + ); + } + // The rest of the class can stay the same + // #enddocregion start + + MigrationStrategy get withForeignKeyCheck { + // #docregion structured + return MigrationStrategy( + onUpgrade: (m, from, to) async { + // disable foreign_keys before migrations + await customStatement('PRAGMA foreign_keys = OFF'); + + await transaction(() async { + // put your migration logic here + }); + + // Assert that the schema is valid after migrations + if (kDebugMode) { + final wrongForeignKeys = + await customSelect('PRAGMA foreign_key_check').get(); + assert(wrongForeignKeys.isEmpty, + '${wrongForeignKeys.map((e) => e.data)}'); + } + }, + beforeOpen: (details) async { + await customStatement('PRAGMA foreign_keys = ON'); + // .... + }, + ); + // #enddocregion structured + } + + MigrationStrategy get changeType { + const yourOldVersion = 4; + // #docregion change_type + return MigrationStrategy( + onUpgrade: (m, old, to) async { + if (old <= yourOldVersion) { + await m.alterTable( + TableMigration(todos, columnTransformer: { + todos.category: todos.category.cast(), + }), + ); + } + }, + ); + // #enddocregion change_type + } +} diff --git a/docs/lib/snippets/migrations/runtime_verification.dart b/docs/lib/snippets/migrations/runtime_verification.dart new file mode 100644 index 000000000..8638f3cf5 --- /dev/null +++ b/docs/lib/snippets/migrations/runtime_verification.dart @@ -0,0 +1,43 @@ +import 'package:drift/drift.dart'; + +// #docregion native +// import the migrations tooling +import 'package:drift_dev/api/migrations_native.dart'; +// #enddocregion native + +const kDebugMode = true; + +abstract class _$MyDatabase extends GeneratedDatabase { + _$MyDatabase(super.executor); +} + +// #docregion native + +class MyDatabase extends _$MyDatabase { +// #enddocregion native + MyDatabase(super.executor); + + @override + Iterable> get allTables => + throw UnimplementedError(); + + @override + int get schemaVersion => throw UnimplementedError(); + + // #docregion native + @override + MigrationStrategy get migration => MigrationStrategy( + onCreate: (m) async {/* ... */}, + onUpgrade: (m, from, to) async {/* your existing migration logic */}, + beforeOpen: (details) async { + // your existing beforeOpen callback, enable foreign keys, etc. + + if (kDebugMode) { + // This check pulls in a fair amount of code that's not needed + // anywhere else, so we recommend only doing it in debug builds. + await validateDatabaseSchema(); + } + }, + ); +} +// #enddocregion native diff --git a/docs/lib/snippets/migrations/schema_versions.dart b/docs/lib/snippets/migrations/schema_versions.dart new file mode 100644 index 000000000..c54e2ce39 --- /dev/null +++ b/docs/lib/snippets/migrations/schema_versions.dart @@ -0,0 +1,140 @@ +// dart format width=80 +import 'package:drift/internal/versioned_schema.dart' as i0; +import 'package:drift/drift.dart' as i1; +import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import + +// GENERATED BY drift_dev, DO NOT MODIFY. +final class Schema2 extends i0.VersionedSchema { + Schema2({required super.database}) : super(version: 2); + @override + late final List entities = [ + todos, + ]; + late final Shape0 todos = Shape0( + source: i0.VersionedTable( + entityName: 'todos', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + ], + attachedDatabase: database, + ), + alias: null); +} + +class Shape0 extends i0.VersionedTable { + Shape0({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get title => + columnsByName['title']! as i1.GeneratedColumn; + i1.GeneratedColumn get content => + columnsByName['body']! as i1.GeneratedColumn; + i1.GeneratedColumn get category => + columnsByName['category']! as i1.GeneratedColumn; + i1.GeneratedColumn get dueDate => + columnsByName['due_date']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_0(String aliasedName) => + i1.GeneratedColumn('id', aliasedName, false, + hasAutoIncrement: true, + type: i1.DriftSqlType.int, + defaultConstraints: + i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); +i1.GeneratedColumn _column_1(String aliasedName) => + i1.GeneratedColumn('title', aliasedName, false, + additionalChecks: i1.GeneratedColumn.checkTextLength( + minTextLength: 6, maxTextLength: 10), + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_2(String aliasedName) => + i1.GeneratedColumn('body', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_3(String aliasedName) => + i1.GeneratedColumn('category', aliasedName, true, + type: i1.DriftSqlType.int); +i1.GeneratedColumn _column_4(String aliasedName) => + i1.GeneratedColumn('due_date', aliasedName, true, + type: i1.DriftSqlType.dateTime); + +final class Schema3 extends i0.VersionedSchema { + Schema3({required super.database}) : super(version: 3); + @override + late final List entities = [ + todos, + ]; + late final Shape1 todos = Shape1( + source: i0.VersionedTable( + entityName: 'todos', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + ], + attachedDatabase: database, + ), + alias: null); +} + +class Shape1 extends i0.VersionedTable { + Shape1({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get title => + columnsByName['title']! as i1.GeneratedColumn; + i1.GeneratedColumn get content => + columnsByName['body']! as i1.GeneratedColumn; + i1.GeneratedColumn get category => + columnsByName['category']! as i1.GeneratedColumn; + i1.GeneratedColumn get dueDate => + columnsByName['due_date']! as i1.GeneratedColumn; + i1.GeneratedColumn get priority => + columnsByName['priority']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_5(String aliasedName) => + i1.GeneratedColumn('priority', aliasedName, true, + type: i1.DriftSqlType.int); +i0.MigrationStepWithVersion migrationSteps({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, +}) { + return (currentVersion, database) async { + switch (currentVersion) { + case 1: + final schema = Schema2(database: database); + final migrator = i1.Migrator(database, schema); + await from1To2(migrator, schema); + return 2; + case 2: + final schema = Schema3(database: database); + final migrator = i1.Migrator(database, schema); + await from2To3(migrator, schema); + return 3; + default: + throw ArgumentError.value('Unknown migration from $currentVersion'); + } + }; +} + +i1.OnUpgrade stepByStep({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, +}) => + i0.VersionedSchema.stepByStepHelper( + step: migrationSteps( + from1To2: from1To2, + from2To3: from2To3, + )); diff --git a/docs/lib/snippets/migrations/step_by_step.dart b/docs/lib/snippets/migrations/step_by_step.dart new file mode 100644 index 000000000..1d964d817 --- /dev/null +++ b/docs/lib/snippets/migrations/step_by_step.dart @@ -0,0 +1,133 @@ +import 'dart:math' as math; + +import 'package:drift/drift.dart'; + +import 'migrations.dart'; + +// #docregion stepbystep +// This file was generated by `drift_dev schema steps drift_schemas/ lib/database/schema_versions.dart` +import 'schema_versions.dart'; + +// #enddocregion stepbystep + +class StepByStep { + // #docregion stepbystep + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (Migrator m) async { + await m.createAll(); + }, + onUpgrade: stepByStep( + from1To2: (m, schema) async { + // we added the dueDate property in the change from version 1 to + // version 2 + await m.addColumn(schema.todos, schema.todos.dueDate); + }, + from2To3: (m, schema) async { + // we added the priority property in the change from version 1 or 2 + // to version 3 + await m.addColumn(schema.todos, schema.todos.priority); + }, + ), + ); + } + // #enddocregion stepbystep +} + +extension StepByStep2 on GeneratedDatabase { + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (Migrator m) async { + await m.createAll(); + }, + // #docregion stepbystep2 + onUpgrade: (m, from, to) async { + // Run migration steps without foreign keys and re-enable them later + // (https://drift.simonbinder.eu/docs/advanced-features/migrations/#tips) + await customStatement('PRAGMA foreign_keys = OFF'); + + await m.runMigrationSteps( + from: from, + to: to, + steps: migrationSteps( + from1To2: (m, schema) async { + // we added the dueDate property in the change from version 1 to + // version 2 + await m.addColumn(schema.todos, schema.todos.dueDate); + }, + from2To3: (m, schema) async { + // we added the priority property in the change from version 1 or 2 + // to version 3 + await m.addColumn(schema.todos, schema.todos.priority); + }, + ), + ); + + if (kDebugMode) { + // Fail if the migration broke foreign keys + final wrongForeignKeys = + await customSelect('PRAGMA foreign_key_check').get(); + assert(wrongForeignKeys.isEmpty, + '${wrongForeignKeys.map((e) => e.data)}'); + } + + await customStatement('PRAGMA foreign_keys = ON;'); + }, + // #enddocregion stepbystep2 + ); + } +} + +extension StepByStep3 on MyDatabase { + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (Migrator m) async { + await m.createAll(); + }, + // #docregion stepbystep3 + onUpgrade: (m, from, to) async { + // Run migration steps without foreign keys and re-enable them later + // (https://drift.simonbinder.eu/docs/advanced-features/migrations/#tips) + await customStatement('PRAGMA foreign_keys = OFF'); + + // Manually running migrations up to schema version 2, after which we've + // enabled step-by-step migrations. + if (from < 2) { + // we added the dueDate property in the change from version 1 to + // version 2 - before switching to step-by-step migrations. + await m.addColumn(todos, todos.dueDate); + } + + // At this point, we should be migrated to schema 3. For future schema + // changes, we will "start" at schema 3. + await m.runMigrationSteps( + from: math.max(2, from), + to: to, + steps: migrationSteps( + // #enddocregion stepbystep3 + from1To2: (m, schema) async { + throw 'users would not have this as v2 would be their first schema'; + }, + // #docregion stepbystep3 + from2To3: (m, schema) async { + // we added the priority property in the change from version 1 or + // 2 to version 3 + await m.addColumn(schema.todos, schema.todos.priority); + }, + ), + ); + + if (kDebugMode) { + // Fail if the migration broke foreign keys + final wrongForeignKeys = + await customSelect('PRAGMA foreign_key_check').get(); + assert(wrongForeignKeys.isEmpty, + '${wrongForeignKeys.map((e) => e.data)}'); + } + + await customStatement('PRAGMA foreign_keys = ON;'); + }, + // #enddocregion stepbystep3 + ); + } +} diff --git a/docs/lib/snippets/migrations/tests/generated_migrations/schema.dart b/docs/lib/snippets/migrations/tests/generated_migrations/schema.dart new file mode 100644 index 000000000..209e70d78 --- /dev/null +++ b/docs/lib/snippets/migrations/tests/generated_migrations/schema.dart @@ -0,0 +1,26 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; +import 'package:drift/internal/migrations.dart'; +import 'schema_v1.dart' as v1; +import 'schema_v2.dart' as v2; +import 'schema_v3.dart' as v3; + +class GeneratedHelper implements SchemaInstantiationHelper { + @override + GeneratedDatabase databaseForVersion(QueryExecutor db, int version) { + switch (version) { + case 1: + return v1.DatabaseAtV1(db); + case 2: + return v2.DatabaseAtV2(db); + case 3: + return v3.DatabaseAtV3(db); + default: + throw MissingSchemaException(version, versions); + } + } + + static const versions = const [1, 2, 3]; +} diff --git a/docs/lib/snippets/migrations/tests/generated_migrations/schema_v1.dart b/docs/lib/snippets/migrations/tests/generated_migrations/schema_v1.dart new file mode 100644 index 000000000..535079e0d --- /dev/null +++ b/docs/lib/snippets/migrations/tests/generated_migrations/schema_v1.dart @@ -0,0 +1,242 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class Todos extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Todos(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn title = GeneratedColumn( + 'title', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 6, maxTextLength: 10), + type: DriftSqlType.string, + requiredDuringInsert: true); + late final GeneratedColumn content = GeneratedColumn( + 'body', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn category = GeneratedColumn( + 'category', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + @override + List get $columns => [id, title, content, category]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'todos'; + @override + Set get $primaryKey => {id}; + @override + TodosData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TodosData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}title'])!, + content: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}body'])!, + category: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}category']), + ); + } + + @override + Todos createAlias(String alias) { + return Todos(attachedDatabase, alias); + } +} + +class TodosData extends DataClass implements Insertable { + final int id; + final String title; + final String content; + final int? category; + const TodosData( + {required this.id, + required this.title, + required this.content, + this.category}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['title'] = Variable(title); + map['body'] = Variable(content); + if (!nullToAbsent || category != null) { + map['category'] = Variable(category); + } + return map; + } + + TodosCompanion toCompanion(bool nullToAbsent) { + return TodosCompanion( + id: Value(id), + title: Value(title), + content: Value(content), + category: category == null && nullToAbsent + ? const Value.absent() + : Value(category), + ); + } + + factory TodosData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TodosData( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + content: serializer.fromJson(json['content']), + category: serializer.fromJson(json['category']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'content': serializer.toJson(content), + 'category': serializer.toJson(category), + }; + } + + TodosData copyWith( + {int? id, + String? title, + String? content, + Value category = const Value.absent()}) => + TodosData( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category.present ? category.value : this.category, + ); + TodosData copyWithCompanion(TodosCompanion data) { + return TodosData( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + content: data.content.present ? data.content.value : this.content, + category: data.category.present ? data.category.value : this.category, + ); + } + + @override + String toString() { + return (StringBuffer('TodosData(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, title, content, category); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TodosData && + other.id == this.id && + other.title == this.title && + other.content == this.content && + other.category == this.category); +} + +class TodosCompanion extends UpdateCompanion { + final Value id; + final Value title; + final Value content; + final Value category; + const TodosCompanion({ + this.id = const Value.absent(), + this.title = const Value.absent(), + this.content = const Value.absent(), + this.category = const Value.absent(), + }); + TodosCompanion.insert({ + this.id = const Value.absent(), + required String title, + required String content, + this.category = const Value.absent(), + }) : title = Value(title), + content = Value(content); + static Insertable custom({ + Expression? id, + Expression? title, + Expression? content, + Expression? category, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (content != null) 'body': content, + if (category != null) 'category': category, + }); + } + + TodosCompanion copyWith( + {Value? id, + Value? title, + Value? content, + Value? category}) { + return TodosCompanion( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category ?? this.category, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (content.present) { + map['body'] = Variable(content.value); + } + if (category.present) { + map['category'] = Variable(category.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TodosCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV1 extends GeneratedDatabase { + DatabaseAtV1(QueryExecutor e) : super(e); + late final Todos todos = Todos(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [todos]; + @override + int get schemaVersion => 1; +} diff --git a/docs/lib/snippets/migrations/tests/generated_migrations/schema_v2.dart b/docs/lib/snippets/migrations/tests/generated_migrations/schema_v2.dart new file mode 100644 index 000000000..5f40f2b8c --- /dev/null +++ b/docs/lib/snippets/migrations/tests/generated_migrations/schema_v2.dart @@ -0,0 +1,273 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class Todos extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Todos(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn title = GeneratedColumn( + 'title', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 6, maxTextLength: 10), + type: DriftSqlType.string, + requiredDuringInsert: true); + late final GeneratedColumn content = GeneratedColumn( + 'body', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn category = GeneratedColumn( + 'category', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn dueDate = GeneratedColumn( + 'due_date', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + @override + List get $columns => [id, title, content, category, dueDate]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'todos'; + @override + Set get $primaryKey => {id}; + @override + TodosData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TodosData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}title'])!, + content: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}body'])!, + category: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}category']), + dueDate: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}due_date']), + ); + } + + @override + Todos createAlias(String alias) { + return Todos(attachedDatabase, alias); + } +} + +class TodosData extends DataClass implements Insertable { + final int id; + final String title; + final String content; + final int? category; + final DateTime? dueDate; + const TodosData( + {required this.id, + required this.title, + required this.content, + this.category, + this.dueDate}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['title'] = Variable(title); + map['body'] = Variable(content); + if (!nullToAbsent || category != null) { + map['category'] = Variable(category); + } + if (!nullToAbsent || dueDate != null) { + map['due_date'] = Variable(dueDate); + } + return map; + } + + TodosCompanion toCompanion(bool nullToAbsent) { + return TodosCompanion( + id: Value(id), + title: Value(title), + content: Value(content), + category: category == null && nullToAbsent + ? const Value.absent() + : Value(category), + dueDate: dueDate == null && nullToAbsent + ? const Value.absent() + : Value(dueDate), + ); + } + + factory TodosData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TodosData( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + content: serializer.fromJson(json['content']), + category: serializer.fromJson(json['category']), + dueDate: serializer.fromJson(json['dueDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'content': serializer.toJson(content), + 'category': serializer.toJson(category), + 'dueDate': serializer.toJson(dueDate), + }; + } + + TodosData copyWith( + {int? id, + String? title, + String? content, + Value category = const Value.absent(), + Value dueDate = const Value.absent()}) => + TodosData( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category.present ? category.value : this.category, + dueDate: dueDate.present ? dueDate.value : this.dueDate, + ); + TodosData copyWithCompanion(TodosCompanion data) { + return TodosData( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + content: data.content.present ? data.content.value : this.content, + category: data.category.present ? data.category.value : this.category, + dueDate: data.dueDate.present ? data.dueDate.value : this.dueDate, + ); + } + + @override + String toString() { + return (StringBuffer('TodosData(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category, ') + ..write('dueDate: $dueDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, title, content, category, dueDate); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TodosData && + other.id == this.id && + other.title == this.title && + other.content == this.content && + other.category == this.category && + other.dueDate == this.dueDate); +} + +class TodosCompanion extends UpdateCompanion { + final Value id; + final Value title; + final Value content; + final Value category; + final Value dueDate; + const TodosCompanion({ + this.id = const Value.absent(), + this.title = const Value.absent(), + this.content = const Value.absent(), + this.category = const Value.absent(), + this.dueDate = const Value.absent(), + }); + TodosCompanion.insert({ + this.id = const Value.absent(), + required String title, + required String content, + this.category = const Value.absent(), + this.dueDate = const Value.absent(), + }) : title = Value(title), + content = Value(content); + static Insertable custom({ + Expression? id, + Expression? title, + Expression? content, + Expression? category, + Expression? dueDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (content != null) 'body': content, + if (category != null) 'category': category, + if (dueDate != null) 'due_date': dueDate, + }); + } + + TodosCompanion copyWith( + {Value? id, + Value? title, + Value? content, + Value? category, + Value? dueDate}) { + return TodosCompanion( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category ?? this.category, + dueDate: dueDate ?? this.dueDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (content.present) { + map['body'] = Variable(content.value); + } + if (category.present) { + map['category'] = Variable(category.value); + } + if (dueDate.present) { + map['due_date'] = Variable(dueDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TodosCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category, ') + ..write('dueDate: $dueDate') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV2 extends GeneratedDatabase { + DatabaseAtV2(QueryExecutor e) : super(e); + late final Todos todos = Todos(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [todos]; + @override + int get schemaVersion => 2; +} diff --git a/docs/lib/snippets/migrations/tests/generated_migrations/schema_v3.dart b/docs/lib/snippets/migrations/tests/generated_migrations/schema_v3.dart new file mode 100644 index 000000000..71e974ce3 --- /dev/null +++ b/docs/lib/snippets/migrations/tests/generated_migrations/schema_v3.dart @@ -0,0 +1,306 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class Todos extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Todos(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn title = GeneratedColumn( + 'title', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 6, maxTextLength: 10), + type: DriftSqlType.string, + requiredDuringInsert: true); + late final GeneratedColumn content = GeneratedColumn( + 'body', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn category = GeneratedColumn( + 'category', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn dueDate = GeneratedColumn( + 'due_date', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn priority = GeneratedColumn( + 'priority', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + @override + List get $columns => + [id, title, content, category, dueDate, priority]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'todos'; + @override + Set get $primaryKey => {id}; + @override + TodosData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TodosData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}title'])!, + content: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}body'])!, + category: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}category']), + dueDate: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}due_date']), + priority: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}priority']), + ); + } + + @override + Todos createAlias(String alias) { + return Todos(attachedDatabase, alias); + } +} + +class TodosData extends DataClass implements Insertable { + final int id; + final String title; + final String content; + final int? category; + final DateTime? dueDate; + final int? priority; + const TodosData( + {required this.id, + required this.title, + required this.content, + this.category, + this.dueDate, + this.priority}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['title'] = Variable(title); + map['body'] = Variable(content); + if (!nullToAbsent || category != null) { + map['category'] = Variable(category); + } + if (!nullToAbsent || dueDate != null) { + map['due_date'] = Variable(dueDate); + } + if (!nullToAbsent || priority != null) { + map['priority'] = Variable(priority); + } + return map; + } + + TodosCompanion toCompanion(bool nullToAbsent) { + return TodosCompanion( + id: Value(id), + title: Value(title), + content: Value(content), + category: category == null && nullToAbsent + ? const Value.absent() + : Value(category), + dueDate: dueDate == null && nullToAbsent + ? const Value.absent() + : Value(dueDate), + priority: priority == null && nullToAbsent + ? const Value.absent() + : Value(priority), + ); + } + + factory TodosData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TodosData( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + content: serializer.fromJson(json['content']), + category: serializer.fromJson(json['category']), + dueDate: serializer.fromJson(json['dueDate']), + priority: serializer.fromJson(json['priority']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'content': serializer.toJson(content), + 'category': serializer.toJson(category), + 'dueDate': serializer.toJson(dueDate), + 'priority': serializer.toJson(priority), + }; + } + + TodosData copyWith( + {int? id, + String? title, + String? content, + Value category = const Value.absent(), + Value dueDate = const Value.absent(), + Value priority = const Value.absent()}) => + TodosData( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category.present ? category.value : this.category, + dueDate: dueDate.present ? dueDate.value : this.dueDate, + priority: priority.present ? priority.value : this.priority, + ); + TodosData copyWithCompanion(TodosCompanion data) { + return TodosData( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + content: data.content.present ? data.content.value : this.content, + category: data.category.present ? data.category.value : this.category, + dueDate: data.dueDate.present ? data.dueDate.value : this.dueDate, + priority: data.priority.present ? data.priority.value : this.priority, + ); + } + + @override + String toString() { + return (StringBuffer('TodosData(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category, ') + ..write('dueDate: $dueDate, ') + ..write('priority: $priority') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, title, content, category, dueDate, priority); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TodosData && + other.id == this.id && + other.title == this.title && + other.content == this.content && + other.category == this.category && + other.dueDate == this.dueDate && + other.priority == this.priority); +} + +class TodosCompanion extends UpdateCompanion { + final Value id; + final Value title; + final Value content; + final Value category; + final Value dueDate; + final Value priority; + const TodosCompanion({ + this.id = const Value.absent(), + this.title = const Value.absent(), + this.content = const Value.absent(), + this.category = const Value.absent(), + this.dueDate = const Value.absent(), + this.priority = const Value.absent(), + }); + TodosCompanion.insert({ + this.id = const Value.absent(), + required String title, + required String content, + this.category = const Value.absent(), + this.dueDate = const Value.absent(), + this.priority = const Value.absent(), + }) : title = Value(title), + content = Value(content); + static Insertable custom({ + Expression? id, + Expression? title, + Expression? content, + Expression? category, + Expression? dueDate, + Expression? priority, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (content != null) 'body': content, + if (category != null) 'category': category, + if (dueDate != null) 'due_date': dueDate, + if (priority != null) 'priority': priority, + }); + } + + TodosCompanion copyWith( + {Value? id, + Value? title, + Value? content, + Value? category, + Value? dueDate, + Value? priority}) { + return TodosCompanion( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category ?? this.category, + dueDate: dueDate ?? this.dueDate, + priority: priority ?? this.priority, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (content.present) { + map['body'] = Variable(content.value); + } + if (category.present) { + map['category'] = Variable(category.value); + } + if (dueDate.present) { + map['due_date'] = Variable(dueDate.value); + } + if (priority.present) { + map['priority'] = Variable(priority.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TodosCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category, ') + ..write('dueDate: $dueDate, ') + ..write('priority: $priority') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV3 extends GeneratedDatabase { + DatabaseAtV3(QueryExecutor e) : super(e); + late final Todos todos = Todos(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [todos]; + @override + int get schemaVersion => 3; +} diff --git a/docs/lib/snippets/migrations/tests/schema_test.dart b/docs/lib/snippets/migrations/tests/schema_test.dart new file mode 100644 index 000000000..6d077ac19 --- /dev/null +++ b/docs/lib/snippets/migrations/tests/schema_test.dart @@ -0,0 +1,32 @@ +// #docregion setup +import 'package:test/test.dart'; +import 'package:drift_dev/api/migrations_native.dart'; + +// The generated directory from before. +import 'generated_migrations/schema.dart'; + +// #enddocregion setup +import '../migrations.dart'; +// #docregion setup + +void main() { + late SchemaVerifier verifier; + + setUpAll(() { + // GeneratedHelper() was generated by drift, the verifier is an api + // provided by drift_dev. + verifier = SchemaVerifier(GeneratedHelper()); + }); + + test('upgrade from v1 to v2', () async { + // Use startAt(1) to obtain a database connection with all tables + // from the v1 schema. + final connection = await verifier.startAt(1); + final db = MyDatabase(connection); + + // Use this to run a migration to v2 and then validate that the + // database has the expected schema. + await verifier.migrateAndValidate(db, 2); + }); +} +// #enddocregion setup diff --git a/docs/lib/snippets/migrations/tests/verify_data_integrity_test.dart b/docs/lib/snippets/migrations/tests/verify_data_integrity_test.dart new file mode 100644 index 000000000..6a42fbbcb --- /dev/null +++ b/docs/lib/snippets/migrations/tests/verify_data_integrity_test.dart @@ -0,0 +1,49 @@ +import 'package:test/test.dart'; +import 'package:drift_dev/api/migrations_native.dart'; + +import '../migrations.dart'; +import 'generated_migrations/schema.dart'; + +// #docregion imports +import 'generated_migrations/schema_v1.dart' as v1; +import 'generated_migrations/schema_v2.dart' as v2; +// #enddocregion imports + +// #docregion main +void main() { +// #enddocregion main + late SchemaVerifier verifier; + + setUpAll(() { + // GeneratedHelper() was generated by drift, the verifier is an api + // provided by drift_dev. + verifier = SchemaVerifier(GeneratedHelper()); + }); + +// #docregion main + // ... + test('upgrade from v1 to v2', () async { + final schema = await verifier.schemaAt(1); + + // Add some data to the table being migrated + final oldDb = v1.DatabaseAtV1(schema.newConnection()); + await oldDb.into(oldDb.todos).insert(v1.TodosCompanion.insert( + title: 'my first todo entry', + content: 'should still be there after the migration', + )); + await oldDb.close(); + + // Run the migration and verify that it adds the name column. + final db = MyDatabase(schema.newConnection()); + await verifier.migrateAndValidate(db, 2); + await db.close(); + + // Make sure the entry is still here + final migratedDb = v2.DatabaseAtV2(schema.newConnection()); + final entry = await migratedDb.select(migratedDb.todos).getSingle(); + expect(entry.id, 1); + expect(entry.dueDate, isNull); // default from the migration + await migratedDb.close(); + }); +} +// #enddocregion main diff --git a/docs/lib/snippets/modular/custom_types/drift_table.drift b/docs/lib/snippets/modular/custom_types/drift_table.drift new file mode 100644 index 000000000..923170184 --- /dev/null +++ b/docs/lib/snippets/modular/custom_types/drift_table.drift @@ -0,0 +1,7 @@ +import 'type.dart'; + +CREATE TABLE periodic_reminders ( + id INTEGER NOT NULL PRIMARY KEY, + frequency `const DurationType()` NOT NULL, + reminder TEXT NOT NULL +); diff --git a/docs/lib/snippets/modular/custom_types/drift_table.drift.dart b/docs/lib/snippets/modular/custom_types/drift_table.drift.dart new file mode 100644 index 000000000..4cb578f1f --- /dev/null +++ b/docs/lib/snippets/modular/custom_types/drift_table.drift.dart @@ -0,0 +1,375 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/custom_types/drift_table.drift.dart' + as i1; +import 'package:drift_docs/snippets/modular/custom_types/type.dart' as i2; + +typedef $PeriodicRemindersCreateCompanionBuilder = i1.PeriodicRemindersCompanion + Function({ + i0.Value id, + required Duration frequency, + required String reminder, +}); +typedef $PeriodicRemindersUpdateCompanionBuilder = i1.PeriodicRemindersCompanion + Function({ + i0.Value id, + i0.Value frequency, + i0.Value reminder, +}); + +class $PeriodicRemindersFilterComposer + extends i0.Composer { + $PeriodicRemindersFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get frequency => $composableBuilder( + column: $table.frequency, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get reminder => $composableBuilder( + column: $table.reminder, builder: (column) => i0.ColumnFilters(column)); +} + +class $PeriodicRemindersOrderingComposer + extends i0.Composer { + $PeriodicRemindersOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get frequency => $composableBuilder( + column: $table.frequency, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get reminder => $composableBuilder( + column: $table.reminder, builder: (column) => i0.ColumnOrderings(column)); +} + +class $PeriodicRemindersAnnotationComposer + extends i0.Composer { + $PeriodicRemindersAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get frequency => + $composableBuilder(column: $table.frequency, builder: (column) => column); + + i0.GeneratedColumn get reminder => + $composableBuilder(column: $table.reminder, builder: (column) => column); +} + +class $PeriodicRemindersTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.PeriodicReminders, + i1.PeriodicReminder, + i1.$PeriodicRemindersFilterComposer, + i1.$PeriodicRemindersOrderingComposer, + i1.$PeriodicRemindersAnnotationComposer, + $PeriodicRemindersCreateCompanionBuilder, + $PeriodicRemindersUpdateCompanionBuilder, + ( + i1.PeriodicReminder, + i0.BaseReferences + ), + i1.PeriodicReminder, + i0.PrefetchHooks Function()> { + $PeriodicRemindersTableManager( + i0.GeneratedDatabase db, i1.PeriodicReminders table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$PeriodicRemindersFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$PeriodicRemindersOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$PeriodicRemindersAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value frequency = const i0.Value.absent(), + i0.Value reminder = const i0.Value.absent(), + }) => + i1.PeriodicRemindersCompanion( + id: id, + frequency: frequency, + reminder: reminder, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required Duration frequency, + required String reminder, + }) => + i1.PeriodicRemindersCompanion.insert( + id: id, + frequency: frequency, + reminder: reminder, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $PeriodicRemindersProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.PeriodicReminders, + i1.PeriodicReminder, + i1.$PeriodicRemindersFilterComposer, + i1.$PeriodicRemindersOrderingComposer, + i1.$PeriodicRemindersAnnotationComposer, + $PeriodicRemindersCreateCompanionBuilder, + $PeriodicRemindersUpdateCompanionBuilder, + ( + i1.PeriodicReminder, + i0.BaseReferences + ), + i1.PeriodicReminder, + i0.PrefetchHooks Function()>; + +class PeriodicReminders extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + PeriodicReminders(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL PRIMARY KEY'); + static const i0.VerificationMeta _frequencyMeta = + const i0.VerificationMeta('frequency'); + late final i0.GeneratedColumn frequency = + i0.GeneratedColumn('frequency', aliasedName, false, + type: const i2.DurationType(), + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + static const i0.VerificationMeta _reminderMeta = + const i0.VerificationMeta('reminder'); + late final i0.GeneratedColumn reminder = i0.GeneratedColumn( + 'reminder', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => [id, frequency, reminder]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'periodic_reminders'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('frequency')) { + context.handle(_frequencyMeta, + frequency.isAcceptableOrUnknown(data['frequency']!, _frequencyMeta)); + } else if (isInserting) { + context.missing(_frequencyMeta); + } + if (data.containsKey('reminder')) { + context.handle(_reminderMeta, + reminder.isAcceptableOrUnknown(data['reminder']!, _reminderMeta)); + } else if (isInserting) { + context.missing(_reminderMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.PeriodicReminder map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.PeriodicReminder( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + frequency: attachedDatabase.typeMapping + .read(const i2.DurationType(), data['${effectivePrefix}frequency'])!, + reminder: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}reminder'])!, + ); + } + + @override + PeriodicReminders createAlias(String alias) { + return PeriodicReminders(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class PeriodicReminder extends i0.DataClass + implements i0.Insertable { + final int id; + final Duration frequency; + final String reminder; + const PeriodicReminder( + {required this.id, required this.frequency, required this.reminder}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['frequency'] = + i0.Variable(frequency, const i2.DurationType()); + map['reminder'] = i0.Variable(reminder); + return map; + } + + i1.PeriodicRemindersCompanion toCompanion(bool nullToAbsent) { + return i1.PeriodicRemindersCompanion( + id: i0.Value(id), + frequency: i0.Value(frequency), + reminder: i0.Value(reminder), + ); + } + + factory PeriodicReminder.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return PeriodicReminder( + id: serializer.fromJson(json['id']), + frequency: serializer.fromJson(json['frequency']), + reminder: serializer.fromJson(json['reminder']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'frequency': serializer.toJson(frequency), + 'reminder': serializer.toJson(reminder), + }; + } + + i1.PeriodicReminder copyWith( + {int? id, Duration? frequency, String? reminder}) => + i1.PeriodicReminder( + id: id ?? this.id, + frequency: frequency ?? this.frequency, + reminder: reminder ?? this.reminder, + ); + PeriodicReminder copyWithCompanion(i1.PeriodicRemindersCompanion data) { + return PeriodicReminder( + id: data.id.present ? data.id.value : this.id, + frequency: data.frequency.present ? data.frequency.value : this.frequency, + reminder: data.reminder.present ? data.reminder.value : this.reminder, + ); + } + + @override + String toString() { + return (StringBuffer('PeriodicReminder(') + ..write('id: $id, ') + ..write('frequency: $frequency, ') + ..write('reminder: $reminder') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, frequency, reminder); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.PeriodicReminder && + other.id == this.id && + other.frequency == this.frequency && + other.reminder == this.reminder); +} + +class PeriodicRemindersCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value frequency; + final i0.Value reminder; + const PeriodicRemindersCompanion({ + this.id = const i0.Value.absent(), + this.frequency = const i0.Value.absent(), + this.reminder = const i0.Value.absent(), + }); + PeriodicRemindersCompanion.insert({ + this.id = const i0.Value.absent(), + required Duration frequency, + required String reminder, + }) : frequency = i0.Value(frequency), + reminder = i0.Value(reminder); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? frequency, + i0.Expression? reminder, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (frequency != null) 'frequency': frequency, + if (reminder != null) 'reminder': reminder, + }); + } + + i1.PeriodicRemindersCompanion copyWith( + {i0.Value? id, + i0.Value? frequency, + i0.Value? reminder}) { + return i1.PeriodicRemindersCompanion( + id: id ?? this.id, + frequency: frequency ?? this.frequency, + reminder: reminder ?? this.reminder, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (frequency.present) { + map['frequency'] = + i0.Variable(frequency.value, const i2.DurationType()); + } + if (reminder.present) { + map['reminder'] = i0.Variable(reminder.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PeriodicRemindersCompanion(') + ..write('id: $id, ') + ..write('frequency: $frequency, ') + ..write('reminder: $reminder') + ..write(')')) + .toString(); + } +} diff --git a/docs/lib/snippets/modular/custom_types/table.dart b/docs/lib/snippets/modular/custom_types/table.dart new file mode 100644 index 000000000..807905712 --- /dev/null +++ b/docs/lib/snippets/modular/custom_types/table.dart @@ -0,0 +1,9 @@ +import 'package:drift/drift.dart'; +import 'type.dart'; + +class PeriodicReminders extends Table { + IntColumn get id => integer().autoIncrement()(); + Column get frequency => customType(const DurationType()) + .clientDefault(() => Duration(minutes: 15))(); + TextColumn get reminder => text()(); +} diff --git a/docs/lib/snippets/modular/custom_types/table.drift.dart b/docs/lib/snippets/modular/custom_types/table.drift.dart new file mode 100644 index 000000000..702c6f84f --- /dev/null +++ b/docs/lib/snippets/modular/custom_types/table.drift.dart @@ -0,0 +1,375 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/custom_types/table.drift.dart' + as i1; +import 'package:drift_docs/snippets/modular/custom_types/type.dart' as i2; +import 'package:drift_docs/snippets/modular/custom_types/table.dart' as i3; + +typedef $$PeriodicRemindersTableCreateCompanionBuilder + = i1.PeriodicRemindersCompanion Function({ + i0.Value id, + i0.Value frequency, + required String reminder, +}); +typedef $$PeriodicRemindersTableUpdateCompanionBuilder + = i1.PeriodicRemindersCompanion Function({ + i0.Value id, + i0.Value frequency, + i0.Value reminder, +}); + +class $$PeriodicRemindersTableFilterComposer + extends i0.Composer { + $$PeriodicRemindersTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get frequency => $composableBuilder( + column: $table.frequency, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get reminder => $composableBuilder( + column: $table.reminder, builder: (column) => i0.ColumnFilters(column)); +} + +class $$PeriodicRemindersTableOrderingComposer + extends i0.Composer { + $$PeriodicRemindersTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get frequency => $composableBuilder( + column: $table.frequency, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get reminder => $composableBuilder( + column: $table.reminder, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$PeriodicRemindersTableAnnotationComposer + extends i0.Composer { + $$PeriodicRemindersTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get frequency => + $composableBuilder(column: $table.frequency, builder: (column) => column); + + i0.GeneratedColumn get reminder => + $composableBuilder(column: $table.reminder, builder: (column) => column); +} + +class $$PeriodicRemindersTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$PeriodicRemindersTable, + i1.PeriodicReminder, + i1.$$PeriodicRemindersTableFilterComposer, + i1.$$PeriodicRemindersTableOrderingComposer, + i1.$$PeriodicRemindersTableAnnotationComposer, + $$PeriodicRemindersTableCreateCompanionBuilder, + $$PeriodicRemindersTableUpdateCompanionBuilder, + ( + i1.PeriodicReminder, + i0.BaseReferences + ), + i1.PeriodicReminder, + i0.PrefetchHooks Function()> { + $$PeriodicRemindersTableTableManager( + i0.GeneratedDatabase db, i1.$PeriodicRemindersTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$PeriodicRemindersTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$PeriodicRemindersTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$PeriodicRemindersTableAnnotationComposer( + $db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value frequency = const i0.Value.absent(), + i0.Value reminder = const i0.Value.absent(), + }) => + i1.PeriodicRemindersCompanion( + id: id, + frequency: frequency, + reminder: reminder, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value frequency = const i0.Value.absent(), + required String reminder, + }) => + i1.PeriodicRemindersCompanion.insert( + id: id, + frequency: frequency, + reminder: reminder, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$PeriodicRemindersTableProcessedTableManager + = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$PeriodicRemindersTable, + i1.PeriodicReminder, + i1.$$PeriodicRemindersTableFilterComposer, + i1.$$PeriodicRemindersTableOrderingComposer, + i1.$$PeriodicRemindersTableAnnotationComposer, + $$PeriodicRemindersTableCreateCompanionBuilder, + $$PeriodicRemindersTableUpdateCompanionBuilder, + ( + i1.PeriodicReminder, + i0.BaseReferences + ), + i1.PeriodicReminder, + i0.PrefetchHooks Function()>; + +class $PeriodicRemindersTable extends i3.PeriodicReminders + with i0.TableInfo<$PeriodicRemindersTable, i1.PeriodicReminder> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $PeriodicRemindersTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _frequencyMeta = + const i0.VerificationMeta('frequency'); + @override + late final i0.GeneratedColumn frequency = + i0.GeneratedColumn('frequency', aliasedName, false, + type: const i2.DurationType(), + requiredDuringInsert: false, + clientDefault: () => Duration(minutes: 15)); + static const i0.VerificationMeta _reminderMeta = + const i0.VerificationMeta('reminder'); + @override + late final i0.GeneratedColumn reminder = i0.GeneratedColumn( + 'reminder', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, frequency, reminder]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'periodic_reminders'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('frequency')) { + context.handle(_frequencyMeta, + frequency.isAcceptableOrUnknown(data['frequency']!, _frequencyMeta)); + } + if (data.containsKey('reminder')) { + context.handle(_reminderMeta, + reminder.isAcceptableOrUnknown(data['reminder']!, _reminderMeta)); + } else if (isInserting) { + context.missing(_reminderMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.PeriodicReminder map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.PeriodicReminder( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + frequency: attachedDatabase.typeMapping + .read(const i2.DurationType(), data['${effectivePrefix}frequency'])!, + reminder: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}reminder'])!, + ); + } + + @override + $PeriodicRemindersTable createAlias(String alias) { + return $PeriodicRemindersTable(attachedDatabase, alias); + } +} + +class PeriodicReminder extends i0.DataClass + implements i0.Insertable { + final int id; + final Duration frequency; + final String reminder; + const PeriodicReminder( + {required this.id, required this.frequency, required this.reminder}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['frequency'] = + i0.Variable(frequency, const i2.DurationType()); + map['reminder'] = i0.Variable(reminder); + return map; + } + + i1.PeriodicRemindersCompanion toCompanion(bool nullToAbsent) { + return i1.PeriodicRemindersCompanion( + id: i0.Value(id), + frequency: i0.Value(frequency), + reminder: i0.Value(reminder), + ); + } + + factory PeriodicReminder.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return PeriodicReminder( + id: serializer.fromJson(json['id']), + frequency: serializer.fromJson(json['frequency']), + reminder: serializer.fromJson(json['reminder']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'frequency': serializer.toJson(frequency), + 'reminder': serializer.toJson(reminder), + }; + } + + i1.PeriodicReminder copyWith( + {int? id, Duration? frequency, String? reminder}) => + i1.PeriodicReminder( + id: id ?? this.id, + frequency: frequency ?? this.frequency, + reminder: reminder ?? this.reminder, + ); + PeriodicReminder copyWithCompanion(i1.PeriodicRemindersCompanion data) { + return PeriodicReminder( + id: data.id.present ? data.id.value : this.id, + frequency: data.frequency.present ? data.frequency.value : this.frequency, + reminder: data.reminder.present ? data.reminder.value : this.reminder, + ); + } + + @override + String toString() { + return (StringBuffer('PeriodicReminder(') + ..write('id: $id, ') + ..write('frequency: $frequency, ') + ..write('reminder: $reminder') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, frequency, reminder); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.PeriodicReminder && + other.id == this.id && + other.frequency == this.frequency && + other.reminder == this.reminder); +} + +class PeriodicRemindersCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value frequency; + final i0.Value reminder; + const PeriodicRemindersCompanion({ + this.id = const i0.Value.absent(), + this.frequency = const i0.Value.absent(), + this.reminder = const i0.Value.absent(), + }); + PeriodicRemindersCompanion.insert({ + this.id = const i0.Value.absent(), + this.frequency = const i0.Value.absent(), + required String reminder, + }) : reminder = i0.Value(reminder); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? frequency, + i0.Expression? reminder, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (frequency != null) 'frequency': frequency, + if (reminder != null) 'reminder': reminder, + }); + } + + i1.PeriodicRemindersCompanion copyWith( + {i0.Value? id, + i0.Value? frequency, + i0.Value? reminder}) { + return i1.PeriodicRemindersCompanion( + id: id ?? this.id, + frequency: frequency ?? this.frequency, + reminder: reminder ?? this.reminder, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (frequency.present) { + map['frequency'] = + i0.Variable(frequency.value, const i2.DurationType()); + } + if (reminder.present) { + map['reminder'] = i0.Variable(reminder.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PeriodicRemindersCompanion(') + ..write('id: $id, ') + ..write('frequency: $frequency, ') + ..write('reminder: $reminder') + ..write(')')) + .toString(); + } +} diff --git a/docs/lib/snippets/modular/custom_types/type.dart b/docs/lib/snippets/modular/custom_types/type.dart new file mode 100644 index 000000000..a8b94eca6 --- /dev/null +++ b/docs/lib/snippets/modular/custom_types/type.dart @@ -0,0 +1,54 @@ +// #docregion duration +import 'package:drift/drift.dart'; + +class DurationType implements CustomSqlType { + const DurationType(); + + @override + String mapToSqlLiteral(Duration dartValue) { + return "interval '${dartValue.inMicroseconds} microseconds'"; + } + + @override + Object mapToSqlParameter(Duration dartValue) => dartValue; + + @override + Duration read(Object fromSql) => fromSql as Duration; + + @override + String sqlTypeName(GenerationContext context) => 'interval'; +} +// #enddocregion duration + +// #docregion fallback +class _FallbackDurationType implements CustomSqlType { + const _FallbackDurationType(); + + @override + String mapToSqlLiteral(Duration dartValue) { + return dartValue.inMicroseconds.toString(); + } + + @override + Object mapToSqlParameter(Duration dartValue) { + return dartValue.inMicroseconds; + } + + @override + Duration read(Object fromSql) { + return Duration(microseconds: fromSql as int); + } + + @override + String sqlTypeName(GenerationContext context) { + return 'integer'; + } +} +// #enddocregion fallback + +const durationType = DialectAwareSqlType.via( + fallback: _FallbackDurationType(), + overrides: { + SqlDialect.postgres: DurationType(), + }, +); diff --git a/docs/lib/snippets/modular/drift/dart_example.dart b/docs/lib/snippets/modular/drift/dart_example.dart new file mode 100644 index 000000000..98ec96f6f --- /dev/null +++ b/docs/lib/snippets/modular/drift/dart_example.dart @@ -0,0 +1,11 @@ +import 'example.drift.dart'; + +class DartExample extends ExampleDrift { + DartExample(super.attachedDatabase); + + // #docregion watchInCategory + Stream> watchInCategory(int category) { + return filterTodos((todos) => todos.category.equals(category)).watch(); + } + // #enddocregion watchInCategory +} diff --git a/docs/lib/snippets/modular/drift/example.drift b/docs/lib/snippets/modular/drift/example.drift new file mode 100644 index 000000000..299d86d78 --- /dev/null +++ b/docs/lib/snippets/modular/drift/example.drift @@ -0,0 +1,18 @@ +CREATE TABLE todos ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + category INTEGER REFERENCES categories(id) +); + +CREATE TABLE categories ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + description TEXT NOT NULL +) AS Category; + +-- #docregion filterTodos +filterTodos: SELECT * FROM todos WHERE $predicate; +-- #enddocregion filterTodos +-- #docregion getTodos +getTodos ($predicate = TRUE): SELECT * FROM todos WHERE $predicate; +-- #enddocregion getTodos diff --git a/docs/lib/snippets/modular/drift/example.drift.dart b/docs/lib/snippets/modular/drift/example.drift.dart new file mode 100644 index 000000000..0c9e7a692 --- /dev/null +++ b/docs/lib/snippets/modular/drift/example.drift.dart @@ -0,0 +1,864 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/drift/example.drift.dart' as i1; +import 'package:drift/internal/modular.dart' as i2; + +typedef $TodosCreateCompanionBuilder = i1.TodosCompanion Function({ + i0.Value id, + required String title, + required String content, + i0.Value category, +}); +typedef $TodosUpdateCompanionBuilder = i1.TodosCompanion Function({ + i0.Value id, + i0.Value title, + i0.Value content, + i0.Value category, +}); + +class $TodosFilterComposer extends i0.Composer { + $TodosFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get content => $composableBuilder( + column: $table.content, builder: (column) => i0.ColumnFilters(column)); + + i1.$CategoriesFilterComposer get category { + final i1.$CategoriesFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.category, + referencedTable: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$CategoriesFilterComposer( + $db: $db, + $table: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $TodosOrderingComposer + extends i0.Composer { + $TodosOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get content => $composableBuilder( + column: $table.content, builder: (column) => i0.ColumnOrderings(column)); + + i1.$CategoriesOrderingComposer get category { + final i1.$CategoriesOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.category, + referencedTable: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$CategoriesOrderingComposer( + $db: $db, + $table: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $TodosAnnotationComposer + extends i0.Composer { + $TodosAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + i0.GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); + + i1.$CategoriesAnnotationComposer get category { + final i1.$CategoriesAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.category, + referencedTable: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$CategoriesAnnotationComposer( + $db: $db, + $table: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $TodosTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.Todos, + i1.Todo, + i1.$TodosFilterComposer, + i1.$TodosOrderingComposer, + i1.$TodosAnnotationComposer, + $TodosCreateCompanionBuilder, + $TodosUpdateCompanionBuilder, + (i1.Todo, i0.BaseReferences), + i1.Todo, + i0.PrefetchHooks Function({bool category})> { + $TodosTableManager(i0.GeneratedDatabase db, i1.Todos table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$TodosFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$TodosOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$TodosAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value title = const i0.Value.absent(), + i0.Value content = const i0.Value.absent(), + i0.Value category = const i0.Value.absent(), + }) => + i1.TodosCompanion( + id: id, + title: title, + content: content, + category: category, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String title, + required String content, + i0.Value category = const i0.Value.absent(), + }) => + i1.TodosCompanion.insert( + id: id, + title: title, + content: content, + category: category, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $TodosProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.Todos, + i1.Todo, + i1.$TodosFilterComposer, + i1.$TodosOrderingComposer, + i1.$TodosAnnotationComposer, + $TodosCreateCompanionBuilder, + $TodosUpdateCompanionBuilder, + (i1.Todo, i0.BaseReferences), + i1.Todo, + i0.PrefetchHooks Function({bool category})>; +typedef $CategoriesCreateCompanionBuilder = i1.CategoriesCompanion Function({ + i0.Value id, + required String description, +}); +typedef $CategoriesUpdateCompanionBuilder = i1.CategoriesCompanion Function({ + i0.Value id, + i0.Value description, +}); + +class $CategoriesFilterComposer + extends i0.Composer { + $CategoriesFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnFilters(column)); + + i0.Expression todosRefs( + i0.Expression Function(i1.$TodosFilterComposer f) f) { + final i1.$TodosFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: + i2.ReadDatabaseContainer($db).resultSet('todos'), + getReferencedColumn: (t) => t.category, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$TodosFilterComposer( + $db: $db, + $table: + i2.ReadDatabaseContainer($db).resultSet('todos'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $CategoriesOrderingComposer + extends i0.Composer { + $CategoriesOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnOrderings(column)); +} + +class $CategoriesAnnotationComposer + extends i0.Composer { + $CategoriesAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get description => $composableBuilder( + column: $table.description, builder: (column) => column); + + i0.Expression todosRefs( + i0.Expression Function(i1.$TodosAnnotationComposer a) f) { + final i1.$TodosAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: + i2.ReadDatabaseContainer($db).resultSet('todos'), + getReferencedColumn: (t) => t.category, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$TodosAnnotationComposer( + $db: $db, + $table: + i2.ReadDatabaseContainer($db).resultSet('todos'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $CategoriesTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.Categories, + i1.Category, + i1.$CategoriesFilterComposer, + i1.$CategoriesOrderingComposer, + i1.$CategoriesAnnotationComposer, + $CategoriesCreateCompanionBuilder, + $CategoriesUpdateCompanionBuilder, + ( + i1.Category, + i0.BaseReferences + ), + i1.Category, + i0.PrefetchHooks Function({bool todosRefs})> { + $CategoriesTableManager(i0.GeneratedDatabase db, i1.Categories table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$CategoriesFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$CategoriesOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$CategoriesAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + }) => + i1.CategoriesCompanion( + id: id, + description: description, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String description, + }) => + i1.CategoriesCompanion.insert( + id: id, + description: description, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CategoriesProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.Categories, + i1.Category, + i1.$CategoriesFilterComposer, + i1.$CategoriesOrderingComposer, + i1.$CategoriesAnnotationComposer, + $CategoriesCreateCompanionBuilder, + $CategoriesUpdateCompanionBuilder, + ( + i1.Category, + i0.BaseReferences + ), + i1.Category, + i0.PrefetchHooks Function({bool todosRefs})>; + +class Todos extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Todos(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT'); + static const i0.VerificationMeta _titleMeta = + const i0.VerificationMeta('title'); + late final i0.GeneratedColumn title = i0.GeneratedColumn( + 'title', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + static const i0.VerificationMeta _contentMeta = + const i0.VerificationMeta('content'); + late final i0.GeneratedColumn content = i0.GeneratedColumn( + 'content', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + static const i0.VerificationMeta _categoryMeta = + const i0.VerificationMeta('category'); + late final i0.GeneratedColumn category = i0.GeneratedColumn( + 'category', aliasedName, true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'REFERENCES categories(id)'); + @override + List get $columns => [id, title, content, category]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'todos'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + } else if (isInserting) { + context.missing(_titleMeta); + } + if (data.containsKey('content')) { + context.handle(_contentMeta, + content.isAcceptableOrUnknown(data['content']!, _contentMeta)); + } else if (isInserting) { + context.missing(_contentMeta); + } + if (data.containsKey('category')) { + context.handle(_categoryMeta, + category.isAcceptableOrUnknown(data['category']!, _categoryMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.Todo map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.Todo( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}title'])!, + content: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}content'])!, + category: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}category']), + ); + } + + @override + Todos createAlias(String alias) { + return Todos(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class Todo extends i0.DataClass implements i0.Insertable { + final int id; + final String title; + final String content; + final int? category; + const Todo( + {required this.id, + required this.title, + required this.content, + this.category}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['title'] = i0.Variable(title); + map['content'] = i0.Variable(content); + if (!nullToAbsent || category != null) { + map['category'] = i0.Variable(category); + } + return map; + } + + i1.TodosCompanion toCompanion(bool nullToAbsent) { + return i1.TodosCompanion( + id: i0.Value(id), + title: i0.Value(title), + content: i0.Value(content), + category: category == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(category), + ); + } + + factory Todo.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Todo( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + content: serializer.fromJson(json['content']), + category: serializer.fromJson(json['category']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'content': serializer.toJson(content), + 'category': serializer.toJson(category), + }; + } + + i1.Todo copyWith( + {int? id, + String? title, + String? content, + i0.Value category = const i0.Value.absent()}) => + i1.Todo( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category.present ? category.value : this.category, + ); + Todo copyWithCompanion(i1.TodosCompanion data) { + return Todo( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + content: data.content.present ? data.content.value : this.content, + category: data.category.present ? data.category.value : this.category, + ); + } + + @override + String toString() { + return (StringBuffer('Todo(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, title, content, category); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.Todo && + other.id == this.id && + other.title == this.title && + other.content == this.content && + other.category == this.category); +} + +class TodosCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value title; + final i0.Value content; + final i0.Value category; + const TodosCompanion({ + this.id = const i0.Value.absent(), + this.title = const i0.Value.absent(), + this.content = const i0.Value.absent(), + this.category = const i0.Value.absent(), + }); + TodosCompanion.insert({ + this.id = const i0.Value.absent(), + required String title, + required String content, + this.category = const i0.Value.absent(), + }) : title = i0.Value(title), + content = i0.Value(content); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? title, + i0.Expression? content, + i0.Expression? category, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (content != null) 'content': content, + if (category != null) 'category': category, + }); + } + + i1.TodosCompanion copyWith( + {i0.Value? id, + i0.Value? title, + i0.Value? content, + i0.Value? category}) { + return i1.TodosCompanion( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category ?? this.category, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (title.present) { + map['title'] = i0.Variable(title.value); + } + if (content.present) { + map['content'] = i0.Variable(content.value); + } + if (category.present) { + map['category'] = i0.Variable(category.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TodosCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category') + ..write(')')) + .toString(); + } +} + +class Categories extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Categories(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT'); + static const i0.VerificationMeta _descriptionMeta = + const i0.VerificationMeta('description'); + late final i0.GeneratedColumn description = + i0.GeneratedColumn('description', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => [id, description]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'categories'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } else if (isInserting) { + context.missing(_descriptionMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.Category map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.Category( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + description: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}description'])!, + ); + } + + @override + Categories createAlias(String alias) { + return Categories(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class Category extends i0.DataClass implements i0.Insertable { + final int id; + final String description; + const Category({required this.id, required this.description}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['description'] = i0.Variable(description); + return map; + } + + i1.CategoriesCompanion toCompanion(bool nullToAbsent) { + return i1.CategoriesCompanion( + id: i0.Value(id), + description: i0.Value(description), + ); + } + + factory Category.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Category( + id: serializer.fromJson(json['id']), + description: serializer.fromJson(json['description']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'description': serializer.toJson(description), + }; + } + + i1.Category copyWith({int? id, String? description}) => i1.Category( + id: id ?? this.id, + description: description ?? this.description, + ); + Category copyWithCompanion(i1.CategoriesCompanion data) { + return Category( + id: data.id.present ? data.id.value : this.id, + description: + data.description.present ? data.description.value : this.description, + ); + } + + @override + String toString() { + return (StringBuffer('Category(') + ..write('id: $id, ') + ..write('description: $description') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, description); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.Category && + other.id == this.id && + other.description == this.description); +} + +class CategoriesCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value description; + const CategoriesCompanion({ + this.id = const i0.Value.absent(), + this.description = const i0.Value.absent(), + }); + CategoriesCompanion.insert({ + this.id = const i0.Value.absent(), + required String description, + }) : description = i0.Value(description); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? description, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (description != null) 'description': description, + }); + } + + i1.CategoriesCompanion copyWith( + {i0.Value? id, i0.Value? description}) { + return i1.CategoriesCompanion( + id: id ?? this.id, + description: description ?? this.description, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (description.present) { + map['description'] = i0.Variable(description.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CategoriesCompanion(') + ..write('id: $id, ') + ..write('description: $description') + ..write(')')) + .toString(); + } +} + +class ExampleDrift extends i2.ModularAccessor { + ExampleDrift(i0.GeneratedDatabase db) : super(db); + i0.Selectable filterTodos(FilterTodos$predicate predicate) { + var $arrayStartIndex = 1; + final generatedpredicate = + $write(predicate(this.todos), startIndex: $arrayStartIndex); + $arrayStartIndex += generatedpredicate.amountOfVariables; + return customSelect('SELECT * FROM todos WHERE ${generatedpredicate.sql}', + variables: [ + ...generatedpredicate.introducedVariables + ], + readsFrom: { + todos, + ...generatedpredicate.watchedTables, + }).asyncMap(todos.mapFromRow); + } + + i0.Selectable getTodos({GetTodos$predicate? predicate}) { + var $arrayStartIndex = 1; + final generatedpredicate = $write( + predicate?.call(this.todos) ?? const i0.CustomExpression('(TRUE)'), + startIndex: $arrayStartIndex); + $arrayStartIndex += generatedpredicate.amountOfVariables; + return customSelect('SELECT * FROM todos WHERE ${generatedpredicate.sql}', + variables: [ + ...generatedpredicate.introducedVariables + ], + readsFrom: { + todos, + ...generatedpredicate.watchedTables, + }).asyncMap(todos.mapFromRow); + } + + i1.Todos get todos => + i2.ReadDatabaseContainer(attachedDatabase).resultSet('todos'); +} + +typedef FilterTodos$predicate = i0.Expression Function(i1.Todos todos); +typedef GetTodos$predicate = i0.Expression Function(i1.Todos todos); diff --git a/docs/lib/snippets/modular/drift/row_class.dart b/docs/lib/snippets/modular/drift/row_class.dart new file mode 100644 index 000000000..e9452cca2 --- /dev/null +++ b/docs/lib/snippets/modular/drift/row_class.dart @@ -0,0 +1,17 @@ +// #docregion user +class User { + final int id; + final String name; + + User(this.id, this.name); +} +// #enddocregion user + +// #docregion userwithfriends +class UserWithFriends { + final User user; + final List friends; + + UserWithFriends(this.user, {this.friends = const []}); +} +// #enddocregion userwithfriends diff --git a/docs/lib/snippets/modular/drift/with_existing.drift b/docs/lib/snippets/modular/drift/with_existing.drift new file mode 100644 index 000000000..ed95d9428 --- /dev/null +++ b/docs/lib/snippets/modular/drift/with_existing.drift @@ -0,0 +1,22 @@ +-- #docregion users +import 'row_class.dart'; --import for where the row class is defined + +CREATE TABLE users ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL +) WITH User; -- This tells drift to use the existing Dart class +-- #enddocregion users + +-- #docregion friends +-- table to demonstrate a more complex select query below. +-- also, remember to add the import for `UserWithFriends` to your drift file. +CREATE TABLE friends ( + user_a INTEGER NOT NULL REFERENCES users(id), + user_b INTEGER NOT NULL REFERENCES users(id), + PRIMARY KEY (user_a, user_b) +); + +allFriendsOf WITH UserWithFriends: SELECT users.** AS user, LIST( + SELECT * FROM users a INNER JOIN friends ON user_a = a.id WHERE user_b = users.id OR user_a = users.id +) AS friends FROM users WHERE id = :id; +-- #enddocregion friends diff --git a/docs/lib/snippets/modular/drift/with_existing.drift.dart b/docs/lib/snippets/modular/drift/with_existing.drift.dart new file mode 100644 index 000000000..e3b9ec15e --- /dev/null +++ b/docs/lib/snippets/modular/drift/with_existing.drift.dart @@ -0,0 +1,704 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/drift/row_class.dart' as i1; +import 'package:drift_docs/snippets/modular/drift/with_existing.drift.dart' + as i2; +import 'package:drift/internal/modular.dart' as i3; + +typedef $UsersCreateCompanionBuilder = i2.UsersCompanion Function({ + i0.Value id, + required String name, +}); +typedef $UsersUpdateCompanionBuilder = i2.UsersCompanion Function({ + i0.Value id, + i0.Value name, +}); + +class $UsersFilterComposer extends i0.Composer { + $UsersFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => i0.ColumnFilters(column)); +} + +class $UsersOrderingComposer + extends i0.Composer { + $UsersOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => i0.ColumnOrderings(column)); +} + +class $UsersAnnotationComposer + extends i0.Composer { + $UsersAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); +} + +class $UsersTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.Users, + i1.User, + i2.$UsersFilterComposer, + i2.$UsersOrderingComposer, + i2.$UsersAnnotationComposer, + $UsersCreateCompanionBuilder, + $UsersUpdateCompanionBuilder, + (i1.User, i0.BaseReferences), + i1.User, + i0.PrefetchHooks Function()> { + $UsersTableManager(i0.GeneratedDatabase db, i2.Users table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i2.$UsersFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i2.$UsersOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i2.$UsersAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + }) => + i2.UsersCompanion( + id: id, + name: name, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String name, + }) => + i2.UsersCompanion.insert( + id: id, + name: name, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $UsersProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.Users, + i1.User, + i2.$UsersFilterComposer, + i2.$UsersOrderingComposer, + i2.$UsersAnnotationComposer, + $UsersCreateCompanionBuilder, + $UsersUpdateCompanionBuilder, + (i1.User, i0.BaseReferences), + i1.User, + i0.PrefetchHooks Function()>; +typedef $FriendsCreateCompanionBuilder = i2.FriendsCompanion Function({ + required int userA, + required int userB, + i0.Value rowid, +}); +typedef $FriendsUpdateCompanionBuilder = i2.FriendsCompanion Function({ + i0.Value userA, + i0.Value userB, + i0.Value rowid, +}); + +class $FriendsFilterComposer + extends i0.Composer { + $FriendsFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i2.$UsersFilterComposer get userA { + final i2.$UsersFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userA, + referencedTable: + i3.ReadDatabaseContainer($db).resultSet('users'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$UsersFilterComposer( + $db: $db, + $table: + i3.ReadDatabaseContainer($db).resultSet('users'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i2.$UsersFilterComposer get userB { + final i2.$UsersFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userB, + referencedTable: + i3.ReadDatabaseContainer($db).resultSet('users'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$UsersFilterComposer( + $db: $db, + $table: + i3.ReadDatabaseContainer($db).resultSet('users'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $FriendsOrderingComposer + extends i0.Composer { + $FriendsOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i2.$UsersOrderingComposer get userA { + final i2.$UsersOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userA, + referencedTable: + i3.ReadDatabaseContainer($db).resultSet('users'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$UsersOrderingComposer( + $db: $db, + $table: + i3.ReadDatabaseContainer($db).resultSet('users'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i2.$UsersOrderingComposer get userB { + final i2.$UsersOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userB, + referencedTable: + i3.ReadDatabaseContainer($db).resultSet('users'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$UsersOrderingComposer( + $db: $db, + $table: + i3.ReadDatabaseContainer($db).resultSet('users'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $FriendsAnnotationComposer + extends i0.Composer { + $FriendsAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i2.$UsersAnnotationComposer get userA { + final i2.$UsersAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userA, + referencedTable: + i3.ReadDatabaseContainer($db).resultSet('users'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$UsersAnnotationComposer( + $db: $db, + $table: + i3.ReadDatabaseContainer($db).resultSet('users'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i2.$UsersAnnotationComposer get userB { + final i2.$UsersAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userB, + referencedTable: + i3.ReadDatabaseContainer($db).resultSet('users'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$UsersAnnotationComposer( + $db: $db, + $table: + i3.ReadDatabaseContainer($db).resultSet('users'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $FriendsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.Friends, + i2.Friend, + i2.$FriendsFilterComposer, + i2.$FriendsOrderingComposer, + i2.$FriendsAnnotationComposer, + $FriendsCreateCompanionBuilder, + $FriendsUpdateCompanionBuilder, + (i2.Friend, i0.BaseReferences), + i2.Friend, + i0.PrefetchHooks Function({bool userA, bool userB})> { + $FriendsTableManager(i0.GeneratedDatabase db, i2.Friends table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i2.$FriendsFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i2.$FriendsOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i2.$FriendsAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value userA = const i0.Value.absent(), + i0.Value userB = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i2.FriendsCompanion( + userA: userA, + userB: userB, + rowid: rowid, + ), + createCompanionCallback: ({ + required int userA, + required int userB, + i0.Value rowid = const i0.Value.absent(), + }) => + i2.FriendsCompanion.insert( + userA: userA, + userB: userB, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $FriendsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.Friends, + i2.Friend, + i2.$FriendsFilterComposer, + i2.$FriendsOrderingComposer, + i2.$FriendsAnnotationComposer, + $FriendsCreateCompanionBuilder, + $FriendsUpdateCompanionBuilder, + (i2.Friend, i0.BaseReferences), + i2.Friend, + i0.PrefetchHooks Function({bool userA, bool userB})>; + +class Users extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Users(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL PRIMARY KEY'); + static const i0.VerificationMeta _nameMeta = + const i0.VerificationMeta('name'); + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'users'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.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 get $primaryKey => {id}; + @override + i1.User map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.User( + attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!, + ); + } + + @override + Users createAlias(String alias) { + return Users(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class UsersCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value name; + const UsersCompanion({ + this.id = const i0.Value.absent(), + this.name = const i0.Value.absent(), + }); + UsersCompanion.insert({ + this.id = const i0.Value.absent(), + required String name, + }) : name = i0.Value(name); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? name, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + }); + } + + i2.UsersCompanion copyWith({i0.Value? id, i0.Value? name}) { + return i2.UsersCompanion( + id: id ?? this.id, + name: name ?? this.name, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (name.present) { + map['name'] = i0.Variable(name.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UsersCompanion(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } +} + +class Friends extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Friends(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _userAMeta = + const i0.VerificationMeta('userA'); + late final i0.GeneratedColumn userA = i0.GeneratedColumn( + 'user_a', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES users(id)'); + static const i0.VerificationMeta _userBMeta = + const i0.VerificationMeta('userB'); + late final i0.GeneratedColumn userB = i0.GeneratedColumn( + 'user_b', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES users(id)'); + @override + List get $columns => [userA, userB]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'friends'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('user_a')) { + context.handle( + _userAMeta, userA.isAcceptableOrUnknown(data['user_a']!, _userAMeta)); + } else if (isInserting) { + context.missing(_userAMeta); + } + if (data.containsKey('user_b')) { + context.handle( + _userBMeta, userB.isAcceptableOrUnknown(data['user_b']!, _userBMeta)); + } else if (isInserting) { + context.missing(_userBMeta); + } + return context; + } + + @override + Set get $primaryKey => {userA, userB}; + @override + i2.Friend map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i2.Friend( + userA: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}user_a'])!, + userB: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}user_b'])!, + ); + } + + @override + Friends createAlias(String alias) { + return Friends(attachedDatabase, alias); + } + + @override + List get customConstraints => const ['PRIMARY KEY(user_a, user_b)']; + @override + bool get dontWriteConstraints => true; +} + +class Friend extends i0.DataClass implements i0.Insertable { + final int userA; + final int userB; + const Friend({required this.userA, required this.userB}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_a'] = i0.Variable(userA); + map['user_b'] = i0.Variable(userB); + return map; + } + + i2.FriendsCompanion toCompanion(bool nullToAbsent) { + return i2.FriendsCompanion( + userA: i0.Value(userA), + userB: i0.Value(userB), + ); + } + + factory Friend.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Friend( + userA: serializer.fromJson(json['user_a']), + userB: serializer.fromJson(json['user_b']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'user_a': serializer.toJson(userA), + 'user_b': serializer.toJson(userB), + }; + } + + i2.Friend copyWith({int? userA, int? userB}) => i2.Friend( + userA: userA ?? this.userA, + userB: userB ?? this.userB, + ); + Friend copyWithCompanion(i2.FriendsCompanion data) { + return Friend( + userA: data.userA.present ? data.userA.value : this.userA, + userB: data.userB.present ? data.userB.value : this.userB, + ); + } + + @override + String toString() { + return (StringBuffer('Friend(') + ..write('userA: $userA, ') + ..write('userB: $userB') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userA, userB); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i2.Friend && + other.userA == this.userA && + other.userB == this.userB); +} + +class FriendsCompanion extends i0.UpdateCompanion { + final i0.Value userA; + final i0.Value userB; + final i0.Value rowid; + const FriendsCompanion({ + this.userA = const i0.Value.absent(), + this.userB = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + FriendsCompanion.insert({ + required int userA, + required int userB, + this.rowid = const i0.Value.absent(), + }) : userA = i0.Value(userA), + userB = i0.Value(userB); + static i0.Insertable custom({ + i0.Expression? userA, + i0.Expression? userB, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (userA != null) 'user_a': userA, + if (userB != null) 'user_b': userB, + if (rowid != null) 'rowid': rowid, + }); + } + + i2.FriendsCompanion copyWith( + {i0.Value? userA, i0.Value? userB, i0.Value? rowid}) { + return i2.FriendsCompanion( + userA: userA ?? this.userA, + userB: userB ?? this.userB, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userA.present) { + map['user_a'] = i0.Variable(userA.value); + } + if (userB.present) { + map['user_b'] = i0.Variable(userB.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('FriendsCompanion(') + ..write('userA: $userA, ') + ..write('userB: $userB, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class WithExistingDrift extends i3.ModularAccessor { + WithExistingDrift(i0.GeneratedDatabase db) : super(db); + i0.Selectable allFriendsOf(int id) { + return customSelect( + 'SELECT"users"."id" AS "nested_0.id", "users"."name" AS "nested_0.name", users.id AS "\$n_0", users.id AS "\$n_1" FROM users WHERE id = ?1', + variables: [ + i0.Variable(id) + ], + readsFrom: { + users, + friends, + }).asyncMap((i0.QueryRow row) async => i1.UserWithFriends( + await users.mapFromRow(row, tablePrefix: 'nested_0'), + friends: await customSelect( + 'SELECT * FROM users AS a INNER JOIN friends ON user_a = a.id WHERE user_b = ?1 OR user_a = ?2', + variables: [ + i0.Variable(row.read('\$n_0')), + i0.Variable(row.read('\$n_1')) + ], + readsFrom: { + users, + friends, + }) + .map((i0.QueryRow row) => i1.User( + row.read('id'), + row.read('name'), + )) + .get(), + )); + } + + i2.Users get users => + i3.ReadDatabaseContainer(attachedDatabase).resultSet('users'); + i2.Friends get friends => i3.ReadDatabaseContainer(attachedDatabase) + .resultSet('friends'); +} diff --git a/docs/lib/snippets/modular/many_to_many/json.dart b/docs/lib/snippets/modular/many_to_many/json.dart new file mode 100644 index 000000000..af15e3ebd --- /dev/null +++ b/docs/lib/snippets/modular/many_to_many/json.dart @@ -0,0 +1,139 @@ +import 'package:drift/drift.dart'; +import 'package:drift/extensions/json1.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import 'json.drift.dart'; +import 'shared.dart' show BuyableItems; +import 'shared.drift.dart'; + +part 'json.g.dart'; + +typedef ShoppingCartWithItems = ({ + ShoppingCart cart, + List items, +}); + +// #docregion tables +@DataClassName('ShoppingCart') +class ShoppingCarts extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get entries => text().map(ShoppingCartEntries.converter)(); + + // we could also store some further information about the user creating + // this cart etc. +} + +@JsonSerializable() +class ShoppingCartEntries { + final List items; + + ShoppingCartEntries({required this.items}); + + factory ShoppingCartEntries.fromJson(Map json) => + _$ShoppingCartEntriesFromJson(json); + + Map toJson() { + return _$ShoppingCartEntriesToJson(this); + } + + static JsonTypeConverter converter = + TypeConverter.json( + fromJson: (json) => + ShoppingCartEntries.fromJson(json as Map), + toJson: (entries) => entries.toJson(), + ); +} + +// #enddocregion tables + +@DriftDatabase(tables: [BuyableItems, ShoppingCarts]) +class JsonBasedDatabase extends $JsonBasedDatabase { + JsonBasedDatabase(super.e); + + @override + int get schemaVersion => 1; + + // #docregion createEmptyCart + Future createEmptyCart() async { + final cart = await into(shoppingCarts) + .insertReturning(const ShoppingCartsCompanion()); + + // we set the items property to [] because we've just created the cart - it + // will be empty + return (cart: cart, items: []); + } + // #enddocregion createEmptyCart + + // #docregion updateCart + Future updateCart(ShoppingCartWithItems entry) async { + await update(shoppingCarts).replace(entry.cart.copyWith( + entries: ShoppingCartEntries(items: [ + for (final item in entry.items) item.id, + ]))); + } + // #enddocregion updateCart + + // #docregion watchCart + Stream watchCart(int id) { + final referencedItems = shoppingCarts.entries.jsonEach(this, r'#$.items'); + + final cartWithEntries = select(shoppingCarts).join( + [ + // Join every referenced item from the json array + innerJoin(referencedItems, const Constant(true), useColumns: false), + // And use that to join the items + innerJoin( + buyableItems, + buyableItems.id.equalsExp(referencedItems.value.cast()), + ), + ], + )..where(shoppingCarts.id.equals(id)); + + return cartWithEntries.watch().map((rows) { + late ShoppingCart cart; + final entries = []; + + for (final row in rows) { + cart = row.readTable(shoppingCarts); + entries.add(row.readTable(buyableItems)); + } + + return (cart: cart, items: entries); + }); + } + // #enddocregion watchCart + + // #docregion watchAllCarts + Stream> watchAllCarts() { + final referencedItems = shoppingCarts.entries.jsonEach(this, r'#$.items'); + + final cartWithEntries = select(shoppingCarts).join( + [ + // Join every referenced item from the json array + innerJoin(referencedItems, const Constant(true), useColumns: false), + // And use that to join the items + innerJoin( + buyableItems, + buyableItems.id.equalsExp(referencedItems.value.cast()), + ), + ], + ); + + return cartWithEntries.watch().map((rows) { + final entriesByCart = >{}; + + for (final row in rows) { + final cart = row.readTable(shoppingCarts); + final item = row.readTable(buyableItems); + + entriesByCart.putIfAbsent(cart, () => []).add(item); + } + + return [ + for (final entry in entriesByCart.entries) + (cart: entry.key, items: entry.value) + ]; + }); + } + // #enddocregion watchAllCarts +} diff --git a/docs/lib/snippets/modular/many_to_many/json.drift.dart b/docs/lib/snippets/modular/many_to_many/json.drift.dart new file mode 100644 index 000000000..9102bb287 --- /dev/null +++ b/docs/lib/snippets/modular/many_to_many/json.drift.dart @@ -0,0 +1,351 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/many_to_many/shared.drift.dart' + as i1; +import 'package:drift_docs/snippets/modular/many_to_many/json.drift.dart' as i2; +import 'package:drift_docs/snippets/modular/many_to_many/json.dart' as i3; + +typedef $$ShoppingCartsTableCreateCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, + required i3.ShoppingCartEntries entries, +}); +typedef $$ShoppingCartsTableUpdateCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, + i0.Value entries, +}); + +class $$ShoppingCartsTableFilterComposer + extends i0.Composer { + $$ShoppingCartsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnWithTypeConverterFilters + get entries => $composableBuilder( + column: $table.entries, + builder: (column) => i0.ColumnWithTypeConverterFilters(column)); +} + +class $$ShoppingCartsTableOrderingComposer + extends i0.Composer { + $$ShoppingCartsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get entries => $composableBuilder( + column: $table.entries, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$ShoppingCartsTableAnnotationComposer + extends i0.Composer { + $$ShoppingCartsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter + get entries => $composableBuilder( + column: $table.entries, builder: (column) => column); +} + +class $$ShoppingCartsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + i2.$$ShoppingCartsTableFilterComposer, + i2.$$ShoppingCartsTableOrderingComposer, + i2.$$ShoppingCartsTableAnnotationComposer, + $$ShoppingCartsTableCreateCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder, + ( + i2.ShoppingCart, + i0.BaseReferences + ), + i2.ShoppingCart, + i0.PrefetchHooks Function()> { + $$ShoppingCartsTableTableManager( + i0.GeneratedDatabase db, i2.$ShoppingCartsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i2.$$ShoppingCartsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i2.$$ShoppingCartsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i2.$$ShoppingCartsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value entries = const i0.Value.absent(), + }) => + i2.ShoppingCartsCompanion( + id: id, + entries: entries, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required i3.ShoppingCartEntries entries, + }) => + i2.ShoppingCartsCompanion.insert( + id: id, + entries: entries, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$ShoppingCartsTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + i2.$$ShoppingCartsTableFilterComposer, + i2.$$ShoppingCartsTableOrderingComposer, + i2.$$ShoppingCartsTableAnnotationComposer, + $$ShoppingCartsTableCreateCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder, + ( + i2.ShoppingCart, + i0.BaseReferences + ), + i2.ShoppingCart, + i0.PrefetchHooks Function()>; + +abstract class $JsonBasedDatabase extends i0.GeneratedDatabase { + $JsonBasedDatabase(i0.QueryExecutor e) : super(e); + $JsonBasedDatabaseManager get managers => $JsonBasedDatabaseManager(this); + late final i1.$BuyableItemsTable buyableItems = i1.$BuyableItemsTable(this); + late final i2.$ShoppingCartsTable shoppingCarts = + i2.$ShoppingCartsTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => + [buyableItems, shoppingCarts]; +} + +class $JsonBasedDatabaseManager { + final $JsonBasedDatabase _db; + $JsonBasedDatabaseManager(this._db); + i1.$$BuyableItemsTableTableManager get buyableItems => + i1.$$BuyableItemsTableTableManager(_db, _db.buyableItems); + i2.$$ShoppingCartsTableTableManager get shoppingCarts => + i2.$$ShoppingCartsTableTableManager(_db, _db.shoppingCarts); +} + +class $ShoppingCartsTable extends i3.ShoppingCarts + with i0.TableInfo<$ShoppingCartsTable, i2.ShoppingCart> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $ShoppingCartsTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _entriesMeta = + const i0.VerificationMeta('entries'); + @override + late final i0.GeneratedColumnWithTypeConverter + entries = i0.GeneratedColumn('entries', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true) + .withConverter( + i2.$ShoppingCartsTable.$converterentries); + @override + List get $columns => [id, entries]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'shopping_carts'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + context.handle(_entriesMeta, const i0.VerificationResult.success()); + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i2.ShoppingCart map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i2.ShoppingCart( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + entries: i2.$ShoppingCartsTable.$converterentries.fromSql(attachedDatabase + .typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}entries'])!), + ); + } + + @override + $ShoppingCartsTable createAlias(String alias) { + return $ShoppingCartsTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 + $converterentries = i3.ShoppingCartEntries.converter; +} + +class ShoppingCart extends i0.DataClass + implements i0.Insertable { + final int id; + final i3.ShoppingCartEntries entries; + const ShoppingCart({required this.id, required this.entries}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + { + map['entries'] = i0.Variable( + i2.$ShoppingCartsTable.$converterentries.toSql(entries)); + } + return map; + } + + i2.ShoppingCartsCompanion toCompanion(bool nullToAbsent) { + return i2.ShoppingCartsCompanion( + id: i0.Value(id), + entries: i0.Value(entries), + ); + } + + factory ShoppingCart.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return ShoppingCart( + id: serializer.fromJson(json['id']), + entries: i2.$ShoppingCartsTable.$converterentries + .fromJson(serializer.fromJson(json['entries'])), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'entries': serializer.toJson( + i2.$ShoppingCartsTable.$converterentries.toJson(entries)), + }; + } + + i2.ShoppingCart copyWith({int? id, i3.ShoppingCartEntries? entries}) => + i2.ShoppingCart( + id: id ?? this.id, + entries: entries ?? this.entries, + ); + ShoppingCart copyWithCompanion(i2.ShoppingCartsCompanion data) { + return ShoppingCart( + id: data.id.present ? data.id.value : this.id, + entries: data.entries.present ? data.entries.value : this.entries, + ); + } + + @override + String toString() { + return (StringBuffer('ShoppingCart(') + ..write('id: $id, ') + ..write('entries: $entries') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, entries); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i2.ShoppingCart && + other.id == this.id && + other.entries == this.entries); +} + +class ShoppingCartsCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value entries; + const ShoppingCartsCompanion({ + this.id = const i0.Value.absent(), + this.entries = const i0.Value.absent(), + }); + ShoppingCartsCompanion.insert({ + this.id = const i0.Value.absent(), + required i3.ShoppingCartEntries entries, + }) : entries = i0.Value(entries); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? entries, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (entries != null) 'entries': entries, + }); + } + + i2.ShoppingCartsCompanion copyWith( + {i0.Value? id, i0.Value? entries}) { + return i2.ShoppingCartsCompanion( + id: id ?? this.id, + entries: entries ?? this.entries, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (entries.present) { + map['entries'] = i0.Variable( + i2.$ShoppingCartsTable.$converterentries.toSql(entries.value)); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ShoppingCartsCompanion(') + ..write('id: $id, ') + ..write('entries: $entries') + ..write(')')) + .toString(); + } +} diff --git a/docs/lib/snippets/modular/many_to_many/relational.dart b/docs/lib/snippets/modular/many_to_many/relational.dart new file mode 100644 index 000000000..186358a9e --- /dev/null +++ b/docs/lib/snippets/modular/many_to_many/relational.dart @@ -0,0 +1,148 @@ +import 'package:drift/drift.dart'; +import 'package:rxdart/rxdart.dart'; + +import 'shared.dart' show BuyableItems; +import 'shared.drift.dart'; + +import 'relational.drift.dart'; + +typedef ShoppingCartWithItems = ({ + ShoppingCart cart, + List items, +}); + +// #docregion cart_tables +class ShoppingCarts extends Table { + IntColumn get id => integer().autoIncrement()(); + // we could also store some further information about the user creating + // this cart etc. +} + +@DataClassName('ShoppingCartEntry') +class ShoppingCartEntries extends Table { + // id of the cart that should contain this item. + IntColumn get shoppingCart => integer().references(ShoppingCarts, #id)(); + // id of the item in this cart + IntColumn get item => integer().references(BuyableItems, #id)(); + // again, we could store additional information like when the item was + // added, an amount, etc. +} +// #enddocregion cart_tables + +@DriftDatabase(tables: [BuyableItems, ShoppingCarts, ShoppingCartEntries]) +class RelationalDatabase extends $RelationalDatabase { + RelationalDatabase(super.e); + + @override + int get schemaVersion => 1; + + // #docregion updateCart + Future updateCart(ShoppingCartWithItems entry) { + return transaction(() async { + final cart = entry.cart; + + // first, we write the shopping cart + await update(shoppingCarts).replace(cart); + + // we replace the entries of the cart, so first delete the old ones + await (delete(shoppingCartEntries) + ..where((entry) => entry.shoppingCart.equals(cart.id))) + .go(); + + // And write the new ones + for (final item in entry.items) { + await into(shoppingCartEntries) + .insert(ShoppingCartEntry(shoppingCart: cart.id, item: item.id)); + } + }); + } + // #enddocregion updateCart + + // #docregion createEmptyCart + Future createEmptyCart() async { + final cart = await into(shoppingCarts) + .insertReturning(const ShoppingCartsCompanion()); + // we set the items property to [] because we've just created the cart - it + // will be empty + return (cart: cart, items: []); + } + // #enddocregion createEmptyCart + + // #docregion watchCart + Stream watchCart(int id) { + // load information about the cart + final cartQuery = select(shoppingCarts) + ..where((cart) => cart.id.equals(id)); + + // and also load information about the entries in this cart + final contentQuery = select(shoppingCartEntries).join( + [ + innerJoin( + buyableItems, + buyableItems.id.equalsExp(shoppingCartEntries.item), + ), + ], + )..where(shoppingCartEntries.shoppingCart.equals(id)); + + final cartStream = cartQuery.watchSingle(); + + final contentStream = contentQuery.watch().map((rows) { + // we join the shoppingCartEntries with the buyableItems, but we + // only care about the item here. + return rows.map((row) => row.readTable(buyableItems)).toList(); + }); + + // now, we can merge the two queries together in one stream + return Rx.combineLatest2(cartStream, contentStream, + (ShoppingCart cart, List items) { + return (cart: cart, items: items); + }); + } + // #enddocregion watchCart + + // #docregion watchAllCarts + Stream> watchAllCarts() { + // start by watching all carts + final cartStream = select(shoppingCarts).watch(); + + return cartStream.switchMap((carts) { + // this method is called whenever the list of carts changes. For each + // cart, now we want to load all the items in it. + // (we create a map from id to cart here just for performance reasons) + final idToCart = {for (var cart in carts) cart.id: cart}; + final ids = idToCart.keys; + + // select all entries that are included in any cart that we found + final entryQuery = select(shoppingCartEntries).join( + [ + innerJoin( + buyableItems, + buyableItems.id.equalsExp(shoppingCartEntries.item), + ) + ], + )..where(shoppingCartEntries.shoppingCart.isIn(ids)); + + return entryQuery.watch().map((rows) { + // Store the list of entries for each cart, again using maps for faster + // lookups. + final idToItems = >{}; + + // for each entry (row) that is included in a cart, put it in the map + // of items. + for (final row in rows) { + final item = row.readTable(buyableItems); + final id = row.readTable(shoppingCartEntries).shoppingCart; + + idToItems.putIfAbsent(id, () => []).add(item); + } + + // finally, all that's left is to merge the map of carts with the map of + // entries + return [ + for (var id in ids) (cart: idToCart[id]!, items: idToItems[id] ?? []), + ]; + }); + }); + } + // #enddocregion watchAllCarts +} diff --git a/docs/lib/snippets/modular/many_to_many/relational.drift.dart b/docs/lib/snippets/modular/many_to_many/relational.drift.dart new file mode 100644 index 000000000..812d100f2 --- /dev/null +++ b/docs/lib/snippets/modular/many_to_many/relational.drift.dart @@ -0,0 +1,807 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/many_to_many/shared.drift.dart' + as i1; +import 'package:drift_docs/snippets/modular/many_to_many/relational.drift.dart' + as i2; +import 'package:drift_docs/snippets/modular/many_to_many/relational.dart' as i3; +import 'package:drift/internal/modular.dart' as i4; + +typedef $$ShoppingCartsTableCreateCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, +}); +typedef $$ShoppingCartsTableUpdateCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, +}); + +class $$ShoppingCartsTableFilterComposer + extends i0.Composer { + $$ShoppingCartsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.Expression shoppingCartEntriesRefs( + i0.Expression Function( + i2.$$ShoppingCartEntriesTableFilterComposer f) + f) { + final i2.$$ShoppingCartEntriesTableFilterComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet( + 'shopping_cart_entries'), + getReferencedColumn: (t) => t.shoppingCart, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$$ShoppingCartEntriesTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet( + 'shopping_cart_entries'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $$ShoppingCartsTableOrderingComposer + extends i0.Composer { + $$ShoppingCartsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$ShoppingCartsTableAnnotationComposer + extends i0.Composer { + $$ShoppingCartsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.Expression shoppingCartEntriesRefs( + i0.Expression Function( + i2.$$ShoppingCartEntriesTableAnnotationComposer a) + f) { + final i2.$$ShoppingCartEntriesTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet( + 'shopping_cart_entries'), + getReferencedColumn: (t) => t.shoppingCart, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$$ShoppingCartEntriesTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet( + 'shopping_cart_entries'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $$ShoppingCartsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + i2.$$ShoppingCartsTableFilterComposer, + i2.$$ShoppingCartsTableOrderingComposer, + i2.$$ShoppingCartsTableAnnotationComposer, + $$ShoppingCartsTableCreateCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder, + ( + i2.ShoppingCart, + i0.BaseReferences + ), + i2.ShoppingCart, + i0.PrefetchHooks Function({bool shoppingCartEntriesRefs})> { + $$ShoppingCartsTableTableManager( + i0.GeneratedDatabase db, i2.$ShoppingCartsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i2.$$ShoppingCartsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i2.$$ShoppingCartsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i2.$$ShoppingCartsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + }) => + i2.ShoppingCartsCompanion( + id: id, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + }) => + i2.ShoppingCartsCompanion.insert( + id: id, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$ShoppingCartsTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + i2.$$ShoppingCartsTableFilterComposer, + i2.$$ShoppingCartsTableOrderingComposer, + i2.$$ShoppingCartsTableAnnotationComposer, + $$ShoppingCartsTableCreateCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder, + ( + i2.ShoppingCart, + i0.BaseReferences + ), + i2.ShoppingCart, + i0.PrefetchHooks Function({bool shoppingCartEntriesRefs})>; +typedef $$ShoppingCartEntriesTableCreateCompanionBuilder + = i2.ShoppingCartEntriesCompanion Function({ + required int shoppingCart, + required int item, + i0.Value rowid, +}); +typedef $$ShoppingCartEntriesTableUpdateCompanionBuilder + = i2.ShoppingCartEntriesCompanion Function({ + i0.Value shoppingCart, + i0.Value item, + i0.Value rowid, +}); + +class $$ShoppingCartEntriesTableFilterComposer + extends i0.Composer { + $$ShoppingCartEntriesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i2.$$ShoppingCartsTableFilterComposer get shoppingCart { + final i2.$$ShoppingCartsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.shoppingCart, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$$ShoppingCartsTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i1.$$BuyableItemsTableFilterComposer get item { + final i1.$$BuyableItemsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.item, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$BuyableItemsTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$ShoppingCartEntriesTableOrderingComposer + extends i0.Composer { + $$ShoppingCartEntriesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i2.$$ShoppingCartsTableOrderingComposer get shoppingCart { + final i2.$$ShoppingCartsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.shoppingCart, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$$ShoppingCartsTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i1.$$BuyableItemsTableOrderingComposer get item { + final i1.$$BuyableItemsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.item, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$BuyableItemsTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$ShoppingCartEntriesTableAnnotationComposer + extends i0.Composer { + $$ShoppingCartEntriesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i2.$$ShoppingCartsTableAnnotationComposer get shoppingCart { + final i2.$$ShoppingCartsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.shoppingCart, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i2.$$ShoppingCartsTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + i1.$$BuyableItemsTableAnnotationComposer get item { + final i1.$$BuyableItemsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.item, + referencedTable: i4.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + i1.$$BuyableItemsTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$ShoppingCartEntriesTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartEntriesTable, + i2.ShoppingCartEntry, + i2.$$ShoppingCartEntriesTableFilterComposer, + i2.$$ShoppingCartEntriesTableOrderingComposer, + i2.$$ShoppingCartEntriesTableAnnotationComposer, + $$ShoppingCartEntriesTableCreateCompanionBuilder, + $$ShoppingCartEntriesTableUpdateCompanionBuilder, + ( + i2.ShoppingCartEntry, + i0.BaseReferences + ), + i2.ShoppingCartEntry, + i0.PrefetchHooks Function({bool shoppingCart, bool item})> { + $$ShoppingCartEntriesTableTableManager( + i0.GeneratedDatabase db, i2.$ShoppingCartEntriesTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => i2 + .$$ShoppingCartEntriesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i2.$$ShoppingCartEntriesTableOrderingComposer( + $db: db, $table: table), + createComputedFieldComposer: () => + i2.$$ShoppingCartEntriesTableAnnotationComposer( + $db: db, $table: table), + updateCompanionCallback: ({ + i0.Value shoppingCart = const i0.Value.absent(), + i0.Value item = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i2.ShoppingCartEntriesCompanion( + shoppingCart: shoppingCart, + item: item, + rowid: rowid, + ), + createCompanionCallback: ({ + required int shoppingCart, + required int item, + i0.Value rowid = const i0.Value.absent(), + }) => + i2.ShoppingCartEntriesCompanion.insert( + shoppingCart: shoppingCart, + item: item, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$ShoppingCartEntriesTableProcessedTableManager + = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartEntriesTable, + i2.ShoppingCartEntry, + i2.$$ShoppingCartEntriesTableFilterComposer, + i2.$$ShoppingCartEntriesTableOrderingComposer, + i2.$$ShoppingCartEntriesTableAnnotationComposer, + $$ShoppingCartEntriesTableCreateCompanionBuilder, + $$ShoppingCartEntriesTableUpdateCompanionBuilder, + ( + i2.ShoppingCartEntry, + i0.BaseReferences + ), + i2.ShoppingCartEntry, + i0.PrefetchHooks Function({bool shoppingCart, bool item})>; + +abstract class $RelationalDatabase extends i0.GeneratedDatabase { + $RelationalDatabase(i0.QueryExecutor e) : super(e); + $RelationalDatabaseManager get managers => $RelationalDatabaseManager(this); + late final i1.$BuyableItemsTable buyableItems = i1.$BuyableItemsTable(this); + late final i2.$ShoppingCartsTable shoppingCarts = + i2.$ShoppingCartsTable(this); + late final i2.$ShoppingCartEntriesTable shoppingCartEntries = + i2.$ShoppingCartEntriesTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => + [buyableItems, shoppingCarts, shoppingCartEntries]; +} + +class $RelationalDatabaseManager { + final $RelationalDatabase _db; + $RelationalDatabaseManager(this._db); + i1.$$BuyableItemsTableTableManager get buyableItems => + i1.$$BuyableItemsTableTableManager(_db, _db.buyableItems); + i2.$$ShoppingCartsTableTableManager get shoppingCarts => + i2.$$ShoppingCartsTableTableManager(_db, _db.shoppingCarts); + i2.$$ShoppingCartEntriesTableTableManager get shoppingCartEntries => + i2.$$ShoppingCartEntriesTableTableManager(_db, _db.shoppingCartEntries); +} + +class $ShoppingCartsTable extends i3.ShoppingCarts + with i0.TableInfo<$ShoppingCartsTable, i2.ShoppingCart> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $ShoppingCartsTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + @override + List get $columns => [id]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'shopping_carts'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i2.ShoppingCart map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i2.ShoppingCart( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + ); + } + + @override + $ShoppingCartsTable createAlias(String alias) { + return $ShoppingCartsTable(attachedDatabase, alias); + } +} + +class ShoppingCart extends i0.DataClass + implements i0.Insertable { + final int id; + const ShoppingCart({required this.id}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + return map; + } + + i2.ShoppingCartsCompanion toCompanion(bool nullToAbsent) { + return i2.ShoppingCartsCompanion( + id: i0.Value(id), + ); + } + + factory ShoppingCart.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return ShoppingCart( + id: serializer.fromJson(json['id']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + }; + } + + i2.ShoppingCart copyWith({int? id}) => i2.ShoppingCart( + id: id ?? this.id, + ); + ShoppingCart copyWithCompanion(i2.ShoppingCartsCompanion data) { + return ShoppingCart( + id: data.id.present ? data.id.value : this.id, + ); + } + + @override + String toString() { + return (StringBuffer('ShoppingCart(') + ..write('id: $id') + ..write(')')) + .toString(); + } + + @override + int get hashCode => id.hashCode; + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i2.ShoppingCart && other.id == this.id); +} + +class ShoppingCartsCompanion extends i0.UpdateCompanion { + final i0.Value id; + const ShoppingCartsCompanion({ + this.id = const i0.Value.absent(), + }); + ShoppingCartsCompanion.insert({ + this.id = const i0.Value.absent(), + }); + static i0.Insertable custom({ + i0.Expression? id, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + }); + } + + i2.ShoppingCartsCompanion copyWith({i0.Value? id}) { + return i2.ShoppingCartsCompanion( + id: id ?? this.id, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ShoppingCartsCompanion(') + ..write('id: $id') + ..write(')')) + .toString(); + } +} + +class $ShoppingCartEntriesTable extends i3.ShoppingCartEntries + with i0.TableInfo<$ShoppingCartEntriesTable, i2.ShoppingCartEntry> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $ShoppingCartEntriesTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _shoppingCartMeta = + const i0.VerificationMeta('shoppingCart'); + @override + late final i0.GeneratedColumn shoppingCart = i0.GeneratedColumn( + 'shopping_cart', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES shopping_carts (id)')); + static const i0.VerificationMeta _itemMeta = + const i0.VerificationMeta('item'); + @override + late final i0.GeneratedColumn item = i0.GeneratedColumn( + 'item', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES buyable_items (id)')); + @override + List get $columns => [shoppingCart, item]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'shopping_cart_entries'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('shopping_cart')) { + context.handle( + _shoppingCartMeta, + shoppingCart.isAcceptableOrUnknown( + data['shopping_cart']!, _shoppingCartMeta)); + } else if (isInserting) { + context.missing(_shoppingCartMeta); + } + if (data.containsKey('item')) { + context.handle( + _itemMeta, item.isAcceptableOrUnknown(data['item']!, _itemMeta)); + } else if (isInserting) { + context.missing(_itemMeta); + } + return context; + } + + @override + Set get $primaryKey => const {}; + @override + i2.ShoppingCartEntry map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i2.ShoppingCartEntry( + shoppingCart: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}shopping_cart'])!, + item: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}item'])!, + ); + } + + @override + $ShoppingCartEntriesTable createAlias(String alias) { + return $ShoppingCartEntriesTable(attachedDatabase, alias); + } +} + +class ShoppingCartEntry extends i0.DataClass + implements i0.Insertable { + final int shoppingCart; + final int item; + const ShoppingCartEntry({required this.shoppingCart, required this.item}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shopping_cart'] = i0.Variable(shoppingCart); + map['item'] = i0.Variable(item); + return map; + } + + i2.ShoppingCartEntriesCompanion toCompanion(bool nullToAbsent) { + return i2.ShoppingCartEntriesCompanion( + shoppingCart: i0.Value(shoppingCart), + item: i0.Value(item), + ); + } + + factory ShoppingCartEntry.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return ShoppingCartEntry( + shoppingCart: serializer.fromJson(json['shoppingCart']), + item: serializer.fromJson(json['item']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'shoppingCart': serializer.toJson(shoppingCart), + 'item': serializer.toJson(item), + }; + } + + i2.ShoppingCartEntry copyWith({int? shoppingCart, int? item}) => + i2.ShoppingCartEntry( + shoppingCart: shoppingCart ?? this.shoppingCart, + item: item ?? this.item, + ); + ShoppingCartEntry copyWithCompanion(i2.ShoppingCartEntriesCompanion data) { + return ShoppingCartEntry( + shoppingCart: data.shoppingCart.present + ? data.shoppingCart.value + : this.shoppingCart, + item: data.item.present ? data.item.value : this.item, + ); + } + + @override + String toString() { + return (StringBuffer('ShoppingCartEntry(') + ..write('shoppingCart: $shoppingCart, ') + ..write('item: $item') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(shoppingCart, item); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i2.ShoppingCartEntry && + other.shoppingCart == this.shoppingCart && + other.item == this.item); +} + +class ShoppingCartEntriesCompanion + extends i0.UpdateCompanion { + final i0.Value shoppingCart; + final i0.Value item; + final i0.Value rowid; + const ShoppingCartEntriesCompanion({ + this.shoppingCart = const i0.Value.absent(), + this.item = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + ShoppingCartEntriesCompanion.insert({ + required int shoppingCart, + required int item, + this.rowid = const i0.Value.absent(), + }) : shoppingCart = i0.Value(shoppingCart), + item = i0.Value(item); + static i0.Insertable custom({ + i0.Expression? shoppingCart, + i0.Expression? item, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (shoppingCart != null) 'shopping_cart': shoppingCart, + if (item != null) 'item': item, + if (rowid != null) 'rowid': rowid, + }); + } + + i2.ShoppingCartEntriesCompanion copyWith( + {i0.Value? shoppingCart, + i0.Value? item, + i0.Value? rowid}) { + return i2.ShoppingCartEntriesCompanion( + shoppingCart: shoppingCart ?? this.shoppingCart, + item: item ?? this.item, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (shoppingCart.present) { + map['shopping_cart'] = i0.Variable(shoppingCart.value); + } + if (item.present) { + map['item'] = i0.Variable(item.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ShoppingCartEntriesCompanion(') + ..write('shoppingCart: $shoppingCart, ') + ..write('item: $item, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} diff --git a/docs/lib/snippets/modular/many_to_many/shared.dart b/docs/lib/snippets/modular/many_to_many/shared.dart new file mode 100644 index 000000000..9074b4823 --- /dev/null +++ b/docs/lib/snippets/modular/many_to_many/shared.dart @@ -0,0 +1,33 @@ +import 'package:drift/drift.dart'; + +import 'shared.drift.dart'; + +abstract class ShoppingCart { + int get id; + + const ShoppingCart(); +} + +// #docregion interface +typedef ShoppingCartWithItems = ({ + ShoppingCart cart, + List items, +}); + +abstract class CartRepository { + Future createEmptyCart(); + Future updateCart(ShoppingCartWithItems entry); + + Stream watchCart(int id); + Stream watchAllCarts(); +} +// #enddocregion interface + +// #docregion buyable_items +class BuyableItems extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get description => text()(); + IntColumn get price => integer()(); + // we could add more columns as we wish. +} +// #enddocregion buyable_items diff --git a/docs/lib/snippets/modular/many_to_many/shared.drift.dart b/docs/lib/snippets/modular/many_to_many/shared.drift.dart new file mode 100644 index 000000000..90078c4e6 --- /dev/null +++ b/docs/lib/snippets/modular/many_to_many/shared.drift.dart @@ -0,0 +1,373 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/many_to_many/shared.drift.dart' + as i1; +import 'package:drift_docs/snippets/modular/many_to_many/shared.dart' as i2; + +typedef $$BuyableItemsTableCreateCompanionBuilder = i1.BuyableItemsCompanion + Function({ + i0.Value id, + required String description, + required int price, +}); +typedef $$BuyableItemsTableUpdateCompanionBuilder = i1.BuyableItemsCompanion + Function({ + i0.Value id, + i0.Value description, + i0.Value price, +}); + +class $$BuyableItemsTableFilterComposer + extends i0.Composer { + $$BuyableItemsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get price => $composableBuilder( + column: $table.price, builder: (column) => i0.ColumnFilters(column)); +} + +class $$BuyableItemsTableOrderingComposer + extends i0.Composer { + $$BuyableItemsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get price => $composableBuilder( + column: $table.price, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$BuyableItemsTableAnnotationComposer + extends i0.Composer { + $$BuyableItemsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get description => $composableBuilder( + column: $table.description, builder: (column) => column); + + i0.GeneratedColumn get price => + $composableBuilder(column: $table.price, builder: (column) => column); +} + +class $$BuyableItemsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$BuyableItemsTable, + i1.BuyableItem, + i1.$$BuyableItemsTableFilterComposer, + i1.$$BuyableItemsTableOrderingComposer, + i1.$$BuyableItemsTableAnnotationComposer, + $$BuyableItemsTableCreateCompanionBuilder, + $$BuyableItemsTableUpdateCompanionBuilder, + ( + i1.BuyableItem, + i0.BaseReferences + ), + i1.BuyableItem, + i0.PrefetchHooks Function()> { + $$BuyableItemsTableTableManager( + i0.GeneratedDatabase db, i1.$BuyableItemsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$BuyableItemsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$BuyableItemsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$BuyableItemsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + i0.Value price = const i0.Value.absent(), + }) => + i1.BuyableItemsCompanion( + id: id, + description: description, + price: price, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String description, + required int price, + }) => + i1.BuyableItemsCompanion.insert( + id: id, + description: description, + price: price, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$BuyableItemsTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$BuyableItemsTable, + i1.BuyableItem, + i1.$$BuyableItemsTableFilterComposer, + i1.$$BuyableItemsTableOrderingComposer, + i1.$$BuyableItemsTableAnnotationComposer, + $$BuyableItemsTableCreateCompanionBuilder, + $$BuyableItemsTableUpdateCompanionBuilder, + ( + i1.BuyableItem, + i0.BaseReferences + ), + i1.BuyableItem, + i0.PrefetchHooks Function()>; + +class $BuyableItemsTable extends i2.BuyableItems + with i0.TableInfo<$BuyableItemsTable, i1.BuyableItem> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $BuyableItemsTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _descriptionMeta = + const i0.VerificationMeta('description'); + @override + late final i0.GeneratedColumn description = + i0.GeneratedColumn('description', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _priceMeta = + const i0.VerificationMeta('price'); + @override + late final i0.GeneratedColumn price = i0.GeneratedColumn( + 'price', aliasedName, false, + type: i0.DriftSqlType.int, requiredDuringInsert: true); + @override + List get $columns => [id, description, price]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'buyable_items'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } else if (isInserting) { + context.missing(_descriptionMeta); + } + if (data.containsKey('price')) { + context.handle( + _priceMeta, price.isAcceptableOrUnknown(data['price']!, _priceMeta)); + } else if (isInserting) { + context.missing(_priceMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.BuyableItem map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.BuyableItem( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + description: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}description'])!, + price: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}price'])!, + ); + } + + @override + $BuyableItemsTable createAlias(String alias) { + return $BuyableItemsTable(attachedDatabase, alias); + } +} + +class BuyableItem extends i0.DataClass + implements i0.Insertable { + final int id; + final String description; + final int price; + const BuyableItem( + {required this.id, required this.description, required this.price}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['description'] = i0.Variable(description); + map['price'] = i0.Variable(price); + return map; + } + + i1.BuyableItemsCompanion toCompanion(bool nullToAbsent) { + return i1.BuyableItemsCompanion( + id: i0.Value(id), + description: i0.Value(description), + price: i0.Value(price), + ); + } + + factory BuyableItem.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return BuyableItem( + id: serializer.fromJson(json['id']), + description: serializer.fromJson(json['description']), + price: serializer.fromJson(json['price']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'description': serializer.toJson(description), + 'price': serializer.toJson(price), + }; + } + + i1.BuyableItem copyWith({int? id, String? description, int? price}) => + i1.BuyableItem( + id: id ?? this.id, + description: description ?? this.description, + price: price ?? this.price, + ); + BuyableItem copyWithCompanion(i1.BuyableItemsCompanion data) { + return BuyableItem( + id: data.id.present ? data.id.value : this.id, + description: + data.description.present ? data.description.value : this.description, + price: data.price.present ? data.price.value : this.price, + ); + } + + @override + String toString() { + return (StringBuffer('BuyableItem(') + ..write('id: $id, ') + ..write('description: $description, ') + ..write('price: $price') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, description, price); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.BuyableItem && + other.id == this.id && + other.description == this.description && + other.price == this.price); +} + +class BuyableItemsCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value description; + final i0.Value price; + const BuyableItemsCompanion({ + this.id = const i0.Value.absent(), + this.description = const i0.Value.absent(), + this.price = const i0.Value.absent(), + }); + BuyableItemsCompanion.insert({ + this.id = const i0.Value.absent(), + required String description, + required int price, + }) : description = i0.Value(description), + price = i0.Value(price); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? description, + i0.Expression? price, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (description != null) 'description': description, + if (price != null) 'price': price, + }); + } + + i1.BuyableItemsCompanion copyWith( + {i0.Value? id, + i0.Value? description, + i0.Value? price}) { + return i1.BuyableItemsCompanion( + id: id ?? this.id, + description: description ?? this.description, + price: price ?? this.price, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (description.present) { + map['description'] = i0.Variable(description.value); + } + if (price.present) { + map['price'] = i0.Variable(price.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BuyableItemsCompanion(') + ..write('id: $id, ') + ..write('description: $description, ') + ..write('price: $price') + ..write(')')) + .toString(); + } +} diff --git a/docs/lib/snippets/modular/schema_inspection.dart b/docs/lib/snippets/modular/schema_inspection.dart new file mode 100644 index 000000000..1bbd75e78 --- /dev/null +++ b/docs/lib/snippets/modular/schema_inspection.dart @@ -0,0 +1,72 @@ +import 'package:drift/drift.dart'; + +import 'drift/example.drift.dart'; + +// #docregion findById +extension FindById + on ResultSetImplementation { + Selectable findById(int id) { + return select() + ..where((row) { + final idColumn = columnsByName['id']; + + if (idColumn == null) { + throw ArgumentError.value( + this, 'this', 'Must be a table with an id column'); + } + + if (idColumn.type != DriftSqlType.int) { + throw ArgumentError('Column `id` is not an integer'); + } + + return idColumn.equals(id); + }); + } +} +// #enddocregion findById + +// #docregion updateTitle +extension UpdateTitle on DatabaseConnectionUser { + Future updateTitle, Row>( + T table, int id, String newTitle) async { + final columnsByName = table.columnsByName; + final stmt = update(table) + ..where((tbl) { + final idColumn = columnsByName['id']; + + if (idColumn == null) { + throw ArgumentError.value( + this, 'this', 'Must be a table with an id column'); + } + + if (idColumn.type != DriftSqlType.int) { + throw ArgumentError('Column `id` is not an integer'); + } + + return idColumn.equals(id); + }); + + final rows = await stmt.writeReturning(RawValuesInsertable({ + 'title': Variable(newTitle), + })); + + return rows.singleOrNull; + } +} +// #enddocregion updateTitle + +extension FindTodoEntryById on GeneratedDatabase { + Todos get todos => Todos(this); + + // #docregion findTodoEntryById + Selectable findTodoEntryById(int id) { + return select(todos)..where((row) => row.id.equals(id)); + } + // #enddocregion findTodoEntryById + + // #docregion updateTodo + Future updateTodoTitle(int id, String newTitle) { + return updateTitle(todos, id, newTitle); + } + // #enddocregion updateTodo +} diff --git a/docs/lib/snippets/modular/upserts.dart b/docs/lib/snippets/modular/upserts.dart new file mode 100644 index 000000000..3a407a573 --- /dev/null +++ b/docs/lib/snippets/modular/upserts.dart @@ -0,0 +1,54 @@ +import 'package:drift/drift.dart'; +import 'package:drift/internal/modular.dart'; + +import 'upserts.drift.dart'; + +// #docregion words-table +class Words extends Table { + TextColumn get word => text()(); + IntColumn get usages => integer().withDefault(const Constant(1))(); + + @override + Set get primaryKey => {word}; +} +// #enddocregion words-table + +// #docregion upsert-target +class MatchResults extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get teamA => text()(); + TextColumn get teamB => text()(); + BoolColumn get teamAWon => boolean()(); + + @override + List>>? get uniqueKeys => [ + {teamA, teamB} + ]; +} +// #enddocregion upsert-target + +extension DocumentationSnippets on ModularAccessor { + $WordsTable get words => throw 'stub'; + $MatchResultsTable get matches => throw 'stub'; + + // #docregion track-word + Future trackWord(String word) { + return into(words).insert( + WordsCompanion.insert(word: word), + onConflict: DoUpdate( + (old) => WordsCompanion.custom(usages: old.usages + Constant(1))), + ); + } + // #enddocregion track-word + + // #docregion upsert-target + Future insertMatch(String teamA, String teamB, bool teamAWon) { + final data = MatchResultsCompanion.insert( + teamA: teamA, teamB: teamB, teamAWon: teamAWon); + + return into(matches).insert(data, + onConflict: + DoUpdate((old) => data, target: [matches.teamA, matches.teamB])); + } + // #enddocregion upsert-target +} diff --git a/docs/lib/snippets/modular/upserts.drift.dart b/docs/lib/snippets/modular/upserts.drift.dart new file mode 100644 index 000000000..385e8821f --- /dev/null +++ b/docs/lib/snippets/modular/upserts.drift.dart @@ -0,0 +1,739 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/upserts.drift.dart' as i1; +import 'package:drift_docs/snippets/modular/upserts.dart' as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; + +typedef $$WordsTableCreateCompanionBuilder = i1.WordsCompanion Function({ + required String word, + i0.Value usages, + i0.Value rowid, +}); +typedef $$WordsTableUpdateCompanionBuilder = i1.WordsCompanion Function({ + i0.Value word, + i0.Value usages, + i0.Value rowid, +}); + +class $$WordsTableFilterComposer + extends i0.Composer { + $$WordsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get word => $composableBuilder( + column: $table.word, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get usages => $composableBuilder( + column: $table.usages, builder: (column) => i0.ColumnFilters(column)); +} + +class $$WordsTableOrderingComposer + extends i0.Composer { + $$WordsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get word => $composableBuilder( + column: $table.word, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get usages => $composableBuilder( + column: $table.usages, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$WordsTableAnnotationComposer + extends i0.Composer { + $$WordsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get word => + $composableBuilder(column: $table.word, builder: (column) => column); + + i0.GeneratedColumn get usages => + $composableBuilder(column: $table.usages, builder: (column) => column); +} + +class $$WordsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$WordsTable, + i1.Word, + i1.$$WordsTableFilterComposer, + i1.$$WordsTableOrderingComposer, + i1.$$WordsTableAnnotationComposer, + $$WordsTableCreateCompanionBuilder, + $$WordsTableUpdateCompanionBuilder, + (i1.Word, i0.BaseReferences), + i1.Word, + i0.PrefetchHooks Function()> { + $$WordsTableTableManager(i0.GeneratedDatabase db, i1.$WordsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$WordsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$WordsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$WordsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value word = const i0.Value.absent(), + i0.Value usages = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.WordsCompanion( + word: word, + usages: usages, + rowid: rowid, + ), + createCompanionCallback: ({ + required String word, + i0.Value usages = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.WordsCompanion.insert( + word: word, + usages: usages, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$WordsTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$WordsTable, + i1.Word, + i1.$$WordsTableFilterComposer, + i1.$$WordsTableOrderingComposer, + i1.$$WordsTableAnnotationComposer, + $$WordsTableCreateCompanionBuilder, + $$WordsTableUpdateCompanionBuilder, + (i1.Word, i0.BaseReferences), + i1.Word, + i0.PrefetchHooks Function()>; +typedef $$MatchResultsTableCreateCompanionBuilder = i1.MatchResultsCompanion + Function({ + i0.Value id, + required String teamA, + required String teamB, + required bool teamAWon, +}); +typedef $$MatchResultsTableUpdateCompanionBuilder = i1.MatchResultsCompanion + Function({ + i0.Value id, + i0.Value teamA, + i0.Value teamB, + i0.Value teamAWon, +}); + +class $$MatchResultsTableFilterComposer + extends i0.Composer { + $$MatchResultsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get teamA => $composableBuilder( + column: $table.teamA, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get teamB => $composableBuilder( + column: $table.teamB, builder: (column) => i0.ColumnFilters(column)); + + i0.ColumnFilters get teamAWon => $composableBuilder( + column: $table.teamAWon, builder: (column) => i0.ColumnFilters(column)); +} + +class $$MatchResultsTableOrderingComposer + extends i0.Composer { + $$MatchResultsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get teamA => $composableBuilder( + column: $table.teamA, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get teamB => $composableBuilder( + column: $table.teamB, builder: (column) => i0.ColumnOrderings(column)); + + i0.ColumnOrderings get teamAWon => $composableBuilder( + column: $table.teamAWon, builder: (column) => i0.ColumnOrderings(column)); +} + +class $$MatchResultsTableAnnotationComposer + extends i0.Composer { + $$MatchResultsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get teamA => + $composableBuilder(column: $table.teamA, builder: (column) => column); + + i0.GeneratedColumn get teamB => + $composableBuilder(column: $table.teamB, builder: (column) => column); + + i0.GeneratedColumn get teamAWon => + $composableBuilder(column: $table.teamAWon, builder: (column) => column); +} + +class $$MatchResultsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$MatchResultsTable, + i1.MatchResult, + i1.$$MatchResultsTableFilterComposer, + i1.$$MatchResultsTableOrderingComposer, + i1.$$MatchResultsTableAnnotationComposer, + $$MatchResultsTableCreateCompanionBuilder, + $$MatchResultsTableUpdateCompanionBuilder, + ( + i1.MatchResult, + i0.BaseReferences + ), + i1.MatchResult, + i0.PrefetchHooks Function()> { + $$MatchResultsTableTableManager( + i0.GeneratedDatabase db, i1.$MatchResultsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$MatchResultsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$MatchResultsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$MatchResultsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value teamA = const i0.Value.absent(), + i0.Value teamB = const i0.Value.absent(), + i0.Value teamAWon = const i0.Value.absent(), + }) => + i1.MatchResultsCompanion( + id: id, + teamA: teamA, + teamB: teamB, + teamAWon: teamAWon, + ), + createCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + required String teamA, + required String teamB, + required bool teamAWon, + }) => + i1.MatchResultsCompanion.insert( + id: id, + teamA: teamA, + teamB: teamB, + teamAWon: teamAWon, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$MatchResultsTableProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$MatchResultsTable, + i1.MatchResult, + i1.$$MatchResultsTableFilterComposer, + i1.$$MatchResultsTableOrderingComposer, + i1.$$MatchResultsTableAnnotationComposer, + $$MatchResultsTableCreateCompanionBuilder, + $$MatchResultsTableUpdateCompanionBuilder, + ( + i1.MatchResult, + i0.BaseReferences + ), + i1.MatchResult, + i0.PrefetchHooks Function()>; + +class $WordsTable extends i2.Words with i0.TableInfo<$WordsTable, i1.Word> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $WordsTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _wordMeta = + const i0.VerificationMeta('word'); + @override + late final i0.GeneratedColumn word = i0.GeneratedColumn( + 'word', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _usagesMeta = + const i0.VerificationMeta('usages'); + @override + late final i0.GeneratedColumn usages = i0.GeneratedColumn( + 'usages', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i3.Constant(1)); + @override + List get $columns => [word, usages]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'words'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('word')) { + context.handle( + _wordMeta, word.isAcceptableOrUnknown(data['word']!, _wordMeta)); + } else if (isInserting) { + context.missing(_wordMeta); + } + if (data.containsKey('usages')) { + context.handle(_usagesMeta, + usages.isAcceptableOrUnknown(data['usages']!, _usagesMeta)); + } + return context; + } + + @override + Set get $primaryKey => {word}; + @override + i1.Word map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.Word( + word: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}word'])!, + usages: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}usages'])!, + ); + } + + @override + $WordsTable createAlias(String alias) { + return $WordsTable(attachedDatabase, alias); + } +} + +class Word extends i0.DataClass implements i0.Insertable { + final String word; + final int usages; + const Word({required this.word, required this.usages}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['word'] = i0.Variable(word); + map['usages'] = i0.Variable(usages); + return map; + } + + i1.WordsCompanion toCompanion(bool nullToAbsent) { + return i1.WordsCompanion( + word: i0.Value(word), + usages: i0.Value(usages), + ); + } + + factory Word.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Word( + word: serializer.fromJson(json['word']), + usages: serializer.fromJson(json['usages']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'word': serializer.toJson(word), + 'usages': serializer.toJson(usages), + }; + } + + i1.Word copyWith({String? word, int? usages}) => i1.Word( + word: word ?? this.word, + usages: usages ?? this.usages, + ); + Word copyWithCompanion(i1.WordsCompanion data) { + return Word( + word: data.word.present ? data.word.value : this.word, + usages: data.usages.present ? data.usages.value : this.usages, + ); + } + + @override + String toString() { + return (StringBuffer('Word(') + ..write('word: $word, ') + ..write('usages: $usages') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(word, usages); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.Word && + other.word == this.word && + other.usages == this.usages); +} + +class WordsCompanion extends i0.UpdateCompanion { + final i0.Value word; + final i0.Value usages; + final i0.Value rowid; + const WordsCompanion({ + this.word = const i0.Value.absent(), + this.usages = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + WordsCompanion.insert({ + required String word, + this.usages = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : word = i0.Value(word); + static i0.Insertable custom({ + i0.Expression? word, + i0.Expression? usages, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (word != null) 'word': word, + if (usages != null) 'usages': usages, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.WordsCompanion copyWith( + {i0.Value? word, i0.Value? usages, i0.Value? rowid}) { + return i1.WordsCompanion( + word: word ?? this.word, + usages: usages ?? this.usages, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (word.present) { + map['word'] = i0.Variable(word.value); + } + if (usages.present) { + map['usages'] = i0.Variable(usages.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('WordsCompanion(') + ..write('word: $word, ') + ..write('usages: $usages, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $MatchResultsTable extends i2.MatchResults + with i0.TableInfo<$MatchResultsTable, i1.MatchResult> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $MatchResultsTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const i0.VerificationMeta _teamAMeta = + const i0.VerificationMeta('teamA'); + @override + late final i0.GeneratedColumn teamA = i0.GeneratedColumn( + 'team_a', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _teamBMeta = + const i0.VerificationMeta('teamB'); + @override + late final i0.GeneratedColumn teamB = i0.GeneratedColumn( + 'team_b', aliasedName, false, + type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _teamAWonMeta = + const i0.VerificationMeta('teamAWon'); + @override + late final i0.GeneratedColumn teamAWon = i0.GeneratedColumn( + 'team_a_won', aliasedName, false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("team_a_won" IN (0, 1))')); + @override + List get $columns => [id, teamA, teamB, teamAWon]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'match_results'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('team_a')) { + context.handle( + _teamAMeta, teamA.isAcceptableOrUnknown(data['team_a']!, _teamAMeta)); + } else if (isInserting) { + context.missing(_teamAMeta); + } + if (data.containsKey('team_b')) { + context.handle( + _teamBMeta, teamB.isAcceptableOrUnknown(data['team_b']!, _teamBMeta)); + } else if (isInserting) { + context.missing(_teamBMeta); + } + if (data.containsKey('team_a_won')) { + context.handle(_teamAWonMeta, + teamAWon.isAcceptableOrUnknown(data['team_a_won']!, _teamAWonMeta)); + } else if (isInserting) { + context.missing(_teamAWonMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + List> get uniqueKeys => [ + {teamA, teamB}, + ]; + @override + i1.MatchResult map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.MatchResult( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + teamA: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}team_a'])!, + teamB: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}team_b'])!, + teamAWon: attachedDatabase.typeMapping + .read(i0.DriftSqlType.bool, data['${effectivePrefix}team_a_won'])!, + ); + } + + @override + $MatchResultsTable createAlias(String alias) { + return $MatchResultsTable(attachedDatabase, alias); + } +} + +class MatchResult extends i0.DataClass + implements i0.Insertable { + final int id; + final String teamA; + final String teamB; + final bool teamAWon; + const MatchResult( + {required this.id, + required this.teamA, + required this.teamB, + required this.teamAWon}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['team_a'] = i0.Variable(teamA); + map['team_b'] = i0.Variable(teamB); + map['team_a_won'] = i0.Variable(teamAWon); + return map; + } + + i1.MatchResultsCompanion toCompanion(bool nullToAbsent) { + return i1.MatchResultsCompanion( + id: i0.Value(id), + teamA: i0.Value(teamA), + teamB: i0.Value(teamB), + teamAWon: i0.Value(teamAWon), + ); + } + + factory MatchResult.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return MatchResult( + id: serializer.fromJson(json['id']), + teamA: serializer.fromJson(json['teamA']), + teamB: serializer.fromJson(json['teamB']), + teamAWon: serializer.fromJson(json['teamAWon']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'teamA': serializer.toJson(teamA), + 'teamB': serializer.toJson(teamB), + 'teamAWon': serializer.toJson(teamAWon), + }; + } + + i1.MatchResult copyWith( + {int? id, String? teamA, String? teamB, bool? teamAWon}) => + i1.MatchResult( + id: id ?? this.id, + teamA: teamA ?? this.teamA, + teamB: teamB ?? this.teamB, + teamAWon: teamAWon ?? this.teamAWon, + ); + MatchResult copyWithCompanion(i1.MatchResultsCompanion data) { + return MatchResult( + id: data.id.present ? data.id.value : this.id, + teamA: data.teamA.present ? data.teamA.value : this.teamA, + teamB: data.teamB.present ? data.teamB.value : this.teamB, + teamAWon: data.teamAWon.present ? data.teamAWon.value : this.teamAWon, + ); + } + + @override + String toString() { + return (StringBuffer('MatchResult(') + ..write('id: $id, ') + ..write('teamA: $teamA, ') + ..write('teamB: $teamB, ') + ..write('teamAWon: $teamAWon') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, teamA, teamB, teamAWon); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.MatchResult && + other.id == this.id && + other.teamA == this.teamA && + other.teamB == this.teamB && + other.teamAWon == this.teamAWon); +} + +class MatchResultsCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value teamA; + final i0.Value teamB; + final i0.Value teamAWon; + const MatchResultsCompanion({ + this.id = const i0.Value.absent(), + this.teamA = const i0.Value.absent(), + this.teamB = const i0.Value.absent(), + this.teamAWon = const i0.Value.absent(), + }); + MatchResultsCompanion.insert({ + this.id = const i0.Value.absent(), + required String teamA, + required String teamB, + required bool teamAWon, + }) : teamA = i0.Value(teamA), + teamB = i0.Value(teamB), + teamAWon = i0.Value(teamAWon); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? teamA, + i0.Expression? teamB, + i0.Expression? teamAWon, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (teamA != null) 'team_a': teamA, + if (teamB != null) 'team_b': teamB, + if (teamAWon != null) 'team_a_won': teamAWon, + }); + } + + i1.MatchResultsCompanion copyWith( + {i0.Value? id, + i0.Value? teamA, + i0.Value? teamB, + i0.Value? teamAWon}) { + return i1.MatchResultsCompanion( + id: id ?? this.id, + teamA: teamA ?? this.teamA, + teamB: teamB ?? this.teamB, + teamAWon: teamAWon ?? this.teamAWon, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (teamA.present) { + map['team_a'] = i0.Variable(teamA.value); + } + if (teamB.present) { + map['team_b'] = i0.Variable(teamB.value); + } + if (teamAWon.present) { + map['team_a_won'] = i0.Variable(teamAWon.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MatchResultsCompanion(') + ..write('id: $id, ') + ..write('teamA: $teamA, ') + ..write('teamB: $teamB, ') + ..write('teamAWon: $teamAWon') + ..write(')')) + .toString(); + } +} diff --git a/docs/lib/snippets/platforms/encryption.dart b/docs/lib/snippets/platforms/encryption.dart new file mode 100644 index 000000000..36e8bd9ba --- /dev/null +++ b/docs/lib/snippets/platforms/encryption.dart @@ -0,0 +1,59 @@ +import 'dart:io'; +import 'package:drift/native.dart'; +import 'package:drift_docs/snippets/isolates.dart'; +import 'package:sqlite3/sqlite3.dart'; + +// #docregion setup +import 'package:sqlite3/open.dart'; +import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart'; + +// call this method before using drift +Future setupSqlCipher() async { + await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions(); + open.overrideFor(OperatingSystem.android, openCipherOnAndroid); +} +// #enddocregion setup + +// #docregion check_cipher +bool _debugCheckHasCipher(Database database) { + return database.select('PRAGMA cipher_version;').isNotEmpty; +} +// #enddocregion check_cipher + +void databases() { + final myDatabaseFile = File('/dev/null'); + + // #docregion encrypted1 + final token = RootIsolateToken.instance!; + NativeDatabase.createInBackground( + myDatabaseFile, + isolateSetup: () async { + BackgroundIsolateBinaryMessenger.ensureInitialized(token); + await setupSqlCipher(); + }, + setup: (rawDb) { + rawDb.execute("PRAGMA key = 'passphrase';"); + + // Recommended option, not enabled by default on SQLCipher + rawDb.config.doubleQuotedStringLiterals = false; + }, + ); + // #enddocregion encrypted1 + + // #docregion encrypted2 + NativeDatabase.createInBackground( + myDatabaseFile, + isolateSetup: () async { + BackgroundIsolateBinaryMessenger.ensureInitialized(token); + await setupSqlCipher(); + }, + setup: (rawDb) { + assert(_debugCheckHasCipher(rawDb)); + rawDb.execute("PRAGMA key = 'passphrase';"); + + // Recommended option, not enabled by default on SQLCipher + rawDb.config.doubleQuotedStringLiterals = false; + }, + ); + // #enddocregion encrypted2 +} diff --git a/docs/lib/snippets/platforms/new_connect.dart b/docs/lib/snippets/platforms/new_connect.dart new file mode 100644 index 000000000..44dc19bac --- /dev/null +++ b/docs/lib/snippets/platforms/new_connect.dart @@ -0,0 +1,12 @@ +import 'package:drift/drift.dart'; +// ignore: deprecated_member_use +import 'package:drift/web/worker.dart'; + +class Approach1 { + // #docregion approach1 + Future connectToWorker() async { + return await connectToDriftWorker('/database_worker.dart.js', + mode: DriftWorkerMode.dedicatedInShared); + } + // #enddocregion approach1 +} diff --git a/docs/lib/snippets/platforms/platforms.dart b/docs/lib/snippets/platforms/platforms.dart new file mode 100644 index 000000000..83c1b1fe6 --- /dev/null +++ b/docs/lib/snippets/platforms/platforms.dart @@ -0,0 +1,16 @@ +import 'dart:ffi'; +import 'dart:io'; +import 'package:sqlite3/open.dart'; + +void main() { + open.overrideFor(OperatingSystem.linux, _openOnLinux); + + // After setting all the overrides, you can use drift! +} + +DynamicLibrary _openOnLinux() { + final scriptDir = File(Platform.script.toFilePath()).parent; + final libraryNextToScript = File('${scriptDir.path}/sqlite3.so'); + return DynamicLibrary.open(libraryNextToScript.path); +} +// _openOnWindows could be implemented similarly by opening `sqlite3.dll` diff --git a/docs/lib/snippets/platforms/postgres.dart b/docs/lib/snippets/platforms/postgres.dart new file mode 100644 index 000000000..1cb9847ff --- /dev/null +++ b/docs/lib/snippets/platforms/postgres.dart @@ -0,0 +1,63 @@ +// #docregion setup +import 'package:drift/drift.dart'; +import 'package:drift_postgres/drift_postgres.dart'; +import 'package:postgres/postgres.dart'; + +part 'postgres.g.dart'; + +class Users extends Table { + UuidColumn get id => customType(PgTypes.uuid).withDefault(genRandomUuid())(); + TextColumn get name => text()(); + Column get birthDate => customType(PgTypes.date).nullable()(); +} + +@DriftDatabase(tables: [Users]) +class MyDatabase extends _$MyDatabase { + MyDatabase(super.e); + + @override + int get schemaVersion => 1; +} + +void main() async { + final pgDatabase = PgDatabase( + endpoint: Endpoint( + host: 'localhost', + database: 'postgres', + username: 'postgres', + password: 'postgres', + ), + settings: ConnectionSettings( + // If you expect to talk to a Postgres database over a public connection, + // please use SslMode.verifyFull instead. + sslMode: SslMode.disable, + ), + ); + + final driftDatabase = MyDatabase(pgDatabase); + + // Insert a new user + await driftDatabase.users.insertOne(UsersCompanion.insert(name: 'Simon')); + + // Print all of them + print(await driftDatabase.users.all().get()); + + await driftDatabase.close(); +} +// #enddocregion setup + +List get yourListOfEndpoints => throw 'stub'; + +// #docregion pool +Future openWithPool() async { + final pool = Pool.withEndpoints(yourListOfEndpoints); + + final driftDatabase = MyDatabase(PgDatabase.opened(pool)); + await driftDatabase.users.select().get(); + + // Note that PgDatabase.opened() doesn't close the underlying connection when + // the drift database is closed. + await driftDatabase.close(); + await pool.close(); +} +// #enddocregion pool diff --git a/docs/lib/snippets/platforms/stable_worker.dart b/docs/lib/snippets/platforms/stable_worker.dart new file mode 100644 index 000000000..060259106 --- /dev/null +++ b/docs/lib/snippets/platforms/stable_worker.dart @@ -0,0 +1,5 @@ +import 'package:drift/wasm.dart'; + +// When compiled with dart2js, this file defines a dedicated or shared web +// worker used by drift. +void main() => WasmDatabase.workerMainForOpen(); diff --git a/docs/lib/snippets/platforms/vm.dart b/docs/lib/snippets/platforms/vm.dart new file mode 100644 index 000000000..9d99c41a0 --- /dev/null +++ b/docs/lib/snippets/platforms/vm.dart @@ -0,0 +1,53 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; + +// #docregion setup +import 'dart:ffi'; +import 'dart:io'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:sqlite3/open.dart'; + +void main() { + open.overrideFor(OperatingSystem.linux, _openOnLinux); + + final db = sqlite3.openInMemory(); + db.dispose(); +} + +DynamicLibrary _openOnLinux() { + final script = File(Platform.script.toFilePath()); + final libraryNextToScript = File('${script.path}/sqlite3.so'); + return DynamicLibrary.open(libraryNextToScript.path); +} +// _openOnWindows could be implemented similarly by opening `sqlite3.dll` +// #enddocregion setup + +// #docregion background-simple +QueryExecutor openDatabase() { + return NativeDatabase.createInBackground( + File('path/to/database.db'), + isolateSetup: () { + open.overrideFor(OperatingSystem.linux, _openOnLinux); + }, + ); +} +// #enddocregion background-simple + +// #docregion background-pool +QueryExecutor openMultiThreadedDatabase() { + return NativeDatabase.createInBackground( + File('path/to/database.db'), + isolateSetup: () { + open.overrideFor(OperatingSystem.linux, _openOnLinux); + }, + setup: (database) { + // This is important, as accessing the database across threads otherwise + // causes "database locked" errors. + // With write-ahead logging (WAL) enabled, a single writer and multiple + // readers can operate on the database in parallel. + database.execute('pragma journal_mode = WAL;'); + }, + readPool: 4, + ); +} +// #enddocregion background-pool diff --git a/docs/lib/snippets/platforms/web.dart b/docs/lib/snippets/platforms/web.dart new file mode 100644 index 000000000..1e137c8b8 --- /dev/null +++ b/docs/lib/snippets/platforms/web.dart @@ -0,0 +1,168 @@ +import 'package:drift/drift.dart'; +import 'package:drift/wasm.dart'; +// ignore: deprecated_member_use +import 'package:drift/web.dart'; +import 'package:flutter/services.dart'; +import 'package:sqlite3/wasm.dart'; + +typedef _$MyWebDatabase = GeneratedDatabase; + +// #docregion connect +DatabaseConnection connectOnWeb() { + return DatabaseConnection.delayed(Future(() async { + final result = await WasmDatabase.open( + databaseName: 'my_app_db', // prefer to only use valid identifiers here + sqlite3Uri: Uri.parse('sqlite3.wasm'), + driftWorkerUri: Uri.parse('drift_worker.dart.js'), + ); + + if (result.missingFeatures.isNotEmpty) { + // Depending how central local persistence is to your app, you may want + // to show a warning to the user if only unrealiable implemetentations + // are available. + print('Using ${result.chosenImplementation} due to missing browser ' + 'features: ${result.missingFeatures}'); + } + + return result.resolvedExecutor; + })); +} + +// You can then use this method to open your database: +class MyWebDatabase extends _$MyWebDatabase { + MyWebDatabase._(super.e); + + factory MyWebDatabase() => MyWebDatabase._(connectOnWeb()); + // ... + // #enddocregion connect + + @override + Iterable> get allTables => + throw UnimplementedError(); + + @override + int get schemaVersion => throw UnimplementedError(); +// #docregion connect +} +// #enddocregion connect + +DatabaseConnection connectWithExisting() { + // #docregion existing + return DatabaseConnection.delayed(Future(() async { + final result = await WasmDatabase.open( + databaseName: 'my_app_db', // prefer to only use valid identifiers here + sqlite3Uri: Uri.parse('sqlite3.wasm'), + driftWorkerUri: Uri.parse('drift_worker.dart.js'), + initializeDatabase: () async { + final data = await rootBundle.load('my_database'); + return data.buffer.asUint8List(); + }, + ); + + // ... + return result.resolvedExecutor; + })); + // #enddocregion existing +} + +// #docregion migrate-wasm +// If you've previously opened your database like this +Future customDatabase() async { + final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('/sqlite3.wasm')); + final fs = await IndexedDbFileSystem.open(dbName: 'my_app'); + sqlite3.registerVirtualFileSystem(fs, makeDefault: true); + + return WasmDatabase( + sqlite3: sqlite3, + path: '/app.db', + ); +} +// #enddocregion migrate-wasm + +DatabaseConnection migrateAndConnect() { + return DatabaseConnection.delayed(Future(() async { + // #docregion migrate-wasm + // Then you can migrate like this + final result = await WasmDatabase.open( + databaseName: 'my_app', + sqlite3Uri: Uri.parse('sqlite3.wasm'), + driftWorkerUri: Uri.parse('drift_worker.dart.js'), + initializeDatabase: () async { + // Manually open the file system previously used + final fs = await IndexedDbFileSystem.open(dbName: 'my_app'); + const oldPath = '/app.db'; // The path passed to WasmDatabase before + + Uint8List? oldDatabase; + + // Check if the old database exists + if (fs.xAccess(oldPath, 0) != 0) { + // It does, then copy the old file + final (file: file, outFlags: _) = + fs.xOpen(Sqlite3Filename(oldPath), 0); + final blob = Uint8List(file.xFileSize()); + file.xRead(blob, 0); + file.xClose(); + fs.xDelete(oldPath, 0); + + oldDatabase = blob; + } + + await fs.close(); + return oldDatabase; + }, + ); + // #enddocregion migrate-wasm + + return result.resolvedExecutor; + })); +} + +DatabaseConnection migrateFromLegacy() { + return DatabaseConnection.delayed(Future(() async { + // #docregion migrate-legacy + final result = await WasmDatabase.open( + databaseName: 'my_app', + sqlite3Uri: Uri.parse('sqlite3.wasm'), + driftWorkerUri: Uri.parse('drift_worker.dart.js'), + initializeDatabase: () async { + final storage = await DriftWebStorage.indexedDbIfSupported('old_db'); + await storage.open(); + + final blob = await storage.restore(); + await storage.close(); + + return blob; + }, + ); + // #enddocregion migrate-legacy + + return result.resolvedExecutor; + })); +} + +// #docregion setupAll +void setupDatabase(CommonDatabase database) { + database.createFunction( + functionName: 'my_function', + function: (args) => args.length, + ); +} + +void main() { + WasmDatabase.workerMainForOpen( + setupAllDatabases: setupDatabase, + ); +} +// #enddocregion setupAll + +void withSetup() async { + // #docregion setupLocal + final result = await WasmDatabase.open( + databaseName: 'my_app_db', // prefer to only use valid identifiers here + sqlite3Uri: Uri.parse('sqlite3.wasm'), + driftWorkerUri: Uri.parse('my_drift_worker.dart.js'), + localSetup: setupDatabase, + ); + // #enddocregion setupLocal + print(result); +} diff --git a/docs/lib/snippets/platforms/workers.dart b/docs/lib/snippets/platforms/workers.dart new file mode 100644 index 000000000..1d2ef2e96 --- /dev/null +++ b/docs/lib/snippets/platforms/workers.dart @@ -0,0 +1,32 @@ +// #docregion worker +// Note: This snippet describes a legacy API! Please consider migrating to +// `package:drift/wasm.dart`, which has builtin support for web workers! +import 'dart:html'; + +import 'package:drift/drift.dart'; +// ignore: deprecated_member_use +import 'package:drift/web.dart'; +// ignore: deprecated_member_use +import 'package:drift/web/worker.dart'; + +void main() { + // Load sql.js library in the worker + WorkerGlobalScope.instance.importScripts('sql-wasm.js'); + + // Call drift function that will set up this worker + driftWorkerMain(() { + return WebDatabase.withStorage(DriftWebStorage.indexedDb('worker', + migrateFromLocalStorage: false, inWebWorker: true)); + }); +} +// #enddocregion worker + +// #docregion client +DatabaseConnection connectToWorker() { + return DatabaseConnection.delayed(connectToDriftWorker( + 'worker.dart.js', + // Note that SharedWorkers may not be available on all browsers and platforms. + mode: DriftWorkerMode.shared, + )); +} +// #enddocregion client diff --git a/docs/lib/snippets/setup/database.dart b/docs/lib/snippets/setup/database.dart new file mode 100644 index 000000000..07eed5941 --- /dev/null +++ b/docs/lib/snippets/setup/database.dart @@ -0,0 +1,109 @@ +// ignore_for_file: unused_element +// #docregion flutter,sqlite3,postgres,before_generation +import 'package:drift/drift.dart'; +// #enddocregion flutter,sqlite3,postgres,before_generation + +// #docregion flutter +import 'package:drift_flutter/drift_flutter.dart'; +// #enddocregion flutter +// #docregion sqlite3 +import 'dart:io'; +import 'package:drift/native.dart'; +// #enddocregion sqlite3 +// #docregion postgres +import 'package:drift_postgres/drift_postgres.dart'; +import 'package:postgres/postgres.dart' as pg; +// #enddocregion postgres + +// #docregion flutter,sqlite3,postgres,before_generation + +part 'database.g.dart'; + +// #docregion table +class TodoItems extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get title => text().withLength(min: 6, max: 32)(); + TextColumn get content => text().named('body')(); + IntColumn get category => + integer().nullable().references(TodoCategory, #id)(); + DateTimeColumn get createdAt => dateTime().nullable()(); +} + +class TodoCategory extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get description => text()(); +} + +// #enddocregion table +@DriftDatabase(tables: [TodoItems, TodoCategory]) +class AppDatabase extends _$AppDatabase { +// #enddocregion before_generation + // After generating code, this class needs to define a `schemaVersion` getter + // and a constructor telling drift where the database should be stored. + // These are described in the getting started guide: https://drift.simonbinder.eu/setup/ + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; + + // #enddocregion flutter,sqlite3,postgres + static QueryExecutor _openConnection() { + throw 'should not show as snippet'; + } + +// #docregion before_generation +} +// #enddocregion before_generation + +class OpenFlutter { +// #docregion flutter + static QueryExecutor _openConnection() { + // `driftDatabase` from `package:drift_flutter` stores the database in + // `getApplicationDocumentsDirectory()`. + return driftDatabase(name: 'my_database'); + } +} +// #enddocregion flutter + +class OpenPostgres { +// #docregion postgres + static QueryExecutor _openConnection() { + return PgDatabase( + endpoint: pg.Endpoint( + host: 'localhost', + database: 'database', + username: 'dart', + password: 'mysecurepassword', + ), + ); + } +} +// #enddocregion postgres + +class OpenSqlite3 { +// #docregion sqlite3 + static QueryExecutor _openConnection() { + return NativeDatabase.createInBackground(File('path/to/your/database')); + } +} +// #enddocregion sqlite3 + +class WidgetsFlutterBinding { + static void ensureInitialized() {} +} + +// #docregion use +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + final database = AppDatabase(); + + await database.into(database.todoItems).insert(TodoItemsCompanion.insert( + title: 'todo: finish drift setup', + content: 'We can now write queries and define our own tables.', + )); + List allItems = await database.select(database.todoItems).get(); + + print('items in database: $allItems'); +} +// #enddocregion use diff --git a/docs/lib/snippets/setup/migrate_to_drift/database.dart b/docs/lib/snippets/setup/migrate_to_drift/database.dart new file mode 100644 index 000000000..0444c3162 --- /dev/null +++ b/docs/lib/snippets/setup/migrate_to_drift/database.dart @@ -0,0 +1,67 @@ +// #docregion start +import 'package:drift/drift.dart'; + +part 'database.g.dart'; + +@DriftDatabase( + // include: {'schema.drift'} + ) +// #enddocregion start +class HackToIncludePartialAnnotationInDocs + extends _$HackToIncludePartialAnnotationInDocs { + HackToIncludePartialAnnotationInDocs(super.e); + + @override + int get schemaVersion => 1; +} + +@DriftDatabase(include: {'schema.drift'}) +// #docregion start +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openDatabase()); + + @override + int get schemaVersion => throw UnimplementedError( + 'todo: The schema version used by your existing database', + ); + + @override + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (m) async { + await m.createAll(); + }, + onUpgrade: (m, from, to) async { + // This is similar to the `onUpgrade` callback from sqflite. When + // migrating to drift, it should contain your existing migration logic. + // You can access the raw database by using `customStatement` + }, + beforeOpen: (details) async { + // This is a good place to enable pragmas you expect, e.g. + await customStatement('pragma foreign_keys = ON;'); + }, + ); + } + + static QueryExecutor _openDatabase() { + throw UnimplementedError( + 'todo: Open database compatible with the one that already exists', + ); + } + + // #enddocregion start + // #docregion drift-query + Future> queryWithGeneratedCode() async { + return findWithValue(12).get(); + } + // #enddocregion drift-query + + // #docregion dart-query + Stream> queryWithDartCode() { + final query = select(test)..where((row) => row.value.isBiggerThanValue(12)); + return query.watch(); + } + // #enddocregion dart-query + // #docregion start +} +// #enddocregion start diff --git a/docs/lib/snippets/setup/migrate_to_drift/schema.drift b/docs/lib/snippets/setup/migrate_to_drift/schema.drift new file mode 100644 index 000000000..a9ed2be6d --- /dev/null +++ b/docs/lib/snippets/setup/migrate_to_drift/schema.drift @@ -0,0 +1,12 @@ +-- #docregion test +CREATE TABLE Test ( + id INTEGER PRIMARY KEY, + name TEXT, + value INTEGER, + num REAL +); +-- #enddocregion test + +-- #docregion query +findWithValue: SELECT * FROM Test WHERE value > ?; +-- #enddocregion query diff --git a/docs/lib/snippets/type_converters/converters.dart b/docs/lib/snippets/type_converters/converters.dart new file mode 100644 index 000000000..79572900b --- /dev/null +++ b/docs/lib/snippets/type_converters/converters.dart @@ -0,0 +1,58 @@ +// #docregion start +import 'dart:convert'; + +import 'package:drift/drift.dart'; +import 'package:json_annotation/json_annotation.dart' as j; + +part 'converters.g.dart'; + +@j.JsonSerializable() +class Preferences { + bool receiveEmails; + String selectedTheme; + + Preferences(this.receiveEmails, this.selectedTheme); + + factory Preferences.fromJson(Map json) => + _$PreferencesFromJson(json); + + Map toJson() => _$PreferencesToJson(this); + // #enddocregion start + + // #docregion simplified + static JsonTypeConverter converter = TypeConverter.json( + fromJson: (json) => Preferences.fromJson(json as Map), + toJson: (pref) => pref.toJson(), + ); + // #enddocregion simplified + // #docregion start +} +// #enddocregion start + +// #docregion converter +// stores preferences as strings +class PreferenceConverter extends TypeConverter + with JsonTypeConverter { + const PreferenceConverter(); + + @override + Preferences fromSql(String fromDb) { + return Preferences.fromJson(json.decode(fromDb) as Map); + } + + @override + String toSql(Preferences value) { + return json.encode(value.toJson()); + } +} +// #enddocregion converter + +// #docregion table +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + + TextColumn get preferences => + text().map(const PreferenceConverter()).nullable()(); +} +// #enddocregion table diff --git a/docs/test/generated/database.dart b/docs/test/generated/database.dart new file mode 100644 index 000000000..a3ce39742 --- /dev/null +++ b/docs/test/generated/database.dart @@ -0,0 +1,21 @@ +import 'package:drift/drift.dart'; + +part 'database.g.dart'; + +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + DateTimeColumn get createdAt => + dateTime().nullable().withDefault(currentDateAndTime)(); +} + +@DriftDatabase(tables: [Users]) +class Database extends _$Database { + Database(super.c); + + @override + int get schemaVersion => 1; + + @override + DriftDatabaseOptions options = DriftDatabaseOptions(); +} diff --git a/docs/test/snippet_test.dart b/docs/test/snippet_test.dart new file mode 100644 index 000000000..2952aac3f --- /dev/null +++ b/docs/test/snippet_test.dart @@ -0,0 +1,164 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:drift_docs/snippets/dart_api/datetime_conversion.dart'; +import 'package:drift_docs/snippets/log_interceptor.dart'; +import 'package:drift_docs/snippets/modular/schema_inspection.dart'; +import 'package:sqlite3/sqlite3.dart' show sqlite3; +import 'package:test/test.dart'; + +import 'generated/database.dart'; + +/// Test for some snippets embedded on https://drift.simonbinder.eu to make sure +/// that they are still up to date and work as intended with the latest drift +/// version. +void main() { + group('changing datetime format', () { + test('unix timestamp to text', () async { + final db = Database(DatabaseConnection(NativeDatabase.memory())); + addTearDown(db.close); + + final time = DateTime.fromMillisecondsSinceEpoch( + // Drop milliseconds which are not supported by the old format + 1000 * (DateTime.now().millisecondsSinceEpoch ~/ 1000), + ); + + // The database is currently using unix timstamps. Let's add some rows in + // that format: + await db.users.insertOne( + UsersCompanion.insert(name: 'name', createdAt: Value(time))); + await db.users.insertOne( + UsersCompanion.insert(name: 'name2', createdAt: Value(null))); + + // Run conversion from unix timestamps to text + await db.migrateFromUnixTimestampsToText(db.createMigrator()); + + // Check that the values are still there! + final rows = await (db.select(db.users) + ..orderBy([(row) => OrderingTerm.asc(row.rowId)])) + .get(); + + expect(rows, [ + User(id: 1, name: 'name', createdAt: time), + const User(id: 2, name: 'name2', createdAt: null), + ]); + }); + + test('text to unix timestamp', () async { + // First, create all tables using text as datetime + final nativeDatabase = sqlite3.openInMemory(); + var db = Database(DatabaseConnection(NativeDatabase.opened(nativeDatabase, + closeUnderlyingOnClose: false))); + db.options = const DriftDatabaseOptions(storeDateTimeAsText: true); + + final time = DateTime.fromMillisecondsSinceEpoch( + 1000 * (DateTime.now().millisecondsSinceEpoch ~/ 1000)); + + // Add rows, storing date time as text + await db.users.insertOne( + UsersCompanion.insert(name: 'name', createdAt: Value(time))); + await db.users.insertOne( + UsersCompanion.insert(name: 'name2', createdAt: Value(null))); + await db.close(); + + db = Database(DatabaseConnection( + NativeDatabase.opened(nativeDatabase, closeUnderlyingOnClose: true))); + addTearDown(db.close); + + // Next, migrate back to unix timestamps + db.options = const DriftDatabaseOptions(storeDateTimeAsText: false); + final migrator = db.createMigrator(); + await db.migrateFromTextDateTimesToUnixTimestamps(migrator); + + final rows = await (db.select(db.users) + ..orderBy([(row) => OrderingTerm.asc(row.rowId)])) + .get(); + + expect(rows, [ + User(id: 1, name: 'name', createdAt: time), + const User(id: 2, name: 'name2', createdAt: null), + ]); + }); + + test('text to unix timestamp, support old sqlite', () async { + // First, create all tables using datetime as text + final nativeDatabase = sqlite3.openInMemory(); + var db = Database(DatabaseConnection(NativeDatabase.opened(nativeDatabase, + closeUnderlyingOnClose: false))); + db.options = const DriftDatabaseOptions(storeDateTimeAsText: true); + + final time = DateTime.fromMillisecondsSinceEpoch( + 1000 * (DateTime.now().millisecondsSinceEpoch ~/ 1000)); + + // Add rows, storing date time as text + await db.users.insertOne( + UsersCompanion.insert(name: 'name', createdAt: Value(time))); + await db.users.insertOne( + UsersCompanion.insert(name: 'name2', createdAt: Value(null))); + await db.close(); + + db = Database(DatabaseConnection( + NativeDatabase.opened(nativeDatabase, closeUnderlyingOnClose: true))); + addTearDown(db.close); + + // Next, migrate back to unix timestamps + db.options = const DriftDatabaseOptions(storeDateTimeAsText: false); + final migrator = db.createMigrator(); + await db.migrateFromTextDateTimesToUnixTimestampsPre338(migrator); + + final rows = await (db.select(db.users) + ..orderBy([(row) => OrderingTerm.asc(row.rowId)])) + .get(); + + expect(rows, [ + User(id: 1, name: 'name', createdAt: time), + const User(id: 2, name: 'name2', createdAt: null), + ]); + }); + }); + + group('runtime schema inspection', () { + test('findById', () async { + final db = Database(NativeDatabase.memory()); + addTearDown(db.close); + + await db.batch((batch) { + batch.insert(db.users, UsersCompanion.insert(name: 'foo')); // 1 + batch.insert(db.users, UsersCompanion.insert(name: 'bar')); // 2 + batch.insert(db.users, UsersCompanion.insert(name: 'baz')); // 3 + }); + + final row = await db.users.findById(2).getSingle(); + expect(row.name, 'bar'); + }); + }); + + test('interceptor', () { + expect( + () async { + final db = + Database(NativeDatabase.memory().interceptWith(LogInterceptor())); + + await db.batch((batch) { + batch.insert(db.users, UsersCompanion.insert(name: 'foo')); + }); + + await db.users.all().get(); + }, + prints( + allOf( + stringContainsInOrder( + [ + 'begin', + 'Running batch with BatchedStatements', + ' => succeeded after ', + 'Running commit', + ' => succeeded after ', + 'Running SELECT * FROM "users"; with []', + ' => succeeded after' + ], + ), + ), + ), + ); + }); +}