write method

Future<DVWriteResult> write(
  1. Map<String, Object?> values, {
  2. DVRecord? base,
  3. DVConflict onConflict = DVConflict.ask,
  4. String? actor,
  5. String? tenant,
})

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