write method
Stores values: inserts a new record, or updates an existing one
checked against base, the record as this writer read it.
An update with no base is refused under DVConflict.ask: a writer that
never read the row cannot know what it is replacing.
Implementation
Future<DVWriteResult> write(
Map<String, Object?> values, {
DVRecord? base,
DVConflict onConflict = DVConflict.ask,
String? actor,
String? tenant,
}) async {
final DVRecordScope? scoped = scope;
if (scoped != null &&
values.containsKey(scoped.column) &&
!_same(values[scoped.column], scoped.value)) {
// Refused rather than overridden: a caller naming another tenant has a
// bug, and quietly storing the row under this one hides it.
throw ArgumentError.value(values[scoped.column], scoped.column,
'is not the scope of this table (${scoped.value})');
}
final Map<String, Object?> mine = _normalize(values);
if (scoped != null) mine[scoped.column] = scoped.value;
tenant = _tenant(tenant);
final Object? id = mine[key];
if (id == null) {
throw ArgumentError.value(values, 'values', 'has no $key');
}
final DVRecord? current = await read(id, withDeleted: true);
if (current == null) {
return DVWriteResult(await _insert(id, mine, actor, tenant),
inserted: true);
}
if (versioned && base?.version != current.version) {
final DVConflictError conflict = DVConflictError(
table: table,
key: id,
mine: mine,
theirs: current.values,
base: base?.values,
expectedVersion: base?.version,
actualVersion: current.version,
);
final Map<String, Object?> target;
switch (onConflict.name) {
case 'ask':
throw conflict;
case 'serverWins':
return DVWriteResult(current, conflict: conflict, discarded: true);
case 'lastWriteWins':
target = mine;
case 'fieldMerge':
final Map<String, Object?>? read = base?.values;
if (read == null) throw conflict;
target = <String, Object?>{
for (final String column in columns)
column: _same(mine[column], read[column])
? current.values[column]
: mine[column],
};
default:
target = _normalize(onConflict._resolve!(conflict));
}
final DVRecord record = await _update(
current,
<String, Object?>{...target, key: id},
deletedAt: current.deletedAt,
actor: actor,
tenant: tenant,
);
return DVWriteResult(record, conflict: conflict);
}
return DVWriteResult(
await _update(current, mine,
deletedAt: current.deletedAt, actor: actor, tenant: tenant),
);
}