chore: Update minimum dart version and run formatter

This commit is contained in:
Marcelo Soares 2026-01-25 23:23:47 -03:00
commit 63a73117e0
No known key found for this signature in database
GPG key ID: CB2F09CE66E99EAD
22 changed files with 588 additions and 563 deletions

View file

@ -123,6 +123,7 @@ linter:
formatter: formatter:
page_width: 88 page_width: 88
trailing_commas: preserve
analyzer: analyzer:
exclude: exclude:

View file

@ -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,
]; ];
} }
} }

View file

@ -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 (

View file

@ -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)";
} }
} }

View file

@ -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:

View file

@ -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,

View file

@ -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'),
); );

View file

@ -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.',
),
), ),
), );
); },
}); );
}); });
} }

View file

@ -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);
});
});
});
} }

View file

@ -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);

View file

@ -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));

View file

@ -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);
}); });
}); },
);
} }

View file

@ -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);
});
}); });
}); },
}); );
} }

View file

@ -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(

View file

@ -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({

View file

@ -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));
});
},
);
}); });
}); });
} }

View file

@ -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,

View file

@ -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);
}); });
} }

View file

@ -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',

View file

@ -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