Skip to content

Commit

Permalink
cascading insert wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mqus committed Apr 25, 2021
1 parent 19cadca commit 02733df
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 14 deletions.
14 changes: 8 additions & 6 deletions floor/lib/src/adapter/insertion_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ class InsertionAdapter<T> {
final DatabaseExecutor _database;
final String _entityName;
final Map<String, Object?> Function(T) _valueMapper;
final StreamController<Set<String>>? _changeListener;
final void Function(bool) _changeHandler;

InsertionAdapter(
final DatabaseExecutor database,
final String entityName,
final Map<String, Object?> Function(T) valueMapper, [
final StreamController<Set<String>>? changeListener,
final void Function(bool)? changeHandler,
]) : assert(entityName.isNotEmpty),
_database = database,
_entityName = entityName,
_valueMapper = valueMapper,
_changeListener = changeListener;
_changeHandler = changeHandler ?? ((bool isReplace) {/* do nothing */});

Future<void> insert(
final T item,
Expand All @@ -42,7 +42,7 @@ class InsertionAdapter<T> {
);
}
await batch.commit(noResult: true);
_changeListener?.add({_entityName});
_changeHandler(onConflictStrategy == OnConflictStrategy.replace);
}

Future<int> insertAndReturnId(
Expand All @@ -66,7 +66,8 @@ class InsertionAdapter<T> {
);
}
final result = (await batch.commit(noResult: false)).cast<int>();
if (result.isNotEmpty) _changeListener?.add({_entityName});
if (result.isNotEmpty)
_changeHandler(onConflictStrategy == OnConflictStrategy.replace);
return result;
}

Expand All @@ -79,7 +80,8 @@ class InsertionAdapter<T> {
_valueMapper(item),
conflictAlgorithm: onConflictStrategy.asSqfliteConflictAlgorithm(),
);
if (result != 0) _changeListener?.add({_entityName});
if (result != 0)
_changeHandler(onConflictStrategy == OnConflictStrategy.replace);
return result;
}
}
5 changes: 4 additions & 1 deletion floor/test/adapter/insertion_adapter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,14 @@ void main() {
// ignore: close_sinks
final mockStreamController = MockStreamController<Set<String>>();

final changeHandler = (bool isReplace) {
mockStreamController.add({entityName});
};
final underTest = InsertionAdapter(
mockDatabaseExecutor,
entityName,
valueMapper,
mockStreamController,
changeHandler,
);

tearDown(() {
Expand Down
7 changes: 7 additions & 0 deletions floor/test/integration/stream_query_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,20 @@ void main() {
actual,
emitsInOrder(<List<Dog>>[
[], // initial state,
[], // after inserting person1, [1]
[], // after inserting person2, [1]
[dog1], // after inserting dog1
[dog1], // after inserting dog2
[], // after removing person1, which triggers cascade remove
]));
// [1] due to insert method having onConflict:replace, dog entries could be affected by this query, so a stream event is triggered.

await personDao.insertPerson(person1);
// avoid that delete happens before the re-execution of
// the select query for the stream
await Future<void>.delayed(const Duration(milliseconds: 100));
await personDao.insertPerson(person2);
await Future<void>.delayed(const Duration(milliseconds: 100));

await database.dogDao.insertDog(dog1);

Expand Down
23 changes: 16 additions & 7 deletions floor_generator/lib/writer/dao_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ class DaoWriter extends Writer {
final valueMapper =
'(${entity.classElement.displayName} item) => ${entity.valueMapping}';

final requiresChangeListener =
dbHasViewStreams || streamEntities.contains(entity);
// create a special change handler which decides case-by-case:
// if the insertion happens with onConflict:replace, consider the insertion like a deletion.
// if it will not replace (e.g. abort or ignore), only output the single entity at most.
final changeHandler = _generateChangeHandler(foreignKeyRelationships.getAffectedByDelete(entity), entity);

constructorBuilder
..initializers.add(Code(
"$fieldName = InsertionAdapter(database, '${entity.name}', $valueMapper${requiresChangeListener ? ', changeListener' : ''})"));
"$fieldName = InsertionAdapter(database, '${entity.name}', $valueMapper$changeHandler)"));
}
}

Expand Down Expand Up @@ -221,15 +223,22 @@ class DaoWriter extends Writer {
///
/// The affected set can be generated with [getAffectedByUpdateEntities]
/// and [getAffectedByDeleteEntities]
String _generateChangeHandler(final Set<Entity> affected) {
String _generateChangeHandler(final Set<Entity> affected ,[Entity? insertionEntity]) {
final toNotify = streamEntities.intersection(affected);

if (toNotify.isNotEmpty || dbHasViewStreams)
if (toNotify.isNotEmpty || dbHasViewStreams) {
// if there are streaming views, create a new handler even if the set
// is empty. This will only trigger a reload of the views.
return ', () => changeListener.add(const {${toNotify.map((e) => e.name.toLiteral()).join(', ')}})';
else
final set = 'const {${toNotify.map((e) => e.name.toLiteral()).join(', ')}}';
if (insertionEntity == null) {
return ', () => changeListener.add($set)';
} else {
final singleSet = 'const {${streamEntities.contains(insertionEntity)?insertionEntity.name.toLiteral():''}}';
return ', (isReplace) => changeListener.add(isReplace?$set:$singleSet)';
}
} else {
// do not generate a Handler if the listener doesn't have to be updated
return '';
}
}
}

0 comments on commit 02733df

Please sign in to comment.