chore: Move the drift_offline_first to legacy in favor of Serverpod implementation (#32)
This commit is contained in:
parent
027aec5ed0
commit
cbc472523b
10 changed files with 13575 additions and 284 deletions
13
legacy/drift_offline_sync/test/utils/crdt_context.dart
Normal file
13
legacy/drift_offline_sync/test/utils/crdt_context.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_offline_sync/drift_offline_sync.dart';
|
||||
|
||||
/// Extensions for [GeneratedDatabase] to get the CRDT context for testing.
|
||||
extension CrdtContextExtensions on GeneratedDatabase {
|
||||
(OfflineSyncCrdt crdt, CrdtDatabase crdtDb, String nodeId) get crdtContext {
|
||||
final migrator = createMigrator();
|
||||
if (migrator is! OfflineSyncMigrator) {
|
||||
throw StateError('Migrator is not an OfflineSyncMigrator');
|
||||
}
|
||||
return (migrator.crdt, migrator.crdtDb, migrator.nodeId);
|
||||
}
|
||||
}
|
||||
114
legacy/drift_offline_sync/test/utils/database.dart
Normal file
114
legacy/drift_offline_sync/test/utils/database.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_offline_sync/drift_offline_sync.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'executor.dart';
|
||||
import 'tables.dart';
|
||||
import 'user.dart';
|
||||
import 'views.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
/// The test database for the SQLite3 database.
|
||||
///
|
||||
/// This is a memory-based SQLite3 database that is used for testing. It follows
|
||||
/// the same pattern as the `test_descriptor` package to ensure one database is
|
||||
/// created for each test. The database is closed once test ends.
|
||||
TodoDb get database {
|
||||
if (_database != null) return _database!;
|
||||
|
||||
final database = TodoDb(testExecutor);
|
||||
_database = database;
|
||||
|
||||
addTearDown(() async {
|
||||
final database = _database!;
|
||||
_database = null;
|
||||
await database.close();
|
||||
});
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
TodoDb? _database;
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [
|
||||
TodosTable,
|
||||
Categories,
|
||||
Users,
|
||||
SharedTodos,
|
||||
TableWithoutPK,
|
||||
PureDefaults,
|
||||
WithCustomType,
|
||||
TableWithEveryColumnType,
|
||||
Department,
|
||||
Product,
|
||||
Listing,
|
||||
Store,
|
||||
],
|
||||
views: [
|
||||
CategoryTodoCountView,
|
||||
TodoWithCategoryView,
|
||||
],
|
||||
daos: [SomeDao],
|
||||
queries: {
|
||||
'allTodosWithCategory':
|
||||
'SELECT t.*, c.id as catId, c."desc" as catDesc '
|
||||
'FROM todos t INNER JOIN categories c ON c.id = t.category',
|
||||
'deleteTodoById': 'DELETE FROM todos WHERE id = ?',
|
||||
'withIn': 'SELECT * FROM todos WHERE title = ?2 OR id IN ? OR title = ?1',
|
||||
'search': 'SELECT * FROM todos WHERE CASE WHEN -1 = :id THEN 1 ELSE id = :id END',
|
||||
'findCustom': 'SELECT custom FROM table_without_p_k WHERE some_float < 10',
|
||||
},
|
||||
)
|
||||
class TodoDb extends _$TodoDb {
|
||||
TodoDb(super.e) {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
}
|
||||
|
||||
@override
|
||||
int schemaVersion = 1;
|
||||
|
||||
// This is what needs to be added to the user database class to make it CRDT aware.
|
||||
@override
|
||||
OfflineSyncMigrator createMigrator() => OfflineSyncMigrator(
|
||||
this,
|
||||
userId: testUserId,
|
||||
nodeId: testNodeId,
|
||||
synchronizedTables: [
|
||||
todosTable,
|
||||
categories,
|
||||
users,
|
||||
sharedTodos,
|
||||
// tableWithoutPK,
|
||||
pureDefaults,
|
||||
// withCustomType,
|
||||
tableWithEveryColumnType,
|
||||
department,
|
||||
// product,
|
||||
],
|
||||
excludeTables: [
|
||||
listing,
|
||||
product,
|
||||
store,
|
||||
tableWithoutPK,
|
||||
withCustomType,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@DriftAccessor(
|
||||
tables: [Users, SharedTodos, TodosTable],
|
||||
views: [TodoWithCategoryView],
|
||||
queries: {
|
||||
'todosForUser':
|
||||
'SELECT t.* FROM todos t '
|
||||
'INNER JOIN shared_todos st ON st.todo = t.id '
|
||||
'INNER JOIN users u ON u.id = st.user '
|
||||
'WHERE u.id = :user',
|
||||
},
|
||||
)
|
||||
class SomeDao extends DatabaseAccessor<TodoDb> with _$SomeDaoMixin {
|
||||
SomeDao(super.attachedDatabase);
|
||||
}
|
||||
6885
legacy/drift_offline_sync/test/utils/database.g.dart
generated
Normal file
6885
legacy/drift_offline_sync/test/utils/database.g.dart
generated
Normal file
File diff suppressed because it is too large
Load diff
44
legacy/drift_offline_sync/test/utils/executor.dart
Normal file
44
legacy/drift_offline_sync/test/utils/executor.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import 'package:drift/native.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_descriptor/test_descriptor.dart';
|
||||
|
||||
/// Sets the test executor for the SQLite3 database.
|
||||
///
|
||||
/// This is a file-based SQLite3 executor that is used for testing. It follows
|
||||
/// the same pattern as the `test_descriptor` package to ensure one executor is
|
||||
/// created for each test. The executor is closed once test ends.
|
||||
///
|
||||
/// After setting the test executor, the database can be accessed using the
|
||||
/// [testExecutor] getter.
|
||||
void setTestFileExecutor([String fileName = 'test.db']) {
|
||||
final databaseFile = file(fileName);
|
||||
final sqliteDatabase = sqlite3.open(databaseFile.io.path);
|
||||
final executor = NativeDatabase.opened(sqliteDatabase);
|
||||
_setTestExecutor(executor);
|
||||
}
|
||||
|
||||
/// The test executor for the SQLite3 database.
|
||||
///
|
||||
/// This is a memory-based SQLite3 executor that is used for testing. It follows
|
||||
/// the same pattern as the `test_descriptor` package to ensure one executor is
|
||||
/// created for each test. The executor is closed once test ends.
|
||||
NativeDatabase get testExecutor {
|
||||
if (_testExecutor != null) return _testExecutor!;
|
||||
final executor = NativeDatabase.memory();
|
||||
_setTestExecutor(executor);
|
||||
return executor;
|
||||
}
|
||||
|
||||
NativeDatabase? _testExecutor;
|
||||
|
||||
void _setTestExecutor(NativeDatabase executor) {
|
||||
_testExecutor = executor;
|
||||
|
||||
addTearDown(() async {
|
||||
final executor = _testExecutor;
|
||||
if (executor == null) return;
|
||||
_testExecutor = null;
|
||||
await executor.close();
|
||||
});
|
||||
}
|
||||
82
legacy/drift_offline_sync/test/utils/migration.dart
Normal file
82
legacy/drift_offline_sync/test/utils/migration.dart
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_offline_sync/drift_offline_sync.dart';
|
||||
|
||||
import 'tables.dart';
|
||||
import 'user.dart';
|
||||
|
||||
part 'migration.g.dart';
|
||||
|
||||
@DriftDatabase(tables: [TodosTable])
|
||||
class FirstDb extends _$FirstDb {
|
||||
FirstDb(super.e) {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
}
|
||||
|
||||
bool created = false;
|
||||
bool didUpgrade = false;
|
||||
bool onBeforeOpen = false;
|
||||
|
||||
@override
|
||||
int schemaVersion = 1;
|
||||
|
||||
@override
|
||||
OfflineSyncMigrator createMigrator() => OfflineSyncMigrator(
|
||||
this,
|
||||
userId: testUserId,
|
||||
nodeId: testNodeId,
|
||||
);
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onCreate: (m) async {
|
||||
created = true;
|
||||
await m.createAll();
|
||||
},
|
||||
onUpgrade: (m, from, to) async {
|
||||
if (from == 1 && to == 2) {
|
||||
didUpgrade = true;
|
||||
}
|
||||
},
|
||||
beforeOpen: (m) async {
|
||||
onBeforeOpen = true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [Users])
|
||||
class SecondDb extends _$SecondDb {
|
||||
SecondDb(super.e) {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
}
|
||||
|
||||
bool created = false;
|
||||
bool didUpgrade = false;
|
||||
bool onBeforeOpen = false;
|
||||
|
||||
@override
|
||||
int schemaVersion = 2;
|
||||
|
||||
@override
|
||||
OfflineSyncMigrator createMigrator() => OfflineSyncMigrator(
|
||||
this,
|
||||
userId: testUserId,
|
||||
nodeId: testNodeId,
|
||||
);
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onCreate: (m) async {
|
||||
created = true;
|
||||
await m.createAll();
|
||||
},
|
||||
onUpgrade: (m, from, to) async {
|
||||
if (from == 1 && to == 2) {
|
||||
await m.createTable(users);
|
||||
didUpgrade = true;
|
||||
}
|
||||
},
|
||||
beforeOpen: (m) async {
|
||||
onBeforeOpen = true;
|
||||
},
|
||||
);
|
||||
}
|
||||
1972
legacy/drift_offline_sync/test/utils/migration.g.dart
generated
Normal file
1972
legacy/drift_offline_sync/test/utils/migration.g.dart
generated
Normal file
File diff suppressed because it is too large
Load diff
140
legacy/drift_offline_sync/test/utils/tables.dart
Normal file
140
legacy/drift_offline_sync/test/utils/tables.dart
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// TODO: Add a note with Drift LICENSE in this file, since it is mostly a copy
|
||||
// of the Drift example tables.
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
export 'types.dart';
|
||||
|
||||
extension type RowId._(int id) {
|
||||
const RowId(this.id);
|
||||
}
|
||||
|
||||
mixin AutoIncrement on Table {
|
||||
late final id = integer().autoIncrement().map(
|
||||
TypeConverter.extensionType<RowId, int>(),
|
||||
)();
|
||||
}
|
||||
|
||||
@DataClassName('TodoEntry')
|
||||
class TodosTable extends Table with AutoIncrement {
|
||||
@override
|
||||
String get tableName => 'todos';
|
||||
|
||||
late final title = text().withLength(min: 4, max: 16).nullable()();
|
||||
late final content = text()();
|
||||
@JsonKey('target_date')
|
||||
late final targetDate = dateTime().nullable().unique()();
|
||||
@ReferenceName('todos')
|
||||
late final category = integer()
|
||||
.references(Categories, #id, initiallyDeferred: true)
|
||||
.map(TypeConverter.extensionType<RowId, int>())
|
||||
.nullable()();
|
||||
|
||||
late final status = textEnum<TodoStatus>().nullable()();
|
||||
|
||||
@override
|
||||
List<Set<Column>>? get uniqueKeys => [
|
||||
{title, category},
|
||||
{title, targetDate},
|
||||
];
|
||||
}
|
||||
|
||||
enum TodoStatus { open, workInProgress, done }
|
||||
|
||||
class Users extends Table with AutoIncrement {
|
||||
late final name = text().withLength(min: 6, max: 32).unique()();
|
||||
late final isAwesome = boolean().withDefault(const Constant(true))();
|
||||
|
||||
late final profilePicture = blob()();
|
||||
late final DateTimeColumn creationTime = dateTime()
|
||||
.check(creationTime.isBiggerThan(Constant(DateTime.utc(1950))))
|
||||
.withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
@DataClassName('Category')
|
||||
class Categories extends Table with AutoIncrement {
|
||||
late final description = text().named('desc').customConstraint('NOT NULL UNIQUE')();
|
||||
late final priority = intEnum<CategoryPriority>().withDefault(const Constant(0))();
|
||||
|
||||
late final descriptionInUpperCase = text().generatedAs(description.upper())();
|
||||
}
|
||||
|
||||
enum CategoryPriority { low, medium, high }
|
||||
|
||||
class SharedTodos extends Table {
|
||||
late final todo = integer()();
|
||||
late final user = integer()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {todo, user};
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
'FOREIGN KEY (todo) REFERENCES todos(id)',
|
||||
'FOREIGN KEY (user) REFERENCES users(id)',
|
||||
];
|
||||
}
|
||||
|
||||
@UseRowClass(CustomRowClass, constructor: 'map', generateInsertable: true)
|
||||
class TableWithoutPK extends Table {
|
||||
IntColumn get notReallyAnId => integer()();
|
||||
RealColumn get someFloat => real()();
|
||||
Int64Column get webSafeInt => int64().nullable()();
|
||||
|
||||
TextColumn get custom =>
|
||||
text().map(const CustomConverter()).clientDefault(const Uuid().v4)();
|
||||
}
|
||||
|
||||
class TableWithEveryColumnType extends Table with AutoIncrement {
|
||||
BoolColumn get aBool => boolean().nullable()();
|
||||
DateTimeColumn get aDateTime => dateTime().nullable()();
|
||||
TextColumn get aText => text().nullable()();
|
||||
IntColumn get anInt => integer().nullable()();
|
||||
Int64Column get anInt64 => int64().nullable()();
|
||||
RealColumn get aReal => real().nullable()();
|
||||
BlobColumn get aBlob => blob().nullable()();
|
||||
IntColumn get anIntEnum => intEnum<TodoStatus>().nullable()();
|
||||
TextColumn get aTextWithConverter =>
|
||||
text().named('insert').map(const CustomJsonConverter()).nullable()();
|
||||
Column<UuidValue> get aUuid => customType(uuidType).nullable()();
|
||||
}
|
||||
|
||||
class Department extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text().nullable()();
|
||||
}
|
||||
|
||||
class Product extends Table {
|
||||
TextColumn get sku => text()();
|
||||
TextColumn get name => text().nullable()();
|
||||
IntColumn get department => integer().references(Department, #id).nullable()();
|
||||
}
|
||||
|
||||
class Listing extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
@ReferenceName('listings')
|
||||
TextColumn get product => text().references(Product, #sku)();
|
||||
@ReferenceName('listings')
|
||||
IntColumn get store => integer().references(Store, #id).nullable()();
|
||||
RealColumn get price => real().nullable()();
|
||||
}
|
||||
|
||||
class Store extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text().nullable()();
|
||||
}
|
||||
|
||||
class PureDefaults extends Table {
|
||||
// name after keyword to ensure it's escaped properly
|
||||
TextColumn get txt =>
|
||||
text().named('insert').map(const CustomJsonConverter()).nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {txt};
|
||||
}
|
||||
|
||||
class WithCustomType extends Table {
|
||||
Column<UuidValue> get id => customType(uuidType)();
|
||||
}
|
||||
124
legacy/drift_offline_sync/test/utils/types.dart
Normal file
124
legacy/drift_offline_sync/test/utils/types.dart
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
export 'package:uuid/uuid.dart' show UuidValue;
|
||||
|
||||
class CustomRowClass {
|
||||
CustomRowClass._(
|
||||
this.notReallyAnId,
|
||||
this.anotherName,
|
||||
this.webSafeInt,
|
||||
this.custom,
|
||||
this.notFromDb,
|
||||
);
|
||||
|
||||
factory CustomRowClass.map(
|
||||
int notReallyAnId,
|
||||
double someFloat, {
|
||||
required MyCustomObject custom,
|
||||
BigInt? webSafeInt,
|
||||
String? notFromDb,
|
||||
}) {
|
||||
return CustomRowClass._(notReallyAnId, someFloat, webSafeInt, custom, notFromDb);
|
||||
}
|
||||
final int notReallyAnId;
|
||||
final double anotherName;
|
||||
final BigInt? webSafeInt;
|
||||
final MyCustomObject custom;
|
||||
|
||||
final String? notFromDb;
|
||||
|
||||
double get someFloat => anotherName;
|
||||
}
|
||||
|
||||
// example object used for custom mapping
|
||||
class MyCustomObject {
|
||||
MyCustomObject(this.data);
|
||||
final String data;
|
||||
|
||||
@override
|
||||
int get hashCode => data.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MyCustomObject && other.data == data;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomConverter extends TypeConverter<MyCustomObject, String> {
|
||||
const CustomConverter();
|
||||
|
||||
@override
|
||||
MyCustomObject fromSql(String fromDb) {
|
||||
return MyCustomObject(fromDb);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(MyCustomObject value) {
|
||||
return value.data;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomJsonConverter extends CustomConverter
|
||||
with JsonTypeConverter2<MyCustomObject, String, Map> {
|
||||
const CustomJsonConverter();
|
||||
|
||||
@override
|
||||
MyCustomObject fromJson(Map json) {
|
||||
return MyCustomObject(json['data'] as String);
|
||||
}
|
||||
|
||||
@override
|
||||
Map toJson(MyCustomObject value) {
|
||||
return {'data': value.data};
|
||||
}
|
||||
}
|
||||
|
||||
class NativeUuidType implements CustomSqlType<UuidValue> {
|
||||
const NativeUuidType();
|
||||
|
||||
@override
|
||||
String mapToSqlLiteral(UuidValue dartValue) {
|
||||
return "'$dartValue'";
|
||||
}
|
||||
|
||||
@override
|
||||
Object mapToSqlParameter(UuidValue dartValue) {
|
||||
return dartValue;
|
||||
}
|
||||
|
||||
@override
|
||||
UuidValue read(Object fromSql) {
|
||||
return fromSql as UuidValue;
|
||||
}
|
||||
|
||||
@override
|
||||
String sqlTypeName(GenerationContext context) => 'uuid';
|
||||
}
|
||||
|
||||
class _UuidAsTextType implements CustomSqlType<UuidValue> {
|
||||
const _UuidAsTextType();
|
||||
|
||||
@override
|
||||
String mapToSqlLiteral(UuidValue dartValue) {
|
||||
return "'$dartValue'";
|
||||
}
|
||||
|
||||
@override
|
||||
Object mapToSqlParameter(UuidValue dartValue) {
|
||||
return dartValue.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
UuidValue read(Object fromSql) {
|
||||
return UuidValue.fromString(fromSql as String);
|
||||
}
|
||||
|
||||
@override
|
||||
String sqlTypeName(GenerationContext context) => 'text';
|
||||
}
|
||||
|
||||
const uuidType = DialectAwareSqlType<UuidValue>.via(
|
||||
fallback: _UuidAsTextType(),
|
||||
overrides: {SqlDialect.postgres: NativeUuidType()},
|
||||
);
|
||||
23
legacy/drift_offline_sync/test/utils/user.dart
Normal file
23
legacy/drift_offline_sync/test/utils/user.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
// NOTE: Generated once per test so that the tests are deterministic, but also
|
||||
// isolated between each other.
|
||||
String get testNodeId {
|
||||
_testNodeId ??= const Uuid().v7();
|
||||
addTearDown(() {
|
||||
_testNodeId = null;
|
||||
});
|
||||
return _testNodeId!;
|
||||
}
|
||||
|
||||
String get testUserId {
|
||||
_testUserId ??= const Uuid().v7();
|
||||
addTearDown(() {
|
||||
_testUserId = null;
|
||||
});
|
||||
return _testUserId!;
|
||||
}
|
||||
|
||||
String? _testNodeId;
|
||||
String? _testUserId;
|
||||
34
legacy/drift_offline_sync/test/utils/views.dart
Normal file
34
legacy/drift_offline_sync/test/utils/views.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:drift/drift.dart';
|
||||
|
||||
import 'tables.dart';
|
||||
|
||||
abstract class CategoryTodoCountView extends View {
|
||||
TodosTable get todos;
|
||||
Categories get categories;
|
||||
|
||||
Expression<int> get categoryId => categories.id;
|
||||
Expression<String> get description => categories.description + const Variable('!');
|
||||
Expression<int> get itemCount => todos.id.count();
|
||||
|
||||
@override
|
||||
Query as() =>
|
||||
select(
|
||||
[categoryId, description, itemCount],
|
||||
).from(categories).join([
|
||||
innerJoin(todos, todos.category.equalsExp(categories.id)),
|
||||
])
|
||||
..groupBy([categories.id]);
|
||||
}
|
||||
|
||||
abstract class TodoWithCategoryView extends View {
|
||||
TodosTable get todos;
|
||||
Categories get categories;
|
||||
|
||||
@override
|
||||
Query as() =>
|
||||
select(
|
||||
[todos.title, categories.description],
|
||||
).from(todos).join([
|
||||
innerJoin(categories, categories.id.equalsExp(todos.category)),
|
||||
]);
|
||||
}
|
||||
Loading…
Reference in a new issue