mirror of https://github.com/AMT-Cheif/drift.git
Merge branch 'simolus3:develop' into develop
This commit is contained in:
commit
78387a3610
|
@ -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<User> friends;
|
||||||
|
|
||||||
|
UserWithFriends(this.user, {this.friends = const []});
|
||||||
|
}
|
||||||
|
// #enddocregion userwithfriends
|
|
@ -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
|
|
@ -0,0 +1,346 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
class Users extends i0.Table with i0.TableInfo<Users, i1.User> {
|
||||||
|
@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<int> id = i0.GeneratedColumn<int>(
|
||||||
|
'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<String> name = i0.GeneratedColumn<String>(
|
||||||
|
'name', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
$customConstraints: 'NOT NULL');
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> 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<i1.User> 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<i0.GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
i1.User map(Map<String, dynamic> 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<i1.User> {
|
||||||
|
final i0.Value<int> id;
|
||||||
|
final i0.Value<String> 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<i1.User> custom({
|
||||||
|
i0.Expression<int>? id,
|
||||||
|
i0.Expression<String>? name,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i2.UsersCompanion copyWith({i0.Value<int>? id, i0.Value<String>? name}) {
|
||||||
|
return i2.UsersCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = i0.Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (name.present) {
|
||||||
|
map['name'] = i0.Variable<String>(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<Friends, i2.Friend> {
|
||||||
|
@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<int> userA = i0.GeneratedColumn<int>(
|
||||||
|
'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<int> userB = i0.GeneratedColumn<int>(
|
||||||
|
'user_b', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.int,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
$customConstraints: 'NOT NULL REFERENCES users(id)');
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> 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<i2.Friend> 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<i0.GeneratedColumn> get $primaryKey => {userA, userB};
|
||||||
|
@override
|
||||||
|
i2.Friend map(Map<String, dynamic> 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<String> get customConstraints => const ['PRIMARY KEY(user_a, user_b)'];
|
||||||
|
@override
|
||||||
|
bool get dontWriteConstraints => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Friend extends i0.DataClass implements i0.Insertable<i2.Friend> {
|
||||||
|
final int userA;
|
||||||
|
final int userB;
|
||||||
|
const Friend({required this.userA, required this.userB});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['user_a'] = i0.Variable<int>(userA);
|
||||||
|
map['user_b'] = i0.Variable<int>(userB);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2.FriendsCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return i2.FriendsCompanion(
|
||||||
|
userA: i0.Value(userA),
|
||||||
|
userB: i0.Value(userB),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Friend.fromJson(Map<String, dynamic> json,
|
||||||
|
{i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return Friend(
|
||||||
|
userA: serializer.fromJson<int>(json['user_a']),
|
||||||
|
userB: serializer.fromJson<int>(json['user_b']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'user_a': serializer.toJson<int>(userA),
|
||||||
|
'user_b': serializer.toJson<int>(userB),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i2.Friend copyWith({int? userA, int? userB}) => i2.Friend(
|
||||||
|
userA: userA ?? this.userA,
|
||||||
|
userB: userB ?? 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<i2.Friend> {
|
||||||
|
final i0.Value<int> userA;
|
||||||
|
final i0.Value<int> userB;
|
||||||
|
final i0.Value<int> 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<i2.Friend> custom({
|
||||||
|
i0.Expression<int>? userA,
|
||||||
|
i0.Expression<int>? userB,
|
||||||
|
i0.Expression<int>? 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<int>? userA, i0.Value<int>? userB, i0.Value<int>? rowid}) {
|
||||||
|
return i2.FriendsCompanion(
|
||||||
|
userA: userA ?? this.userA,
|
||||||
|
userB: userB ?? this.userB,
|
||||||
|
rowid: rowid ?? this.rowid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (userA.present) {
|
||||||
|
map['user_a'] = i0.Variable<int>(userA.value);
|
||||||
|
}
|
||||||
|
if (userB.present) {
|
||||||
|
map['user_b'] = i0.Variable<int>(userB.value);
|
||||||
|
}
|
||||||
|
if (rowid.present) {
|
||||||
|
map['rowid'] = i0.Variable<int>(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<i1.UserWithFriends> 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<int>(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<int>(row.read('\$n_0')),
|
||||||
|
i0.Variable<int>(row.read('\$n_1'))
|
||||||
|
],
|
||||||
|
readsFrom: {
|
||||||
|
users,
|
||||||
|
friends,
|
||||||
|
})
|
||||||
|
.map((i0.QueryRow row) => i1.User(
|
||||||
|
row.read<int>('id'),
|
||||||
|
row.read<String>('name'),
|
||||||
|
))
|
||||||
|
.get(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
i2.Users get users => this.resultSet<i2.Users>('users');
|
||||||
|
i2.Friends get friends => this.resultSet<i2.Friends>('friends');
|
||||||
|
}
|
|
@ -25,6 +25,36 @@ extension FindById<Table extends HasResultSet, Row>
|
||||||
}
|
}
|
||||||
// #enddocregion findById
|
// #enddocregion findById
|
||||||
|
|
||||||
|
// #docregion updateTitle
|
||||||
|
extension UpdateTitle on DatabaseConnectionUser {
|
||||||
|
Future<Row?> updateTitle<T extends TableInfo<Table, Row>, 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<String>(newTitle),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return rows.singleOrNull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion updateTitle
|
||||||
|
|
||||||
extension FindTodoEntryById on GeneratedDatabase {
|
extension FindTodoEntryById on GeneratedDatabase {
|
||||||
Todos get todos => Todos(this);
|
Todos get todos => Todos(this);
|
||||||
|
|
||||||
|
@ -33,4 +63,10 @@ extension FindTodoEntryById on GeneratedDatabase {
|
||||||
return select(todos)..where((row) => row.id.equals(id));
|
return select(todos)..where((row) => row.id.equals(id));
|
||||||
}
|
}
|
||||||
// #enddocregion findTodoEntryById
|
// #enddocregion findTodoEntryById
|
||||||
|
|
||||||
|
// #docregion updateTodo
|
||||||
|
Future<Todo?> updateTodoTitle(int id, String newTitle) {
|
||||||
|
return updateTitle(todos, id, newTitle);
|
||||||
|
}
|
||||||
|
// #enddocregion updateTodo
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,12 @@ class TodoItems extends Table {
|
||||||
|
|
||||||
@DriftDatabase(tables: [TodoItems])
|
@DriftDatabase(tables: [TodoItems])
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
|
// #enddocregion open
|
||||||
|
// 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/getting-started/#open
|
||||||
// #enddocregion before_generation
|
// #enddocregion before_generation
|
||||||
|
// #docregion open
|
||||||
AppDatabase() : super(_openConnection());
|
AppDatabase() : super(_openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -49,8 +49,24 @@ To call this extension, `await myDatabase.todos.findById(3).getSingle()` could b
|
||||||
A nice thing about defining the method as an extension is that type inference works really well - calling `findById` on `todos`
|
A nice thing about defining the method as an extension is that type inference works really well - calling `findById` on `todos`
|
||||||
returns a `Todo` instance, the generated data class for this table.
|
returns a `Todo` instance, the generated data class for this table.
|
||||||
|
|
||||||
|
## Updates and inserts
|
||||||
|
|
||||||
The same approach also works to construct update, delete and insert statements (although those require a [TableInfo] instead of a [ResultSetImplementation]
|
The same approach also works to construct update, delete and insert statements (although those require a [TableInfo] instead of a [ResultSetImplementation]
|
||||||
as views are read-only).
|
as views are read-only).
|
||||||
|
Also, updates and inserts use an `Insertable` object which represents a partial row of updated or
|
||||||
|
inserted columns, respectively.
|
||||||
|
With a known table, one would use the generated typed `Companion` objects for that.
|
||||||
|
But this can also be done with schema introspection thanks to the `RawValuesInsertable`, which
|
||||||
|
can be used as a generic `Insertable` backed by a map of column names to values.
|
||||||
|
|
||||||
|
This example builds on the previous one to update the `title` column of a generic table based on a filter
|
||||||
|
of the `id` column:
|
||||||
|
|
||||||
|
{% include "blocks/snippet" snippets = snippets name = 'updateTitle' %}
|
||||||
|
|
||||||
|
In a database or database accessor class, the method can then be called like this:
|
||||||
|
|
||||||
|
{% include "blocks/snippet" snippets = snippets name = 'updateTodo' %}
|
||||||
|
|
||||||
Hopefully, this page gives you some pointers to start reflectively inspecting your drift databases.
|
Hopefully, this page gives you some pointers to start reflectively inspecting your drift databases.
|
||||||
The linked Dart documentation also expains the concepts in more detail.
|
The linked Dart documentation also expains the concepts in more detail.
|
||||||
|
@ -59,4 +75,4 @@ If you have questions about this, or have a suggestion for more examples to incl
|
||||||
[ResultSetImplementation]: https://drift.simonbinder.eu/api/drift/resultsetimplementation-class
|
[ResultSetImplementation]: https://drift.simonbinder.eu/api/drift/resultsetimplementation-class
|
||||||
[TableInfo]: https://drift.simonbinder.eu/api/drift/tableinfo-mixin
|
[TableInfo]: https://drift.simonbinder.eu/api/drift/tableinfo-mixin
|
||||||
[ViewInfo]: https://drift.simonbinder.eu/api/drift/viewinfo-class
|
[ViewInfo]: https://drift.simonbinder.eu/api/drift/viewinfo-class
|
||||||
[GeneratedColumn]: https://drift.simonbinder.eu/api/drift/generatedcolumn-class
|
[GeneratedColumn]: https://drift.simonbinder.eu/api/drift/generatedcolumn-class
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
data:
|
data:
|
||||||
title: "Selects"
|
title: "Selects"
|
||||||
description: "Select rows or invidiual columns from tables in Dart"
|
description: "Select rows or individual columns from tables in Dart"
|
||||||
weight: 2
|
weight: 2
|
||||||
template: layouts/docs/single
|
template: layouts/docs/single
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,8 @@ At the moment, drift supports these options:
|
||||||
(so a column named `user_name` would also use `user_name` as a json key instead of `userName`).
|
(so a column named `user_name` would also use `user_name` as a json key instead of `userName`).
|
||||||
You can always override the json key by using a `JSON KEY` column constraint
|
You can always override the json key by using a `JSON KEY` column constraint
|
||||||
(e.g. `user_name VARCHAR NOT NULL JSON KEY userName`).
|
(e.g. `user_name VARCHAR NOT NULL JSON KEY userName`).
|
||||||
|
* `use_sql_column_name_as_json_key` (defaults to false): Uses the column name in SQL as the JSON key for serialization,
|
||||||
|
regardless of whether the table was defined in a drift file or not.
|
||||||
* `generate_connect_constructor` (deprecated): Generates a named `connect()` constructor on database classes
|
* `generate_connect_constructor` (deprecated): Generates a named `connect()` constructor on database classes
|
||||||
that takes a `DatabaseConnection` instead of a `QueryExecutor`.
|
that takes a `DatabaseConnection` instead of a `QueryExecutor`.
|
||||||
This option was deprecated in drift 2.5 because `DatabaseConnection` now implements `QueryExecutor`.
|
This option was deprecated in drift 2.5 because `DatabaseConnection` now implements `QueryExecutor`.
|
||||||
|
|
|
@ -167,6 +167,8 @@ statement before it runs it.
|
||||||
a defined query by appending `WITH YourDartClass` to a `CREATE TABLE` statement.
|
a defined query by appending `WITH YourDartClass` to a `CREATE TABLE` statement.
|
||||||
- Alternatively, you may use `AS DesiredRowClassName` to change the name of the
|
- Alternatively, you may use `AS DesiredRowClassName` to change the name of the
|
||||||
row class generated by drift.
|
row class generated by drift.
|
||||||
|
- Both custom row classes and custom table names also work for views, e.g. with
|
||||||
|
`CREATE VIEW my_view AS DartName AS SELECT ...;`.
|
||||||
- In a column definition, `MAPPED BY` can be used to [apply a converter](#type-converters)
|
- In a column definition, `MAPPED BY` can be used to [apply a converter](#type-converters)
|
||||||
to that column.
|
to that column.
|
||||||
- Similarly, a `JSON KEY` constraint can be used to define the key drift will
|
- Similarly, a `JSON KEY` constraint can be used to define the key drift will
|
||||||
|
@ -356,28 +358,17 @@ With that option, the variable will be inferred to `Preferences` instead of `Str
|
||||||
|
|
||||||
### Existing row classes
|
### Existing row classes
|
||||||
|
|
||||||
|
{% assign existingDrift = "package:drift_docs/snippets/modular/drift/with_existing.drift.excerpt.json" | readString | json_decode %}
|
||||||
|
{% assign rowClassDart = "package:drift_docs/snippets/modular/drift/row_class.dart.excerpt.json" | readString | json_decode %}
|
||||||
|
|
||||||
You can use custom row classes instead of having drift generate one for you.
|
You can use custom row classes instead of having drift generate one for you.
|
||||||
For instance, let's say you had a Dart class defined as
|
For instance, let's say you had a Dart class defined as
|
||||||
|
|
||||||
```dart
|
{% include "blocks/snippet" snippets = rowClassDart name = "user" %}
|
||||||
class User {
|
|
||||||
final int id;
|
|
||||||
final String name;
|
|
||||||
|
|
||||||
User(this.id, this.name);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, you can instruct drift to use that class as a row class as follows:
|
Then, you can instruct drift to use that class as a row class as follows:
|
||||||
|
|
||||||
```sql
|
{% include "blocks/snippet" snippets = existingDrift name = "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
|
|
||||||
```
|
|
||||||
|
|
||||||
When using custom row classes defined in another Dart file, you also need to import that file into the file where you define
|
When using custom row classes defined in another Dart file, you also need to import that file into the file where you define
|
||||||
the database.
|
the database.
|
||||||
|
@ -388,32 +379,11 @@ can be added after the name of the query.
|
||||||
|
|
||||||
For instance, let's say we expand the existing Dart code in `row_class.dart` by adding another class:
|
For instance, let's say we expand the existing Dart code in `row_class.dart` by adding another class:
|
||||||
|
|
||||||
```dart
|
{% include "blocks/snippet" snippets = rowClassDart name = "userwithfriends" %}
|
||||||
class UserWithFriends {
|
|
||||||
final User user;
|
|
||||||
final List<User> friends;
|
|
||||||
|
|
||||||
UserWithFriends(this.user, {this.friends = const []});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, we can add a corresponding query using the new class for its rows:
|
Now, we can add a corresponding query using the new class for its rows:
|
||||||
|
|
||||||
```sql
|
{% include "blocks/snippet" snippets = existingDrift name = "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.**, LIST(
|
|
||||||
SELECT * FROM users a INNER JOIN friends ON user_a = a.id WHERE user_b = users.id
|
|
||||||
UNION ALL
|
|
||||||
SELECT * FROM users b INNER JOIN friends ON user_b = b.id WHERE user_a = users.id
|
|
||||||
) AS friends FROM users WHERE id = :id;
|
|
||||||
```
|
|
||||||
|
|
||||||
The `WITH UserWithFriends` syntax will make drift consider the `UserWithFriends` class.
|
The `WITH UserWithFriends` syntax will make drift consider the `UserWithFriends` class.
|
||||||
For every field in the constructor, drift will check the column from the query and
|
For every field in the constructor, drift will check the column from the query and
|
||||||
|
|
|
@ -99,7 +99,7 @@ You will now see errors related to missing overrides and a missing constructor.
|
||||||
is responsible for telling drift how to open the database. The `schemaVersion` getter is relevant
|
is responsible for telling drift how to open the database. The `schemaVersion` getter is relevant
|
||||||
for migrations after changing the database, we can leave it at `1` for now. The database class
|
for migrations after changing the database, we can leave it at `1` for now. The database class
|
||||||
now looks like this:
|
now looks like this:
|
||||||
|
<a name="open">
|
||||||
{% include "blocks/snippet" snippets = snippets name = 'open' %}
|
{% include "blocks/snippet" snippets = snippets name = 'open' %}
|
||||||
|
|
||||||
The Android-specific workarounds are necessary because sqlite3 attempts to use `/tmp` to store
|
The Android-specific workarounds are necessary because sqlite3 attempts to use `/tmp` to store
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
## 2.16.0-dev
|
## 2.17.0-dev
|
||||||
|
|
||||||
|
- Adds `companion` entry to `DataClassName` to override the name of the
|
||||||
|
generated companion class.
|
||||||
|
- Add the `TypeConverter.extensionType` factory to create type converters for
|
||||||
|
extension types.
|
||||||
|
- Fix invalid SQL syntax being generated for `BLOB` literals on postgres.
|
||||||
|
|
||||||
|
## 2.16.0
|
||||||
|
|
||||||
- When a migration throws, the database will now block subsequent operations
|
- When a migration throws, the database will now block subsequent operations
|
||||||
instead of potentially allowing them to operate on a database in an
|
instead of potentially allowing them to operate on a database in an
|
||||||
|
@ -12,6 +20,9 @@
|
||||||
- Improve stack traces for errors happening on drift isolates (which includes
|
- Improve stack traces for errors happening on drift isolates (which includes
|
||||||
usages of `NativeDatabase.createInBackground`).
|
usages of `NativeDatabase.createInBackground`).
|
||||||
- Don't cache `EXPLAIN` statements, avoiding schema locks.
|
- Don't cache `EXPLAIN` statements, avoiding schema locks.
|
||||||
|
- Deprecate `Value.ofNullable` in favor of `Value.absentIfNull`, which is more
|
||||||
|
explicit about its behavior and allows nullable types too.
|
||||||
|
- Migrate `WasmDatabase` to `dart:js_interop` and `package:web`.
|
||||||
|
|
||||||
## 2.15.0
|
## 2.15.0
|
||||||
|
|
||||||
|
|
|
@ -308,7 +308,10 @@ final class TableIndex {
|
||||||
class DataClassName {
|
class DataClassName {
|
||||||
/// The overridden name to use when generating the data class for a table.
|
/// The overridden name to use when generating the data class for a table.
|
||||||
/// {@macro drift_custom_data_class}
|
/// {@macro drift_custom_data_class}
|
||||||
final String name;
|
final String? name;
|
||||||
|
|
||||||
|
/// The overridden name to use when generating the companion class for a table.
|
||||||
|
final String? companion;
|
||||||
|
|
||||||
/// The parent type of the data class generated by drift.
|
/// The parent type of the data class generated by drift.
|
||||||
///
|
///
|
||||||
|
@ -345,7 +348,11 @@ class DataClassName {
|
||||||
|
|
||||||
/// Customize the data class name for a given table.
|
/// Customize the data class name for a given table.
|
||||||
/// {@macro drift_custom_data_class}
|
/// {@macro drift_custom_data_class}
|
||||||
const DataClassName(this.name, {this.extending});
|
const DataClassName(this.name, {this.extending, this.companion});
|
||||||
|
|
||||||
|
/// Customize the data class name for a given table.
|
||||||
|
/// {@macro drift_custom_data_class}
|
||||||
|
const DataClassName.custom({this.name, this.extending, this.companion});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An annotation specifying an existing class to be used as a data class.
|
/// An annotation specifying an existing class to be used as a data class.
|
||||||
|
|
|
@ -145,7 +145,9 @@ class DriftProtocol {
|
||||||
|
|
||||||
result.add(rows.length);
|
result.add(rows.length);
|
||||||
for (final row in rows) {
|
for (final row in rows) {
|
||||||
result.addAll(row.values);
|
for (final value in row.values) {
|
||||||
|
result.add(_encodeDbValue(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -234,7 +236,7 @@ class DriftProtocol {
|
||||||
|
|
||||||
result.add({
|
result.add({
|
||||||
for (var c = 0; c < columnCount; c++)
|
for (var c = 0; c < columnCount; c++)
|
||||||
columns[c]: fullMessage[rowOffset + c]
|
columns[c]: _decodeDbValue(fullMessage[rowOffset + c])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return SelectResult(result);
|
return SelectResult(result);
|
||||||
|
|
|
@ -158,6 +158,7 @@ class Value<T> {
|
||||||
/// This constructor should only be used when [T] is not nullable. If [T] were
|
/// This constructor should only be used when [T] is not nullable. If [T] were
|
||||||
/// nullable, there wouldn't be a clear interpretation for a `null` [value].
|
/// nullable, there wouldn't be a clear interpretation for a `null` [value].
|
||||||
/// See the overall documentation on [Value] for details.
|
/// See the overall documentation on [Value] for details.
|
||||||
|
@Deprecated('Use Value.absentIfNull instead')
|
||||||
const Value.ofNullable(T? value)
|
const Value.ofNullable(T? value)
|
||||||
: assert(
|
: assert(
|
||||||
value != null || null is! T,
|
value != null || null is! T,
|
||||||
|
@ -167,6 +168,15 @@ class Value<T> {
|
||||||
_value = value,
|
_value = value,
|
||||||
present = value != null;
|
present = value != null;
|
||||||
|
|
||||||
|
/// Create a value that is absent if [value] is `null` and [present] if it's
|
||||||
|
/// not.
|
||||||
|
///
|
||||||
|
/// The functionality is equiavalent to the following:
|
||||||
|
/// `x != null ? Value(x) : Value.absent()`.
|
||||||
|
const Value.absentIfNull(T? value)
|
||||||
|
: _value = value,
|
||||||
|
present = value != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => present ? 'Value($value)' : 'Value.absent()';
|
String toString() => present ? 'Value($value)' : 'Value.absent()';
|
||||||
|
|
||||||
|
|
|
@ -24,14 +24,14 @@ class CustomExpression<D extends Object> extends Expression<D> {
|
||||||
@override
|
@override
|
||||||
final Precedence precedence;
|
final Precedence precedence;
|
||||||
|
|
||||||
final CustomSqlType<D>? _customSqlType;
|
final UserDefinedSqlType<D>? _customSqlType;
|
||||||
|
|
||||||
/// Constructs a custom expression by providing the raw sql [content].
|
/// Constructs a custom expression by providing the raw sql [content].
|
||||||
const CustomExpression(
|
const CustomExpression(
|
||||||
this.content, {
|
this.content, {
|
||||||
this.watchedTables = const [],
|
this.watchedTables = const [],
|
||||||
this.precedence = Precedence.unknown,
|
this.precedence = Precedence.unknown,
|
||||||
CustomSqlType<D>? customType,
|
UserDefinedSqlType<D>? customType,
|
||||||
}) : _dialectSpecificContent = null,
|
}) : _dialectSpecificContent = null,
|
||||||
_customSqlType = customType;
|
_customSqlType = customType;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class CustomExpression<D extends Object> extends Expression<D> {
|
||||||
Map<SqlDialect, String> content, {
|
Map<SqlDialect, String> content, {
|
||||||
this.watchedTables = const [],
|
this.watchedTables = const [],
|
||||||
this.precedence = Precedence.unknown,
|
this.precedence = Precedence.unknown,
|
||||||
CustomSqlType<D>? customType,
|
UserDefinedSqlType<D>? customType,
|
||||||
}) : _dialectSpecificContent = content,
|
}) : _dialectSpecificContent = content,
|
||||||
content = '',
|
content = '',
|
||||||
_customSqlType = customType;
|
_customSqlType = customType;
|
||||||
|
|
|
@ -636,7 +636,7 @@ class _SubqueryExpression<R extends Object> extends Expression<R> {
|
||||||
int get hashCode => statement.hashCode;
|
int get hashCode => statement.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object? other) {
|
bool operator ==(Object other) {
|
||||||
return other is _SubqueryExpression && other.statement == statement;
|
return other is _SubqueryExpression && other.statement == statement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,10 +137,31 @@ extension StringExpressionOperators on Expression<String> {
|
||||||
/// and [length] can be negative to return a section of the string before
|
/// and [length] can be negative to return a section of the string before
|
||||||
/// [start].
|
/// [start].
|
||||||
Expression<String> substr(int start, [int? length]) {
|
Expression<String> substr(int start, [int? length]) {
|
||||||
|
return substrExpr(
|
||||||
|
Constant(start), length != null ? Constant(length) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls the [`substr`](https://sqlite.org/lang_corefunc.html#substr)
|
||||||
|
/// function with arbitrary expressions as arguments.
|
||||||
|
///
|
||||||
|
/// For instance, this call uses [substrExpr] to remove the last 5 characters
|
||||||
|
/// from a column. As this depends on its [StringExpressionOperators.length],
|
||||||
|
/// it needs to use expressions:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// update(table).write(TableCompanion.custom(
|
||||||
|
/// column: column.substrExpr(Variable(1), column.length - Variable(5))
|
||||||
|
/// ));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// When both [start] and [length] are Dart values (e.g. [Variable]s or
|
||||||
|
/// [Constant]s), consider using [substr] instead.
|
||||||
|
Expression<String> substrExpr(Expression<int> start,
|
||||||
|
[Expression<int>? length]) {
|
||||||
return FunctionCallExpression('SUBSTR', [
|
return FunctionCallExpression('SUBSTR', [
|
||||||
this,
|
this,
|
||||||
Constant<int>(start),
|
start,
|
||||||
if (length != null) Constant<int>(length),
|
if (length != null) length,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ extension WithTypes<T extends Object> on Expression<T> {
|
||||||
/// Creates a variable with a matching [driftSqlType].
|
/// Creates a variable with a matching [driftSqlType].
|
||||||
Variable<T> variable(T? value) {
|
Variable<T> variable(T? value) {
|
||||||
return switch (driftSqlType) {
|
return switch (driftSqlType) {
|
||||||
CustomSqlType<T> custom => Variable(value, custom),
|
UserDefinedSqlType<T> custom => Variable(value, custom),
|
||||||
_ => Variable(value),
|
_ => Variable(value),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ class Migrator {
|
||||||
return _issueCustomQuery(context.sql, context.boundVariables);
|
return _issueCustomQuery(context.sql, context.boundVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alter columns of an existing tabe.
|
/// Alter columns of an existing table.
|
||||||
///
|
///
|
||||||
/// Since sqlite does not provide a way to alter the type or constraint of an
|
/// Since sqlite does not provide a way to alter the type or constraint of an
|
||||||
/// individual column, one needs to write a fairly complex migration procedure
|
/// individual column, one needs to write a fairly complex migration procedure
|
||||||
|
|
|
@ -162,7 +162,7 @@ class GeneratedColumn<T extends Object> extends Column<T> {
|
||||||
|
|
||||||
// these custom constraints refer to builtin constraints from drift
|
// these custom constraints refer to builtin constraints from drift
|
||||||
if (!isSerial && _defaultConstraints != null) {
|
if (!isSerial && _defaultConstraints != null) {
|
||||||
_defaultConstraints!(into);
|
_defaultConstraints(into);
|
||||||
}
|
}
|
||||||
} else if ($customConstraints?.isNotEmpty == true) {
|
} else if ($customConstraints?.isNotEmpty == true) {
|
||||||
into.buffer
|
into.buffer
|
||||||
|
|
|
@ -276,8 +276,15 @@ class InsertStatement<T extends Table, D> {
|
||||||
if (ctx.dialect == SqlDialect.mariadb) {
|
if (ctx.dialect == SqlDialect.mariadb) {
|
||||||
ctx.buffer.write(' ON DUPLICATE');
|
ctx.buffer.write(' ON DUPLICATE');
|
||||||
} else {
|
} else {
|
||||||
ctx.buffer.write(' ON CONFLICT(');
|
ctx.buffer.write(' ON CONFLICT');
|
||||||
|
|
||||||
|
if (target != null && target.isEmpty) {
|
||||||
|
// An empty list indicates that no explicit target should be generated
|
||||||
|
// by drift, the default rules by the database will apply instead.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.buffer.write('(');
|
||||||
final conflictTarget = target ?? table.$primaryKey.toList();
|
final conflictTarget = target ?? table.$primaryKey.toList();
|
||||||
|
|
||||||
if (conflictTarget.isEmpty) {
|
if (conflictTarget.isEmpty) {
|
||||||
|
@ -348,7 +355,7 @@ class InsertStatement<T extends Table, D> {
|
||||||
|
|
||||||
if (onConflict._where != null) {
|
if (onConflict._where != null) {
|
||||||
ctx.writeWhitespace();
|
ctx.writeWhitespace();
|
||||||
final where = onConflict._where!(
|
final where = onConflict._where(
|
||||||
table.asDslTable, table.createAlias('excluded').asDslTable);
|
table.asDslTable, table.createAlias('excluded').asDslTable);
|
||||||
where.writeInto(ctx);
|
where.writeInto(ctx);
|
||||||
}
|
}
|
||||||
|
@ -473,6 +480,8 @@ class DoUpdate<T extends Table, D> extends UpsertClause<T, D> {
|
||||||
/// specifies the uniqueness constraint that will trigger the upsert.
|
/// specifies the uniqueness constraint that will trigger the upsert.
|
||||||
///
|
///
|
||||||
/// By default, the primary key of the table will be used.
|
/// By default, the primary key of the table will be used.
|
||||||
|
/// This can be set to an empty list, in which case no explicit conflict
|
||||||
|
/// target will be generated by drift.
|
||||||
final List<Column>? target;
|
final List<Column>? target;
|
||||||
|
|
||||||
/// Creates a `DO UPDATE` clause.
|
/// Creates a `DO UPDATE` clause.
|
||||||
|
|
|
@ -47,6 +47,35 @@ abstract class TypeConverter<D, S> {
|
||||||
json: json,
|
json: json,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type converter mapping [extension types] to their underlying
|
||||||
|
/// representation to store them in databases.
|
||||||
|
///
|
||||||
|
/// Here, [ExtType] is the extension type to use in Dart classes, and [Inner]
|
||||||
|
/// is the underlying type stored in the database. For instance, if you had
|
||||||
|
/// a type to represent ids in a database:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// extension type IdNumber(int id) {}
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You could use `TypeConverter.extensionType<IdNumber, int>()` in a column
|
||||||
|
/// definition:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// class Users extends Table {
|
||||||
|
/// IntColumn get id => integer()
|
||||||
|
/// .autoIncrement()
|
||||||
|
/// .map(TypeConverter.extensionType<IdNumber, int>())();
|
||||||
|
/// TextColumn get name => text()();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [extension types]: https://dart.dev/language/extension-types
|
||||||
|
static JsonTypeConverter<ExtType, Inner>
|
||||||
|
extensionType<ExtType, Inner extends Object>() {
|
||||||
|
return _ExtensionTypeConverter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mixin for [TypeConverter]s that should also apply to drift's builtin
|
/// A mixin for [TypeConverter]s that should also apply to drift's builtin
|
||||||
|
@ -264,3 +293,17 @@ class _NullWrappingTypeConverterWithJson<D, S extends Object, J extends Object>
|
||||||
return value == null ? null : requireToJson(value);
|
return value == null ? null : requireToJson(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ExtensionTypeConverter<ExtType, Inner extends Object>
|
||||||
|
extends TypeConverter<ExtType, Inner>
|
||||||
|
with JsonTypeConverter<ExtType, Inner> {
|
||||||
|
const _ExtensionTypeConverter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ExtType fromSql(Inner fromDb) {
|
||||||
|
return fromDb as ExtType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Inner toSql(ExtType value) => value as Inner;
|
||||||
|
}
|
||||||
|
|
|
@ -124,9 +124,17 @@ final class SqlTypes {
|
||||||
return (dart.millisecondsSinceEpoch ~/ 1000).toString();
|
return (dart.millisecondsSinceEpoch ~/ 1000).toString();
|
||||||
}
|
}
|
||||||
} else if (dart is Uint8List) {
|
} else if (dart is Uint8List) {
|
||||||
// BLOB literals are string literals containing hexadecimal data and
|
final String hexString = hex.encode(dart);
|
||||||
// preceded by a single "x" or "X" character. Example: X'53514C697465'
|
|
||||||
return "x'${hex.encode(dart)}'";
|
if (dialect == SqlDialect.postgres) {
|
||||||
|
// Postgres BYTEA hex format
|
||||||
|
// https://www.postgresql.org/docs/current/datatype-binary.html#DATATYPE-BINARY-BYTEA-HEX-FORMAT
|
||||||
|
return "'\\x$hexString'::bytea";
|
||||||
|
} else {
|
||||||
|
// BLOB literals are string literals containing hexadecimal data and
|
||||||
|
// preceded by a single "x" or "X" character. Example: X'53514C697465'
|
||||||
|
return "x'$hexString'";
|
||||||
|
}
|
||||||
} else if (dart is DriftAny) {
|
} else if (dart is DriftAny) {
|
||||||
return mapToSqlLiteral(dart.rawSqlValue);
|
return mapToSqlLiteral(dart.rawSqlValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,47 @@
|
||||||
|
@JS()
|
||||||
|
library;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
|
import 'dart:js_interop_unsafe';
|
||||||
|
|
||||||
import 'package:drift/src/runtime/api/runtime_api.dart';
|
import 'package:drift/src/runtime/api/runtime_api.dart';
|
||||||
import 'package:drift/src/runtime/executor/stream_queries.dart';
|
import 'package:drift/src/runtime/executor/stream_queries.dart';
|
||||||
import 'package:js/js.dart';
|
import 'package:web/web.dart' as web;
|
||||||
import 'package:js/js_util.dart';
|
|
||||||
|
@JS('Array')
|
||||||
|
extension type _ArrayWrapper._(JSArray _) implements JSObject {
|
||||||
|
external static JSBoolean isArray(JSAny? value);
|
||||||
|
}
|
||||||
|
|
||||||
/// A [StreamQueryStore] using [web broadcast] APIs
|
/// A [StreamQueryStore] using [web broadcast] APIs
|
||||||
///
|
///
|
||||||
/// [web broadcast]: https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
|
/// [web broadcast]: https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
|
||||||
class BroadcastStreamQueryStore extends StreamQueryStore {
|
class BroadcastStreamQueryStore extends StreamQueryStore {
|
||||||
final BroadcastChannel _channel;
|
final web.BroadcastChannel _channel;
|
||||||
StreamSubscription<MessageEvent>? _messageFromChannel;
|
StreamSubscription<web.MessageEvent>? _messageFromChannel;
|
||||||
|
|
||||||
/// Constructs a broadcast query store with the given [identifier].
|
/// Constructs a broadcast query store with the given [identifier].
|
||||||
///
|
///
|
||||||
/// All query stores with the same identifier will share stream query updates.
|
/// All query stores with the same identifier will share stream query updates.
|
||||||
BroadcastStreamQueryStore(String identifier)
|
BroadcastStreamQueryStore(String identifier)
|
||||||
: _channel = BroadcastChannel('drift_updates_$identifier') {
|
: _channel = web.BroadcastChannel('drift_updates_$identifier') {
|
||||||
_messageFromChannel = _channel.onMessage.listen(_handleMessage);
|
_messageFromChannel = web.EventStreamProviders.messageEvent
|
||||||
|
.forTarget(_channel)
|
||||||
|
.listen(_handleMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleMessage(MessageEvent message) {
|
void _handleMessage(web.MessageEvent message) {
|
||||||
// Using getProperty to avoid dart2js structured clone that turns the
|
final data = message.data;
|
||||||
// anonymous object into a map.
|
if (!_ArrayWrapper.isArray(data).toDart) {
|
||||||
final data = getProperty<Object?>(message, 'data');
|
return;
|
||||||
if (data is! List || data.isEmpty) return;
|
}
|
||||||
|
|
||||||
|
final asList = (data as JSArray).toDart;
|
||||||
|
if (asList.isEmpty) return;
|
||||||
|
|
||||||
super.handleTableUpdates({
|
super.handleTableUpdates({
|
||||||
for (final entry in data.cast<_SerializedTableUpdate>())
|
for (final entry in asList.cast<_SerializedTableUpdate>())
|
||||||
entry.toTableUpdate,
|
entry.toTableUpdate,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -39,7 +52,7 @@ class BroadcastStreamQueryStore extends StreamQueryStore {
|
||||||
|
|
||||||
_channel.postMessage([
|
_channel.postMessage([
|
||||||
for (final update in updates) _SerializedTableUpdate.of(update),
|
for (final update in updates) _SerializedTableUpdate.of(update),
|
||||||
]);
|
].toJS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -50,34 +63,31 @@ class BroadcastStreamQueryStore extends StreamQueryStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the current JavaScript context supports broadcast channels.
|
/// Whether the current JavaScript context supports broadcast channels.
|
||||||
static bool get supported => hasProperty(globalThis, 'BroadcastChannel');
|
static bool get supported => globalContext.has('BroadcastChannel');
|
||||||
}
|
}
|
||||||
|
|
||||||
@JS()
|
@JS()
|
||||||
@anonymous
|
@anonymous
|
||||||
@staticInterop
|
extension type _SerializedTableUpdate._(JSObject _) implements JSObject {
|
||||||
class _SerializedTableUpdate {
|
|
||||||
external factory _SerializedTableUpdate({
|
external factory _SerializedTableUpdate({
|
||||||
required String? kind,
|
required JSString? kind,
|
||||||
required String table,
|
required JSString table,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory _SerializedTableUpdate.of(TableUpdate update) {
|
factory _SerializedTableUpdate.of(TableUpdate update) {
|
||||||
return _SerializedTableUpdate(kind: update.kind?.name, table: update.table);
|
return _SerializedTableUpdate(
|
||||||
|
kind: update.kind?.name.toJS,
|
||||||
|
table: update.table.toJS,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension on _SerializedTableUpdate {
|
external JSString? get kind;
|
||||||
@JS()
|
external JSString get table;
|
||||||
external String? get kind;
|
|
||||||
|
|
||||||
@JS()
|
|
||||||
external String get table;
|
|
||||||
|
|
||||||
TableUpdate get toTableUpdate {
|
TableUpdate get toTableUpdate {
|
||||||
final updateKind = _updateKindByName[kind];
|
final updateKind = _updateKindByName[kind?.toDart];
|
||||||
|
|
||||||
return TableUpdate(table, kind: updateKind);
|
return TableUpdate(table.toDart, kind: updateKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final _updateKindByName = UpdateKind.values.asNameMap();
|
static final _updateKindByName = UpdateKind.values.asNameMap();
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import 'dart:js_interop';
|
||||||
|
|
||||||
|
import 'package:stream_channel/stream_channel.dart';
|
||||||
|
|
||||||
|
import 'package:web/web.dart' as web;
|
||||||
|
|
||||||
|
/// Extension to transform a raw [MessagePort] from web workers into a Dart
|
||||||
|
/// [StreamChannel].
|
||||||
|
extension WebPortToChannel on web.MessagePort {
|
||||||
|
static const _disconnectMessage = '_disconnect';
|
||||||
|
|
||||||
|
/// Converts this port to a two-way communication channel, exposed as a
|
||||||
|
/// [StreamChannel].
|
||||||
|
///
|
||||||
|
/// This can be used to implement a remote database connection over service
|
||||||
|
/// workers.
|
||||||
|
///
|
||||||
|
/// The [explicitClose] parameter can be used to control whether a close
|
||||||
|
/// message should be sent through the channel when it is closed. This will
|
||||||
|
/// cause it to be closed on the other end as well. Note that this is not a
|
||||||
|
/// reliable way of determining channel closures though, as there is no event
|
||||||
|
/// for channels being closed due to a tab or worker being closed.
|
||||||
|
/// Both "ends" of a JS channel calling [channel] on their part must use the
|
||||||
|
/// value for [explicitClose].
|
||||||
|
StreamChannel<Object?> channel({bool explicitClose = false}) {
|
||||||
|
final controller = StreamChannelController<Object?>();
|
||||||
|
|
||||||
|
onmessage = (web.MessageEvent event) {
|
||||||
|
final message = event.data;
|
||||||
|
|
||||||
|
if (explicitClose && message == _disconnectMessage.toJS) {
|
||||||
|
// Other end has closed the connection
|
||||||
|
controller.local.sink.close();
|
||||||
|
} else {
|
||||||
|
controller.local.sink.add(message.dartify());
|
||||||
|
}
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
controller.local.stream.listen((e) => postMessage(e.jsify()), onDone: () {
|
||||||
|
// Closed locally, inform the other end.
|
||||||
|
if (explicitClose) {
|
||||||
|
postMessage(_disconnectMessage.toJS);
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
|
||||||
|
return controller.foreign;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,22 +7,23 @@
|
||||||
/// asynchronous
|
/// asynchronous
|
||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
@internal
|
@internal
|
||||||
|
@JS()
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
|
import 'dart:js_interop_unsafe';
|
||||||
|
|
||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift/remote.dart';
|
import 'package:drift/remote.dart';
|
||||||
import 'package:drift/wasm.dart';
|
import 'package:drift/wasm.dart';
|
||||||
import 'package:js/js.dart';
|
|
||||||
import 'package:js/js_util.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:sqlite3/wasm.dart';
|
import 'package:sqlite3/wasm.dart';
|
||||||
|
import 'package:web/web.dart' as web;
|
||||||
|
|
||||||
import 'broadcast_stream_queries.dart';
|
import 'broadcast_stream_queries.dart';
|
||||||
import 'channel.dart';
|
import 'new_channel.dart';
|
||||||
import 'wasm_setup/shared.dart';
|
import 'wasm_setup/shared.dart';
|
||||||
import 'wasm_setup/protocol.dart';
|
import 'wasm_setup/protocol.dart';
|
||||||
|
|
||||||
|
@ -32,10 +33,10 @@ import 'wasm_setup/protocol.dart';
|
||||||
external bool get crossOriginIsolated;
|
external bool get crossOriginIsolated;
|
||||||
|
|
||||||
/// Whether shared workers can be constructed in the current context.
|
/// Whether shared workers can be constructed in the current context.
|
||||||
bool get supportsSharedWorkers => hasProperty(globalThis, 'SharedWorker');
|
bool get supportsSharedWorkers => globalContext.has('SharedWorker');
|
||||||
|
|
||||||
/// Whether dedicated workers can be constructed in the current context.
|
/// Whether dedicated workers can be constructed in the current context.
|
||||||
bool get supportsWorkers => hasProperty(globalThis, 'Worker');
|
bool get supportsWorkers => globalContext.has('Worker');
|
||||||
|
|
||||||
class WasmDatabaseOpener {
|
class WasmDatabaseOpener {
|
||||||
final Uri sqlite3WasmUri;
|
final Uri sqlite3WasmUri;
|
||||||
|
@ -107,7 +108,7 @@ class WasmDatabaseOpener {
|
||||||
Future<void> _probeDedicated() async {
|
Future<void> _probeDedicated() async {
|
||||||
if (supportsWorkers) {
|
if (supportsWorkers) {
|
||||||
final dedicatedWorker = _dedicatedWorker =
|
final dedicatedWorker = _dedicatedWorker =
|
||||||
_DriftWorker.dedicated(Worker(driftWorkerUri.toString()));
|
_DriftWorker.dedicated(web.Worker(driftWorkerUri.toString()));
|
||||||
_createCompatibilityCheck().sendTo(dedicatedWorker.send);
|
_createCompatibilityCheck().sendTo(dedicatedWorker.send);
|
||||||
|
|
||||||
final status = await dedicatedWorker.workerMessages.nextNoError
|
final status = await dedicatedWorker.workerMessages.nextNoError
|
||||||
|
@ -133,8 +134,8 @@ class WasmDatabaseOpener {
|
||||||
Future<void> _probeShared() async {
|
Future<void> _probeShared() async {
|
||||||
if (supportsSharedWorkers) {
|
if (supportsSharedWorkers) {
|
||||||
final sharedWorker =
|
final sharedWorker =
|
||||||
SharedWorker(driftWorkerUri.toString(), 'drift worker');
|
web.SharedWorker(driftWorkerUri.toString(), 'drift worker'.toJS);
|
||||||
final port = sharedWorker.port!;
|
final port = sharedWorker.port;
|
||||||
final shared = _sharedWorker = _DriftWorker.shared(sharedWorker, port);
|
final shared = _sharedWorker = _DriftWorker.shared(sharedWorker, port);
|
||||||
|
|
||||||
// First, the shared worker will tell us which features it supports.
|
// First, the shared worker will tell us which features it supports.
|
||||||
|
@ -161,40 +162,38 @@ class WasmDatabaseOpener {
|
||||||
}
|
}
|
||||||
|
|
||||||
final class _DriftWorker {
|
final class _DriftWorker {
|
||||||
final AbstractWorker worker;
|
/// Either a [web.SharedWorker] or a [web.Worker].
|
||||||
|
final JSObject worker;
|
||||||
ProtocolVersion version = ProtocolVersion.legacy;
|
ProtocolVersion version = ProtocolVersion.legacy;
|
||||||
|
|
||||||
/// The message port to communicate with the worker, if it's a shared worker.
|
/// The message port to communicate with the worker, if it's a shared worker.
|
||||||
final MessagePort? portForShared;
|
final web.MessagePort? portForShared;
|
||||||
|
|
||||||
final StreamQueue<WasmInitializationMessage> workerMessages;
|
final StreamQueue<WasmInitializationMessage> workerMessages;
|
||||||
|
|
||||||
_DriftWorker.dedicated(Worker this.worker)
|
_DriftWorker.dedicated(web.Worker this.worker)
|
||||||
: portForShared = null,
|
: portForShared = null,
|
||||||
workerMessages =
|
workerMessages = StreamQueue(_readMessages(worker, worker));
|
||||||
StreamQueue(_readMessages(worker.onMessage, worker.onError));
|
|
||||||
|
|
||||||
_DriftWorker.shared(SharedWorker this.worker, this.portForShared)
|
_DriftWorker.shared(web.SharedWorker this.worker, this.portForShared)
|
||||||
: workerMessages =
|
: workerMessages = StreamQueue(_readMessages(worker.port, worker)) {
|
||||||
StreamQueue(_readMessages(worker.port!.onMessage, worker.onError));
|
(worker as web.SharedWorker).port.start();
|
||||||
|
}
|
||||||
|
|
||||||
void send(Object? msg, [List<Object>? transfer]) {
|
void send(JSAny? msg, List<JSObject>? transfer) {
|
||||||
switch (worker) {
|
if (portForShared case final port?) {
|
||||||
case final Worker worker:
|
port.postMessage(msg, (transfer ?? const []).toJS);
|
||||||
worker.postMessage(msg, transfer);
|
} else {
|
||||||
case SharedWorker():
|
(worker as web.Worker).postMessage(msg, (transfer ?? const []).toJS);
|
||||||
portForShared!.postMessage(msg, transfer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
workerMessages.cancel();
|
workerMessages.cancel();
|
||||||
|
if (portForShared case final port?) {
|
||||||
switch (worker) {
|
port.close();
|
||||||
case final Worker dedicated:
|
} else {
|
||||||
dedicated.terminate();
|
(worker as web.Worker).terminate();
|
||||||
case SharedWorker():
|
|
||||||
portForShared!.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,9 +224,9 @@ final class _ProbeResult implements WasmProbeResult {
|
||||||
FutureOr<Uint8List?> Function()? initializeDatabase,
|
FutureOr<Uint8List?> Function()? initializeDatabase,
|
||||||
WasmDatabaseSetup? localSetup,
|
WasmDatabaseSetup? localSetup,
|
||||||
}) async {
|
}) async {
|
||||||
final channel = MessageChannel();
|
final channel = web.MessageChannel();
|
||||||
final initializer = initializeDatabase;
|
final initializer = initializeDatabase;
|
||||||
final initChannel = initializer != null ? MessageChannel() : null;
|
final initChannel = initializer != null ? web.MessageChannel() : null;
|
||||||
|
|
||||||
ServeDriftDatabase message;
|
ServeDriftDatabase message;
|
||||||
final sharedWorker = opener._sharedWorker;
|
final sharedWorker = opener._sharedWorker;
|
||||||
|
@ -276,18 +275,24 @@ final class _ProbeResult implements WasmProbeResult {
|
||||||
initializeDatabase, localSetup);
|
initializeDatabase, localSetup);
|
||||||
}
|
}
|
||||||
|
|
||||||
initChannel?.port1.onMessage.listen((event) async {
|
if (initChannel != null) {
|
||||||
// The worker hosting the database is asking for the initial blob because
|
initChannel.port1.start();
|
||||||
// the database doesn't exist.
|
web.EventStreamProviders.messageEvent
|
||||||
Uint8List? result;
|
.forTarget(initChannel.port1)
|
||||||
try {
|
.listen((event) async {
|
||||||
result = await initializer?.call();
|
// The worker hosting the database is asking for the initial blob because
|
||||||
} finally {
|
// the database doesn't exist.
|
||||||
initChannel.port1
|
Uint8List? result;
|
||||||
..postMessage(result, [if (result != null) result.buffer])
|
try {
|
||||||
..close();
|
result = await initializer?.call();
|
||||||
}
|
} finally {
|
||||||
});
|
initChannel.port1
|
||||||
|
..postMessage(
|
||||||
|
result?.toJS, [if (result != null) result.buffer.toJS].toJS)
|
||||||
|
..close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
final local = channel.port1
|
final local = channel.port1
|
||||||
.channel(explicitClose: message.protocolVersion >= ProtocolVersion.v1);
|
.channel(explicitClose: message.protocolVersion >= ProtocolVersion.v1);
|
||||||
|
@ -350,7 +355,13 @@ final class _ProbeResult implements WasmProbeResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<WasmInitializationMessage> _readMessages(
|
Stream<WasmInitializationMessage> _readMessages(
|
||||||
Stream<MessageEvent> messages, Stream<Event> errors) {
|
web.EventTarget messageTarget,
|
||||||
|
web.EventTarget errorTarget,
|
||||||
|
) {
|
||||||
|
final messages =
|
||||||
|
web.EventStreamProviders.messageEvent.forTarget(messageTarget);
|
||||||
|
final errors = web.EventStreamProviders.errorEvent.forTarget(errorTarget);
|
||||||
|
|
||||||
final mappedMessages = messages.map(WasmInitializationMessage.read);
|
final mappedMessages = messages.map(WasmInitializationMessage.read);
|
||||||
|
|
||||||
return Stream.multi((listener) {
|
return Stream.multi((listener) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
|
import 'dart:js_interop_unsafe';
|
||||||
|
|
||||||
import 'package:js/js_util.dart';
|
|
||||||
import 'package:sqlite3/wasm.dart';
|
import 'package:sqlite3/wasm.dart';
|
||||||
|
import 'package:web/web.dart'
|
||||||
|
show DedicatedWorkerGlobalScope, EventStreamProviders;
|
||||||
|
|
||||||
import '../../utils/synchronized.dart';
|
import '../../utils/synchronized.dart';
|
||||||
import 'protocol.dart';
|
import 'protocol.dart';
|
||||||
|
@ -22,7 +24,7 @@ class DedicatedDriftWorker {
|
||||||
: _servers = DriftServerController(setup);
|
: _servers = DriftServerController(setup);
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
self.onMessage.listen((event) {
|
EventStreamProviders.messageEvent.forTarget(self).listen((event) {
|
||||||
final message = WasmInitializationMessage.read(event);
|
final message = WasmInitializationMessage.read(event);
|
||||||
_handleMessage(message);
|
_handleMessage(message);
|
||||||
});
|
});
|
||||||
|
@ -69,11 +71,10 @@ class DedicatedDriftWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
DedicatedWorkerCompatibilityResult(
|
DedicatedWorkerCompatibilityResult(
|
||||||
supportsNestedWorkers: hasProperty(globalThis, 'Worker'),
|
supportsNestedWorkers: globalContext.has('Worker'),
|
||||||
canAccessOpfs: supportsOpfs,
|
canAccessOpfs: supportsOpfs,
|
||||||
supportsIndexedDb: supportsIndexedDb,
|
supportsIndexedDb: supportsIndexedDb,
|
||||||
supportsSharedArrayBuffers:
|
supportsSharedArrayBuffers: globalContext.has('SharedArrayBuffer'),
|
||||||
hasProperty(globalThis, 'SharedArrayBuffer'),
|
|
||||||
opfsExists: opfsExists,
|
opfsExists: opfsExists,
|
||||||
indexedDbExists: indexedDbExists,
|
indexedDbExists: indexedDbExists,
|
||||||
existingDatabases: existingDatabases,
|
existingDatabases: existingDatabases,
|
||||||
|
@ -83,7 +84,7 @@ class DedicatedDriftWorker {
|
||||||
_servers.serve(message);
|
_servers.serve(message);
|
||||||
case StartFileSystemServer(sqlite3Options: final options):
|
case StartFileSystemServer(sqlite3Options: final options):
|
||||||
final worker = await VfsWorker.create(options);
|
final worker = await VfsWorker.create(options);
|
||||||
self.postMessage(true);
|
self.postMessage(true.toJS);
|
||||||
await worker.start();
|
await worker.start();
|
||||||
case DeleteDatabase(database: (final storage, final name)):
|
case DeleteDatabase(database: (final storage, final name)):
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
import 'dart:js';
|
import 'dart:js_interop_unsafe';
|
||||||
|
|
||||||
import 'package:js/js_util.dart';
|
import 'package:web/web.dart' hide WorkerOptions;
|
||||||
import 'package:sqlite3/wasm.dart';
|
import 'package:sqlite3/wasm.dart';
|
||||||
|
|
||||||
import 'types.dart';
|
import 'types.dart';
|
||||||
|
@ -18,8 +18,8 @@ class ProtocolVersion {
|
||||||
|
|
||||||
const ProtocolVersion._(this.versionCode);
|
const ProtocolVersion._(this.versionCode);
|
||||||
|
|
||||||
void writeToJs(Object object) {
|
void writeToJs(JSObject object) {
|
||||||
setProperty(object, 'v', versionCode);
|
object['v'] = versionCode.toJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator >=(ProtocolVersion other) {
|
bool operator >=(ProtocolVersion other) {
|
||||||
|
@ -36,9 +36,9 @@ class ProtocolVersion {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static ProtocolVersion fromJsObject(Object object) {
|
static ProtocolVersion fromJsObject(JSObject object) {
|
||||||
if (hasProperty(object, 'v')) {
|
if (object.has('v')) {
|
||||||
return negotiate(getProperty<int>(object, 'v'));
|
return negotiate((object['v'] as JSNumber).toDartInt);
|
||||||
} else {
|
} else {
|
||||||
return legacy;
|
return legacy;
|
||||||
}
|
}
|
||||||
|
@ -58,52 +58,56 @@ class ProtocolVersion {
|
||||||
static const current = v1;
|
static const current = v1;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef PostMessage = void Function(Object? msg, [List<Object>? transfer]);
|
typedef PostMessage = void Function(JSObject? msg, List<JSObject>? transfer);
|
||||||
|
|
||||||
/// Sealed superclass for JavaScript objects exchanged between the UI tab and
|
/// Sealed superclass for JavaScript objects exchanged between the UI tab and
|
||||||
/// workers spawned by drift to find a suitable database implementation.
|
/// workers spawned by drift to find a suitable database implementation.
|
||||||
sealed class WasmInitializationMessage {
|
sealed class WasmInitializationMessage {
|
||||||
WasmInitializationMessage();
|
WasmInitializationMessage();
|
||||||
|
|
||||||
factory WasmInitializationMessage.fromJs(Object jsObject) {
|
factory WasmInitializationMessage.fromJs(JSObject jsObject) {
|
||||||
final type = getProperty<String>(jsObject, 'type');
|
final type = (jsObject['type'] as JSString).toDart;
|
||||||
final payload = getProperty<Object?>(jsObject, 'payload');
|
final payload = jsObject['payload'];
|
||||||
|
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
WorkerError.type => WorkerError.fromJsPayload(payload!),
|
WorkerError.type => WorkerError.fromJsPayload(payload as JSObject),
|
||||||
ServeDriftDatabase.type => ServeDriftDatabase.fromJsPayload(payload!),
|
ServeDriftDatabase.type =>
|
||||||
|
ServeDriftDatabase.fromJsPayload(payload as JSObject),
|
||||||
StartFileSystemServer.type =>
|
StartFileSystemServer.type =>
|
||||||
StartFileSystemServer.fromJsPayload(payload!),
|
StartFileSystemServer.fromJsPayload(payload as JSObject),
|
||||||
RequestCompatibilityCheck.type =>
|
RequestCompatibilityCheck.type =>
|
||||||
RequestCompatibilityCheck.fromJsPayload(payload),
|
RequestCompatibilityCheck.fromJsPayload(payload),
|
||||||
DedicatedWorkerCompatibilityResult.type =>
|
DedicatedWorkerCompatibilityResult.type =>
|
||||||
DedicatedWorkerCompatibilityResult.fromJsPayload(payload!),
|
DedicatedWorkerCompatibilityResult.fromJsPayload(payload as JSObject),
|
||||||
SharedWorkerCompatibilityResult.type =>
|
SharedWorkerCompatibilityResult.type =>
|
||||||
SharedWorkerCompatibilityResult.fromJsPayload(payload!),
|
SharedWorkerCompatibilityResult.fromJsPayload(payload as JSArray),
|
||||||
DeleteDatabase.type => DeleteDatabase.fromJsPayload(payload!),
|
DeleteDatabase.type => DeleteDatabase.fromJsPayload(payload as JSAny),
|
||||||
_ => throw ArgumentError('Unknown type $type'),
|
_ => throw ArgumentError('Unknown type $type'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
factory WasmInitializationMessage.read(MessageEvent event) {
|
factory WasmInitializationMessage.read(MessageEvent event) {
|
||||||
// Not using event.data because we don't want the SDK to dartify the raw JS
|
return WasmInitializationMessage.fromJs(event.data as JSObject);
|
||||||
// object we're passing around.
|
|
||||||
final rawData = getProperty<Object>(event, 'data');
|
|
||||||
return WasmInitializationMessage.fromJs(rawData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendTo(PostMessage sender);
|
void sendTo(PostMessage sender);
|
||||||
|
|
||||||
void sendToWorker(Worker worker) {
|
void sendToWorker(Worker worker) {
|
||||||
sendTo(worker.postMessage);
|
sendTo((msg, transfer) {
|
||||||
|
worker.postMessage(msg, (transfer ?? const []).toJS);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendToPort(MessagePort port) {
|
void sendToPort(MessagePort port) {
|
||||||
sendTo(port.postMessage);
|
sendTo((msg, transfer) {
|
||||||
|
port.postMessage(msg, (transfer ?? const []).toJS);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendToClient(DedicatedWorkerGlobalScope worker) {
|
void sendToClient(DedicatedWorkerGlobalScope worker) {
|
||||||
sendTo(worker.postMessage);
|
sendTo((msg, transfer) {
|
||||||
|
worker.postMessage(msg, (transfer ?? const []).toJS);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,16 +160,15 @@ final class SharedWorkerCompatibilityResult extends CompatibilityResult {
|
||||||
required super.version,
|
required super.version,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory SharedWorkerCompatibilityResult.fromJsPayload(Object payload) {
|
factory SharedWorkerCompatibilityResult.fromJsPayload(JSArray payload) {
|
||||||
final asList = payload as List;
|
final asList = payload.toDart;
|
||||||
final asBooleans = asList.cast<bool>();
|
final asBooleans = asList.cast<bool>();
|
||||||
|
|
||||||
final List<ExistingDatabase> existingDatabases;
|
final List<ExistingDatabase> existingDatabases;
|
||||||
var version = ProtocolVersion.legacy;
|
var version = ProtocolVersion.legacy;
|
||||||
|
|
||||||
if (asList.length > 5) {
|
if (asList.length > 5) {
|
||||||
existingDatabases =
|
existingDatabases = EncodeLocations.readFromJs(asList[5] as JSArray);
|
||||||
EncodeLocations.readFromJs(asList[5] as List<dynamic>);
|
|
||||||
|
|
||||||
if (asList.length > 6) {
|
if (asList.length > 6) {
|
||||||
version = ProtocolVersion.negotiate(asList[6] as int);
|
version = ProtocolVersion.negotiate(asList[6] as int);
|
||||||
|
@ -187,15 +190,17 @@ final class SharedWorkerCompatibilityResult extends CompatibilityResult {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendTo(PostMessage sender) {
|
void sendTo(PostMessage sender) {
|
||||||
sender.sendTyped(type, [
|
sender.sendTyped(
|
||||||
canSpawnDedicatedWorkers,
|
type,
|
||||||
dedicatedWorkersCanUseOpfs,
|
[
|
||||||
canUseIndexedDb,
|
canSpawnDedicatedWorkers.toJS,
|
||||||
indexedDbExists,
|
dedicatedWorkersCanUseOpfs.toJS,
|
||||||
opfsExists,
|
canUseIndexedDb.toJS,
|
||||||
existingDatabases.encodeToJs(),
|
indexedDbExists.toJS,
|
||||||
version.versionCode,
|
opfsExists.toJS,
|
||||||
]);
|
existingDatabases.encodeToJs(),
|
||||||
|
version.versionCode.toJS,
|
||||||
|
].toJS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -216,13 +221,13 @@ final class WorkerError extends WasmInitializationMessage implements Exception {
|
||||||
|
|
||||||
WorkerError(this.error);
|
WorkerError(this.error);
|
||||||
|
|
||||||
factory WorkerError.fromJsPayload(Object payload) {
|
factory WorkerError.fromJsPayload(JSObject payload) {
|
||||||
return WorkerError(payload as String);
|
return WorkerError((payload as JSString).toDart);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendTo(PostMessage sender) {
|
void sendTo(PostMessage sender) {
|
||||||
sender.sendTyped(type, error);
|
sender.sendTyped(type, error.toJS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -252,32 +257,32 @@ final class ServeDriftDatabase extends WasmInitializationMessage {
|
||||||
required this.protocolVersion,
|
required this.protocolVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ServeDriftDatabase.fromJsPayload(Object payload) {
|
factory ServeDriftDatabase.fromJsPayload(JSObject payload) {
|
||||||
return ServeDriftDatabase(
|
return ServeDriftDatabase(
|
||||||
sqlite3WasmUri: Uri.parse(getProperty(payload, 'sqlite')),
|
sqlite3WasmUri: Uri.parse((payload['sqlite'] as JSString).toDart),
|
||||||
port: getProperty(payload, 'port'),
|
port: payload['port'] as MessagePort,
|
||||||
storage: WasmStorageImplementation.values
|
storage: WasmStorageImplementation.values
|
||||||
.byName(getProperty(payload, 'storage')),
|
.byName((payload['storage'] as JSString).toDart),
|
||||||
databaseName: getProperty(payload, 'database'),
|
databaseName: (payload['database'] as JSString).toDart,
|
||||||
initializationPort: getProperty(payload, 'initPort'),
|
initializationPort: payload['initPort'] as MessagePort?,
|
||||||
protocolVersion: ProtocolVersion.fromJsObject(payload),
|
protocolVersion: ProtocolVersion.fromJsObject(payload),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendTo(PostMessage sender) {
|
void sendTo(PostMessage sender) {
|
||||||
final object = newObject<Object>();
|
final object = JSObject()
|
||||||
setProperty(object, 'sqlite', sqlite3WasmUri.toString());
|
..['sqlite'] = sqlite3WasmUri.toString().toJS
|
||||||
setProperty(object, 'port', port);
|
..['port'] = port
|
||||||
setProperty(object, 'storage', storage.name);
|
..['storage'] = storage.name.toJS
|
||||||
setProperty(object, 'database', databaseName);
|
..['database'] = databaseName.toJS
|
||||||
final initPort = initializationPort;
|
..['initPort'] = initializationPort;
|
||||||
setProperty(object, 'initPort', initPort);
|
|
||||||
protocolVersion.writeToJs(object);
|
protocolVersion.writeToJs(object);
|
||||||
|
|
||||||
sender.sendTyped(type, object, [
|
sender.sendTyped(type, object, [
|
||||||
port,
|
port,
|
||||||
if (initPort != null) initPort,
|
if (initializationPort != null) initializationPort!,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,13 +298,13 @@ final class RequestCompatibilityCheck extends WasmInitializationMessage {
|
||||||
|
|
||||||
RequestCompatibilityCheck(this.databaseName);
|
RequestCompatibilityCheck(this.databaseName);
|
||||||
|
|
||||||
factory RequestCompatibilityCheck.fromJsPayload(Object? payload) {
|
factory RequestCompatibilityCheck.fromJsPayload(JSAny? payload) {
|
||||||
return RequestCompatibilityCheck(payload as String);
|
return RequestCompatibilityCheck((payload as JSString).toDart);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendTo(PostMessage sender) {
|
void sendTo(PostMessage sender) {
|
||||||
sender.sendTyped(type, databaseName);
|
sender.sendTyped(type, databaseName.toJS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,22 +327,23 @@ final class DedicatedWorkerCompatibilityResult extends CompatibilityResult {
|
||||||
required super.version,
|
required super.version,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DedicatedWorkerCompatibilityResult.fromJsPayload(Object payload) {
|
factory DedicatedWorkerCompatibilityResult.fromJsPayload(JSObject payload) {
|
||||||
final existingDatabases = <ExistingDatabase>[];
|
final existingDatabases = <ExistingDatabase>[];
|
||||||
|
|
||||||
if (hasProperty(payload, 'existing')) {
|
if (payload.has('existing')) {
|
||||||
existingDatabases
|
existingDatabases
|
||||||
.addAll(EncodeLocations.readFromJs(getProperty(payload, 'existing')));
|
.addAll(EncodeLocations.readFromJs(payload['existing'] as JSArray));
|
||||||
}
|
}
|
||||||
|
|
||||||
return DedicatedWorkerCompatibilityResult(
|
return DedicatedWorkerCompatibilityResult(
|
||||||
supportsNestedWorkers: getProperty(payload, 'supportsNestedWorkers'),
|
supportsNestedWorkers:
|
||||||
canAccessOpfs: getProperty(payload, 'canAccessOpfs'),
|
(payload['supportsNestedWorkers'] as JSBoolean).toDart,
|
||||||
|
canAccessOpfs: (payload['canAccessOpfs'] as JSBoolean).toDart,
|
||||||
supportsSharedArrayBuffers:
|
supportsSharedArrayBuffers:
|
||||||
getProperty(payload, 'supportsSharedArrayBuffers'),
|
(payload['supportsSharedArrayBuffers'] as JSBoolean).toDart,
|
||||||
supportsIndexedDb: getProperty(payload, 'supportsIndexedDb'),
|
supportsIndexedDb: (payload['supportsIndexedDb'] as JSBoolean).toDart,
|
||||||
indexedDbExists: getProperty(payload, 'indexedDbExists'),
|
indexedDbExists: (payload['indexedDbExists'] as JSBoolean).toDart,
|
||||||
opfsExists: getProperty(payload, 'opfsExists'),
|
opfsExists: (payload['opfsExists'] as JSBoolean).toDart,
|
||||||
existingDatabases: existingDatabases,
|
existingDatabases: existingDatabases,
|
||||||
version: ProtocolVersion.fromJsObject(payload),
|
version: ProtocolVersion.fromJsObject(payload),
|
||||||
);
|
);
|
||||||
|
@ -345,16 +351,14 @@ final class DedicatedWorkerCompatibilityResult extends CompatibilityResult {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendTo(PostMessage sender) {
|
void sendTo(PostMessage sender) {
|
||||||
final object = newObject<Object>();
|
final object = JSObject()
|
||||||
|
..['supportsNestedWorkers'] = supportsNestedWorkers.toJS
|
||||||
setProperty(object, 'supportsNestedWorkers', supportsNestedWorkers);
|
..['canAccessOpfs'] = canAccessOpfs.toJS
|
||||||
setProperty(object, 'canAccessOpfs', canAccessOpfs);
|
..['supportsIndexedDb'] = supportsIndexedDb.toJS
|
||||||
setProperty(object, 'supportsIndexedDb', supportsIndexedDb);
|
..['supportsSharedArrayBuffers'] = supportsSharedArrayBuffers.toJS
|
||||||
setProperty(
|
..['indexedDbExists'] = indexedDbExists.toJS
|
||||||
object, 'supportsSharedArrayBuffers', supportsSharedArrayBuffers);
|
..['opfsExists'] = opfsExists.toJS
|
||||||
setProperty(object, 'indexedDbExists', indexedDbExists);
|
..['existing'] = existingDatabases.encodeToJs();
|
||||||
setProperty(object, 'opfsExists', opfsExists);
|
|
||||||
setProperty(object, 'existing', existingDatabases.encodeToJs());
|
|
||||||
version.writeToJs(object);
|
version.writeToJs(object);
|
||||||
|
|
||||||
sender.sendTyped(type, object);
|
sender.sendTyped(type, object);
|
||||||
|
@ -381,13 +385,13 @@ final class StartFileSystemServer extends WasmInitializationMessage {
|
||||||
|
|
||||||
StartFileSystemServer(this.sqlite3Options);
|
StartFileSystemServer(this.sqlite3Options);
|
||||||
|
|
||||||
factory StartFileSystemServer.fromJsPayload(Object payload) {
|
factory StartFileSystemServer.fromJsPayload(JSObject payload) {
|
||||||
return StartFileSystemServer(payload as WorkerOptions);
|
return StartFileSystemServer(payload as WorkerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendTo(PostMessage sender) {
|
void sendTo(PostMessage sender) {
|
||||||
sender.sendTyped(type, sqlite3Options);
|
sender.sendTyped(type, sqlite3Options as JSObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,53 +402,51 @@ final class DeleteDatabase extends WasmInitializationMessage {
|
||||||
|
|
||||||
DeleteDatabase(this.database);
|
DeleteDatabase(this.database);
|
||||||
|
|
||||||
factory DeleteDatabase.fromJsPayload(Object payload) {
|
factory DeleteDatabase.fromJsPayload(JSAny payload) {
|
||||||
final asList = payload as List<Object?>;
|
final asList = (payload as JSArray).toDart;
|
||||||
return DeleteDatabase((
|
return DeleteDatabase((
|
||||||
WebStorageApi.byName[asList[0] as String]!,
|
WebStorageApi.byName[(asList[0] as JSString).toDart]!,
|
||||||
asList[1] as String,
|
(asList[1] as JSString).toDart,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendTo(PostMessage sender) {
|
void sendTo(PostMessage sender) {
|
||||||
sender.sendTyped(type, [database.$1.name, database.$2]);
|
sender.sendTyped(type, [database.$1.name.toJS, database.$2.toJS].toJS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EncodeLocations on List<ExistingDatabase> {
|
extension EncodeLocations on List<ExistingDatabase> {
|
||||||
static List<ExistingDatabase> readFromJs(List<Object?> object) {
|
static List<ExistingDatabase> readFromJs(JSArray object) {
|
||||||
final existing = <ExistingDatabase>[];
|
final existing = <ExistingDatabase>[];
|
||||||
|
|
||||||
for (final entry in object) {
|
for (final entry in object.toDart.cast<JSObject>()) {
|
||||||
existing.add((
|
existing.add((
|
||||||
WebStorageApi.byName[getProperty(entry as Object, 'l')]!,
|
WebStorageApi.byName[(entry['l'] as JSString).toDart]!,
|
||||||
getProperty(entry, 'n'),
|
(entry['n'] as JSString).toDart,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encodeToJs() {
|
JSObject encodeToJs() {
|
||||||
final existing = JsArray<Object>();
|
final existing = <JSObject>[];
|
||||||
for (final entry in this) {
|
for (final entry in this) {
|
||||||
final object = newObject<Object>();
|
existing.add(JSObject()
|
||||||
setProperty(object, 'l', entry.$1.name);
|
..['l'] = entry.$1.name.toJS
|
||||||
setProperty(object, 'n', entry.$2);
|
..['n'] = entry.$2.toJS);
|
||||||
|
|
||||||
existing.add(object);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return existing;
|
return existing.toJS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on PostMessage {
|
extension on PostMessage {
|
||||||
void sendTyped(String type, Object? payload, [List<Object>? transfer]) {
|
void sendTyped(String type, JSAny? payload, [List<JSObject>? transfer]) {
|
||||||
final object = newObject<Object>();
|
final object = JSObject()
|
||||||
setProperty(object, 'type', type);
|
..['type'] = type.toJS
|
||||||
setProperty(object, 'payload', payload);
|
..['payload'] = payload;
|
||||||
|
|
||||||
call(object, transfer);
|
call(object, transfer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
import 'dart:indexed_db';
|
import 'dart:js_interop_unsafe';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift/remote.dart';
|
import 'package:drift/remote.dart';
|
||||||
import 'package:drift/wasm.dart';
|
import 'package:drift/wasm.dart';
|
||||||
import 'package:js/js_util.dart';
|
import 'package:web/web.dart'
|
||||||
|
show
|
||||||
|
Worker,
|
||||||
|
IDBFactory,
|
||||||
|
IDBRequest,
|
||||||
|
IDBDatabase,
|
||||||
|
IDBVersionChangeEvent,
|
||||||
|
EventStreamProviders,
|
||||||
|
MessageEvent;
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:sqlite3/src/wasm/js_interop/file_system_access.dart';
|
import 'package:sqlite3/src/wasm/js_interop/file_system_access.dart';
|
||||||
import 'package:sqlite3/wasm.dart';
|
import 'package:sqlite3/wasm.dart';
|
||||||
import 'package:stream_channel/stream_channel.dart';
|
import 'package:stream_channel/stream_channel.dart';
|
||||||
|
|
||||||
import '../channel.dart';
|
import '../new_channel.dart';
|
||||||
import 'protocol.dart';
|
import 'protocol.dart';
|
||||||
|
|
||||||
/// Checks whether the OPFS API is likely to be correctly implemented in the
|
/// Checks whether the OPFS API is likely to be correctly implemented in the
|
||||||
|
@ -38,10 +46,10 @@ Future<bool> checkOpfsSupport() async {
|
||||||
// In earlier versions of the OPFS standard, some methods like `getSize()`
|
// In earlier versions of the OPFS standard, some methods like `getSize()`
|
||||||
// on a sync file handle have actually been asynchronous. We don't support
|
// on a sync file handle have actually been asynchronous. We don't support
|
||||||
// Browsers that implement the outdated spec.
|
// Browsers that implement the outdated spec.
|
||||||
final getSizeResult = callMethod<Object?>(openedFile, 'getSize', []);
|
final getSizeResult = (openedFile as JSObject).callMethod('getSize'.toJS);
|
||||||
if (typeofEquals<Object?>(getSizeResult, 'object')) {
|
if (getSizeResult.typeofEquals('object')) {
|
||||||
// Returned a promise, that's no good.
|
// Returned a promise, that's no good.
|
||||||
await promiseToFuture<Object?>(getSizeResult!);
|
await (getSizeResult as JSPromise).toDart;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,18 +69,18 @@ Future<bool> checkOpfsSupport() async {
|
||||||
|
|
||||||
/// Checks whether IndexedDB is working in the current browser.
|
/// Checks whether IndexedDB is working in the current browser.
|
||||||
Future<bool> checkIndexedDbSupport() async {
|
Future<bool> checkIndexedDbSupport() async {
|
||||||
if (!hasProperty(globalThis, 'indexedDB') ||
|
if (!globalContext.has('indexedDB') ||
|
||||||
// FileReader needed to read and write blobs efficiently
|
// FileReader needed to read and write blobs efficiently
|
||||||
!hasProperty(globalThis, 'FileReader')) {
|
!globalContext.has('FileReader')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final idb = getProperty<IdbFactory>(globalThis, 'indexedDB');
|
final idb = globalContext['indexedDB'] as IDBFactory;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const name = 'drift_mock_db';
|
const name = 'drift_mock_db';
|
||||||
|
|
||||||
final mockDb = await idb.open(name);
|
final mockDb = await idb.open(name).complete<IDBDatabase>();
|
||||||
mockDb.close();
|
mockDb.close();
|
||||||
idb.deleteDatabase(name);
|
idb.deleteDatabase(name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -87,19 +95,16 @@ Future<bool> checkIndexedDbExists(String databaseName) async {
|
||||||
bool? indexedDbExists;
|
bool? indexedDbExists;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final idb = getProperty<IdbFactory>(globalThis, 'indexedDB');
|
final idb = globalContext['indexedDB'] as IDBFactory;
|
||||||
|
|
||||||
final database = await idb.open(
|
final openRequest = idb.open(databaseName, 1);
|
||||||
databaseName,
|
openRequest.onupgradeneeded = (IDBVersionChangeEvent event) {
|
||||||
// Current schema version used by the [IndexedDbFileSystem]
|
// If there's an upgrade, we're going from 0 to 1 - the database doesn't
|
||||||
version: 1,
|
// exist! Abort the transaction so that we don't create it here.
|
||||||
onUpgradeNeeded: (event) {
|
openRequest.transaction!.abort();
|
||||||
// If there's an upgrade, we're going from 0 to 1 - the database doesn't
|
indexedDbExists = false;
|
||||||
// exist! Abort the transaction so that we don't create it here.
|
}.toJS;
|
||||||
event.target.transaction!.abort();
|
final database = await openRequest.complete<IDBDatabase>();
|
||||||
indexedDbExists = false;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
indexedDbExists ??= true;
|
indexedDbExists ??= true;
|
||||||
database.close();
|
database.close();
|
||||||
|
@ -112,9 +117,9 @@ Future<bool> checkIndexedDbExists(String databaseName) async {
|
||||||
|
|
||||||
/// Deletes a database from IndexedDb if supported.
|
/// Deletes a database from IndexedDb if supported.
|
||||||
Future<void> deleteDatabaseInIndexedDb(String databaseName) async {
|
Future<void> deleteDatabaseInIndexedDb(String databaseName) async {
|
||||||
final idb = window.indexedDB;
|
if (globalContext.has('indexedDB')) {
|
||||||
if (idb != null) {
|
final idb = globalContext['indexedDB'] as IDBFactory;
|
||||||
await idb.deleteDatabase(databaseName);
|
await idb.deleteDatabase(databaseName).complete<JSAny?>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,12 +186,16 @@ class DriftServerController {
|
||||||
final initPort = message.initializationPort;
|
final initPort = message.initializationPort;
|
||||||
|
|
||||||
final initializer = initPort != null
|
final initializer = initPort != null
|
||||||
? () async {
|
? () {
|
||||||
initPort.postMessage(true);
|
final completer = Completer<Uint8List?>();
|
||||||
|
initPort.postMessage(true.toJS);
|
||||||
|
|
||||||
return await initPort.onMessage
|
initPort.onmessage = (MessageEvent e) {
|
||||||
.map((e) => e.data as Uint8List?)
|
final data = (e.data as JSUint8Array?);
|
||||||
.first;
|
completer.complete(data?.toDart);
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
@ -269,7 +278,7 @@ class DriftServerController {
|
||||||
StartFileSystemServer(options).sendToWorker(worker);
|
StartFileSystemServer(options).sendToWorker(worker);
|
||||||
|
|
||||||
// Wait for the server worker to report that it's ready
|
// Wait for the server worker to report that it's ready
|
||||||
await worker.onMessage.first;
|
await EventStreamProviders.messageEvent.forTarget(worker).first;
|
||||||
|
|
||||||
return WasmVfs(workerOptions: options);
|
return WasmVfs(workerOptions: options);
|
||||||
}
|
}
|
||||||
|
@ -349,3 +358,21 @@ extension StorageClassification on WasmStorageImplementation {
|
||||||
this == WasmStorageImplementation.sharedIndexedDb ||
|
this == WasmStorageImplementation.sharedIndexedDb ||
|
||||||
this == WasmStorageImplementation.unsafeIndexedDb;
|
this == WasmStorageImplementation.unsafeIndexedDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Utilities to complete an IndexedDB request.
|
||||||
|
extension CompleteIdbRequest on IDBRequest {
|
||||||
|
/// Turns this request into a Dart future that completes with the first
|
||||||
|
/// success or error event.
|
||||||
|
Future<T> complete<T extends JSAny?>() {
|
||||||
|
final completer = Completer<T>.sync();
|
||||||
|
|
||||||
|
EventStreamProviders.successEvent.forTarget(this).listen((event) {
|
||||||
|
completer.complete(result as T);
|
||||||
|
});
|
||||||
|
EventStreamProviders.errorEvent.forTarget(this).listen((event) {
|
||||||
|
completer.completeError(error ?? event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
|
|
||||||
import 'package:js/js_util.dart';
|
import 'package:web/web.dart';
|
||||||
|
|
||||||
import '../wasm_setup.dart';
|
import '../wasm_setup.dart';
|
||||||
import 'protocol.dart';
|
import 'protocol.dart';
|
||||||
|
@ -22,13 +22,15 @@ class SharedDriftWorker {
|
||||||
: _servers = DriftServerController(setup);
|
: _servers = DriftServerController(setup);
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
const event = EventStreamProvider<MessageEvent>('connect');
|
const event = EventStreamProviders.connectEvent;
|
||||||
event.forTarget(self).listen(_newConnection);
|
event.forTarget(self).listen((e) => _newConnection(e as MessageEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _newConnection(MessageEvent event) async {
|
void _newConnection(MessageEvent event) async {
|
||||||
final clientPort = event.ports[0];
|
final clientPort = event.ports.toDart[0];
|
||||||
clientPort.onMessage
|
clientPort.start();
|
||||||
|
EventStreamProviders.messageEvent
|
||||||
|
.forTarget(clientPort)
|
||||||
.listen((event) => _messageFromClient(clientPort, event));
|
.listen((event) => _messageFromClient(clientPort, event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,9 +113,9 @@ class SharedDriftWorker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageSubscription = worker.onMessage.listen((event) {
|
messageSubscription =
|
||||||
final data =
|
EventStreamProviders.messageEvent.forTarget(worker).listen((event) {
|
||||||
WasmInitializationMessage.fromJs(getProperty(event, 'data'));
|
final data = WasmInitializationMessage.read(event);
|
||||||
final compatibilityResult = data as DedicatedWorkerCompatibilityResult;
|
final compatibilityResult = data as DedicatedWorkerCompatibilityResult;
|
||||||
|
|
||||||
result(
|
result(
|
||||||
|
@ -124,7 +126,8 @@ class SharedDriftWorker {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
errorSubscription = worker.onError.listen((event) {
|
errorSubscription =
|
||||||
|
EventStreamProviders.errorEvent.forTarget(worker).listen((event) {
|
||||||
result(false, false, false, const []);
|
result(false, false, false, const []);
|
||||||
worker.terminate();
|
worker.terminate();
|
||||||
_dedicatedWorker = null;
|
_dedicatedWorker = null;
|
||||||
|
|
|
@ -7,15 +7,17 @@
|
||||||
library drift.wasm;
|
library drift.wasm;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/src/web/wasm_setup.dart';
|
import 'package:web/web.dart'
|
||||||
|
show DedicatedWorkerGlobalScope, SharedWorkerGlobalScope;
|
||||||
import 'package:sqlite3/wasm.dart';
|
import 'package:sqlite3/wasm.dart';
|
||||||
|
|
||||||
import 'backends.dart';
|
import 'backends.dart';
|
||||||
import 'src/sqlite3/database.dart';
|
import 'src/sqlite3/database.dart';
|
||||||
|
import 'src/web/wasm_setup.dart';
|
||||||
import 'src/web/wasm_setup/dedicated_worker.dart';
|
import 'src/web/wasm_setup/dedicated_worker.dart';
|
||||||
import 'src/web/wasm_setup/shared_worker.dart';
|
import 'src/web/wasm_setup/shared_worker.dart';
|
||||||
import 'src/web/wasm_setup/types.dart';
|
import 'src/web/wasm_setup/types.dart';
|
||||||
|
@ -205,12 +207,15 @@ class WasmDatabase extends DelegatedDatabase {
|
||||||
static void workerMainForOpen({
|
static void workerMainForOpen({
|
||||||
WasmDatabaseSetup? setupAllDatabases,
|
WasmDatabaseSetup? setupAllDatabases,
|
||||||
}) {
|
}) {
|
||||||
final self = WorkerGlobalScope.instance;
|
final self = globalContext;
|
||||||
|
|
||||||
if (self is DedicatedWorkerGlobalScope) {
|
if (self.instanceOfString('DedicatedWorkerGlobalScope')) {
|
||||||
DedicatedDriftWorker(self, setupAllDatabases).start();
|
DedicatedDriftWorker(
|
||||||
} else if (self is SharedWorkerGlobalScope) {
|
self as DedicatedWorkerGlobalScope, setupAllDatabases)
|
||||||
SharedDriftWorker(self, setupAllDatabases).start();
|
.start();
|
||||||
|
} else if (self.instanceOfString('SharedWorkerGlobalScope')) {
|
||||||
|
SharedDriftWorker(self as SharedWorkerGlobalScope, setupAllDatabases)
|
||||||
|
.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,4 +10,4 @@ import 'package:meta/meta.dart';
|
||||||
export 'src/web/sql_js.dart';
|
export 'src/web/sql_js.dart';
|
||||||
export 'src/web/storage.dart' hide CustomSchemaVersionSave;
|
export 'src/web/storage.dart' hide CustomSchemaVersionSave;
|
||||||
export 'src/web/web_db.dart';
|
export 'src/web/web_db.dart';
|
||||||
export 'src/web/channel.dart';
|
export 'src/web/channel.dart' show PortToChannel;
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
name: drift
|
name: drift
|
||||||
description: Drift is a reactive library to store relational data in Dart and Flutter applications.
|
description: Drift is a reactive library to store relational data in Dart and Flutter applications.
|
||||||
version: 2.15.0
|
version: 2.16.0
|
||||||
repository: https://github.com/simolus3/drift
|
repository: https://github.com/simolus3/drift
|
||||||
homepage: https://drift.simonbinder.eu/
|
homepage: https://drift.simonbinder.eu/
|
||||||
issue_tracker: https://github.com/simolus3/drift/issues
|
issue_tracker: https://github.com/simolus3/drift/issues
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
async: ^2.5.0
|
async: ^2.5.0
|
||||||
convert: ^3.0.0
|
convert: ^3.0.0
|
||||||
collection: ^1.15.0
|
collection: ^1.15.0
|
||||||
js: ^0.6.3
|
js: '>=0.6.3 <0.8.0'
|
||||||
meta: ^1.3.0
|
meta: ^1.3.0
|
||||||
stream_channel: ^2.1.0
|
stream_channel: ^2.1.0
|
||||||
sqlite3: ^2.4.0
|
sqlite3: ^2.4.0
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
stack_trace: ^1.11.1
|
stack_trace: ^1.11.1
|
||||||
|
web: ^0.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
archive: ^3.3.1
|
archive: ^3.3.1
|
||||||
|
analyzer: ^6.4.1
|
||||||
build_test: ^2.0.0
|
build_test: ^2.0.0
|
||||||
build_runner_core: ^7.0.0
|
build_runner_core: ^7.0.0
|
||||||
build_verify: ^3.0.0
|
build_verify: ^3.0.0
|
||||||
|
|
|
@ -35,14 +35,14 @@ void main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
b.replaceAll(db.categories, const [
|
b.replaceAll(db.categories, const [
|
||||||
CategoriesCompanion(id: Value(1), description: Value('new1')),
|
CategoriesCompanion(id: Value(RowId(1)), description: Value('new1')),
|
||||||
CategoriesCompanion(id: Value(2), description: Value('new2')),
|
CategoriesCompanion(id: Value(RowId(2)), description: Value('new2')),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
b.deleteWhere<$CategoriesTable, Category>(
|
b.deleteWhere<$CategoriesTable, Category>(
|
||||||
db.categories, (tbl) => tbl.id.equals(1));
|
db.categories, (tbl) => tbl.id.equals(1));
|
||||||
b.deleteAll(db.categories);
|
b.deleteAll(db.categories);
|
||||||
b.delete(db.todosTable, const TodosTableCompanion(id: Value(3)));
|
b.delete(db.todosTable, const TodosTableCompanion(id: Value(RowId(3))));
|
||||||
|
|
||||||
b.update(db.users, const UsersCompanion(name: Value('new name 2')));
|
b.update(db.users, const UsersCompanion(name: Value('new name 2')));
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ void main() {
|
||||||
db.categories,
|
db.categories,
|
||||||
CategoriesCompanion.insert(description: 'description'),
|
CategoriesCompanion.insert(description: 'description'),
|
||||||
onConflict: DoUpdate((old) {
|
onConflict: DoUpdate((old) {
|
||||||
return const CategoriesCompanion(id: Value(42));
|
return const CategoriesCompanion(id: Value(RowId(42)));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -203,16 +203,17 @@ void main() {
|
||||||
|
|
||||||
test('updates stream queries', () async {
|
test('updates stream queries', () async {
|
||||||
await db.batch((b) {
|
await db.batch((b) {
|
||||||
b.insert(db.todosTable, const TodoEntry(id: 3, content: 'content'));
|
b.insert(
|
||||||
|
db.todosTable, const TodoEntry(id: RowId(3), content: 'content'));
|
||||||
|
|
||||||
b.update(db.users, const UsersCompanion(name: Value('new user name')));
|
b.update(db.users, const UsersCompanion(name: Value('new user name')));
|
||||||
b.replace(
|
b.replace(
|
||||||
db.todosTable,
|
db.todosTable,
|
||||||
const TodosTableCompanion(id: Value(3), content: Value('new')),
|
const TodosTableCompanion(id: Value(RowId(3)), content: Value('new')),
|
||||||
);
|
);
|
||||||
|
|
||||||
b.deleteWhere(db.todosTable, (TodosTable row) => row.id.equals(3));
|
b.deleteWhere(db.todosTable, (TodosTable row) => row.id.equals(3));
|
||||||
b.delete(db.todosTable, const TodosTableCompanion(id: Value(3)));
|
b.delete(db.todosTable, const TodosTableCompanion(id: Value(RowId(3))));
|
||||||
});
|
});
|
||||||
|
|
||||||
verify(
|
verify(
|
||||||
|
|
|
@ -6,7 +6,7 @@ import '../generated/todos.dart';
|
||||||
void main() {
|
void main() {
|
||||||
test('data classes can be serialized', () {
|
test('data classes can be serialized', () {
|
||||||
final entry = TodoEntry(
|
final entry = TodoEntry(
|
||||||
id: 13,
|
id: RowId(13),
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
targetDate: DateTime.now(),
|
targetDate: DateTime.now(),
|
||||||
|
@ -36,7 +36,7 @@ void main() {
|
||||||
driftRuntimeOptions.defaultSerializer = _MySerializer();
|
driftRuntimeOptions.defaultSerializer = _MySerializer();
|
||||||
|
|
||||||
final entry = TodoEntry(
|
final entry = TodoEntry(
|
||||||
id: 13,
|
id: RowId(13),
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
category: 3,
|
category: 3,
|
||||||
|
@ -59,7 +59,7 @@ void main() {
|
||||||
|
|
||||||
test('can serialize and deserialize blob columns', () {
|
test('can serialize and deserialize blob columns', () {
|
||||||
final user = User(
|
final user = User(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
name: 'Username',
|
name: 'Username',
|
||||||
isAwesome: true,
|
isAwesome: true,
|
||||||
profilePicture: Uint8List.fromList(const [1, 2, 3, 4]),
|
profilePicture: Uint8List.fromList(const [1, 2, 3, 4]),
|
||||||
|
@ -79,7 +79,7 @@ void main() {
|
||||||
|
|
||||||
test('generated data classes can be converted to companions', () {
|
test('generated data classes can be converted to companions', () {
|
||||||
const entry = Category(
|
const entry = Category(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
description: 'description',
|
description: 'description',
|
||||||
priority: CategoryPriority.low,
|
priority: CategoryPriority.low,
|
||||||
descriptionInUpperCase: 'ignored',
|
descriptionInUpperCase: 'ignored',
|
||||||
|
@ -91,7 +91,7 @@ void main() {
|
||||||
companion,
|
companion,
|
||||||
equals(CategoriesCompanion.insert(
|
equals(CategoriesCompanion.insert(
|
||||||
description: 'description',
|
description: 'description',
|
||||||
id: const Value(3),
|
id: const Value(RowId(3)),
|
||||||
priority: const Value(CategoryPriority.low),
|
priority: const Value(CategoryPriority.low),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
@ -105,15 +105,16 @@ void main() {
|
||||||
expect(entry.toCompanion(true), const PureDefaultsCompanion());
|
expect(entry.toCompanion(true), const PureDefaultsCompanion());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('nullable values cannot be used with nullOrAbsent', () {
|
test('utilities to wrap nullable values', () {
|
||||||
expect(
|
expect(
|
||||||
// ignore: prefer_const_constructors
|
// ignore: prefer_const_constructors, deprecated_member_use_from_same_package
|
||||||
() => Value<int?>.ofNullable(null),
|
() => Value<int?>.ofNullable(null),
|
||||||
throwsA(isA<AssertionError>()));
|
throwsA(isA<AssertionError>()));
|
||||||
|
|
||||||
expect(const Value<int>.ofNullable(null).present, isFalse);
|
expect(const Value<int?>.absentIfNull(null).present, isFalse);
|
||||||
expect(const Value<int?>.ofNullable(12).present, isTrue);
|
expect(const Value<int>.absentIfNull(null).present, isFalse);
|
||||||
expect(const Value<int>.ofNullable(23).present, isTrue);
|
expect(const Value<int?>.absentIfNull(12).present, isTrue);
|
||||||
|
expect(const Value<int>.absentIfNull(23).present, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('companions support hash and equals', () {
|
test('companions support hash and equals', () {
|
||||||
|
|
|
@ -72,7 +72,7 @@ void main() {
|
||||||
final executor = MockExecutor();
|
final executor = MockExecutor();
|
||||||
final db = TodoDb(executor);
|
final db = TodoDb(executor);
|
||||||
|
|
||||||
await db.someDao.todosForUser(user: 1).get();
|
await db.someDao.todosForUser(user: RowId(1)).get();
|
||||||
|
|
||||||
verify(executor.runSelect(argThat(contains('SELECT t.* FROM todos')), [1]));
|
verify(executor.runSelect(argThat(contains('SELECT t.* FROM todos')), [1]));
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,6 +44,23 @@ void main() {
|
||||||
expect(exp, generates('?', [10]));
|
expect(exp, generates('?', [10]));
|
||||||
expect(exp.driftSqlType, isA<_NegatedIntType>());
|
expect(exp.driftSqlType, isA<_NegatedIntType>());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('also supports dialect-aware types', () {
|
||||||
|
const b = CustomExpression(
|
||||||
|
'b',
|
||||||
|
customType: DialectAwareSqlType<int>.via(
|
||||||
|
fallback: _NegatedIntType(),
|
||||||
|
overrides: {SqlDialect.postgres: DriftSqlType.int},
|
||||||
|
),
|
||||||
|
precedence: Precedence.primary,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(b.equals(3), generates('b = ?', [-3]));
|
||||||
|
expect(
|
||||||
|
b.equals(3),
|
||||||
|
generatesWithOptions('b = \$1',
|
||||||
|
variables: [3], dialect: SqlDialect.postgres));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NegatedIntType implements CustomSqlType<int> {
|
class _NegatedIntType implements CustomSqlType<int> {
|
||||||
|
|
|
@ -125,7 +125,11 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('substring', () {
|
test('substring', () {
|
||||||
expect(eval(Constant('hello world').substr(7)), completion('world'));
|
final input = Constant('hello world');
|
||||||
|
expect(eval(input.substr(7)), completion('world'));
|
||||||
|
|
||||||
|
expect(eval(input.substrExpr(Variable(1), input.length - Variable(6))),
|
||||||
|
completion('hello'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,5 +52,8 @@ void main() {
|
||||||
test('substr', () {
|
test('substr', () {
|
||||||
expect(expression.substr(10), generates('SUBSTR(col, 10)'));
|
expect(expression.substr(10), generates('SUBSTR(col, 10)'));
|
||||||
expect(expression.substr(10, 2), generates('SUBSTR(col, 10, 2)'));
|
expect(expression.substr(10, 2), generates('SUBSTR(col, 10, 2)'));
|
||||||
|
|
||||||
|
expect(expression.substrExpr(Variable(1), expression.length - Variable(5)),
|
||||||
|
generates('SUBSTR(col, ?, LENGTH(col) - ?)', [1, 5]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ void main() {
|
||||||
group('compiled custom queries', () {
|
group('compiled custom queries', () {
|
||||||
// defined query: SELECT * FROM todos WHERE title = ?2 OR id IN ? OR title = ?1
|
// defined query: SELECT * FROM todos WHERE title = ?2 OR id IN ? OR title = ?1
|
||||||
test('work with arrays', () async {
|
test('work with arrays', () async {
|
||||||
await db.withIn('one', 'two', [1, 2, 3]).get();
|
await db.withIn('one', 'two', [RowId(1), RowId(2), RowId(3)]).get();
|
||||||
|
|
||||||
verify(
|
verify(
|
||||||
executor.runSelect(
|
executor.runSelect(
|
||||||
|
|
|
@ -58,13 +58,14 @@ void main() {
|
||||||
|
|
||||||
final returnedValue = await db
|
final returnedValue = await db
|
||||||
.delete(db.todosTable)
|
.delete(db.todosTable)
|
||||||
.deleteReturning(const TodosTableCompanion(id: Value(10)));
|
.deleteReturning(const TodosTableCompanion(id: Value(RowId(10))));
|
||||||
|
|
||||||
verify(executor.runSelect(
|
verify(executor.runSelect(
|
||||||
'DELETE FROM "todos" WHERE "id" = ? RETURNING *;', [10]));
|
'DELETE FROM "todos" WHERE "id" = ? RETURNING *;', [10]));
|
||||||
verify(streamQueries.handleTableUpdates(
|
verify(streamQueries.handleTableUpdates(
|
||||||
{TableUpdate.onTable(db.todosTable, kind: UpdateKind.delete)}));
|
{TableUpdate.onTable(db.todosTable, kind: UpdateKind.delete)}));
|
||||||
expect(returnedValue, const TodoEntry(id: 10, content: 'Content'));
|
expect(
|
||||||
|
returnedValue, const TodoEntry(id: RowId(10), content: 'Content'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('for multiple rows', () async {
|
test('for multiple rows', () async {
|
||||||
|
@ -112,7 +113,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deleteOne()', () async {
|
test('deleteOne()', () async {
|
||||||
await db.users.deleteOne(const UsersCompanion(id: Value(3)));
|
await db.users.deleteOne(const UsersCompanion(id: Value(RowId(3))));
|
||||||
|
|
||||||
verify(
|
verify(
|
||||||
executor.runDelete('DELETE FROM "users" WHERE "id" = ?;', const [3]));
|
executor.runDelete('DELETE FROM "users" WHERE "id" = ?;', const [3]));
|
||||||
|
|
|
@ -56,7 +56,7 @@ void main() {
|
||||||
test('generates insert or replace statements', () async {
|
test('generates insert or replace statements', () async {
|
||||||
await db.into(db.todosTable).insert(
|
await db.into(db.todosTable).insert(
|
||||||
const TodoEntry(
|
const TodoEntry(
|
||||||
id: 113,
|
id: RowId(113),
|
||||||
content: 'Done',
|
content: 'Done',
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace);
|
mode: InsertMode.insertOrReplace);
|
||||||
|
@ -263,6 +263,22 @@ void main() {
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can ignore conflict target', () async {
|
||||||
|
await db.into(db.todosTable).insert(
|
||||||
|
TodosTableCompanion.insert(content: 'my content'),
|
||||||
|
onConflict: DoUpdate((old) {
|
||||||
|
return TodosTableCompanion.custom(
|
||||||
|
content: const Variable('important: ') + old.content);
|
||||||
|
}, target: []),
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(executor.runInsert(
|
||||||
|
'INSERT INTO "todos" ("content") VALUES (?) '
|
||||||
|
'ON CONFLICT DO UPDATE SET "content" = ? || "content"',
|
||||||
|
argThat(equals(['my content', 'important: '])),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'can use multiple upsert targets',
|
'can use multiple upsert targets',
|
||||||
() async {
|
() async {
|
||||||
|
@ -389,7 +405,8 @@ void main() {
|
||||||
when(executor.runInsert(any, any)).thenAnswer((_) => Future.value(3));
|
when(executor.runInsert(any, any)).thenAnswer((_) => Future.value(3));
|
||||||
|
|
||||||
final id = await db.into(db.todosTable).insertOnConflictUpdate(
|
final id = await db.into(db.todosTable).insertOnConflictUpdate(
|
||||||
TodosTableCompanion.insert(content: 'content', id: const Value(3)));
|
TodosTableCompanion.insert(
|
||||||
|
content: 'content', id: const Value(RowId(3))));
|
||||||
|
|
||||||
verify(executor.runInsert(
|
verify(executor.runInsert(
|
||||||
'INSERT INTO "todos" ("id", "content") VALUES (?, ?) '
|
'INSERT INTO "todos" ("id", "content") VALUES (?, ?) '
|
||||||
|
@ -599,7 +616,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
row,
|
row,
|
||||||
const Category(
|
const Category(
|
||||||
id: 1,
|
id: RowId(1),
|
||||||
description: 'description',
|
description: 'description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
priority: CategoryPriority.medium,
|
priority: CategoryPriority.medium,
|
||||||
|
|
|
@ -81,7 +81,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
row.readTable(todos),
|
row.readTable(todos),
|
||||||
TodoEntry(
|
TodoEntry(
|
||||||
id: 5,
|
id: RowId(5),
|
||||||
title: 'title',
|
title: 'title',
|
||||||
content: 'content',
|
content: 'content',
|
||||||
targetDate: date,
|
targetDate: date,
|
||||||
|
@ -92,7 +92,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
row.readTable(categories),
|
row.readTable(categories),
|
||||||
const Category(
|
const Category(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
description: 'description',
|
description: 'description',
|
||||||
priority: CategoryPriority.high,
|
priority: CategoryPriority.high,
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
|
@ -134,7 +134,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
row.readTable(db.todosTable),
|
row.readTable(db.todosTable),
|
||||||
const TodoEntry(
|
const TodoEntry(
|
||||||
id: 5,
|
id: RowId(5),
|
||||||
title: 'title',
|
title: 'title',
|
||||||
content: 'content',
|
content: 'content',
|
||||||
));
|
));
|
||||||
|
@ -256,7 +256,7 @@ void main() {
|
||||||
result.readTable(categories),
|
result.readTable(categories),
|
||||||
equals(
|
equals(
|
||||||
const Category(
|
const Category(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
priority: CategoryPriority.medium,
|
priority: CategoryPriority.medium,
|
||||||
|
@ -306,7 +306,7 @@ void main() {
|
||||||
result.readTable(categories),
|
result.readTable(categories),
|
||||||
equals(
|
equals(
|
||||||
const Category(
|
const Category(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
priority: CategoryPriority.medium,
|
priority: CategoryPriority.medium,
|
||||||
|
@ -362,7 +362,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
result.readTable(categories),
|
result.readTable(categories),
|
||||||
const Category(
|
const Category(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
description: 'desc',
|
description: 'desc',
|
||||||
descriptionInUpperCase: 'DESC',
|
descriptionInUpperCase: 'DESC',
|
||||||
priority: CategoryPriority.low,
|
priority: CategoryPriority.low,
|
||||||
|
|
|
@ -16,7 +16,7 @@ final _dataOfTodoEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const _todoEntry = TodoEntry(
|
const _todoEntry = TodoEntry(
|
||||||
id: 10,
|
id: RowId(10),
|
||||||
title: 'A todo title',
|
title: 'A todo title',
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
category: 3,
|
category: 3,
|
||||||
|
@ -126,7 +126,7 @@ void main() {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const resolved = TodoEntry(
|
const resolved = TodoEntry(
|
||||||
id: 10,
|
id: RowId(10),
|
||||||
title: null,
|
title: null,
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
category: null,
|
category: null,
|
||||||
|
@ -198,7 +198,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
category,
|
category,
|
||||||
const Category(
|
const Category(
|
||||||
id: 1,
|
id: RowId(1),
|
||||||
description: 'description',
|
description: 'description',
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
priority: CategoryPriority.high,
|
priority: CategoryPriority.high,
|
||||||
|
@ -232,7 +232,7 @@ void main() {
|
||||||
|
|
||||||
expect(rows, [
|
expect(rows, [
|
||||||
TodoEntry(
|
TodoEntry(
|
||||||
id: 10,
|
id: RowId(10),
|
||||||
title: null,
|
title: null,
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
category: null,
|
category: null,
|
||||||
|
|
|
@ -55,7 +55,7 @@ void main() {
|
||||||
group('generates replace statements', () {
|
group('generates replace statements', () {
|
||||||
test('regular', () async {
|
test('regular', () async {
|
||||||
await db.update(db.todosTable).replace(const TodoEntry(
|
await db.update(db.todosTable).replace(const TodoEntry(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
content: 'Updated content',
|
content: 'Updated content',
|
||||||
status: TodoStatus.workInProgress,
|
status: TodoStatus.workInProgress,
|
||||||
|
@ -71,7 +71,7 @@ void main() {
|
||||||
test('applies default values', () async {
|
test('applies default values', () async {
|
||||||
await db.update(db.users).replace(
|
await db.update(db.users).replace(
|
||||||
UsersCompanion(
|
UsersCompanion(
|
||||||
id: const Value(3),
|
id: const Value(RowId(3)),
|
||||||
name: const Value('Hummingbird'),
|
name: const Value('Hummingbird'),
|
||||||
profilePicture: Value(Uint8List(0)),
|
profilePicture: Value(Uint8List(0)),
|
||||||
),
|
),
|
||||||
|
@ -167,14 +167,14 @@ void main() {
|
||||||
|
|
||||||
group('update on table instances', () {
|
group('update on table instances', () {
|
||||||
test('update()', () async {
|
test('update()', () async {
|
||||||
await db.users.update().write(const UsersCompanion(id: Value(3)));
|
await db.users.update().write(const UsersCompanion(id: Value(RowId(3))));
|
||||||
|
|
||||||
verify(executor.runUpdate('UPDATE "users" SET "id" = ?;', [3]));
|
verify(executor.runUpdate('UPDATE "users" SET "id" = ?;', [3]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('replace', () async {
|
test('replace', () async {
|
||||||
await db.categories.replaceOne(const CategoriesCompanion(
|
await db.categories.replaceOne(const CategoriesCompanion(
|
||||||
id: Value(3), description: Value('new name')));
|
id: Value(RowId(3)), description: Value('new name')));
|
||||||
|
|
||||||
verify(executor.runUpdate(
|
verify(executor.runUpdate(
|
||||||
'UPDATE "categories" SET "desc" = ?, "priority" = 0 WHERE "id" = ?;',
|
'UPDATE "categories" SET "desc" = ?, "priority" = 0 WHERE "id" = ?;',
|
||||||
|
@ -205,7 +205,7 @@ void main() {
|
||||||
|
|
||||||
expect(rows, const [
|
expect(rows, const [
|
||||||
Category(
|
Category(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
description: 'test',
|
description: 'test',
|
||||||
priority: CategoryPriority.low,
|
priority: CategoryPriority.low,
|
||||||
descriptionInUpperCase: 'TEST',
|
descriptionInUpperCase: 'TEST',
|
||||||
|
|
|
@ -59,7 +59,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
todo,
|
todo,
|
||||||
const TodoEntry(
|
const TodoEntry(
|
||||||
id: 1,
|
id: RowId(1),
|
||||||
title: 'some title',
|
title: 'some title',
|
||||||
content: 'do this',
|
content: 'do this',
|
||||||
targetDate: null,
|
targetDate: null,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:drift/drift.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../../generated/converter.dart';
|
import '../../generated/converter.dart';
|
||||||
|
import '../../generated/todos.dart';
|
||||||
|
|
||||||
enum _MyEnum { one, two, three }
|
enum _MyEnum { one, two, three }
|
||||||
|
|
||||||
|
@ -34,6 +35,16 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('TypeConverter.extensionType', () {
|
||||||
|
final converter = TypeConverter.extensionType<RowId, int>();
|
||||||
|
|
||||||
|
expect(converter.toSql(RowId(123)), 123);
|
||||||
|
expect(converter.fromSql(15), RowId(15));
|
||||||
|
expect(converter.fromSql(15), 15);
|
||||||
|
expect(converter.fromJson(16), RowId(16));
|
||||||
|
expect(converter.toJson(RowId(124)), 124);
|
||||||
|
});
|
||||||
|
|
||||||
group('enum name', () {
|
group('enum name', () {
|
||||||
const converter = EnumNameConverter(_MyEnum.values);
|
const converter = EnumNameConverter(_MyEnum.values);
|
||||||
const values = {
|
const values = {
|
||||||
|
|
|
@ -4,8 +4,14 @@ import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
part 'todos.g.dart';
|
part 'todos.g.dart';
|
||||||
|
|
||||||
|
extension type RowId._(int id) {
|
||||||
|
const RowId(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
mixin AutoIncrement on Table {
|
mixin AutoIncrement on Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer()
|
||||||
|
.autoIncrement()
|
||||||
|
.map(TypeConverter.extensionType<RowId, int>())();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataClassName('TodoEntry')
|
@DataClassName('TodoEntry')
|
||||||
|
|
|
@ -11,13 +11,14 @@ class $CategoriesTable extends Categories
|
||||||
$CategoriesTable(this.attachedDatabase, [this._alias]);
|
$CategoriesTable(this.attachedDatabase, [this._alias]);
|
||||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
late final GeneratedColumnWithTypeConverter<RowId, int> id = GeneratedColumn<
|
||||||
'id', aliasedName, false,
|
int>('id', aliasedName, false,
|
||||||
hasAutoIncrement: true,
|
hasAutoIncrement: true,
|
||||||
type: DriftSqlType.int,
|
type: DriftSqlType.int,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultConstraints:
|
defaultConstraints:
|
||||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'))
|
||||||
|
.withConverter<RowId>($CategoriesTable.$converterid);
|
||||||
static const VerificationMeta _descriptionMeta =
|
static const VerificationMeta _descriptionMeta =
|
||||||
const VerificationMeta('description');
|
const VerificationMeta('description');
|
||||||
@override
|
@override
|
||||||
|
@ -56,9 +57,7 @@ class $CategoriesTable extends Categories
|
||||||
{bool isInserting = false}) {
|
{bool isInserting = false}) {
|
||||||
final context = VerificationContext();
|
final context = VerificationContext();
|
||||||
final data = instance.toColumns(true);
|
final data = instance.toColumns(true);
|
||||||
if (data.containsKey('id')) {
|
context.handle(_idMeta, const VerificationResult.success());
|
||||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
|
||||||
}
|
|
||||||
if (data.containsKey('desc')) {
|
if (data.containsKey('desc')) {
|
||||||
context.handle(_descriptionMeta,
|
context.handle(_descriptionMeta,
|
||||||
description.isAcceptableOrUnknown(data['desc']!, _descriptionMeta));
|
description.isAcceptableOrUnknown(data['desc']!, _descriptionMeta));
|
||||||
|
@ -81,8 +80,8 @@ class $CategoriesTable extends Categories
|
||||||
Category map(Map<String, dynamic> data, {String? tablePrefix}) {
|
Category map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return Category(
|
return Category(
|
||||||
id: attachedDatabase.typeMapping
|
id: $CategoriesTable.$converterid.fromSql(attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!),
|
||||||
description: attachedDatabase.typeMapping
|
description: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}desc'])!,
|
.read(DriftSqlType.string, data['${effectivePrefix}desc'])!,
|
||||||
priority: $CategoriesTable.$converterpriority.fromSql(attachedDatabase
|
priority: $CategoriesTable.$converterpriority.fromSql(attachedDatabase
|
||||||
|
@ -99,12 +98,14 @@ class $CategoriesTable extends Categories
|
||||||
return $CategoriesTable(attachedDatabase, alias);
|
return $CategoriesTable(attachedDatabase, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JsonTypeConverter2<RowId, int, int> $converterid =
|
||||||
|
TypeConverter.extensionType<RowId, int>();
|
||||||
static JsonTypeConverter2<CategoryPriority, int, int> $converterpriority =
|
static JsonTypeConverter2<CategoryPriority, int, int> $converterpriority =
|
||||||
const EnumIndexConverter<CategoryPriority>(CategoryPriority.values);
|
const EnumIndexConverter<CategoryPriority>(CategoryPriority.values);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Category extends DataClass implements Insertable<Category> {
|
class Category extends DataClass implements Insertable<Category> {
|
||||||
final int id;
|
final RowId id;
|
||||||
final String description;
|
final String description;
|
||||||
final CategoryPriority priority;
|
final CategoryPriority priority;
|
||||||
final String descriptionInUpperCase;
|
final String descriptionInUpperCase;
|
||||||
|
@ -116,7 +117,9 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
map['id'] = Variable<int>(id);
|
{
|
||||||
|
map['id'] = Variable<int>($CategoriesTable.$converterid.toSql(id));
|
||||||
|
}
|
||||||
map['desc'] = Variable<String>(description);
|
map['desc'] = Variable<String>(description);
|
||||||
{
|
{
|
||||||
map['priority'] =
|
map['priority'] =
|
||||||
|
@ -137,7 +140,8 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return Category(
|
return Category(
|
||||||
id: serializer.fromJson<int>(json['id']),
|
id: $CategoriesTable.$converterid
|
||||||
|
.fromJson(serializer.fromJson<int>(json['id'])),
|
||||||
description: serializer.fromJson<String>(json['description']),
|
description: serializer.fromJson<String>(json['description']),
|
||||||
priority: $CategoriesTable.$converterpriority
|
priority: $CategoriesTable.$converterpriority
|
||||||
.fromJson(serializer.fromJson<int>(json['priority'])),
|
.fromJson(serializer.fromJson<int>(json['priority'])),
|
||||||
|
@ -154,7 +158,7 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'id': serializer.toJson<int>(id),
|
'id': serializer.toJson<int>($CategoriesTable.$converterid.toJson(id)),
|
||||||
'description': serializer.toJson<String>(description),
|
'description': serializer.toJson<String>(description),
|
||||||
'priority': serializer
|
'priority': serializer
|
||||||
.toJson<int>($CategoriesTable.$converterpriority.toJson(priority)),
|
.toJson<int>($CategoriesTable.$converterpriority.toJson(priority)),
|
||||||
|
@ -164,7 +168,7 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Category copyWith(
|
Category copyWith(
|
||||||
{int? id,
|
{RowId? id,
|
||||||
String? description,
|
String? description,
|
||||||
CategoryPriority? priority,
|
CategoryPriority? priority,
|
||||||
String? descriptionInUpperCase}) =>
|
String? descriptionInUpperCase}) =>
|
||||||
|
@ -200,7 +204,7 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CategoriesCompanion extends UpdateCompanion<Category> {
|
class CategoriesCompanion extends UpdateCompanion<Category> {
|
||||||
final Value<int> id;
|
final Value<RowId> id;
|
||||||
final Value<String> description;
|
final Value<String> description;
|
||||||
final Value<CategoryPriority> priority;
|
final Value<CategoryPriority> priority;
|
||||||
const CategoriesCompanion({
|
const CategoriesCompanion({
|
||||||
|
@ -226,7 +230,7 @@ class CategoriesCompanion extends UpdateCompanion<Category> {
|
||||||
}
|
}
|
||||||
|
|
||||||
CategoriesCompanion copyWith(
|
CategoriesCompanion copyWith(
|
||||||
{Value<int>? id,
|
{Value<RowId>? id,
|
||||||
Value<String>? description,
|
Value<String>? description,
|
||||||
Value<CategoryPriority>? priority}) {
|
Value<CategoryPriority>? priority}) {
|
||||||
return CategoriesCompanion(
|
return CategoriesCompanion(
|
||||||
|
@ -240,7 +244,7 @@ class CategoriesCompanion extends UpdateCompanion<Category> {
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
if (id.present) {
|
if (id.present) {
|
||||||
map['id'] = Variable<int>(id.value);
|
map['id'] = Variable<int>($CategoriesTable.$converterid.toSql(id.value));
|
||||||
}
|
}
|
||||||
if (description.present) {
|
if (description.present) {
|
||||||
map['desc'] = Variable<String>(description.value);
|
map['desc'] = Variable<String>(description.value);
|
||||||
|
@ -271,13 +275,14 @@ class $TodosTableTable extends TodosTable
|
||||||
$TodosTableTable(this.attachedDatabase, [this._alias]);
|
$TodosTableTable(this.attachedDatabase, [this._alias]);
|
||||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
late final GeneratedColumnWithTypeConverter<RowId, int> id = GeneratedColumn<
|
||||||
'id', aliasedName, false,
|
int>('id', aliasedName, false,
|
||||||
hasAutoIncrement: true,
|
hasAutoIncrement: true,
|
||||||
type: DriftSqlType.int,
|
type: DriftSqlType.int,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultConstraints:
|
defaultConstraints:
|
||||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'))
|
||||||
|
.withConverter<RowId>($TodosTableTable.$converterid);
|
||||||
static const VerificationMeta _titleMeta = const VerificationMeta('title');
|
static const VerificationMeta _titleMeta = const VerificationMeta('title');
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<String> title = GeneratedColumn<String>(
|
late final GeneratedColumn<String> title = GeneratedColumn<String>(
|
||||||
|
@ -328,9 +333,7 @@ class $TodosTableTable extends TodosTable
|
||||||
{bool isInserting = false}) {
|
{bool isInserting = false}) {
|
||||||
final context = VerificationContext();
|
final context = VerificationContext();
|
||||||
final data = instance.toColumns(true);
|
final data = instance.toColumns(true);
|
||||||
if (data.containsKey('id')) {
|
context.handle(_idMeta, const VerificationResult.success());
|
||||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
|
||||||
}
|
|
||||||
if (data.containsKey('title')) {
|
if (data.containsKey('title')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta));
|
_titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta));
|
||||||
|
@ -366,8 +369,8 @@ class $TodosTableTable extends TodosTable
|
||||||
TodoEntry map(Map<String, dynamic> data, {String? tablePrefix}) {
|
TodoEntry map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return TodoEntry(
|
return TodoEntry(
|
||||||
id: attachedDatabase.typeMapping
|
id: $TodosTableTable.$converterid.fromSql(attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!),
|
||||||
title: attachedDatabase.typeMapping
|
title: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}title']),
|
.read(DriftSqlType.string, data['${effectivePrefix}title']),
|
||||||
content: attachedDatabase.typeMapping
|
content: attachedDatabase.typeMapping
|
||||||
|
@ -387,6 +390,8 @@ class $TodosTableTable extends TodosTable
|
||||||
return $TodosTableTable(attachedDatabase, alias);
|
return $TodosTableTable(attachedDatabase, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JsonTypeConverter2<RowId, int, int> $converterid =
|
||||||
|
TypeConverter.extensionType<RowId, int>();
|
||||||
static JsonTypeConverter2<TodoStatus, String, String> $converterstatus =
|
static JsonTypeConverter2<TodoStatus, String, String> $converterstatus =
|
||||||
const EnumNameConverter<TodoStatus>(TodoStatus.values);
|
const EnumNameConverter<TodoStatus>(TodoStatus.values);
|
||||||
static JsonTypeConverter2<TodoStatus?, String?, String?> $converterstatusn =
|
static JsonTypeConverter2<TodoStatus?, String?, String?> $converterstatusn =
|
||||||
|
@ -394,7 +399,7 @@ class $TodosTableTable extends TodosTable
|
||||||
}
|
}
|
||||||
|
|
||||||
class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
||||||
final int id;
|
final RowId id;
|
||||||
final String? title;
|
final String? title;
|
||||||
final String content;
|
final String content;
|
||||||
final DateTime? targetDate;
|
final DateTime? targetDate;
|
||||||
|
@ -410,7 +415,9 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
map['id'] = Variable<int>(id);
|
{
|
||||||
|
map['id'] = Variable<int>($TodosTableTable.$converterid.toSql(id));
|
||||||
|
}
|
||||||
if (!nullToAbsent || title != null) {
|
if (!nullToAbsent || title != null) {
|
||||||
map['title'] = Variable<String>(title);
|
map['title'] = Variable<String>(title);
|
||||||
}
|
}
|
||||||
|
@ -449,7 +456,8 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return TodoEntry(
|
return TodoEntry(
|
||||||
id: serializer.fromJson<int>(json['id']),
|
id: $TodosTableTable.$converterid
|
||||||
|
.fromJson(serializer.fromJson<int>(json['id'])),
|
||||||
title: serializer.fromJson<String?>(json['title']),
|
title: serializer.fromJson<String?>(json['title']),
|
||||||
content: serializer.fromJson<String>(json['content']),
|
content: serializer.fromJson<String>(json['content']),
|
||||||
targetDate: serializer.fromJson<DateTime?>(json['target_date']),
|
targetDate: serializer.fromJson<DateTime?>(json['target_date']),
|
||||||
|
@ -467,7 +475,7 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
||||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'id': serializer.toJson<int>(id),
|
'id': serializer.toJson<int>($TodosTableTable.$converterid.toJson(id)),
|
||||||
'title': serializer.toJson<String?>(title),
|
'title': serializer.toJson<String?>(title),
|
||||||
'content': serializer.toJson<String>(content),
|
'content': serializer.toJson<String>(content),
|
||||||
'target_date': serializer.toJson<DateTime?>(targetDate),
|
'target_date': serializer.toJson<DateTime?>(targetDate),
|
||||||
|
@ -478,7 +486,7 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
TodoEntry copyWith(
|
TodoEntry copyWith(
|
||||||
{int? id,
|
{RowId? id,
|
||||||
Value<String?> title = const Value.absent(),
|
Value<String?> title = const Value.absent(),
|
||||||
String? content,
|
String? content,
|
||||||
Value<DateTime?> targetDate = const Value.absent(),
|
Value<DateTime?> targetDate = const Value.absent(),
|
||||||
|
@ -521,7 +529,7 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TodosTableCompanion extends UpdateCompanion<TodoEntry> {
|
class TodosTableCompanion extends UpdateCompanion<TodoEntry> {
|
||||||
final Value<int> id;
|
final Value<RowId> id;
|
||||||
final Value<String?> title;
|
final Value<String?> title;
|
||||||
final Value<String> content;
|
final Value<String> content;
|
||||||
final Value<DateTime?> targetDate;
|
final Value<DateTime?> targetDate;
|
||||||
|
@ -562,7 +570,7 @@ class TodosTableCompanion extends UpdateCompanion<TodoEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
TodosTableCompanion copyWith(
|
TodosTableCompanion copyWith(
|
||||||
{Value<int>? id,
|
{Value<RowId>? id,
|
||||||
Value<String?>? title,
|
Value<String?>? title,
|
||||||
Value<String>? content,
|
Value<String>? content,
|
||||||
Value<DateTime?>? targetDate,
|
Value<DateTime?>? targetDate,
|
||||||
|
@ -582,7 +590,7 @@ class TodosTableCompanion extends UpdateCompanion<TodoEntry> {
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
if (id.present) {
|
if (id.present) {
|
||||||
map['id'] = Variable<int>(id.value);
|
map['id'] = Variable<int>($TodosTableTable.$converterid.toSql(id.value));
|
||||||
}
|
}
|
||||||
if (title.present) {
|
if (title.present) {
|
||||||
map['title'] = Variable<String>(title.value);
|
map['title'] = Variable<String>(title.value);
|
||||||
|
@ -624,13 +632,14 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
||||||
$UsersTable(this.attachedDatabase, [this._alias]);
|
$UsersTable(this.attachedDatabase, [this._alias]);
|
||||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
late final GeneratedColumnWithTypeConverter<RowId, int> id = GeneratedColumn<
|
||||||
'id', aliasedName, false,
|
int>('id', aliasedName, false,
|
||||||
hasAutoIncrement: true,
|
hasAutoIncrement: true,
|
||||||
type: DriftSqlType.int,
|
type: DriftSqlType.int,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultConstraints:
|
defaultConstraints:
|
||||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'))
|
||||||
|
.withConverter<RowId>($UsersTable.$converterid);
|
||||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||||
|
@ -678,9 +687,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
||||||
{bool isInserting = false}) {
|
{bool isInserting = false}) {
|
||||||
final context = VerificationContext();
|
final context = VerificationContext();
|
||||||
final data = instance.toColumns(true);
|
final data = instance.toColumns(true);
|
||||||
if (data.containsKey('id')) {
|
context.handle(_idMeta, const VerificationResult.success());
|
||||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
|
||||||
}
|
|
||||||
if (data.containsKey('name')) {
|
if (data.containsKey('name')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
||||||
|
@ -714,8 +721,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
||||||
User map(Map<String, dynamic> data, {String? tablePrefix}) {
|
User map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return User(
|
return User(
|
||||||
id: attachedDatabase.typeMapping
|
id: $UsersTable.$converterid.fromSql(attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!),
|
||||||
name: attachedDatabase.typeMapping
|
name: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
|
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||||
isAwesome: attachedDatabase.typeMapping
|
isAwesome: attachedDatabase.typeMapping
|
||||||
|
@ -731,10 +738,13 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
||||||
$UsersTable createAlias(String alias) {
|
$UsersTable createAlias(String alias) {
|
||||||
return $UsersTable(attachedDatabase, alias);
|
return $UsersTable(attachedDatabase, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JsonTypeConverter2<RowId, int, int> $converterid =
|
||||||
|
TypeConverter.extensionType<RowId, int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class User extends DataClass implements Insertable<User> {
|
class User extends DataClass implements Insertable<User> {
|
||||||
final int id;
|
final RowId id;
|
||||||
final String name;
|
final String name;
|
||||||
final bool isAwesome;
|
final bool isAwesome;
|
||||||
final Uint8List profilePicture;
|
final Uint8List profilePicture;
|
||||||
|
@ -748,7 +758,9 @@ class User extends DataClass implements Insertable<User> {
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
map['id'] = Variable<int>(id);
|
{
|
||||||
|
map['id'] = Variable<int>($UsersTable.$converterid.toSql(id));
|
||||||
|
}
|
||||||
map['name'] = Variable<String>(name);
|
map['name'] = Variable<String>(name);
|
||||||
map['is_awesome'] = Variable<bool>(isAwesome);
|
map['is_awesome'] = Variable<bool>(isAwesome);
|
||||||
map['profile_picture'] = Variable<Uint8List>(profilePicture);
|
map['profile_picture'] = Variable<Uint8List>(profilePicture);
|
||||||
|
@ -770,7 +782,8 @@ class User extends DataClass implements Insertable<User> {
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return User(
|
return User(
|
||||||
id: serializer.fromJson<int>(json['id']),
|
id: $UsersTable.$converterid
|
||||||
|
.fromJson(serializer.fromJson<int>(json['id'])),
|
||||||
name: serializer.fromJson<String>(json['name']),
|
name: serializer.fromJson<String>(json['name']),
|
||||||
isAwesome: serializer.fromJson<bool>(json['isAwesome']),
|
isAwesome: serializer.fromJson<bool>(json['isAwesome']),
|
||||||
profilePicture: serializer.fromJson<Uint8List>(json['profilePicture']),
|
profilePicture: serializer.fromJson<Uint8List>(json['profilePicture']),
|
||||||
|
@ -785,7 +798,7 @@ class User extends DataClass implements Insertable<User> {
|
||||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'id': serializer.toJson<int>(id),
|
'id': serializer.toJson<int>($UsersTable.$converterid.toJson(id)),
|
||||||
'name': serializer.toJson<String>(name),
|
'name': serializer.toJson<String>(name),
|
||||||
'isAwesome': serializer.toJson<bool>(isAwesome),
|
'isAwesome': serializer.toJson<bool>(isAwesome),
|
||||||
'profilePicture': serializer.toJson<Uint8List>(profilePicture),
|
'profilePicture': serializer.toJson<Uint8List>(profilePicture),
|
||||||
|
@ -794,7 +807,7 @@ class User extends DataClass implements Insertable<User> {
|
||||||
}
|
}
|
||||||
|
|
||||||
User copyWith(
|
User copyWith(
|
||||||
{int? id,
|
{RowId? id,
|
||||||
String? name,
|
String? name,
|
||||||
bool? isAwesome,
|
bool? isAwesome,
|
||||||
Uint8List? profilePicture,
|
Uint8List? profilePicture,
|
||||||
|
@ -834,7 +847,7 @@ class User extends DataClass implements Insertable<User> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UsersCompanion extends UpdateCompanion<User> {
|
class UsersCompanion extends UpdateCompanion<User> {
|
||||||
final Value<int> id;
|
final Value<RowId> id;
|
||||||
final Value<String> name;
|
final Value<String> name;
|
||||||
final Value<bool> isAwesome;
|
final Value<bool> isAwesome;
|
||||||
final Value<Uint8List> profilePicture;
|
final Value<Uint8List> profilePicture;
|
||||||
|
@ -871,7 +884,7 @@ class UsersCompanion extends UpdateCompanion<User> {
|
||||||
}
|
}
|
||||||
|
|
||||||
UsersCompanion copyWith(
|
UsersCompanion copyWith(
|
||||||
{Value<int>? id,
|
{Value<RowId>? id,
|
||||||
Value<String>? name,
|
Value<String>? name,
|
||||||
Value<bool>? isAwesome,
|
Value<bool>? isAwesome,
|
||||||
Value<Uint8List>? profilePicture,
|
Value<Uint8List>? profilePicture,
|
||||||
|
@ -889,7 +902,7 @@ class UsersCompanion extends UpdateCompanion<User> {
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
if (id.present) {
|
if (id.present) {
|
||||||
map['id'] = Variable<int>(id.value);
|
map['id'] = Variable<int>($UsersTable.$converterid.toSql(id.value));
|
||||||
}
|
}
|
||||||
if (name.present) {
|
if (name.present) {
|
||||||
map['name'] = Variable<String>(name.value);
|
map['name'] = Variable<String>(name.value);
|
||||||
|
@ -1872,7 +1885,7 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
todosTable,
|
todosTable,
|
||||||
}).map((QueryRow row) => AllTodosWithCategoryResult(
|
}).map((QueryRow row) => AllTodosWithCategoryResult(
|
||||||
row: row,
|
row: row,
|
||||||
id: row.read<int>('id'),
|
id: $TodosTableTable.$converterid.fromSql(row.read<int>('id')),
|
||||||
title: row.readNullable<String>('title'),
|
title: row.readNullable<String>('title'),
|
||||||
content: row.read<String>('content'),
|
content: row.read<String>('content'),
|
||||||
targetDate: row.readNullable<DateTime>('target_date'),
|
targetDate: row.readNullable<DateTime>('target_date'),
|
||||||
|
@ -1880,21 +1893,21 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
status: NullAwareTypeConverter.wrapFromSql(
|
status: NullAwareTypeConverter.wrapFromSql(
|
||||||
$TodosTableTable.$converterstatus,
|
$TodosTableTable.$converterstatus,
|
||||||
row.readNullable<String>('status')),
|
row.readNullable<String>('status')),
|
||||||
catId: row.read<int>('catId'),
|
catId: $CategoriesTable.$converterid.fromSql(row.read<int>('catId')),
|
||||||
catDesc: row.read<String>('catDesc'),
|
catDesc: row.read<String>('catDesc'),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteTodoById(int var1) {
|
Future<int> deleteTodoById(RowId var1) {
|
||||||
return customUpdate(
|
return customUpdate(
|
||||||
'DELETE FROM todos WHERE id = ?1',
|
'DELETE FROM todos WHERE id = ?1',
|
||||||
variables: [Variable<int>(var1)],
|
variables: [Variable<int>($TodosTableTable.$converterid.toSql(var1))],
|
||||||
updates: {todosTable},
|
updates: {todosTable},
|
||||||
updateKind: UpdateKind.delete,
|
updateKind: UpdateKind.delete,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<TodoEntry> withIn(String? var1, String? var2, List<int> var3) {
|
Selectable<TodoEntry> withIn(String? var1, String? var2, List<RowId> var3) {
|
||||||
var $arrayStartIndex = 3;
|
var $arrayStartIndex = 3;
|
||||||
final expandedvar3 = $expandVar($arrayStartIndex, var3.length);
|
final expandedvar3 = $expandVar($arrayStartIndex, var3.length);
|
||||||
$arrayStartIndex += var3.length;
|
$arrayStartIndex += var3.length;
|
||||||
|
@ -1903,18 +1916,19 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
variables: [
|
variables: [
|
||||||
Variable<String>(var1),
|
Variable<String>(var1),
|
||||||
Variable<String>(var2),
|
Variable<String>(var2),
|
||||||
for (var $ in var3) Variable<int>($)
|
for (var $ in var3)
|
||||||
|
Variable<int>($TodosTableTable.$converterid.toSql($))
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
todosTable,
|
todosTable,
|
||||||
}).asyncMap(todosTable.mapFromRow);
|
}).asyncMap(todosTable.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<TodoEntry> search({required int id}) {
|
Selectable<TodoEntry> search({required RowId id}) {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'SELECT * FROM todos WHERE CASE WHEN -1 = ?1 THEN 1 ELSE id = ?1 END',
|
'SELECT * FROM todos WHERE CASE WHEN -1 = ?1 THEN 1 ELSE id = ?1 END',
|
||||||
variables: [
|
variables: [
|
||||||
Variable<int>(id)
|
Variable<int>($TodosTableTable.$converterid.toSql(id))
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
todosTable,
|
todosTable,
|
||||||
|
@ -1949,13 +1963,13 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AllTodosWithCategoryResult extends CustomResultSet {
|
class AllTodosWithCategoryResult extends CustomResultSet {
|
||||||
final int id;
|
final RowId id;
|
||||||
final String? title;
|
final String? title;
|
||||||
final String content;
|
final String content;
|
||||||
final DateTime? targetDate;
|
final DateTime? targetDate;
|
||||||
final int? category;
|
final int? category;
|
||||||
final TodoStatus? status;
|
final TodoStatus? status;
|
||||||
final int catId;
|
final RowId catId;
|
||||||
final String catDesc;
|
final String catDesc;
|
||||||
AllTodosWithCategoryResult({
|
AllTodosWithCategoryResult({
|
||||||
required QueryRow row,
|
required QueryRow row,
|
||||||
|
@ -2006,11 +2020,11 @@ mixin _$SomeDaoMixin on DatabaseAccessor<TodoDb> {
|
||||||
$SharedTodosTable get sharedTodos => attachedDatabase.sharedTodos;
|
$SharedTodosTable get sharedTodos => attachedDatabase.sharedTodos;
|
||||||
$TodoWithCategoryViewView get todoWithCategoryView =>
|
$TodoWithCategoryViewView get todoWithCategoryView =>
|
||||||
attachedDatabase.todoWithCategoryView;
|
attachedDatabase.todoWithCategoryView;
|
||||||
Selectable<TodoEntry> todosForUser({required int user}) {
|
Selectable<TodoEntry> todosForUser({required RowId user}) {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'SELECT t.* FROM todos AS t INNER JOIN shared_todos AS st ON st.todo = t.id INNER JOIN users AS u ON u.id = st.user WHERE u.id = ?1',
|
'SELECT t.* FROM todos AS t INNER JOIN shared_todos AS st ON st.todo = t.id INNER JOIN users AS u ON u.id = st.user WHERE u.id = ?1',
|
||||||
variables: [
|
variables: [
|
||||||
Variable<int>(user)
|
Variable<int>($UsersTable.$converterid.toSql(user))
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
todosTable,
|
todosTable,
|
||||||
|
|
|
@ -109,7 +109,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
entry,
|
entry,
|
||||||
const Category(
|
const Category(
|
||||||
id: 1,
|
id: RowId(1),
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
priority: CategoryPriority.low,
|
priority: CategoryPriority.low,
|
||||||
descriptionInUpperCase: 'DESCRIPTION',
|
descriptionInUpperCase: 'DESCRIPTION',
|
||||||
|
|
|
@ -26,7 +26,7 @@ void main() {
|
||||||
final rows = await (db.select(db.users)
|
final rows = await (db.select(db.users)
|
||||||
..orderBy([(_) => OrderingTerm.random()]))
|
..orderBy([(_) => OrderingTerm.random()]))
|
||||||
.get();
|
.get();
|
||||||
expect(rows.isSorted((a, b) => a.id.compareTo(b.id)), isFalse);
|
expect(rows.isSorted((a, b) => a.id.id.compareTo(b.id.id)), isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can select view', () async {
|
test('can select view', () async {
|
||||||
|
@ -35,7 +35,7 @@ void main() {
|
||||||
await db.todosTable.insertOne(TodosTableCompanion.insert(
|
await db.todosTable.insertOne(TodosTableCompanion.insert(
|
||||||
content: 'some content',
|
content: 'some content',
|
||||||
title: const Value('title'),
|
title: const Value('title'),
|
||||||
category: Value(category.id)));
|
category: Value(category.id.id)));
|
||||||
|
|
||||||
final result = await db.todoWithCategoryView.select().getSingle();
|
final result = await db.todoWithCategoryView.select().getSingle();
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -190,7 +190,7 @@ void main() {
|
||||||
stream,
|
stream,
|
||||||
emits([
|
emits([
|
||||||
Category(
|
Category(
|
||||||
id: 1,
|
id: RowId(1),
|
||||||
description: 'From remote isolate!',
|
description: 'From remote isolate!',
|
||||||
priority: CategoryPriority.low,
|
priority: CategoryPriority.low,
|
||||||
descriptionInUpperCase: 'FROM REMOTE ISOLATE!',
|
descriptionInUpperCase: 'FROM REMOTE ISOLATE!',
|
||||||
|
@ -240,6 +240,34 @@ void main() {
|
||||||
await testWith(DatabaseConnection(NativeDatabase.memory()));
|
await testWith(DatabaseConnection(NativeDatabase.memory()));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('uses correct dialect', () async {
|
||||||
|
// Regression test for https://github.com/simolus3/drift/issues/2894
|
||||||
|
final isolate = await DriftIsolate.spawn(() {
|
||||||
|
return NativeDatabase.memory()
|
||||||
|
.interceptWith(PretendDialectInterceptor(SqlDialect.postgres));
|
||||||
|
});
|
||||||
|
final database = TodoDb(await isolate.connect(singleClientMode: true));
|
||||||
|
addTearDown(database.close);
|
||||||
|
|
||||||
|
await database.transaction(() async {
|
||||||
|
await expectLater(
|
||||||
|
database.into(database.users).insertReturning(UsersCompanion.insert(
|
||||||
|
name: 'test user', profilePicture: Uint8List(0))),
|
||||||
|
throwsA(
|
||||||
|
isA<DriftRemoteException>().having(
|
||||||
|
(e) => e.remoteCause,
|
||||||
|
'remoteCause',
|
||||||
|
isA<SqliteException>().having(
|
||||||
|
(e) => e.causingStatement,
|
||||||
|
'causingStatement',
|
||||||
|
contains(r'VALUES ($1, $2)'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,
|
void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,
|
||||||
|
@ -290,7 +318,7 @@ void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,
|
||||||
await database.into(database.todosTable).insert(initialCompanion);
|
await database.into(database.todosTable).insert(initialCompanion);
|
||||||
await expectLater(
|
await expectLater(
|
||||||
stream,
|
stream,
|
||||||
emits(const TodoEntry(id: 1, content: 'my content')),
|
emits(const TodoEntry(id: RowId(1), content: 'my content')),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ void main() {
|
||||||
ExecuteQuery(StatementMethod.select, 'SELECT ?', [BigInt.one]),
|
ExecuteQuery(StatementMethod.select, 'SELECT ?', [BigInt.one]),
|
||||||
);
|
);
|
||||||
|
|
||||||
final mapped = protocol.deserialize(protocol.serialize(request)!);
|
final mapped = _checkSimpleRoundtrip(protocol, request);
|
||||||
expect(
|
expect(
|
||||||
mapped,
|
mapped,
|
||||||
isA<Request>().having((e) => e.id, 'id', 1).having(
|
isA<Request>().having((e) => e.id, 'id', 1).having(
|
||||||
|
@ -109,6 +109,27 @@ void main() {
|
||||||
.having((e) => e.args, 'args', [isA<BigInt>()]),
|
.having((e) => e.args, 'args', [isA<BigInt>()]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final response = SuccessResponse(
|
||||||
|
1,
|
||||||
|
SelectResult([
|
||||||
|
{'col': BigInt.one}
|
||||||
|
]));
|
||||||
|
final mappedResponse = _checkSimpleRoundtrip(protocol, response);
|
||||||
|
expect(
|
||||||
|
mappedResponse,
|
||||||
|
isA<SuccessResponse>().having((e) => e.requestId, 'requestId', 1).having(
|
||||||
|
(e) => e.response,
|
||||||
|
'response',
|
||||||
|
isA<SelectResult>().having(
|
||||||
|
(e) => e.rows,
|
||||||
|
'rows',
|
||||||
|
([
|
||||||
|
{'col': BigInt.one}
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can run protocol without using complex types', () async {
|
test('can run protocol without using complex types', () async {
|
||||||
|
@ -244,6 +265,12 @@ void _checkSimple(Object? object) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message _checkSimpleRoundtrip(DriftProtocol protocol, Message source) {
|
||||||
|
final serialized = protocol.serialize(source);
|
||||||
|
_checkSimple(serialized);
|
||||||
|
return protocol.deserialize(serialized!);
|
||||||
|
}
|
||||||
|
|
||||||
extension<T> on StreamChannel<T> {
|
extension<T> on StreamChannel<T> {
|
||||||
StreamChannel<T> get expectedToClose {
|
StreamChannel<T> get expectedToClose {
|
||||||
return transformStream(StreamTransformer.fromHandlers(
|
return transformStream(StreamTransformer.fromHandlers(
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'generated/todos.dart';
|
||||||
final DateTime _someDate = DateTime(2019, 06, 08);
|
final DateTime _someDate = DateTime(2019, 06, 08);
|
||||||
|
|
||||||
final TodoEntry _someTodoEntry = TodoEntry(
|
final TodoEntry _someTodoEntry = TodoEntry(
|
||||||
id: 3,
|
id: RowId(3),
|
||||||
title: null,
|
title: null,
|
||||||
content: 'content',
|
content: 'content',
|
||||||
targetDate: _someDate,
|
targetDate: _someDate,
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
@TestOn('vm')
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('drift does not import legacy JS interop files', () {
|
||||||
|
// The old web APIs can't be used in dart2wasm, so we shouldn't use them in
|
||||||
|
// web-specific drift code.
|
||||||
|
// Legacy APIs (involving `WebDatabase`) are excempt from this.
|
||||||
|
const allowedLegacyCode = [
|
||||||
|
'lib/web/worker.dart', // Wasm uses a different worker
|
||||||
|
'lib/src/web/channel.dart',
|
||||||
|
'lib/src/web/storage.dart',
|
||||||
|
'lib/src/web/sql_js.dart',
|
||||||
|
];
|
||||||
|
|
||||||
|
final failures = <(String, String)>[];
|
||||||
|
|
||||||
|
void check(FileSystemEntity e) {
|
||||||
|
switch (e) {
|
||||||
|
case File():
|
||||||
|
if (allowedLegacyCode.contains(e.path)) return;
|
||||||
|
|
||||||
|
final text = e.readAsStringSync();
|
||||||
|
final parsed = parseString(content: text).unit;
|
||||||
|
|
||||||
|
for (final directive in parsed.directives) {
|
||||||
|
if (directive is ImportDirective) {
|
||||||
|
final uri = directive.uri.stringValue!;
|
||||||
|
if (uri.contains('package:js') ||
|
||||||
|
uri == 'dart:js' ||
|
||||||
|
uri == 'dart:js_util' ||
|
||||||
|
uri == 'dart:html' ||
|
||||||
|
uri == 'dart:indexeddb') {
|
||||||
|
failures.add((e.path, directive.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case Directory():
|
||||||
|
for (final entry in e.listSync()) {
|
||||||
|
check(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final root = Directory('lib/');
|
||||||
|
check(root);
|
||||||
|
|
||||||
|
expect(failures, isEmpty,
|
||||||
|
reason: 'Drift should not import legacy JS code.');
|
||||||
|
});
|
||||||
|
}
|
|
@ -109,7 +109,7 @@ class _GeneratesSqlMatcher extends Matcher {
|
||||||
|
|
||||||
final argsMatchState = <String, Object?>{};
|
final argsMatchState = <String, Object?>{};
|
||||||
if (_matchVariables != null &&
|
if (_matchVariables != null &&
|
||||||
!_matchVariables!.matches(ctx.boundVariables, argsMatchState)) {
|
!_matchVariables.matches(ctx.boundVariables, argsMatchState)) {
|
||||||
matchState['vars'] = ctx.boundVariables;
|
matchState['vars'] = ctx.boundVariables;
|
||||||
matchState['vars_match'] = argsMatchState;
|
matchState['vars_match'] = argsMatchState;
|
||||||
matches = false;
|
matches = false;
|
||||||
|
|
|
@ -100,3 +100,14 @@ class CustomTable extends Table with TableInfo<CustomTable, void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PretendDialectInterceptor extends QueryInterceptor {
|
||||||
|
final SqlDialect _dialect;
|
||||||
|
|
||||||
|
PretendDialectInterceptor(this._dialect);
|
||||||
|
|
||||||
|
@override
|
||||||
|
SqlDialect dialect(QueryExecutor executor) {
|
||||||
|
return _dialect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Mocks generated by Mockito 5.4.4 from annotations
|
// Mocks generated by Mockito 5.4.3 from annotations
|
||||||
// in drift/test/test_utils/test_utils.dart.
|
// in drift/test/test_utils/test_utils.dart.
|
||||||
// Do not manually edit this file.
|
// Do not manually edit this file.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
## 2.15.1-dev
|
## 2.17.0-dev
|
||||||
|
|
||||||
|
- Fix drift using the wrong import alias in generated part files.
|
||||||
|
- Add the `use_sql_column_name_as_json_key` builder option.
|
||||||
|
- Add a `setup` parameter to `SchemaVerifier`. It is called when the verifier
|
||||||
|
creates database connections (similar to the callback on `NativeDatabase`)
|
||||||
|
and can be used to register custom functions.
|
||||||
|
|
||||||
|
## 2.16.0
|
||||||
|
|
||||||
- Keep import alias when referencing existing elements in generated code
|
- Keep import alias when referencing existing elements in generated code
|
||||||
([#2845](https://github.com/simolus3/drift/issues/2845)).
|
([#2845](https://github.com/simolus3/drift/issues/2845)).
|
||||||
|
|
|
@ -11,8 +11,18 @@ export 'package:drift_dev/src/services/schema/verifier_common.dart'
|
||||||
show SchemaMismatch;
|
show SchemaMismatch;
|
||||||
|
|
||||||
abstract class SchemaVerifier {
|
abstract class SchemaVerifier {
|
||||||
factory SchemaVerifier(SchemaInstantiationHelper helper) =
|
/// Creates a schema verifier for the drift-generated [helper].
|
||||||
VerifierImplementation;
|
///
|
||||||
|
/// See [tests] for more information.
|
||||||
|
/// The optional [setup] parameter is used internally by the verifier for
|
||||||
|
/// every database connection it opens. This can be used to, for instance,
|
||||||
|
/// register custom functions expected by your database.
|
||||||
|
///
|
||||||
|
/// [tests]: https://drift.simonbinder.eu/docs/migrations/tests/
|
||||||
|
factory SchemaVerifier(
|
||||||
|
SchemaInstantiationHelper helper, {
|
||||||
|
void Function(Database raw)? setup,
|
||||||
|
}) = VerifierImplementation;
|
||||||
|
|
||||||
/// Creates a [DatabaseConnection] that contains empty tables created for the
|
/// Creates a [DatabaseConnection] that contains empty tables created for the
|
||||||
/// known schema [version].
|
/// known schema [version].
|
||||||
|
|
|
@ -45,13 +45,19 @@ class DriftOptions {
|
||||||
defaultValue: true)
|
defaultValue: true)
|
||||||
final bool useColumnNameAsJsonKeyWhenDefinedInMoorFile;
|
final bool useColumnNameAsJsonKeyWhenDefinedInMoorFile;
|
||||||
|
|
||||||
|
/// Uses the sql column name as the json key instead of the name in dart.
|
||||||
|
///
|
||||||
|
/// Overrides [useColumnNameAsJsonKeyWhenDefinedInMoorFile] when set to `true`.
|
||||||
|
@JsonKey(name: 'use_sql_column_name_as_json_key', defaultValue: false)
|
||||||
|
final bool useSqlColumnNameAsJsonKey;
|
||||||
|
|
||||||
/// Generate a `connect` constructor in database superclasses.
|
/// Generate a `connect` constructor in database superclasses.
|
||||||
///
|
///
|
||||||
/// This makes drift generate a constructor for database classes that takes a
|
/// This makes drift generate a constructor for database classes that takes a
|
||||||
/// `DatabaseConnection` instead of just a `QueryExecutor` - meaning that
|
/// `DatabaseConnection` instead of just a `QueryExecutor` - meaning that
|
||||||
/// stream queries can also be shared across multiple database instances.
|
/// stream queries can also be shared across multiple database instances.
|
||||||
/// Starting from drift 2.5, the database connection class implements the
|
/// Starting from drift 2.5, the database connection class implements the
|
||||||
/// `QueryExecutor` interface, making this option unecessary.
|
/// `QueryExecutor` interface, making this option unnecessary.
|
||||||
@JsonKey(name: 'generate_connect_constructor', defaultValue: false)
|
@JsonKey(name: 'generate_connect_constructor', defaultValue: false)
|
||||||
final bool generateConnectConstructor;
|
final bool generateConnectConstructor;
|
||||||
|
|
||||||
|
@ -120,6 +126,7 @@ class DriftOptions {
|
||||||
this.skipVerificationCode = false,
|
this.skipVerificationCode = false,
|
||||||
this.useDataClassNameForCompanions = false,
|
this.useDataClassNameForCompanions = false,
|
||||||
this.useColumnNameAsJsonKeyWhenDefinedInMoorFile = true,
|
this.useColumnNameAsJsonKeyWhenDefinedInMoorFile = true,
|
||||||
|
this.useSqlColumnNameAsJsonKey = false,
|
||||||
this.generateConnectConstructor = false,
|
this.generateConnectConstructor = false,
|
||||||
this.dataClassToCompanions = true,
|
this.dataClassToCompanions = true,
|
||||||
this.generateMutableClasses = false,
|
this.generateMutableClasses = false,
|
||||||
|
@ -147,6 +154,7 @@ class DriftOptions {
|
||||||
required this.skipVerificationCode,
|
required this.skipVerificationCode,
|
||||||
required this.useDataClassNameForCompanions,
|
required this.useDataClassNameForCompanions,
|
||||||
required this.useColumnNameAsJsonKeyWhenDefinedInMoorFile,
|
required this.useColumnNameAsJsonKeyWhenDefinedInMoorFile,
|
||||||
|
required this.useSqlColumnNameAsJsonKey,
|
||||||
required this.generateConnectConstructor,
|
required this.generateConnectConstructor,
|
||||||
required this.dataClassToCompanions,
|
required this.dataClassToCompanions,
|
||||||
required this.generateMutableClasses,
|
required this.generateMutableClasses,
|
||||||
|
|
|
@ -197,12 +197,14 @@ extension TypeUtils on DartType {
|
||||||
}
|
}
|
||||||
|
|
||||||
class DataClassInformation {
|
class DataClassInformation {
|
||||||
final String enforcedName;
|
final String? enforcedName;
|
||||||
|
final String? companionName;
|
||||||
final CustomParentClass? extending;
|
final CustomParentClass? extending;
|
||||||
final ExistingRowClass? existingClass;
|
final ExistingRowClass? existingClass;
|
||||||
|
|
||||||
DataClassInformation(
|
DataClassInformation(
|
||||||
this.enforcedName,
|
this.enforcedName,
|
||||||
|
this.companionName,
|
||||||
this.extending,
|
this.extending,
|
||||||
this.existingClass,
|
this.existingClass,
|
||||||
);
|
);
|
||||||
|
@ -233,16 +235,15 @@ class DataClassInformation {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
String name;
|
var name = dataClassName?.getField('name')!.toStringValue();
|
||||||
|
final companionName =
|
||||||
|
dataClassName?.getField('companionName')?.toStringValue();
|
||||||
CustomParentClass? customParentClass;
|
CustomParentClass? customParentClass;
|
||||||
ExistingRowClass? existingClass;
|
ExistingRowClass? existingClass;
|
||||||
|
|
||||||
if (dataClassName != null) {
|
if (dataClassName != null) {
|
||||||
name = dataClassName.getField('name')!.toStringValue()!;
|
|
||||||
customParentClass =
|
customParentClass =
|
||||||
parseCustomParentClass(name, dataClassName, element, resolver);
|
parseCustomParentClass(name, dataClassName, element, resolver);
|
||||||
} else {
|
|
||||||
name = dataClassNameForClassName(element.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useRowClass != null) {
|
if (useRowClass != null) {
|
||||||
|
@ -277,7 +278,12 @@ class DataClassInformation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return DataClassInformation(name, customParentClass, existingClass);
|
return DataClassInformation(
|
||||||
|
name,
|
||||||
|
companionName,
|
||||||
|
customParentClass,
|
||||||
|
existingClass,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:analyzer/dart/ast/ast.dart';
|
||||||
import 'package:analyzer/dart/ast/syntactic_entity.dart';
|
import 'package:analyzer/dart/ast/syntactic_entity.dart';
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:drift_dev/src/analysis/resolver/shared/data_class.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart' as sql;
|
import 'package:sqlparser/sqlparser.dart' as sql;
|
||||||
|
|
||||||
import '../../driver/error.dart';
|
import '../../driver/error.dart';
|
||||||
|
@ -54,7 +55,9 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
|
||||||
DriftDeclaration.dartElement(element),
|
DriftDeclaration.dartElement(element),
|
||||||
columns: columns,
|
columns: columns,
|
||||||
references: references.toList(),
|
references: references.toList(),
|
||||||
nameOfRowClass: dataClassInfo.enforcedName,
|
nameOfRowClass:
|
||||||
|
dataClassInfo.enforcedName ?? dataClassNameForClassName(element.name),
|
||||||
|
nameOfCompanionClass: dataClassInfo.companionName,
|
||||||
existingRowClass: dataClassInfo.existingClass,
|
existingRowClass: dataClassInfo.existingClass,
|
||||||
customParentClass: dataClassInfo.extending,
|
customParentClass: dataClassInfo.extending,
|
||||||
baseDartName: element.name,
|
baseDartName: element.name,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:recase/recase.dart';
|
||||||
import '../../results/results.dart';
|
import '../../results/results.dart';
|
||||||
import '../intermediate_state.dart';
|
import '../intermediate_state.dart';
|
||||||
import '../resolver.dart';
|
import '../resolver.dart';
|
||||||
|
import '../shared/data_class.dart';
|
||||||
import 'helper.dart';
|
import 'helper.dart';
|
||||||
|
|
||||||
class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
|
class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
|
||||||
|
@ -26,7 +27,8 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
|
||||||
discovered.ownId,
|
discovered.ownId,
|
||||||
DriftDeclaration.dartElement(discovered.dartElement),
|
DriftDeclaration.dartElement(discovered.dartElement),
|
||||||
columns: columns,
|
columns: columns,
|
||||||
nameOfRowClass: dataClassInfo.enforcedName,
|
nameOfRowClass: dataClassInfo.enforcedName ??
|
||||||
|
dataClassNameForClassName(discovered.dartElement.name),
|
||||||
existingRowClass: dataClassInfo.existingClass,
|
existingRowClass: dataClassInfo.existingClass,
|
||||||
customParentClass: dataClassInfo.extending,
|
customParentClass: dataClassInfo.extending,
|
||||||
entityInfoName: '\$${discovered.dartElement.name}View',
|
entityInfoName: '\$${discovered.dartElement.name}View',
|
||||||
|
|
|
@ -31,7 +31,7 @@ String dataClassNameForClassName(String tableName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomParentClass? parseCustomParentClass(
|
CustomParentClass? parseCustomParentClass(
|
||||||
String dartTypeName,
|
String? dartTypeName,
|
||||||
DartObject dataClassName,
|
DartObject dataClassName,
|
||||||
ClassElement element,
|
ClassElement element,
|
||||||
LocalElementResolver resolver,
|
LocalElementResolver resolver,
|
||||||
|
@ -87,7 +87,10 @@ CustomParentClass? parseCustomParentClass(
|
||||||
code = AnnotatedDartCode([
|
code = AnnotatedDartCode([
|
||||||
DartTopLevelSymbol.topLevelElement(extendingType.element),
|
DartTopLevelSymbol.topLevelElement(extendingType.element),
|
||||||
'<',
|
'<',
|
||||||
DartTopLevelSymbol(dartTypeName, null),
|
DartTopLevelSymbol(
|
||||||
|
dartTypeName ?? dataClassNameForClassName(element.name),
|
||||||
|
null,
|
||||||
|
),
|
||||||
'>',
|
'>',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -108,12 +108,13 @@ class DriftColumn implements HasType {
|
||||||
/// The actual json key to use when serializing a data class of this table
|
/// The actual json key to use when serializing a data class of this table
|
||||||
/// to json.
|
/// to json.
|
||||||
///
|
///
|
||||||
/// This respectts the [overriddenJsonName], if any, as well as [options].
|
/// This respects the [overriddenJsonName], if any, as well as [options].
|
||||||
String getJsonKey([DriftOptions options = const DriftOptions.defaults()]) {
|
String getJsonKey([DriftOptions options = const DriftOptions.defaults()]) {
|
||||||
if (overriddenJsonName != null) return overriddenJsonName!;
|
if (overriddenJsonName != null) return overriddenJsonName!;
|
||||||
|
|
||||||
final useColumnName = options.useColumnNameAsJsonKeyWhenDefinedInMoorFile &&
|
final useColumnName = options.useSqlColumnNameAsJsonKey ||
|
||||||
declaredInDriftFile;
|
(options.useColumnNameAsJsonKeyWhenDefinedInMoorFile &&
|
||||||
|
declaredInDriftFile);
|
||||||
return useColumnName ? nameInSql : nameInDart;
|
return useColumnName ? nameInSql : nameInDart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -559,4 +559,11 @@ class _AddFromAst extends GeneralizingAstVisitor<void> {
|
||||||
_visitCommaSeparated(node.elements);
|
_visitCommaSeparated(node.elements);
|
||||||
_childEntities([node.rightBracket]);
|
_childEntities([node.rightBracket]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitTypeArgumentList(TypeArgumentList node) {
|
||||||
|
_builder.addText('<');
|
||||||
|
_visitCommaSeparated(node.arguments);
|
||||||
|
_builder.addText('>');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,9 @@ abstract class DriftElementWithResultSet extends DriftSchemaElement {
|
||||||
/// The name for the data class associated with this table or view.
|
/// The name for the data class associated with this table or view.
|
||||||
String get nameOfRowClass;
|
String get nameOfRowClass;
|
||||||
|
|
||||||
|
/// The name for the companion class associated with this table or view.
|
||||||
|
String? get nameOfCompanionClass;
|
||||||
|
|
||||||
/// All [columns] of this table, indexed by their name in SQL.
|
/// All [columns] of this table, indexed by their name in SQL.
|
||||||
late final Map<String, DriftColumn> columnBySqlName = CaseInsensitiveMap.of({
|
late final Map<String, DriftColumn> columnBySqlName = CaseInsensitiveMap.of({
|
||||||
for (final column in columns) column.nameInSql: column,
|
for (final column in columns) column.nameInSql: column,
|
||||||
|
|
|
@ -32,6 +32,9 @@ class DriftTable extends DriftElementWithResultSet {
|
||||||
@override
|
@override
|
||||||
final String nameOfRowClass;
|
final String nameOfRowClass;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? nameOfCompanionClass;
|
||||||
|
|
||||||
final bool withoutRowId;
|
final bool withoutRowId;
|
||||||
|
|
||||||
/// Information about the virtual table creating statement backing this table,
|
/// Information about the virtual table creating statement backing this table,
|
||||||
|
@ -69,6 +72,7 @@ class DriftTable extends DriftElementWithResultSet {
|
||||||
required this.columns,
|
required this.columns,
|
||||||
required this.baseDartName,
|
required this.baseDartName,
|
||||||
required this.nameOfRowClass,
|
required this.nameOfRowClass,
|
||||||
|
this.nameOfCompanionClass,
|
||||||
this.references = const [],
|
this.references = const [],
|
||||||
this.existingRowClass,
|
this.existingRowClass,
|
||||||
this.customParentClass,
|
this.customParentClass,
|
||||||
|
|
|
@ -25,6 +25,9 @@ class DriftView extends DriftElementWithResultSet {
|
||||||
@override
|
@override
|
||||||
final String nameOfRowClass;
|
final String nameOfRowClass;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? nameOfCompanionClass;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<DriftElement> references;
|
List<DriftElement> references;
|
||||||
|
|
||||||
|
@ -38,6 +41,7 @@ class DriftView extends DriftElementWithResultSet {
|
||||||
required this.existingRowClass,
|
required this.existingRowClass,
|
||||||
required this.nameOfRowClass,
|
required this.nameOfRowClass,
|
||||||
required this.references,
|
required this.references,
|
||||||
|
this.nameOfCompanionClass,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -57,6 +57,7 @@ class ElementSerializer {
|
||||||
'fixed_entity_info_name': element.fixedEntityInfoName,
|
'fixed_entity_info_name': element.fixedEntityInfoName,
|
||||||
'base_dart_name': element.baseDartName,
|
'base_dart_name': element.baseDartName,
|
||||||
'row_class_name': element.nameOfRowClass,
|
'row_class_name': element.nameOfRowClass,
|
||||||
|
'companion_class_name': element.nameOfCompanionClass,
|
||||||
'without_rowid': element.withoutRowId,
|
'without_rowid': element.withoutRowId,
|
||||||
'strict': element.strict,
|
'strict': element.strict,
|
||||||
if (element.isVirtual)
|
if (element.isVirtual)
|
||||||
|
@ -146,6 +147,7 @@ class ElementSerializer {
|
||||||
'custom_parent_class':
|
'custom_parent_class':
|
||||||
_serializeCustomParentClass(element.customParentClass),
|
_serializeCustomParentClass(element.customParentClass),
|
||||||
'name_of_row_class': element.nameOfRowClass,
|
'name_of_row_class': element.nameOfRowClass,
|
||||||
|
'name_of_companion_class': element.nameOfCompanionClass,
|
||||||
'source': serializedSource,
|
'source': serializedSource,
|
||||||
};
|
};
|
||||||
} else if (element is BaseDriftAccessor) {
|
} else if (element is BaseDriftAccessor) {
|
||||||
|
@ -539,6 +541,7 @@ class ElementDeserializer {
|
||||||
fixedEntityInfoName: json['fixed_entity_info_name'] as String?,
|
fixedEntityInfoName: json['fixed_entity_info_name'] as String?,
|
||||||
baseDartName: json['base_dart_name'] as String,
|
baseDartName: json['base_dart_name'] as String,
|
||||||
nameOfRowClass: json['row_class_name'] as String,
|
nameOfRowClass: json['row_class_name'] as String,
|
||||||
|
nameOfCompanionClass: json['companion_class_name'] as String?,
|
||||||
withoutRowId: json['without_rowid'] as bool,
|
withoutRowId: json['without_rowid'] as bool,
|
||||||
strict: json['strict'] as bool,
|
strict: json['strict'] as bool,
|
||||||
virtualTableData: virtualTableData,
|
virtualTableData: virtualTableData,
|
||||||
|
@ -678,6 +681,7 @@ class ElementDeserializer {
|
||||||
customParentClass:
|
customParentClass:
|
||||||
_readCustomParentClass(json['custom_parent_class'] as Map?),
|
_readCustomParentClass(json['custom_parent_class'] as Map?),
|
||||||
nameOfRowClass: json['name_of_row_class'] as String,
|
nameOfRowClass: json['name_of_row_class'] as String,
|
||||||
|
nameOfCompanionClass: json['name_of_companion_class'] as String,
|
||||||
existingRowClass: json['existing_data_class'] != null
|
existingRowClass: json['existing_data_class'] != null
|
||||||
? await _readExistingRowClass(
|
? await _readExistingRowClass(
|
||||||
id.libraryUri, json['existing_data_class'] as Map)
|
id.libraryUri, json['existing_data_class'] as Map)
|
||||||
|
|
|
@ -18,6 +18,7 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate(
|
||||||
'skip_verification_code',
|
'skip_verification_code',
|
||||||
'use_data_class_name_for_companions',
|
'use_data_class_name_for_companions',
|
||||||
'use_column_name_as_json_key_when_defined_in_moor_file',
|
'use_column_name_as_json_key_when_defined_in_moor_file',
|
||||||
|
'use_sql_column_name_as_json_key',
|
||||||
'generate_connect_constructor',
|
'generate_connect_constructor',
|
||||||
'sqlite_modules',
|
'sqlite_modules',
|
||||||
'sqlite',
|
'sqlite',
|
||||||
|
@ -52,6 +53,8 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate(
|
||||||
useColumnNameAsJsonKeyWhenDefinedInMoorFile: $checkedConvert(
|
useColumnNameAsJsonKeyWhenDefinedInMoorFile: $checkedConvert(
|
||||||
'use_column_name_as_json_key_when_defined_in_moor_file',
|
'use_column_name_as_json_key_when_defined_in_moor_file',
|
||||||
(v) => v as bool? ?? true),
|
(v) => v as bool? ?? true),
|
||||||
|
useSqlColumnNameAsJsonKey: $checkedConvert(
|
||||||
|
'use_sql_column_name_as_json_key', (v) => v as bool? ?? false),
|
||||||
generateConnectConstructor: $checkedConvert(
|
generateConnectConstructor: $checkedConvert(
|
||||||
'generate_connect_constructor', (v) => v as bool? ?? false),
|
'generate_connect_constructor', (v) => v as bool? ?? false),
|
||||||
dataClassToCompanions: $checkedConvert(
|
dataClassToCompanions: $checkedConvert(
|
||||||
|
@ -111,6 +114,7 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate(
|
||||||
'useDataClassNameForCompanions': 'use_data_class_name_for_companions',
|
'useDataClassNameForCompanions': 'use_data_class_name_for_companions',
|
||||||
'useColumnNameAsJsonKeyWhenDefinedInMoorFile':
|
'useColumnNameAsJsonKeyWhenDefinedInMoorFile':
|
||||||
'use_column_name_as_json_key_when_defined_in_moor_file',
|
'use_column_name_as_json_key_when_defined_in_moor_file',
|
||||||
|
'useSqlColumnNameAsJsonKey': 'use_sql_column_name_as_json_key',
|
||||||
'generateConnectConstructor': 'generate_connect_constructor',
|
'generateConnectConstructor': 'generate_connect_constructor',
|
||||||
'dataClassToCompanions': 'data_class_to_companions',
|
'dataClassToCompanions': 'data_class_to_companions',
|
||||||
'generateMutableClasses': 'mutable_classes',
|
'generateMutableClasses': 'mutable_classes',
|
||||||
|
@ -143,6 +147,7 @@ Map<String, dynamic> _$DriftOptionsToJson(DriftOptions instance) =>
|
||||||
instance.useDataClassNameForCompanions,
|
instance.useDataClassNameForCompanions,
|
||||||
'use_column_name_as_json_key_when_defined_in_moor_file':
|
'use_column_name_as_json_key_when_defined_in_moor_file':
|
||||||
instance.useColumnNameAsJsonKeyWhenDefinedInMoorFile,
|
instance.useColumnNameAsJsonKeyWhenDefinedInMoorFile,
|
||||||
|
'use_sql_column_name_as_json_key': instance.useSqlColumnNameAsJsonKey,
|
||||||
'generate_connect_constructor': instance.generateConnectConstructor,
|
'generate_connect_constructor': instance.generateConnectConstructor,
|
||||||
'sqlite_modules':
|
'sqlite_modules':
|
||||||
instance.modules.map((e) => _$SqlModuleEnumMap[e]!).toList(),
|
instance.modules.map((e) => _$SqlModuleEnumMap[e]!).toList(),
|
||||||
|
|
|
@ -13,8 +13,9 @@ Expando<List<Input>> expectedSchema = Expando();
|
||||||
class VerifierImplementation implements SchemaVerifier {
|
class VerifierImplementation implements SchemaVerifier {
|
||||||
final SchemaInstantiationHelper helper;
|
final SchemaInstantiationHelper helper;
|
||||||
final Random _random = Random();
|
final Random _random = Random();
|
||||||
|
final void Function(Database)? setup;
|
||||||
|
|
||||||
VerifierImplementation(this.helper);
|
VerifierImplementation(this.helper, {this.setup});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> migrateAndValidate(GeneratedDatabase db, int expectedVersion,
|
Future<void> migrateAndValidate(GeneratedDatabase db, int expectedVersion,
|
||||||
|
@ -57,14 +58,20 @@ class VerifierImplementation implements SchemaVerifier {
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Database _setupDatabase(String uri) {
|
||||||
|
final database = sqlite3.open(uri, uri: true);
|
||||||
|
setup?.call(database);
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<InitializedSchema> schemaAt(int version) async {
|
Future<InitializedSchema> schemaAt(int version) async {
|
||||||
// Use distinct executors for setup and use, allowing us to close the helper
|
// Use distinct executors for setup and use, allowing us to close the helper
|
||||||
// db here and avoid creating it twice.
|
// db here and avoid creating it twice.
|
||||||
// https://www.sqlite.org/inmemorydb.html#sharedmemdb
|
// https://www.sqlite.org/inmemorydb.html#sharedmemdb
|
||||||
final uri = 'file:mem${_randomString()}?mode=memory&cache=shared';
|
final uri = 'file:mem${_randomString()}?mode=memory&cache=shared';
|
||||||
final dbForSetup = sqlite3.open(uri, uri: true);
|
final dbForSetup = _setupDatabase(uri);
|
||||||
final dbForUse = sqlite3.open(uri, uri: true);
|
final dbForUse = _setupDatabase(uri);
|
||||||
|
|
||||||
final executor = NativeDatabase.opened(dbForSetup);
|
final executor = NativeDatabase.opened(dbForSetup);
|
||||||
final db = helper.databaseForVersion(executor, version);
|
final db = helper.databaseForVersion(executor, version);
|
||||||
|
@ -74,7 +81,7 @@ class VerifierImplementation implements SchemaVerifier {
|
||||||
await db.close();
|
await db.close();
|
||||||
|
|
||||||
return InitializedSchema(dbForUse, () {
|
return InitializedSchema(dbForUse, () {
|
||||||
final db = sqlite3.open(uri, uri: true);
|
final db = _setupDatabase(uri);
|
||||||
return DatabaseConnection(NativeDatabase.opened(db));
|
return DatabaseConnection(NativeDatabase.opened(db));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,23 @@ class ImportManagerForPartFiles extends ImportManager {
|
||||||
// Part files can't add their own imports, so try to find the element in an
|
// Part files can't add their own imports, so try to find the element in an
|
||||||
// existing import.
|
// existing import.
|
||||||
for (final MapEntry(:key, :value) in _namedImports.entries) {
|
for (final MapEntry(:key, :value) in _namedImports.entries) {
|
||||||
if (value.containsKey(elementName)) {
|
final foundHere = value[elementName];
|
||||||
|
if (foundHere != null && _matchingUrl(definitionUri, foundHere)) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _matchingUrl(Uri wanted, Element target) {
|
||||||
|
final targetUri = target.librarySource?.uri;
|
||||||
|
if (targetUri == null || targetUri.scheme != wanted.scheme) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NullImportManager extends ImportManager {
|
class NullImportManager extends ImportManager {
|
||||||
|
|
|
@ -74,9 +74,10 @@ abstract class _NodeOrWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
AnnotatedDartCode companionType(DriftTable table) {
|
AnnotatedDartCode companionType(DriftTable table) {
|
||||||
final baseName = writer.options.useDataClassNameForCompanions
|
final baseName = table.nameOfCompanionClass ??
|
||||||
? table.nameOfRowClass
|
(writer.options.useDataClassNameForCompanions
|
||||||
: table.baseDartName;
|
? table.nameOfRowClass
|
||||||
|
: table.baseDartName);
|
||||||
|
|
||||||
return generatedElement(table, '${baseName}Companion');
|
return generatedElement(table, '${baseName}Companion');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: drift_dev
|
name: drift_dev
|
||||||
description: Dev-dependency for users of drift. Contains the generator and development tools.
|
description: Dev-dependency for users of drift. Contains the generator and development tools.
|
||||||
version: 2.15.0
|
version: 2.16.0
|
||||||
repository: https://github.com/simolus3/drift
|
repository: https://github.com/simolus3/drift
|
||||||
homepage: https://drift.simonbinder.eu/
|
homepage: https://drift.simonbinder.eu/
|
||||||
issue_tracker: https://github.com/simolus3/drift/issues
|
issue_tracker: https://github.com/simolus3/drift/issues
|
||||||
|
@ -30,7 +30,7 @@ dependencies:
|
||||||
io: ^1.0.3
|
io: ^1.0.3
|
||||||
|
|
||||||
# Drift-specific analysis and apis
|
# Drift-specific analysis and apis
|
||||||
drift: '>=2.15.0 <2.16.0'
|
drift: '>=2.16.0 <2.17.0'
|
||||||
sqlite3: '>=0.1.6 <3.0.0'
|
sqlite3: '>=0.1.6 <3.0.0'
|
||||||
sqlparser: '^0.34.0'
|
sqlparser: '^0.34.0'
|
||||||
|
|
||||||
|
|
|
@ -38,16 +38,17 @@ CREATE INDEX b_idx /* comment should be stripped */ ON b (foo, upper(foo));
|
||||||
final result = await emulateDriftBuild(
|
final result = await emulateDriftBuild(
|
||||||
inputs: {
|
inputs: {
|
||||||
'a|lib/main.dart': r'''
|
'a|lib/main.dart': r'''
|
||||||
|
import 'dart:io' as io;
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
import 'tables.dart' as tables;
|
import 'tables.dart' as tables;
|
||||||
|
|
||||||
@drift.DriftDatabase(tables: [tables.Texts])
|
@drift.DriftDatabase(tables: [tables.Files])
|
||||||
class MyDatabase extends _$MyDatabase {}
|
class MyDatabase extends _$MyDatabase {}
|
||||||
''',
|
''',
|
||||||
'a|lib/tables.dart': '''
|
'a|lib/tables.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
class Texts extends Table {
|
class Files extends Table {
|
||||||
TextColumn get content => text()();
|
TextColumn get content => text()();
|
||||||
}
|
}
|
||||||
''',
|
''',
|
||||||
|
@ -59,12 +60,12 @@ class Texts extends Table {
|
||||||
'a|lib/main.drift.dart': decodedMatches(
|
'a|lib/main.drift.dart': decodedMatches(
|
||||||
allOf(
|
allOf(
|
||||||
contains(
|
contains(
|
||||||
r'class $TextsTable extends tables.Texts with '
|
r'class $FilesTable extends tables.Files with '
|
||||||
r'drift.TableInfo<$TextsTable, Text>',
|
r'drift.TableInfo<$FilesTable, File>',
|
||||||
),
|
),
|
||||||
contains(
|
contains(
|
||||||
'class Text extends drift.DataClass implements '
|
'class File extends drift.DataClass implements '
|
||||||
'drift.Insertable<Text>',
|
'drift.Insertable<File>',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,7 +5,11 @@ import 'package:drift_dev/src/services/schema/verifier_impl.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
final verifier = SchemaVerifier(_TestHelper());
|
final verifier = SchemaVerifier(
|
||||||
|
_TestHelper(),
|
||||||
|
setup: (rawDb) => rawDb.createFunction(
|
||||||
|
functionName: 'test_function', function: (args) => 1),
|
||||||
|
);
|
||||||
|
|
||||||
group('startAt', () {
|
group('startAt', () {
|
||||||
test('starts at the requested version', () async {
|
test('starts at the requested version', () async {
|
||||||
|
@ -15,6 +19,12 @@ void main() {
|
||||||
expect(details.hadUpgrade, isFalse, reason: 'no upgrade expected');
|
expect(details.hadUpgrade, isFalse, reason: 'no upgrade expected');
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('registers custom functions', () async {
|
||||||
|
final db = (await verifier.startAt(17)).executor;
|
||||||
|
await db.ensureOpen(_DelegatedUser(17, (_, details) async {}));
|
||||||
|
await db.runSelect('select test_function()', []);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('migrateAndValidate', () {
|
group('migrateAndValidate', () {
|
||||||
|
|
|
@ -267,6 +267,120 @@ extension ItemToInsertable on i1.Item {
|
||||||
result.writer,
|
result.writer,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'generates fromJson and toJson with the sql column names as json keys',
|
||||||
|
() async {
|
||||||
|
final writer = await emulateDriftBuild(
|
||||||
|
options: const BuilderOptions({
|
||||||
|
'use_sql_column_name_as_json_key': true,
|
||||||
|
}),
|
||||||
|
inputs: const {
|
||||||
|
'a|lib/main.dart': r'''
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
part 'main.drift.dart';
|
||||||
|
|
||||||
|
class MyTable extends Table {
|
||||||
|
TextColumn get myFirstColumn => text()();
|
||||||
|
IntColumn get mySecondColumn => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@DriftDatabase(
|
||||||
|
tables: [MyTable],
|
||||||
|
)
|
||||||
|
class Database extends _$Database {}
|
||||||
|
'''
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
checkOutputs({
|
||||||
|
'a|lib/main.drift.dart': decodedMatches(contains(r'''
|
||||||
|
factory MyTableData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return MyTableData(
|
||||||
|
myFirstColumn: serializer.fromJson<String>(json['my_first_column']),
|
||||||
|
mySecondColumn: serializer.fromJson<int>(json['my_second_column']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'my_first_column': serializer.toJson<String>(myFirstColumn),
|
||||||
|
'my_second_column': serializer.toJson<int>(mySecondColumn),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
''')),
|
||||||
|
}, writer.dartOutputs, writer.writer);
|
||||||
|
},
|
||||||
|
tags: 'analyzer',
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'It should use the provided names for the data classes and the companion class',
|
||||||
|
() async {
|
||||||
|
final writer = await emulateDriftBuild(
|
||||||
|
inputs: const {
|
||||||
|
'a|lib/main.dart': r'''
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
part 'main.drift.dart';
|
||||||
|
|
||||||
|
@DataClassName('FirstDataClass', companion: 'FirstCompanionClass')
|
||||||
|
class FirstTable extends Table {
|
||||||
|
TextColumn get foo => text()();
|
||||||
|
IntColumn get bar => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataClassName.custom(name: 'SecondDataClass', companion: 'SecondCompanionClass')
|
||||||
|
class SecondTable extends Table {
|
||||||
|
TextColumn get foo => text()();
|
||||||
|
IntColumn get bar => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataClassName.custom(companion: 'ThirdCompanionClass')
|
||||||
|
class ThirdTable extends Table {
|
||||||
|
TextColumn get foo => text()();
|
||||||
|
IntColumn get bar => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DriftDatabase(
|
||||||
|
tables: [FirstTable, SecondTable, ThirdTable],
|
||||||
|
)
|
||||||
|
class Database extends _$Database {}
|
||||||
|
'''
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
checkOutputs({
|
||||||
|
'a|lib/main.drift.dart': decodedMatches(allOf([
|
||||||
|
contains(
|
||||||
|
'class FirstDataClass extends DataClass implements Insertable<FirstDataClass> {',
|
||||||
|
),
|
||||||
|
contains(
|
||||||
|
'class FirstTableCompanion extends UpdateCompanion<FirstDataClass> {',
|
||||||
|
),
|
||||||
|
contains(
|
||||||
|
'class SecondDataClass extends DataClass implements Insertable<SecondDataClass> {',
|
||||||
|
),
|
||||||
|
contains(
|
||||||
|
'class SecondTableCompanion extends UpdateCompanion<SecondDataClass> {',
|
||||||
|
),
|
||||||
|
contains(
|
||||||
|
'class ThirdTableData extends DataClass implements Insertable<ThirdTableData> {',
|
||||||
|
),
|
||||||
|
contains(
|
||||||
|
'class ThirdTableCompanion extends UpdateCompanion<ThirdTableData> {',
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
}, writer.dartOutputs, writer.writer);
|
||||||
|
},
|
||||||
|
tags: 'analyzer',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GeneratesConstDataClasses extends Matcher {
|
class _GeneratesConstDataClasses extends Matcher {
|
||||||
|
|
|
@ -3,7 +3,7 @@ version: 1.0.0
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.18.0 <3.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
drift:
|
drift:
|
||||||
|
|
|
@ -3,7 +3,7 @@ publish_to: none
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.14.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
drift: ^2.11.0
|
drift: ^2.11.0
|
||||||
|
|
|
@ -120,7 +120,8 @@ class RemoteDatabase {
|
||||||
isAlive: isAlive,
|
isAlive: isAlive,
|
||||||
scope: {'db': database.database.id!},
|
scope: {'db': database.database.id!},
|
||||||
);
|
);
|
||||||
final value = await eval.retrieveFullValueAsString(stringVal);
|
final value = await eval.service
|
||||||
|
.retrieveFullStringValue(eval.isolateRef!.id!, stringVal);
|
||||||
|
|
||||||
final description = DatabaseDescription.fromJson(json.decode(value!));
|
final description = DatabaseDescription.fromJson(json.decode(value!));
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,11 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
devtools_extensions: ^0.0.8
|
devtools_extensions: ^0.0.8
|
||||||
devtools_app_shared: '>=0.0.5 <0.0.6' # 0.0.6 requires unstable Flutter
|
devtools_app_shared: ^0.0.9
|
||||||
db_viewer: ^1.0.3
|
db_viewer: ^1.0.3
|
||||||
rxdart: ^0.27.7
|
rxdart: ^0.27.7
|
||||||
flutter_riverpod: ^3.0.0-dev.0
|
flutter_riverpod: ^3.0.0-dev.0
|
||||||
vm_service: ^11.10.0
|
vm_service: ^13.0.0
|
||||||
path: ^1.8.3
|
path: ^1.8.3
|
||||||
drift: ^2.12.1
|
drift: ^2.12.1
|
||||||
logging: ^1.2.0
|
logging: ^1.2.0
|
||||||
|
@ -29,5 +29,8 @@ dev_dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^3.0.0
|
flutter_lints: ^3.0.0
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
web: ^0.5.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
|
@ -4,8 +4,10 @@ talking to PostgreSQL databases by using the `postgres` package.
|
||||||
## Using this
|
## Using this
|
||||||
|
|
||||||
For general notes on using drift, see [this guide](https://drift.simonbinder.eu/getting-started/).
|
For general notes on using drift, see [this guide](https://drift.simonbinder.eu/getting-started/).
|
||||||
|
Detailed docs on getting started with `drift_postgres` are available [here](https://drift.simonbinder.eu/docs/platforms/postgres/#setup).
|
||||||
|
|
||||||
To use drift_postgres, add this to your `pubspec.yaml`
|
To use drift_postgres, add this to your `pubspec.yaml`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
drift: "$latest version"
|
drift: "$latest version"
|
||||||
|
@ -25,6 +27,21 @@ final database = AppDatabase(PgDatabase(
|
||||||
));
|
));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Also, consider adding builder options to make drift generate postgres-specific code:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# build.yaml
|
||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
builders:
|
||||||
|
drift_dev:
|
||||||
|
options:
|
||||||
|
sql:
|
||||||
|
dialects:
|
||||||
|
- sqlite # remove this line if you only need postgres
|
||||||
|
- postgres
|
||||||
|
```
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
|
|
||||||
To test this package, first run
|
To test this package, first run
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
builders:
|
||||||
|
drift_dev:
|
||||||
|
options:
|
||||||
|
sql:
|
||||||
|
dialects:
|
||||||
|
- sqlite # remove this line if you only need postgres
|
||||||
|
- postgres
|
|
@ -54,7 +54,8 @@ class _PgDelegate extends DatabaseDelegate {
|
||||||
late DbVersionDelegate versionDelegate;
|
late DbVersionDelegate versionDelegate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> get isOpen => Future.value(_openedSession != null);
|
Future<bool> get isOpen =>
|
||||||
|
Future.value(_openedSession != null && _openedSession!.isOpen);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> open(QueryExecutorUser user) async {
|
Future<void> open(QueryExecutorUser user) async {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: drift_postgres
|
name: drift_postgres
|
||||||
description: Postgres implementation and APIs for the drift database package.
|
description: Postgres implementation and APIs for the drift database package.
|
||||||
version: 1.2.0-dev
|
version: 1.2.0
|
||||||
repository: https://github.com/simolus3/drift
|
repository: https://github.com/simolus3/drift
|
||||||
homepage: https://drift.simonbinder.eu/docs/platforms/postgres/
|
homepage: https://drift.simonbinder.eu/docs/platforms/postgres/
|
||||||
issue_tracker: https://github.com/simolus3/drift/issues
|
issue_tracker: https://github.com/simolus3/drift/issues
|
||||||
|
|
|
@ -26,19 +26,19 @@ void main() {
|
||||||
return row.read(expression)!;
|
return row.read(expression)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testWith<T extends Object>(CustomSqlType<T>? type, T value) {
|
||||||
|
test('with variable', () async {
|
||||||
|
final variable = Variable(value, type);
|
||||||
|
expect(await eval(variable), value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('with constant', () async {
|
||||||
|
final constant = Constant(value, type);
|
||||||
|
expect(await eval(constant), value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
group('custom types pass through', () {
|
group('custom types pass through', () {
|
||||||
void testWith<T extends Object>(CustomSqlType<T> type, T value) {
|
|
||||||
test('with variable', () async {
|
|
||||||
final variable = Variable(value, type);
|
|
||||||
expect(await eval(variable), value);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with constant', () async {
|
|
||||||
final constant = Constant(value, type);
|
|
||||||
expect(await eval(constant), value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
group('uuid', () => testWith(PgTypes.uuid, Uuid().v4obj()));
|
group('uuid', () => testWith(PgTypes.uuid, Uuid().v4obj()));
|
||||||
group(
|
group(
|
||||||
'interval',
|
'interval',
|
||||||
|
@ -60,6 +60,8 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('bytea', () => testWith(null, Uint8List.fromList([1, 2, 3, 4, 5])));
|
||||||
|
|
||||||
test('compare datetimes', () async {
|
test('compare datetimes', () async {
|
||||||
final time = DateTime.now();
|
final time = DateTime.now();
|
||||||
final before = Variable(
|
final before = Variable(
|
||||||
|
|
|
@ -162,6 +162,17 @@ class DriftWebDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setSchemaVersion(int version) async {
|
||||||
|
final result = await driver.executeAsync(
|
||||||
|
'set_schema_version(arguments[0], arguments[1])',
|
||||||
|
[version.toString()],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != true) {
|
||||||
|
throw 'Could not set schema version';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> deleteDatabase(WebStorageApi storageApi, String name) async {
|
Future<void> deleteDatabase(WebStorageApi storageApi, String name) async {
|
||||||
await driver.executeAsync('delete_database(arguments[0], arguments[1])', [
|
await driver.executeAsync('delete_database(arguments[0], arguments[1])', [
|
||||||
json.encode([storageApi.name, name]),
|
json.encode([storageApi.name, name]),
|
||||||
|
|
|
@ -12,5 +12,13 @@ class TestDatabase extends _$TestDatabase {
|
||||||
TestDatabase(super.e);
|
TestDatabase(super.e);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 1;
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
|
onUpgrade: (m, from, to) async {
|
||||||
|
await into(testTable).insert(
|
||||||
|
TestTableCompanion.insert(content: 'from onUpgrade migration'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int schemaVersion = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ dependencies:
|
||||||
shelf: ^1.4.1
|
shelf: ^1.4.1
|
||||||
shelf_proxy: ^1.0.4
|
shelf_proxy: ^1.0.4
|
||||||
path: ^1.8.3
|
path: ^1.8.3
|
||||||
js: ^0.6.7
|
js: ^0.7.0
|
||||||
package_config: ^2.1.0
|
package_config: ^2.1.0
|
||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
http: ^1.0.0
|
http: ^1.0.0
|
||||||
|
|
|
@ -40,7 +40,11 @@ enum Browser {
|
||||||
|
|
||||||
Future<Process> spawnDriver() async {
|
Future<Process> spawnDriver() async {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
firefox => Process.start('geckodriver', []),
|
firefox => Process.start('geckodriver', []).then((result) async {
|
||||||
|
// geckodriver seems to take a while to initialize
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
chrome =>
|
chrome =>
|
||||||
Process.start('chromedriver', ['--port=4444', '--url-base=/wd/hub']),
|
Process.start('chromedriver', ['--port=4444', '--url-base=/wd/hub']),
|
||||||
};
|
};
|
||||||
|
@ -156,6 +160,20 @@ void main() {
|
||||||
final finalImpls = await driver.probeImplementations();
|
final finalImpls = await driver.probeImplementations();
|
||||||
expect(finalImpls.existing, isEmpty);
|
expect(finalImpls.existing, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('migrations', () async {
|
||||||
|
await driver.openDatabase(entry);
|
||||||
|
await driver.insertIntoDatabase();
|
||||||
|
await driver.waitForTableUpdate();
|
||||||
|
|
||||||
|
await driver.closeDatabase();
|
||||||
|
await driver.driver.refresh();
|
||||||
|
|
||||||
|
await driver.setSchemaVersion(2);
|
||||||
|
await driver.openDatabase(entry);
|
||||||
|
// The migration adds a row
|
||||||
|
expect(await driver.amountOfRows, 2);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
group(
|
group(
|
||||||
|
|
|
@ -18,6 +18,7 @@ TestDatabase? openedDatabase;
|
||||||
StreamQueue<void>? tableUpdates;
|
StreamQueue<void>? tableUpdates;
|
||||||
|
|
||||||
InitializationMode initializationMode = InitializationMode.none;
|
InitializationMode initializationMode = InitializationMode.none;
|
||||||
|
int schemaVersion = 1;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
_addCallbackForWebDriver('detectImplementations', _detectImplementations);
|
_addCallbackForWebDriver('detectImplementations', _detectImplementations);
|
||||||
|
@ -32,6 +33,10 @@ void main() {
|
||||||
initializationMode = InitializationMode.values.byName(arg!);
|
initializationMode = InitializationMode.values.byName(arg!);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
_addCallbackForWebDriver('set_schema_version', (arg) async {
|
||||||
|
schemaVersion = int.parse(arg!);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
_addCallbackForWebDriver('delete_database', (arg) async {
|
_addCallbackForWebDriver('delete_database', (arg) async {
|
||||||
final result = await WasmDatabase.probe(
|
final result = await WasmDatabase.probe(
|
||||||
sqlite3Uri: sqlite3WasmUri,
|
sqlite3Uri: sqlite3WasmUri,
|
||||||
|
@ -85,7 +90,7 @@ Future<Uint8List?> _initializeDatabase() async {
|
||||||
|
|
||||||
// Let's first open a custom WasmDatabase, the way it would have been
|
// Let's first open a custom WasmDatabase, the way it would have been
|
||||||
// done before WasmDatabase.open.
|
// done before WasmDatabase.open.
|
||||||
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm'));
|
final sqlite3 = await WasmSqlite3.loadFromUrl(sqlite3WasmUri);
|
||||||
final fs = await IndexedDbFileSystem.open(dbName: dbName);
|
final fs = await IndexedDbFileSystem.open(dbName: dbName);
|
||||||
sqlite3.registerVirtualFileSystem(fs, makeDefault: true);
|
sqlite3.registerVirtualFileSystem(fs, makeDefault: true);
|
||||||
|
|
||||||
|
@ -150,7 +155,7 @@ Future<void> _open(String? implementationName) async {
|
||||||
db.createFunction(
|
db.createFunction(
|
||||||
functionName: 'database_host',
|
functionName: 'database_host',
|
||||||
function: (args) => 'document',
|
function: (args) => 'document',
|
||||||
argumentCount: const AllowedArgumentCount(1),
|
argumentCount: const AllowedArgumentCount(0),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -158,7 +163,8 @@ Future<void> _open(String? implementationName) async {
|
||||||
connection = result.resolvedExecutor;
|
connection = result.resolvedExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
final db = openedDatabase = TestDatabase(connection);
|
final db =
|
||||||
|
openedDatabase = TestDatabase(connection)..schemaVersion = schemaVersion;
|
||||||
|
|
||||||
// Make sure it works!
|
// Make sure it works!
|
||||||
await db.customSelect('SELECT database_host()').get();
|
await db.customSelect('SELECT database_host()').get();
|
||||||
|
|
|
@ -6,7 +6,7 @@ void main() {
|
||||||
db.createFunction(
|
db.createFunction(
|
||||||
functionName: 'database_host',
|
functionName: 'database_host',
|
||||||
function: (args) => 'worker',
|
function: (args) => 'worker',
|
||||||
argumentCount: const AllowedArgumentCount(1),
|
argumentCount: const AllowedArgumentCount(0),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
## 0.34.1-dev
|
## 3.35.0-dev
|
||||||
|
|
||||||
|
- Fix parsing binary literals.
|
||||||
|
- Drift extensions: Allow custom class names for `CREATE VIEW` statements.
|
||||||
|
|
||||||
|
## 0.34.1
|
||||||
|
|
||||||
- Allow selecting from virtual tables using the table-valued function
|
- Allow selecting from virtual tables using the table-valued function
|
||||||
syntax.
|
syntax.
|
||||||
|
|
|
@ -82,7 +82,7 @@ class ResolvedType {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
other is ResolvedType &&
|
other is ResolvedType &&
|
||||||
other.type == type &&
|
other.type == type &&
|
||||||
|
@ -114,7 +114,7 @@ abstract class TypeHint {
|
||||||
int get hashCode => runtimeType.hashCode;
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) => other.runtimeType == runtimeType;
|
bool operator ==(Object other) => other.runtimeType == runtimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type hint to mark that this type will contain a boolean value.
|
/// Type hint to mark that this type will contain a boolean value.
|
||||||
|
@ -191,7 +191,7 @@ class ResolveResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
other is ResolveResult &&
|
other is ResolveResult &&
|
||||||
other.type == type &&
|
other.type == type &&
|
||||||
|
|
|
@ -64,7 +64,7 @@ class SimpleName extends DeclaredStatementIdentifier {
|
||||||
int get hashCode => name.hashCode;
|
int get hashCode => name.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other is SimpleName && other.name == name);
|
(other is SimpleName && other.name == name);
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ class SpecialStatementIdentifier extends DeclaredStatementIdentifier {
|
||||||
String get name => specialName;
|
String get name => specialName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other is SpecialStatementIdentifier &&
|
(other is SpecialStatementIdentifier &&
|
||||||
other.specialName == specialName);
|
other.specialName == specialName);
|
||||||
|
|
|
@ -241,7 +241,7 @@ class FrameBoundary {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
if (other.runtimeType != runtimeType) return false;
|
if (other.runtimeType != runtimeType) return false;
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ abstract class TriggerTarget extends AstNode {
|
||||||
int get hashCode => runtimeType.hashCode;
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) => other.runtimeType == runtimeType;
|
bool operator ==(Object other) => other.runtimeType == runtimeType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Iterable<AstNode> get childNodes => const Iterable.empty();
|
Iterable<AstNode> get childNodes => const Iterable.empty();
|
||||||
|
|
|
@ -2294,27 +2294,30 @@ class Parser {
|
||||||
supportAs ? const [TokenType.as, TokenType.$with] : [TokenType.$with];
|
supportAs ? const [TokenType.as, TokenType.$with] : [TokenType.$with];
|
||||||
|
|
||||||
if (enableDriftExtensions && (_match(types))) {
|
if (enableDriftExtensions && (_match(types))) {
|
||||||
final first = _previous;
|
return _startedDriftTableName(_previous);
|
||||||
final useExisting = _previous.type == TokenType.$with;
|
|
||||||
final name =
|
|
||||||
_consumeIdentifier('Expected the name for the data class').identifier;
|
|
||||||
String? constructorName;
|
|
||||||
|
|
||||||
if (_matchOne(TokenType.dot)) {
|
|
||||||
constructorName = _consumeIdentifier(
|
|
||||||
'Expected name of the constructor to use after the dot')
|
|
||||||
.identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DriftTableName(
|
|
||||||
useExistingDartClass: useExisting,
|
|
||||||
overriddenDataClassName: name,
|
|
||||||
constructorName: constructorName,
|
|
||||||
)..setSpan(first, _previous);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DriftTableName _startedDriftTableName(Token first) {
|
||||||
|
final useExisting = _previous.type == TokenType.$with;
|
||||||
|
final name =
|
||||||
|
_consumeIdentifier('Expected the name for the data class').identifier;
|
||||||
|
String? constructorName;
|
||||||
|
|
||||||
|
if (_matchOne(TokenType.dot)) {
|
||||||
|
constructorName = _consumeIdentifier(
|
||||||
|
'Expected name of the constructor to use after the dot')
|
||||||
|
.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DriftTableName(
|
||||||
|
useExistingDartClass: useExisting,
|
||||||
|
overriddenDataClassName: name,
|
||||||
|
constructorName: constructorName,
|
||||||
|
)..setSpan(first, _previous);
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a "CREATE TRIGGER" statement, assuming that the create token has
|
/// Parses a "CREATE TRIGGER" statement, assuming that the create token has
|
||||||
/// already been consumed.
|
/// already been consumed.
|
||||||
CreateTriggerStatement? _createTrigger() {
|
CreateTriggerStatement? _createTrigger() {
|
||||||
|
@ -2409,17 +2412,33 @@ class Parser {
|
||||||
final ifNotExists = _ifNotExists();
|
final ifNotExists = _ifNotExists();
|
||||||
final name = _consumeIdentifier('Expected a name for this view');
|
final name = _consumeIdentifier('Expected a name for this view');
|
||||||
|
|
||||||
// Don't allow the "AS ClassName" syntax for views since it causes an
|
DriftTableName? driftTableName;
|
||||||
// ambiguity with the regular view syntax.
|
var skippedToSelect = false;
|
||||||
final driftTableName = _driftTableName(supportAs: false);
|
|
||||||
|
|
||||||
List<String>? columnNames;
|
if (enableDriftExtensions) {
|
||||||
if (_matchOne(TokenType.leftParen)) {
|
if (_check(TokenType.$with)) {
|
||||||
columnNames = _columnNames();
|
driftTableName = _driftTableName();
|
||||||
_consume(TokenType.rightParen, 'Expected closing bracket');
|
} else if (_matchOne(TokenType.as)) {
|
||||||
|
// This can either be a data class name or the beginning of the select
|
||||||
|
if (_check(TokenType.identifier)) {
|
||||||
|
// It's a data class name
|
||||||
|
driftTableName = _startedDriftTableName(_previous);
|
||||||
|
} else {
|
||||||
|
// No, we'll expect the SELECT next.
|
||||||
|
skippedToSelect = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_consume(TokenType.as, 'Expected AS SELECT');
|
List<String>? columnNames;
|
||||||
|
if (!skippedToSelect) {
|
||||||
|
if (_matchOne(TokenType.leftParen)) {
|
||||||
|
columnNames = _columnNames();
|
||||||
|
_consume(TokenType.rightParen, 'Expected closing bracket');
|
||||||
|
}
|
||||||
|
|
||||||
|
_consume(TokenType.as, 'Expected AS SELECT');
|
||||||
|
}
|
||||||
|
|
||||||
final query = _fullSelect();
|
final query = _fullSelect();
|
||||||
if (query == null) {
|
if (query == null) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue