chore: Update minimum dart version and run formatter
This commit is contained in:
parent
966fec27db
commit
63a73117e0
22 changed files with 588 additions and 563 deletions
|
|
@ -123,6 +123,7 @@ linter:
|
||||||
|
|
||||||
formatter:
|
formatter:
|
||||||
page_width: 88
|
page_width: 88
|
||||||
|
trailing_commas: preserve
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
exclude:
|
exclude:
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,13 @@ extension ConvertCrdtDataEntry on TableInfo {
|
||||||
return [
|
return [
|
||||||
for (final rowId in foundRowIds)
|
for (final rowId in foundRowIds)
|
||||||
await map({
|
await map({
|
||||||
for (final c in $columns)
|
for (final c in $columns)
|
||||||
c.$name: entries
|
c.$name: entries
|
||||||
.firstWhere((e) => e.rowId == rowId && e.columnName == c.$name)
|
.firstWhere((e) => e.rowId == rowId && e.columnName == c.$name)
|
||||||
.rawValue
|
.rawValue
|
||||||
?.rawSqlValue,
|
?.rawSqlValue,
|
||||||
}) as T,
|
})
|
||||||
|
as T,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,26 +55,26 @@ abstract class OfflineSyncTriggers extends CrdtDataSqlBuilder {
|
||||||
final columnNamesWithoutPrimaryKey = operation == Operation.delete
|
final columnNamesWithoutPrimaryKey = operation == Operation.delete
|
||||||
? [isDeletedColumnName]
|
? [isDeletedColumnName]
|
||||||
: table.$columns
|
: table.$columns
|
||||||
.map((c) => c.$name)
|
.map((c) => c.$name)
|
||||||
// MAYBE: If we remove the primary key columns from the list, some tables
|
// MAYBE: If we remove the primary key columns from the list, some tables
|
||||||
// might be completely ignored (like many-to-many relationship tables).
|
// might be completely ignored (like many-to-many relationship tables).
|
||||||
// Do we really need them? Probably yes if we want to avoid deserializing
|
// Do we really need them? Probably yes if we want to avoid deserializing
|
||||||
// the split rowId into the original primary key columns, which can have
|
// the split rowId into the original primary key columns, which can have
|
||||||
// different types.
|
// different types.
|
||||||
// .where((c) => !table.$primaryKey.map((p) => p.$name).contains(c))
|
// .where((c) => !table.$primaryKey.map((p) => p.$name).contains(c))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final columnInserts = columnNamesWithoutPrimaryKey.map((c) {
|
final columnInserts = columnNamesWithoutPrimaryKey.map((c) {
|
||||||
return ' SELECT\n${[
|
return ' SELECT\n${[
|
||||||
' \'$c\' AS "column_name"',
|
' \'$c\' AS "column_name"',
|
||||||
' ${c == isDeletedColumnName ? 'TRUE' : 'NEW."$c"'} AS "raw_value"',
|
' ${c == isDeletedColumnName ? 'TRUE' : 'NEW."$c"'} AS "raw_value"',
|
||||||
if (operation == Operation.update)
|
if (operation == Operation.update) ' NEW."$c" IS NOT OLD."$c" AS "value_changed"',
|
||||||
' NEW."$c" IS NOT OLD."$c" AS "value_changed"',
|
|
||||||
].join(',\n')}';
|
].join(',\n')}';
|
||||||
});
|
});
|
||||||
|
|
||||||
final whereClause =
|
final whereClause = operation == Operation.update
|
||||||
operation == Operation.update ? '\n WHERE (value_changed = TRUE)' : '';
|
? '\n WHERE (value_changed = TRUE)'
|
||||||
|
: '';
|
||||||
|
|
||||||
return '''
|
return '''
|
||||||
INSERT OR REPLACE INTO $crdtDataTableName (
|
INSERT OR REPLACE INTO $crdtDataTableName (
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,9 @@ class CrdtDataSqlBuilder {
|
||||||
if (table.$primaryKey.length == 1) {
|
if (table.$primaryKey.length == 1) {
|
||||||
return '$tableAlias."${table.$primaryKey.first.$name}"';
|
return '$tableAlias."${table.$primaryKey.first.$name}"';
|
||||||
}
|
}
|
||||||
final columnNames =
|
final columnNames = table.$primaryKey
|
||||||
table.$primaryKey.map((c) => '$tableAlias."${c.$name}"').join(', ');
|
.map((c) => '$tableAlias."${c.$name}"')
|
||||||
|
.join(', ');
|
||||||
return "CONCAT_WS('$rowIdSeparator', $columnNames)";
|
return "CONCAT_WS('$rowIdSeparator', $columnNames)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version: 0.0.1
|
||||||
repository: https://github.com/marcelomendoncasoares/drift_offline_first
|
repository: https://github.com/marcelomendoncasoares/drift_offline_first
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.3.0
|
sdk: ^3.8.0
|
||||||
|
|
||||||
# TODO: Relax the package constraints to expand compatibility.
|
# TODO: Relax the package constraints to expand compatibility.
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ void main() {
|
||||||
(crdt, _, nodeId) = database.crdtContext;
|
(crdt, _, nodeId) = database.crdtContext;
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when getting the canonical time '
|
||||||
'when getting the canonical time '
|
|
||||||
'then it returns Hlc.zero for the nodeId.', () async {
|
'then it returns Hlc.zero for the nodeId.', () async {
|
||||||
expect(crdt.canonicalTime, equals(Hlc.zero(nodeId)));
|
expect(crdt.canonicalTime, equals(Hlc.zero(nodeId)));
|
||||||
});
|
});
|
||||||
|
|
@ -38,8 +37,7 @@ void main() {
|
||||||
createdHlc = allCrdtDataEntries.first.hlcTimestamp;
|
createdHlc = allCrdtDataEntries.first.hlcTimestamp;
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when getting the canonical time '
|
||||||
'when getting the canonical time '
|
|
||||||
'then it returns the HLC timestamp of the created data.', () async {
|
'then it returns the HLC timestamp of the created data.', () async {
|
||||||
expect(crdt.canonicalTime, equals(createdHlc));
|
expect(crdt.canonicalTime, equals(createdHlc));
|
||||||
});
|
});
|
||||||
|
|
@ -60,8 +58,7 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when getting the canonical time '
|
||||||
'when getting the canonical time '
|
|
||||||
'then the canonical time matches the cached HLC for the nodeId.', () async {
|
'then the canonical time matches the cached HLC for the nodeId.', () async {
|
||||||
expect(
|
expect(
|
||||||
crdt.canonicalTime,
|
crdt.canonicalTime,
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,9 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then all entries have expected column names and values.', () async {
|
test('then all entries have expected column names and values.', () async {
|
||||||
final mappedData =
|
final mappedData = await database.tableWithEveryColumnType.fromCrdtDataEntries(
|
||||||
await database.tableWithEveryColumnType.fromCrdtDataEntries(entries);
|
entries,
|
||||||
|
);
|
||||||
expect(mappedData.single, equals(createdData));
|
expect(mappedData.single, equals(createdData));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -104,15 +105,15 @@ void main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
TableWithEveryColumnTypeData _createTestData() => TableWithEveryColumnTypeData(
|
TableWithEveryColumnTypeData _createTestData() => TableWithEveryColumnTypeData(
|
||||||
id: const RowId(1),
|
id: const RowId(1),
|
||||||
aBool: true,
|
aBool: true,
|
||||||
aDateTime: DateTime.now(),
|
aDateTime: DateTime.now(),
|
||||||
aText: 'test',
|
aText: 'test',
|
||||||
anInt: 1,
|
anInt: 1,
|
||||||
anInt64: BigInt.one,
|
anInt64: BigInt.one,
|
||||||
aReal: 1.0,
|
aReal: 1.0,
|
||||||
aBlob: Uint8List.fromList([1, 2, 3]),
|
aBlob: Uint8List.fromList([1, 2, 3]),
|
||||||
anIntEnum: TodoStatus.open,
|
anIntEnum: TodoStatus.open,
|
||||||
aTextWithConverter: MyCustomObject('test'),
|
aTextWithConverter: MyCustomObject('test'),
|
||||||
aUuid: UuidValue.fromString('123e4567-e89b-12d3-a456-426614174000'),
|
aUuid: UuidValue.fromString('123e4567-e89b-12d3-a456-426614174000'),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,7 @@ void main() {
|
||||||
createdData = await database.managers.tableWithEveryColumnType.getSingle();
|
createdData = await database.managers.tableWithEveryColumnType.getSingle();
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when getting the data from the CRDT data table of the CRDT database '
|
||||||
'when getting the data from the CRDT data table of the CRDT database '
|
|
||||||
'then it rebuilds the data perfectly.', () async {
|
'then it rebuilds the data perfectly.', () async {
|
||||||
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
|
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
|
||||||
rowId,
|
rowId,
|
||||||
|
|
@ -44,17 +43,18 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'when trying to get the data from the CRDT data table with a non-existent row id '
|
'when trying to get the data from the CRDT data table with a non-existent row id '
|
||||||
'then it returns null.', () async {
|
'then it returns null.',
|
||||||
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
|
() async {
|
||||||
'non-existent-row-id',
|
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
|
||||||
);
|
'non-existent-row-id',
|
||||||
|
);
|
||||||
|
|
||||||
expect(data, isNull);
|
expect(data, isNull);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test(
|
test('when trying to get the data from the CRDT data table with multiple row ids '
|
||||||
'when trying to get the data from the CRDT data table with multiple row ids '
|
|
||||||
'then it returns the data for the existing row ids.', () async {
|
'then it returns the data for the existing row ids.', () async {
|
||||||
final data = await crdtDb.getFromCrdtData<TableWithEveryColumnTypeData>(
|
final data = await crdtDb.getFromCrdtData<TableWithEveryColumnTypeData>(
|
||||||
[rowId, 'non-existent-row-id'],
|
[rowId, 'non-existent-row-id'],
|
||||||
|
|
@ -64,18 +64,20 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'when trying to get the data from the CRDT data table for a non-synchronized table '
|
'when trying to get the data from the CRDT data table for a non-synchronized table '
|
||||||
'then it throws an error.', () async {
|
'then it throws an error.',
|
||||||
expect(
|
() async {
|
||||||
() => crdtDb.getSingleFromCrdtData<WithCustomTypeData>(rowId),
|
expect(
|
||||||
throwsA(
|
() => crdtDb.getSingleFromCrdtData<WithCustomTypeData>(rowId),
|
||||||
isA<ArgumentError>().having(
|
throwsA(
|
||||||
(e) => e.message,
|
isA<ArgumentError>().having(
|
||||||
'message',
|
(e) => e.message,
|
||||||
'Table for type "WithCustomTypeData" not found in synchronized tables.',
|
'message',
|
||||||
|
'Table for type "WithCustomTypeData" not found in synchronized tables.',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,146 +13,152 @@ void main() {
|
||||||
late String otherNodeId;
|
late String otherNodeId;
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given CRDT for an empty database and a saved changeset during a merge operation',
|
'Given CRDT for an empty database and a saved changeset during a merge operation',
|
||||||
() {
|
() {
|
||||||
late TableWithEveryColumnTypeData testData;
|
late TableWithEveryColumnTypeData testData;
|
||||||
late Iterable<CrdtDataEntry> changeset;
|
late Iterable<CrdtDataEntry> changeset;
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
(crdt, crdtDb, _) = database.crdtContext;
|
|
||||||
otherNodeId = const Uuid().v4();
|
|
||||||
|
|
||||||
testData = TableWithEveryColumnTypeData(
|
|
||||||
id: const RowId(1),
|
|
||||||
aBool: true,
|
|
||||||
aDateTime: DateTime.now(),
|
|
||||||
aText: 'test',
|
|
||||||
anInt: 1,
|
|
||||||
anInt64: BigInt.one,
|
|
||||||
aReal: 1.0,
|
|
||||||
aBlob: Uint8List.fromList([1, 2, 3]),
|
|
||||||
anIntEnum: TodoStatus.open,
|
|
||||||
aTextWithConverter: MyCustomObject('test'),
|
|
||||||
aUuid: UuidValue.fromString('123e4567-e89b-12d3-a456-426614174000'),
|
|
||||||
);
|
|
||||||
|
|
||||||
await database.tableWithEveryColumnType.insertOne(testData);
|
|
||||||
await database.tableWithEveryColumnType.deleteAll();
|
|
||||||
|
|
||||||
changeset = await crdtDb.crdtDataTable.all().get();
|
|
||||||
});
|
|
||||||
|
|
||||||
group('when syncing tables to the database', () {
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await crdtDb.syncCrdtDataToTables();
|
(crdt, crdtDb, _) = database.crdtContext;
|
||||||
|
otherNodeId = const Uuid().v4();
|
||||||
|
|
||||||
|
testData = TableWithEveryColumnTypeData(
|
||||||
|
id: const RowId(1),
|
||||||
|
aBool: true,
|
||||||
|
aDateTime: DateTime.now(),
|
||||||
|
aText: 'test',
|
||||||
|
anInt: 1,
|
||||||
|
anInt64: BigInt.one,
|
||||||
|
aReal: 1.0,
|
||||||
|
aBlob: Uint8List.fromList([1, 2, 3]),
|
||||||
|
anIntEnum: TodoStatus.open,
|
||||||
|
aTextWithConverter: MyCustomObject('test'),
|
||||||
|
aUuid: UuidValue.fromString('123e4567-e89b-12d3-a456-426614174000'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await database.tableWithEveryColumnType.insertOne(testData);
|
||||||
|
await database.tableWithEveryColumnType.deleteAll();
|
||||||
|
|
||||||
|
changeset = await crdtDb.crdtDataTable.all().get();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then the data is inserted into the database according to the changeset.',
|
group('when syncing tables to the database', () {
|
||||||
|
setUp(() async {
|
||||||
|
await crdtDb.syncCrdtDataToTables();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'then the data is inserted into the database according to the changeset.',
|
||||||
() async {
|
() async {
|
||||||
final savedData = await database.managers.tableWithEveryColumnType.getSingle();
|
final savedData = await database.managers.tableWithEveryColumnType
|
||||||
expect(savedData, equals(testData));
|
.getSingle();
|
||||||
|
expect(savedData, equals(testData));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'then no changes are made to the CRDT data table due to the sync.',
|
||||||
|
() async {
|
||||||
|
final savedChangeset = await crdtDb.managers.crdtDataTable.get();
|
||||||
|
expect(savedChangeset, changeset);
|
||||||
|
},
|
||||||
|
skip: 'Skipping test until the trigger disable mechanism is implemented.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Given CRDT for a database with data and a pending changeset with conflicting data and older HLC',
|
||||||
|
() {
|
||||||
|
late Iterable<CrdtDataEntry> firstChangeset;
|
||||||
|
late Iterable<CrdtDataEntry> lateOldChangeset;
|
||||||
|
late Hlc lastHlc;
|
||||||
|
late Hlc lateOldHlc;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
(crdt, crdtDb, _) = database.crdtContext;
|
||||||
|
otherNodeId = const Uuid().v4();
|
||||||
|
|
||||||
|
final todo = TodoEntry(
|
||||||
|
id: const RowId(1),
|
||||||
|
content: 'test',
|
||||||
|
targetDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
lastHlc = Hlc.now(otherNodeId);
|
||||||
|
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
||||||
|
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
||||||
|
|
||||||
|
lateOldHlc = Hlc.zero(otherNodeId);
|
||||||
|
lateOldChangeset = crdtDb.convertToCrdtDataEntry(todo, lateOldHlc);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
group('when saving a changeset with older HLC', () {
|
||||||
'then no changes are made to the CRDT data table due to the sync.',
|
setUp(() async {
|
||||||
() async {
|
await crdt.saveChangeset(lateOldChangeset, [lateOldHlc]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('then no changes are saved to the database.', () async {
|
||||||
final savedChangeset = await crdtDb.managers.crdtDataTable.get();
|
final savedChangeset = await crdtDb.managers.crdtDataTable.get();
|
||||||
expect(savedChangeset, changeset);
|
expect(savedChangeset, firstChangeset);
|
||||||
},
|
});
|
||||||
skip: 'Skipping test until the trigger disable mechanism is implemented.',
|
|
||||||
);
|
test('then the last received HLC is not updated in the database.', () async {
|
||||||
});
|
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
||||||
});
|
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, lastHlc);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given CRDT for a database with data and a pending changeset with conflicting data and older HLC',
|
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
|
||||||
() {
|
() {
|
||||||
late Iterable<CrdtDataEntry> firstChangeset;
|
late Iterable<CrdtDataEntry> firstChangeset;
|
||||||
late Iterable<CrdtDataEntry> lateOldChangeset;
|
late Iterable<CrdtDataEntry> newerChangeset;
|
||||||
late Hlc lastHlc;
|
late Hlc lastHlc;
|
||||||
late Hlc lateOldHlc;
|
late Hlc newerHlc;
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
(crdt, crdtDb, _) = database.crdtContext;
|
|
||||||
otherNodeId = const Uuid().v4();
|
|
||||||
|
|
||||||
final todo = TodoEntry(
|
|
||||||
id: const RowId(1),
|
|
||||||
content: 'test',
|
|
||||||
targetDate: DateTime.now(),
|
|
||||||
);
|
|
||||||
|
|
||||||
lastHlc = Hlc.now(otherNodeId);
|
|
||||||
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
|
||||||
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
|
||||||
|
|
||||||
lateOldHlc = Hlc.zero(otherNodeId);
|
|
||||||
lateOldChangeset = crdtDb.convertToCrdtDataEntry(todo, lateOldHlc);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('when saving a changeset with older HLC', () {
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await crdt.saveChangeset(lateOldChangeset, [lateOldHlc]);
|
(crdt, crdtDb, _) = database.crdtContext;
|
||||||
|
otherNodeId = const Uuid().v4();
|
||||||
|
|
||||||
|
final todo = TodoEntry(
|
||||||
|
id: const RowId(1),
|
||||||
|
content: 'test',
|
||||||
|
targetDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
lastHlc = Hlc.now(otherNodeId);
|
||||||
|
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
||||||
|
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
||||||
|
|
||||||
|
newerHlc = Hlc.now(otherNodeId);
|
||||||
|
newerChangeset = crdtDb
|
||||||
|
.convertToCrdtDataEntry(todo, newerHlc)
|
||||||
|
.where((e) => e.columnName == 'content');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then no changes are saved to the database.', () async {
|
group('when saving a changeset with some entries with newer HLC', () {
|
||||||
final savedChangeset = await crdtDb.managers.crdtDataTable.get();
|
setUp(() async {
|
||||||
expect(savedChangeset, firstChangeset);
|
await crdt.saveChangeset(newerChangeset, [newerHlc]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('then the entries with newer HLC are saved to the database.', () async {
|
||||||
|
final expectedResultingChangeset = [
|
||||||
|
for (final e in firstChangeset)
|
||||||
|
if (e.columnName == 'content') newerChangeset.first else e,
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(await crdtDb.managers.crdtDataTable.get(), expectedResultingChangeset);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('then the last received HLC is updated in the database.', () async {
|
||||||
|
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
||||||
|
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, newerHlc);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
test('then the last received HLC is not updated in the database.', () async {
|
);
|
||||||
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
|
||||||
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, lastHlc);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group(
|
|
||||||
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
|
|
||||||
() {
|
|
||||||
late Iterable<CrdtDataEntry> firstChangeset;
|
|
||||||
late Iterable<CrdtDataEntry> newerChangeset;
|
|
||||||
late Hlc lastHlc;
|
|
||||||
late Hlc newerHlc;
|
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
(crdt, crdtDb, _) = database.crdtContext;
|
|
||||||
otherNodeId = const Uuid().v4();
|
|
||||||
|
|
||||||
final todo = TodoEntry(
|
|
||||||
id: const RowId(1),
|
|
||||||
content: 'test',
|
|
||||||
targetDate: DateTime.now(),
|
|
||||||
);
|
|
||||||
|
|
||||||
lastHlc = Hlc.now(otherNodeId);
|
|
||||||
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
|
||||||
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
|
||||||
|
|
||||||
newerHlc = Hlc.now(otherNodeId);
|
|
||||||
newerChangeset = crdtDb
|
|
||||||
.convertToCrdtDataEntry(todo, newerHlc)
|
|
||||||
.where((e) => e.columnName == 'content');
|
|
||||||
});
|
|
||||||
|
|
||||||
group('when saving a changeset with some entries with newer HLC', () {
|
|
||||||
setUp(() async {
|
|
||||||
await crdt.saveChangeset(newerChangeset, [newerHlc]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('then the entries with newer HLC are saved to the database.', () async {
|
|
||||||
final expectedResultingChangeset = [
|
|
||||||
for (final e in firstChangeset)
|
|
||||||
if (e.columnName == 'content') newerChangeset.first else e,
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(await crdtDb.managers.crdtDataTable.get(), expectedResultingChangeset);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('then the last received HLC is updated in the database.', () async {
|
|
||||||
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
|
||||||
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, newerHlc);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,14 @@ void main() {
|
||||||
(crdt, _, _) = database.crdtContext;
|
(crdt, _, _) = database.crdtContext;
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when getting a changeset '
|
||||||
'when getting a changeset '
|
|
||||||
'then it returns an empty changeset.', () async {
|
'then it returns an empty changeset.', () async {
|
||||||
final changeset = await crdt.getChangeset();
|
final changeset = await crdt.getChangeset();
|
||||||
|
|
||||||
expect(changeset, isEmpty);
|
expect(changeset, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when adding data to the database '
|
||||||
'when adding data to the database '
|
|
||||||
'then the changeset contains the data.', () async {
|
'then the changeset contains the data.', () async {
|
||||||
await database.managers.todosTable.create(
|
await database.managers.todosTable.create(
|
||||||
(t) => t(content: 'test'),
|
(t) => t(content: 'test'),
|
||||||
|
|
@ -57,15 +55,16 @@ void main() {
|
||||||
group('when getting a changeset', () {
|
group('when getting a changeset', () {
|
||||||
group('without any filters', () {
|
group('without any filters', () {
|
||||||
test(
|
test(
|
||||||
'then it returns a changeset with one entry per column of the created data.',
|
'then it returns a changeset with one entry per column of the created data.',
|
||||||
() async {
|
() async {
|
||||||
final changeset = await crdt.getChangeset();
|
final changeset = await crdt.getChangeset();
|
||||||
final changesetColumns = changeset.map((e) => e.columnName).toSet();
|
final changesetColumns = changeset.map((e) => e.columnName).toSet();
|
||||||
final expectedColumns = database.todosTable.$columns.map((c) => c.$name);
|
final expectedColumns = database.todosTable.$columns.map((c) => c.$name);
|
||||||
|
|
||||||
expect(changeset.length, expectedColumns.length);
|
expect(changeset.length, expectedColumns.length);
|
||||||
expect(changesetColumns, equals(expectedColumns));
|
expect(changesetColumns, equals(expectedColumns));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('then the raw values are set correctly.', () async {
|
test('then the raw values are set correctly.', () async {
|
||||||
final changeset = await crdt.getChangeset();
|
final changeset = await crdt.getChangeset();
|
||||||
|
|
@ -81,8 +80,7 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with onlyNodeId set to the nodeId '
|
||||||
'with onlyNodeId set to the nodeId '
|
|
||||||
'then it returns a changeset with the data for the nodeId.', () async {
|
'then it returns a changeset with the data for the nodeId.', () async {
|
||||||
final changeset = await crdt.getChangeset(onlyNodeId: nodeId);
|
final changeset = await crdt.getChangeset(onlyNodeId: nodeId);
|
||||||
final nodeIds = changeset.map((e) => e.hlcTimestamp.nodeId).toSet();
|
final nodeIds = changeset.map((e) => e.hlcTimestamp.nodeId).toSet();
|
||||||
|
|
@ -91,24 +89,21 @@ void main() {
|
||||||
expect(nodeIds, equals({nodeId}));
|
expect(nodeIds, equals({nodeId}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with onlyNodeId set to a different nodeId '
|
||||||
'with onlyNodeId set to a different nodeId '
|
|
||||||
'then it returns an empty changeset.', () async {
|
'then it returns an empty changeset.', () async {
|
||||||
final changeset = await crdt.getChangeset(onlyNodeId: 'wrong-node');
|
final changeset = await crdt.getChangeset(onlyNodeId: 'wrong-node');
|
||||||
|
|
||||||
expect(changeset, isEmpty);
|
expect(changeset, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with exceptNodeId set to the nodeId '
|
||||||
'with exceptNodeId set to the nodeId '
|
|
||||||
'then it returns an empty changeset.', () async {
|
'then it returns an empty changeset.', () async {
|
||||||
final changeset = await crdt.getChangeset(exceptNodeId: nodeId);
|
final changeset = await crdt.getChangeset(exceptNodeId: nodeId);
|
||||||
|
|
||||||
expect(changeset, isEmpty);
|
expect(changeset, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with exceptNodeId set to a different nodeId '
|
||||||
'with exceptNodeId set to a different nodeId '
|
|
||||||
'then it returns a changeset with data from other nodes.', () async {
|
'then it returns a changeset with data from other nodes.', () async {
|
||||||
const otherNodeId = 'other-node';
|
const otherNodeId = 'other-node';
|
||||||
final changeset = await crdt.getChangeset(exceptNodeId: otherNodeId);
|
final changeset = await crdt.getChangeset(exceptNodeId: otherNodeId);
|
||||||
|
|
@ -119,18 +114,18 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'with modifiedAfter set to a timestamp before the created data '
|
'with modifiedAfter set to a timestamp before the created data '
|
||||||
'then it returns a changeset with the entries from the created data.',
|
'then it returns a changeset with the entries from the created data.',
|
||||||
() async {
|
() async {
|
||||||
final earlierHlc = Hlc.zero(nodeId);
|
final earlierHlc = Hlc.zero(nodeId);
|
||||||
final changeset = await crdt.getChangeset(modifiedAfter: earlierHlc);
|
final changeset = await crdt.getChangeset(modifiedAfter: earlierHlc);
|
||||||
|
|
||||||
expect(changeset, isNotEmpty);
|
expect(changeset, isNotEmpty);
|
||||||
expect(changeset.map((e) => e.rowId).toSet(), equals({rowId}));
|
expect(changeset.map((e) => e.rowId).toSet(), equals({rowId}));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test(
|
test('with modifiedAfter set to a timestamp after the created data '
|
||||||
'with modifiedAfter set to a timestamp after the created data '
|
|
||||||
'then it returns an empty changeset.', () async {
|
'then it returns an empty changeset.', () async {
|
||||||
final futureHlc = createdHlc.increment();
|
final futureHlc = createdHlc.increment();
|
||||||
final changeset = await crdt.getChangeset(modifiedAfter: futureHlc);
|
final changeset = await crdt.getChangeset(modifiedAfter: futureHlc);
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ void main() {
|
||||||
(crdt, _, _) = database.crdtContext;
|
(crdt, _, _) = database.crdtContext;
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when getting the last modified timestamp '
|
||||||
'when getting the last modified timestamp '
|
|
||||||
'then it returns null since the database is empty.', () async {
|
'then it returns null since the database is empty.', () async {
|
||||||
final lastModified = await crdt.getLastModified();
|
final lastModified = await crdt.getLastModified();
|
||||||
expect(lastModified, isNull);
|
expect(lastModified, isNull);
|
||||||
|
|
@ -39,36 +38,31 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
group('when getting the last modified timestamp', () {
|
group('when getting the last modified timestamp', () {
|
||||||
test(
|
test('without any filters '
|
||||||
'without any filters '
|
|
||||||
'then it returns the HLC timestamp of the created data.', () async {
|
'then it returns the HLC timestamp of the created data.', () async {
|
||||||
final lastModified = await crdt.getLastModified();
|
final lastModified = await crdt.getLastModified();
|
||||||
expect(lastModified, equals(createdHlc));
|
expect(lastModified, equals(createdHlc));
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with onlyNodeId set to the nodeId '
|
||||||
'with onlyNodeId set to the nodeId '
|
|
||||||
'then it returns the HLC timestamp of the created data.', () async {
|
'then it returns the HLC timestamp of the created data.', () async {
|
||||||
final lastModified = await crdt.getLastModified(onlyNodeId: nodeId);
|
final lastModified = await crdt.getLastModified(onlyNodeId: nodeId);
|
||||||
expect(lastModified, equals(createdHlc));
|
expect(lastModified, equals(createdHlc));
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with onlyNodeId set to a different nodeId '
|
||||||
'with onlyNodeId set to a different nodeId '
|
|
||||||
'then it returns null.', () async {
|
'then it returns null.', () async {
|
||||||
final lastModified = await crdt.getLastModified(onlyNodeId: 'other-node');
|
final lastModified = await crdt.getLastModified(onlyNodeId: 'other-node');
|
||||||
expect(lastModified, isNull);
|
expect(lastModified, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with exceptNodeId set to the nodeId '
|
||||||
'with exceptNodeId set to the nodeId '
|
|
||||||
'then it returns null.', () async {
|
'then it returns null.', () async {
|
||||||
final lastModified = await crdt.getLastModified(exceptNodeId: nodeId);
|
final lastModified = await crdt.getLastModified(exceptNodeId: nodeId);
|
||||||
expect(lastModified, isNull);
|
expect(lastModified, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('with exceptNodeId set to a different nodeId '
|
||||||
'with exceptNodeId set to a different nodeId '
|
|
||||||
'then it returns the HLC timestamp of the created data.', () async {
|
'then it returns the HLC timestamp of the created data.', () async {
|
||||||
final lastModified = await crdt.getLastModified(exceptNodeId: 'other-node');
|
final lastModified = await crdt.getLastModified(exceptNodeId: 'other-node');
|
||||||
expect(lastModified, equals(createdHlc));
|
expect(lastModified, equals(createdHlc));
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ void main() {
|
||||||
changeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.now(nodeId));
|
changeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.now(nodeId));
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when merging a changeset '
|
||||||
'when merging a changeset '
|
|
||||||
'then the changeset is saved to the database.', () async {
|
'then the changeset is saved to the database.', () async {
|
||||||
await crdt.merge(changeset);
|
await crdt.merge(changeset);
|
||||||
|
|
||||||
|
|
@ -35,69 +34,69 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given CRDT for a database with data and a pending changeset with conflicting data and older HLC',
|
'Given CRDT for a database with data and a pending changeset with conflicting data and older HLC',
|
||||||
() {
|
() {
|
||||||
late Iterable<CrdtDataEntry> firstChangeset;
|
late Iterable<CrdtDataEntry> firstChangeset;
|
||||||
late Iterable<CrdtDataEntry> lateOldChangeset;
|
late Iterable<CrdtDataEntry> lateOldChangeset;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
(crdt, crdtDb, nodeId) = database.crdtContext;
|
(crdt, crdtDb, nodeId) = database.crdtContext;
|
||||||
|
|
||||||
final todo = TodoEntry(
|
final todo = TodoEntry(
|
||||||
id: const RowId(1),
|
id: const RowId(1),
|
||||||
content: 'test',
|
content: 'test',
|
||||||
targetDate: DateTime.now(),
|
targetDate: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.now(nodeId));
|
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.now(nodeId));
|
||||||
await crdt.merge(firstChangeset);
|
await crdt.merge(firstChangeset);
|
||||||
|
|
||||||
lateOldChangeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.zero(nodeId));
|
lateOldChangeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.zero(nodeId));
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when merging a changeset with older HLC '
|
||||||
'when merging a changeset with older HLC '
|
'then no changes are saved to the database.', () async {
|
||||||
'then no changes are saved to the database.', () async {
|
await crdt.merge(lateOldChangeset);
|
||||||
await crdt.merge(lateOldChangeset);
|
|
||||||
|
|
||||||
expect(await crdtDb.managers.crdtDataTable.get(), firstChangeset);
|
expect(await crdtDb.managers.crdtDataTable.get(), firstChangeset);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
|
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
|
||||||
() {
|
() {
|
||||||
late Iterable<CrdtDataEntry> firstChangeset;
|
late Iterable<CrdtDataEntry> firstChangeset;
|
||||||
late Iterable<CrdtDataEntry> newerChangeset;
|
late Iterable<CrdtDataEntry> newerChangeset;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
(crdt, crdtDb, nodeId) = database.crdtContext;
|
(crdt, crdtDb, nodeId) = database.crdtContext;
|
||||||
|
|
||||||
final todo = TodoEntry(
|
final todo = TodoEntry(
|
||||||
id: const RowId(1),
|
id: const RowId(1),
|
||||||
content: 'test',
|
content: 'test',
|
||||||
targetDate: DateTime.now(),
|
targetDate: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.now(nodeId));
|
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.now(nodeId));
|
||||||
await crdt.merge(firstChangeset);
|
await crdt.merge(firstChangeset);
|
||||||
|
|
||||||
newerChangeset = crdtDb
|
newerChangeset = crdtDb
|
||||||
.convertToCrdtDataEntry(todo, Hlc.now(nodeId))
|
.convertToCrdtDataEntry(todo, Hlc.now(nodeId))
|
||||||
.where((e) => e.columnName == 'content');
|
.where((e) => e.columnName == 'content');
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when merging a changeset with some entries with newer HLC '
|
||||||
'when merging a changeset with some entries with newer HLC '
|
'then the entries with newer HLC are saved to the database.', () async {
|
||||||
'then the entries with newer HLC are saved to the database.', () async {
|
await crdt.merge(newerChangeset);
|
||||||
await crdt.merge(newerChangeset);
|
|
||||||
|
|
||||||
final expectedResultingChangeset = [
|
final expectedResultingChangeset = [
|
||||||
for (final e in firstChangeset)
|
for (final e in firstChangeset)
|
||||||
if (e.columnName == 'content') newerChangeset.first else e,
|
if (e.columnName == 'content') newerChangeset.first else e,
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(await crdtDb.managers.crdtDataTable.get(), expectedResultingChangeset);
|
expect(await crdtDb.managers.crdtDataTable.get(), expectedResultingChangeset);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,94 +47,96 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given CRDT for a database with data and a pending changeset with conflicting data and older HLC',
|
'Given CRDT for a database with data and a pending changeset with conflicting data and older HLC',
|
||||||
() {
|
() {
|
||||||
late Iterable<CrdtDataEntry> firstChangeset;
|
late Iterable<CrdtDataEntry> firstChangeset;
|
||||||
late Iterable<CrdtDataEntry> lateOldChangeset;
|
late Iterable<CrdtDataEntry> lateOldChangeset;
|
||||||
late Hlc lastHlc;
|
late Hlc lastHlc;
|
||||||
late Hlc lateOldHlc;
|
late Hlc lateOldHlc;
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
(crdt, crdtDb, _) = database.crdtContext;
|
|
||||||
otherNodeId = const Uuid().v4();
|
|
||||||
|
|
||||||
final todo = TodoEntry(
|
|
||||||
id: const RowId(1),
|
|
||||||
content: 'test',
|
|
||||||
targetDate: DateTime.now(),
|
|
||||||
);
|
|
||||||
|
|
||||||
lastHlc = Hlc.now(otherNodeId);
|
|
||||||
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
|
||||||
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
|
||||||
|
|
||||||
lateOldHlc = Hlc.zero(otherNodeId);
|
|
||||||
lateOldChangeset = crdtDb.convertToCrdtDataEntry(todo, lateOldHlc);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('when saving a changeset with older HLC', () {
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await crdt.saveChangeset(lateOldChangeset, [lateOldHlc]);
|
(crdt, crdtDb, _) = database.crdtContext;
|
||||||
|
otherNodeId = const Uuid().v4();
|
||||||
|
|
||||||
|
final todo = TodoEntry(
|
||||||
|
id: const RowId(1),
|
||||||
|
content: 'test',
|
||||||
|
targetDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
lastHlc = Hlc.now(otherNodeId);
|
||||||
|
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
||||||
|
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
||||||
|
|
||||||
|
lateOldHlc = Hlc.zero(otherNodeId);
|
||||||
|
lateOldChangeset = crdtDb.convertToCrdtDataEntry(todo, lateOldHlc);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then no changes are saved to the database.', () async {
|
group('when saving a changeset with older HLC', () {
|
||||||
final savedChangeset = await crdtDb.managers.crdtDataTable.get();
|
setUp(() async {
|
||||||
expect(savedChangeset, firstChangeset);
|
await crdt.saveChangeset(lateOldChangeset, [lateOldHlc]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then the last received HLC is not updated in the database.', () async {
|
test('then no changes are saved to the database.', () async {
|
||||||
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
final savedChangeset = await crdtDb.managers.crdtDataTable.get();
|
||||||
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, lastHlc);
|
expect(savedChangeset, firstChangeset);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('then the last received HLC is not updated in the database.', () async {
|
||||||
|
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
||||||
|
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, lastHlc);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
|
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
|
||||||
() {
|
() {
|
||||||
late Iterable<CrdtDataEntry> firstChangeset;
|
late Iterable<CrdtDataEntry> firstChangeset;
|
||||||
late Iterable<CrdtDataEntry> newerChangeset;
|
late Iterable<CrdtDataEntry> newerChangeset;
|
||||||
late Hlc lastHlc;
|
late Hlc lastHlc;
|
||||||
late Hlc newerHlc;
|
late Hlc newerHlc;
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
(crdt, crdtDb, _) = database.crdtContext;
|
|
||||||
otherNodeId = const Uuid().v4();
|
|
||||||
|
|
||||||
final todo = TodoEntry(
|
|
||||||
id: const RowId(1),
|
|
||||||
content: 'test',
|
|
||||||
targetDate: DateTime.now(),
|
|
||||||
);
|
|
||||||
|
|
||||||
lastHlc = Hlc.now(otherNodeId);
|
|
||||||
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
|
||||||
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
|
||||||
|
|
||||||
newerHlc = Hlc.now(otherNodeId);
|
|
||||||
newerChangeset = crdtDb
|
|
||||||
.convertToCrdtDataEntry(todo, newerHlc)
|
|
||||||
.where((e) => e.columnName == 'content');
|
|
||||||
});
|
|
||||||
|
|
||||||
group('when saving a changeset with some entries with newer HLC', () {
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await crdt.saveChangeset(newerChangeset, [newerHlc]);
|
(crdt, crdtDb, _) = database.crdtContext;
|
||||||
|
otherNodeId = const Uuid().v4();
|
||||||
|
|
||||||
|
final todo = TodoEntry(
|
||||||
|
id: const RowId(1),
|
||||||
|
content: 'test',
|
||||||
|
targetDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
lastHlc = Hlc.now(otherNodeId);
|
||||||
|
firstChangeset = crdtDb.convertToCrdtDataEntry(todo, lastHlc);
|
||||||
|
await crdt.saveChangeset(firstChangeset, [lastHlc]);
|
||||||
|
|
||||||
|
newerHlc = Hlc.now(otherNodeId);
|
||||||
|
newerChangeset = crdtDb
|
||||||
|
.convertToCrdtDataEntry(todo, newerHlc)
|
||||||
|
.where((e) => e.columnName == 'content');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then the entries with newer HLC are saved to the database.', () async {
|
group('when saving a changeset with some entries with newer HLC', () {
|
||||||
final expectedResultingChangeset = [
|
setUp(() async {
|
||||||
for (final e in firstChangeset)
|
await crdt.saveChangeset(newerChangeset, [newerHlc]);
|
||||||
if (e.columnName == 'content') newerChangeset.first else e,
|
});
|
||||||
];
|
|
||||||
|
|
||||||
expect(await crdtDb.managers.crdtDataTable.get(), expectedResultingChangeset);
|
test('then the entries with newer HLC are saved to the database.', () async {
|
||||||
});
|
final expectedResultingChangeset = [
|
||||||
|
for (final e in firstChangeset)
|
||||||
|
if (e.columnName == 'content') newerChangeset.first else e,
|
||||||
|
];
|
||||||
|
|
||||||
test('then the last received HLC is updated in the database.', () async {
|
expect(await crdtDb.managers.crdtDataTable.get(), expectedResultingChangeset);
|
||||||
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
});
|
||||||
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, newerHlc);
|
|
||||||
|
test('then the last received HLC is updated in the database.', () async {
|
||||||
|
final savedMergedHlcs = await crdtDb.managers.crdtMergeHlcTable.get();
|
||||||
|
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, newerHlc);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@ void main() {
|
||||||
await executor.close();
|
await executor.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when calling the HLC function with valid arguments '
|
||||||
'when calling the HLC function with valid arguments '
|
|
||||||
'then it returns a valid HLC timestamp.', () async {
|
'then it returns a valid HLC timestamp.', () async {
|
||||||
final result = await executor.runSelect(
|
final result = await executor.runSelect(
|
||||||
'SELECT $nextHlcFunction(?, ?) as hlc_timestamp;',
|
'SELECT $nextHlcFunction(?, ?) as hlc_timestamp;',
|
||||||
|
|
@ -30,8 +29,7 @@ void main() {
|
||||||
expect(hlcConverter.fromSql(hlcTimestamp!), isA<Hlc>());
|
expect(hlcConverter.fromSql(hlcTimestamp!), isA<Hlc>());
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when calling the HLC function with no arguments '
|
||||||
'when calling the HLC function with no arguments '
|
|
||||||
'then it throws a SqliteException due to the MissingArgumentsError.', () async {
|
'then it throws a SqliteException due to the MissingArgumentsError.', () async {
|
||||||
expect(
|
expect(
|
||||||
() => executor.runSelect(
|
() => executor.runSelect(
|
||||||
|
|
@ -48,8 +46,7 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when calling the HLC function with only the first argument '
|
||||||
'when calling the HLC function with only the first argument '
|
|
||||||
'then it throws a SqliteException due to the MissingNodeIdError.', () async {
|
'then it throws a SqliteException due to the MissingNodeIdError.', () async {
|
||||||
expect(
|
expect(
|
||||||
() => executor.runSelect(
|
() => executor.runSelect(
|
||||||
|
|
@ -66,8 +63,7 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when calling the HLC function with only the second argument '
|
||||||
'when calling the HLC function with only the second argument '
|
|
||||||
'then it throws a SqliteException due to the MissingUserIdError.', () async {
|
'then it throws a SqliteException due to the MissingUserIdError.', () async {
|
||||||
expect(
|
expect(
|
||||||
() => executor.runSelect(
|
() => executor.runSelect(
|
||||||
|
|
@ -84,8 +80,7 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when calling the HLC function with more than two arguments '
|
||||||
'when calling the HLC function with more than two arguments '
|
|
||||||
'then it throws a SqliteException due to the TooManyArgumentsError.', () async {
|
'then it throws a SqliteException due to the TooManyArgumentsError.', () async {
|
||||||
expect(
|
expect(
|
||||||
() => executor.runSelect(
|
() => executor.runSelect(
|
||||||
|
|
|
||||||
|
|
@ -8,59 +8,66 @@ import 'utils/executor.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group(
|
group(
|
||||||
'Given a database opened with a CRDT control table having initial HLC from same user and node',
|
'Given a database opened with a CRDT control table having initial HLC from same user and node',
|
||||||
() {
|
() {
|
||||||
late Hlc initialHlc;
|
late Hlc initialHlc;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
initialHlc = Hlc.now(testNodeId);
|
initialHlc = Hlc.now(testNodeId);
|
||||||
await testDatabaseFixture(initialHlc: initialHlc);
|
await testDatabaseFixture(initialHlc: initialHlc);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when retrieving the cached HLC '
|
||||||
'when retrieving the cached HLC '
|
'then the HLC for the node is the initial HLC.', () async {
|
||||||
'then the HLC for the node is the initial HLC.', () async {
|
expect(StatefulHlc.cached(testUserId, testNodeId).lastHlc, equals(initialHlc));
|
||||||
expect(StatefulHlc.cached(testUserId, testNodeId).lastHlc, equals(initialHlc));
|
});
|
||||||
});
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given a database opened with a CRDT control table having initial HLC from same user and different node',
|
'Given a database opened with a CRDT control table having initial HLC from same user and different node',
|
||||||
() {
|
() {
|
||||||
late Hlc initialHlc;
|
late Hlc initialHlc;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
initialHlc = Hlc.now('other_node_id');
|
initialHlc = Hlc.now('other_node_id');
|
||||||
await testDatabaseFixture(initialHlc: initialHlc);
|
await testDatabaseFixture(initialHlc: initialHlc);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'when retrieving the cached HLC '
|
'when retrieving the cached HLC '
|
||||||
'then the HLC for the node is the initial HLC for the requested node.',
|
'then the HLC for the node is the initial HLC for the requested node.',
|
||||||
() async {
|
() async {
|
||||||
final expectedHlc = initialHlc.apply(nodeId: testNodeId);
|
final expectedHlc = initialHlc.apply(nodeId: testNodeId);
|
||||||
expect(StatefulHlc.cached(testUserId, testNodeId).lastHlc, equals(expectedHlc));
|
expect(
|
||||||
});
|
StatefulHlc.cached(testUserId, testNodeId).lastHlc,
|
||||||
});
|
equals(expectedHlc),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'Given a database opened with a CRDT control table having initial HLC from different user and node',
|
'Given a database opened with a CRDT control table having initial HLC from different user and node',
|
||||||
() {
|
() {
|
||||||
late Hlc initialHlc;
|
late Hlc initialHlc;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
initialHlc = Hlc.now('other_node_id');
|
initialHlc = Hlc.now('other_node_id');
|
||||||
await testDatabaseFixture(initialHlc: initialHlc);
|
await testDatabaseFixture(initialHlc: initialHlc);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('when retrieving the cached HLC '
|
||||||
'when retrieving the cached HLC '
|
'then the HLC for the node is zero for the requested node.', () async {
|
||||||
'then the HLC for the node is zero for the requested node.', () async {
|
final expectedHlc = Hlc.zero(testNodeId);
|
||||||
final expectedHlc = Hlc.zero(testNodeId);
|
expect(
|
||||||
expect(
|
StatefulHlc.cached('other_user_id', testNodeId).lastHlc,
|
||||||
StatefulHlc.cached('other_user_id', testNodeId).lastHlc, equals(expectedHlc));
|
equals(expectedHlc),
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TodoDb> testDatabaseFixture({
|
Future<TodoDb> testDatabaseFixture({
|
||||||
|
|
|
||||||
|
|
@ -118,49 +118,54 @@ void main() {
|
||||||
expect(row.targetDate, targetDate);
|
expect(row.targetDate, targetDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('then the trigger is executed and the updated row in the CRDT data table',
|
group(
|
||||||
() {
|
'then the trigger is executed and the updated row in the CRDT data table',
|
||||||
late List<CrdtDataEntry> allCrdtDataEntries;
|
() {
|
||||||
|
late List<CrdtDataEntry> allCrdtDataEntries;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
final migrator = database.createMigrator();
|
final migrator = database.createMigrator();
|
||||||
allCrdtDataEntries = await migrator.crdtDb.managers.crdtDataTable.get();
|
allCrdtDataEntries = await migrator.crdtDb.managers.crdtDataTable.get();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has the raw values updated correctly.', () async {
|
test('has the raw values updated correctly.', () async {
|
||||||
final crdtData = allCrdtDataEntries.getField(rowId, 'content');
|
final crdtData = allCrdtDataEntries.getField(rowId, 'content');
|
||||||
expect(crdtData.unwrappedValue, targetContent);
|
expect(crdtData.unwrappedValue, targetContent);
|
||||||
|
|
||||||
final targetDateCrdtData = allCrdtDataEntries.getField(rowId, 'target_date');
|
final targetDateCrdtData = allCrdtDataEntries.getField(
|
||||||
expect(
|
rowId,
|
||||||
targetDateCrdtData.unwrappedValue,
|
'target_date',
|
||||||
targetDate.toIso8600StringWithOffset(),
|
);
|
||||||
);
|
expect(
|
||||||
});
|
targetDateCrdtData.unwrappedValue,
|
||||||
|
targetDate.toIso8600StringWithOffset(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('has the same HLC timestamp for each updated column.', () async {
|
test('has the same HLC timestamp for each updated column.', () async {
|
||||||
final updatedEntriesHlc = allCrdtDataEntries
|
final updatedEntriesHlc = allCrdtDataEntries
|
||||||
.where((entry) => entry.rowId == rowId)
|
.where((entry) => entry.rowId == rowId)
|
||||||
.where((entry) => updatedColumnNames.contains(entry.columnName))
|
.where((entry) => updatedColumnNames.contains(entry.columnName))
|
||||||
.map((entry) => entry.hlcTimestamp);
|
.map((entry) => entry.hlcTimestamp);
|
||||||
|
|
||||||
expect(updatedEntriesHlc.toSet(), hasLength(1));
|
expect(updatedEntriesHlc.toSet(), hasLength(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has the updated HLC timestamp greater than the created.', () async {
|
test('has the updated HLC timestamp greater than the created.', () async {
|
||||||
final updatedEntry = allCrdtDataEntries.getField(rowId, 'content');
|
final updatedEntry = allCrdtDataEntries.getField(rowId, 'content');
|
||||||
expect(updatedEntry.hlcTimestamp, greaterThan(createdHlc));
|
expect(updatedEntry.hlcTimestamp, greaterThan(createdHlc));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has not changed the HLC timestamp for non-updated columns.', () async {
|
test('has not changed the HLC timestamp for non-updated columns.', () async {
|
||||||
final otherEntriesHlc = allCrdtDataEntries
|
final otherEntriesHlc = allCrdtDataEntries
|
||||||
.where((entry) => entry.rowId == rowId)
|
.where((entry) => entry.rowId == rowId)
|
||||||
.where((entry) => !updatedColumnNames.contains(entry.columnName))
|
.where((entry) => !updatedColumnNames.contains(entry.columnName))
|
||||||
.map((entry) => entry.hlcTimestamp);
|
.map((entry) => entry.hlcTimestamp);
|
||||||
|
|
||||||
expect(otherEntriesHlc, everyElement(equals(createdHlc)));
|
expect(otherEntriesHlc, everyElement(equals(createdHlc)));
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('when updating the existing row with the same value', () {
|
group('when updating the existing row with the same value', () {
|
||||||
|
|
@ -173,20 +178,22 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('then the trigger is executed and the updated row in the CRDT data table',
|
group(
|
||||||
() {
|
'then the trigger is executed and the updated row in the CRDT data table',
|
||||||
late List<CrdtDataEntry> allCrdtDataEntries;
|
() {
|
||||||
|
late List<CrdtDataEntry> allCrdtDataEntries;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
final migrator = database.createMigrator();
|
final migrator = database.createMigrator();
|
||||||
allCrdtDataEntries = await migrator.crdtDb.managers.crdtDataTable.get();
|
allCrdtDataEntries = await migrator.crdtDb.managers.crdtDataTable.get();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has not changed the HLC timestamp for the updated column.', () async {
|
test('has not changed the HLC timestamp for the updated column.', () async {
|
||||||
final updatedEntry = allCrdtDataEntries.getField(rowId, 'content');
|
final updatedEntry = allCrdtDataEntries.getField(rowId, 'content');
|
||||||
expect(updatedEntry.hlcTimestamp, equals(createdHlc));
|
expect(updatedEntry.hlcTimestamp, equals(createdHlc));
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('when deleting the existing row', () {
|
group('when deleting the existing row', () {
|
||||||
|
|
@ -202,28 +209,33 @@ void main() {
|
||||||
expect(row, isNull);
|
expect(row, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('then the trigger is executed and the deleted row in the CRDT data table',
|
group(
|
||||||
() {
|
'then the trigger is executed and the deleted row in the CRDT data table',
|
||||||
late List<CrdtDataEntry> allCrdtDataEntries;
|
() {
|
||||||
late String rowId;
|
late List<CrdtDataEntry> allCrdtDataEntries;
|
||||||
|
late String rowId;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
final migrator = database.createMigrator();
|
final migrator = database.createMigrator();
|
||||||
allCrdtDataEntries = await migrator.crdtDb.managers.crdtDataTable.get();
|
allCrdtDataEntries = await migrator.crdtDb.managers.crdtDataTable.get();
|
||||||
|
|
||||||
rowId = createdRowId.toString();
|
rowId = createdRowId.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has the deleted flag column set to true.', () async {
|
test('has the deleted flag column set to true.', () async {
|
||||||
final crdtData = allCrdtDataEntries.getField(rowId, '__crdt_is_deleted');
|
final crdtData = allCrdtDataEntries.getField(rowId, '__crdt_is_deleted');
|
||||||
expect(crdtData.unwrappedValue, 1);
|
expect(crdtData.unwrappedValue, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has the deleted HLC timestamp greater than the created.', () async {
|
test('has the deleted HLC timestamp greater than the created.', () async {
|
||||||
final deletedEntry = allCrdtDataEntries.getField(rowId, '__crdt_is_deleted');
|
final deletedEntry = allCrdtDataEntries.getField(
|
||||||
expect(deletedEntry.hlcTimestamp, greaterThan(createdHlc));
|
rowId,
|
||||||
});
|
'__crdt_is_deleted',
|
||||||
});
|
);
|
||||||
|
expect(deletedEntry.hlcTimestamp, greaterThan(createdHlc));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,9 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then the insert trigger statement is correct.', () {
|
test('then the insert trigger statement is correct.', () {
|
||||||
final insertStatement = triggers
|
final insertStatement = triggers.firstWhere(
|
||||||
.firstWhere((trigger) => trigger.contains('AFTER INSERT ON "todos"'));
|
(trigger) => trigger.contains('AFTER INSERT ON "todos"'),
|
||||||
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
insertStatement,
|
insertStatement,
|
||||||
|
|
@ -79,8 +80,9 @@ END;''',
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then the update trigger statement is correct.', () {
|
test('then the update trigger statement is correct.', () {
|
||||||
final updateStatement = triggers
|
final updateStatement = triggers.firstWhere(
|
||||||
.firstWhere((trigger) => trigger.contains('AFTER UPDATE ON "todos"'));
|
(trigger) => trigger.contains('AFTER UPDATE ON "todos"'),
|
||||||
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
updateStatement,
|
updateStatement,
|
||||||
|
|
@ -137,8 +139,9 @@ END;''',
|
||||||
});
|
});
|
||||||
|
|
||||||
test('then the delete trigger statement is correct.', () {
|
test('then the delete trigger statement is correct.', () {
|
||||||
final deleteStatement = triggers
|
final deleteStatement = triggers.firstWhere(
|
||||||
.firstWhere((trigger) => trigger.contains('AFTER DELETE ON "todos"'));
|
(trigger) => trigger.contains('AFTER DELETE ON "todos"'),
|
||||||
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
deleteStatement,
|
deleteStatement,
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ void main() {
|
||||||
final _ = second.users.all();
|
final _ = second.users.all();
|
||||||
expect(second.didUpgrade, isFalse);
|
expect(second.didUpgrade, isFalse);
|
||||||
|
|
||||||
final __ = first.todosTable.all();
|
final _ = first.todosTable.all();
|
||||||
expect(first.didUpgrade, isTrue);
|
expect(first.didUpgrade, isTrue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,8 @@ TodoDb? _database;
|
||||||
],
|
],
|
||||||
daos: [SomeDao],
|
daos: [SomeDao],
|
||||||
queries: {
|
queries: {
|
||||||
'allTodosWithCategory': 'SELECT t.*, c.id as catId, c."desc" as catDesc '
|
'allTodosWithCategory':
|
||||||
|
'SELECT t.*, c.id as catId, c."desc" as catDesc '
|
||||||
'FROM todos t INNER JOIN categories c ON c.id = t.category',
|
'FROM todos t INNER JOIN categories c ON c.id = t.category',
|
||||||
'deleteTodoById': 'DELETE FROM todos WHERE id = ?',
|
'deleteTodoById': 'DELETE FROM todos WHERE id = ?',
|
||||||
'withIn': 'SELECT * FROM todos WHERE title = ?2 OR id IN ? OR title = ?1',
|
'withIn': 'SELECT * FROM todos WHERE title = ?2 OR id IN ? OR title = ?1',
|
||||||
|
|
@ -71,36 +72,37 @@ class TodoDb extends _$TodoDb {
|
||||||
// This is what needs to be added to the user database class to make it CRDT aware.
|
// This is what needs to be added to the user database class to make it CRDT aware.
|
||||||
@override
|
@override
|
||||||
OfflineSyncMigrator createMigrator() => OfflineSyncMigrator(
|
OfflineSyncMigrator createMigrator() => OfflineSyncMigrator(
|
||||||
this,
|
this,
|
||||||
userId: testUserId,
|
userId: testUserId,
|
||||||
nodeId: testNodeId,
|
nodeId: testNodeId,
|
||||||
synchronizedTables: [
|
synchronizedTables: [
|
||||||
todosTable,
|
todosTable,
|
||||||
categories,
|
categories,
|
||||||
users,
|
users,
|
||||||
sharedTodos,
|
sharedTodos,
|
||||||
// tableWithoutPK,
|
// tableWithoutPK,
|
||||||
pureDefaults,
|
pureDefaults,
|
||||||
// withCustomType,
|
// withCustomType,
|
||||||
tableWithEveryColumnType,
|
tableWithEveryColumnType,
|
||||||
department,
|
department,
|
||||||
// product,
|
// product,
|
||||||
],
|
],
|
||||||
excludeTables: [
|
excludeTables: [
|
||||||
listing,
|
listing,
|
||||||
product,
|
product,
|
||||||
store,
|
store,
|
||||||
tableWithoutPK,
|
tableWithoutPK,
|
||||||
withCustomType,
|
withCustomType,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DriftAccessor(
|
@DriftAccessor(
|
||||||
tables: [Users, SharedTodos, TodosTable],
|
tables: [Users, SharedTodos, TodosTable],
|
||||||
views: [TodoWithCategoryView],
|
views: [TodoWithCategoryView],
|
||||||
queries: {
|
queries: {
|
||||||
'todosForUser': 'SELECT t.* FROM todos t '
|
'todosForUser':
|
||||||
|
'SELECT t.* FROM todos t '
|
||||||
'INNER JOIN shared_todos st ON st.todo = t.id '
|
'INNER JOIN shared_todos st ON st.todo = t.id '
|
||||||
'INNER JOIN users u ON u.id = st.user '
|
'INNER JOIN users u ON u.id = st.user '
|
||||||
'WHERE u.id = :user',
|
'WHERE u.id = :user',
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,19 @@ class FirstDb extends _$FirstDb {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
onCreate: (m) async {
|
onCreate: (m) async {
|
||||||
created = true;
|
created = true;
|
||||||
await m.createAll();
|
await m.createAll();
|
||||||
},
|
},
|
||||||
onUpgrade: (m, from, to) async {
|
onUpgrade: (m, from, to) async {
|
||||||
if (from == 1 && to == 2) {
|
if (from == 1 && to == 2) {
|
||||||
didUpgrade = true;
|
didUpgrade = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeOpen: (m) async {
|
beforeOpen: (m) async {
|
||||||
onBeforeOpen = true;
|
onBeforeOpen = true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DriftDatabase(tables: [Users])
|
@DriftDatabase(tables: [Users])
|
||||||
|
|
@ -47,17 +47,17 @@ class SecondDb extends _$SecondDb {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
onCreate: (m) async {
|
onCreate: (m) async {
|
||||||
created = true;
|
created = true;
|
||||||
await m.createAll();
|
await m.createAll();
|
||||||
},
|
},
|
||||||
onUpgrade: (m, from, to) async {
|
onUpgrade: (m, from, to) async {
|
||||||
if (from == 1 && to == 2) {
|
if (from == 1 && to == 2) {
|
||||||
didUpgrade = true;
|
didUpgrade = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeOpen: (m) async {
|
beforeOpen: (m) async {
|
||||||
onBeforeOpen = true;
|
onBeforeOpen = true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue