fix: Repair foreign keys on columns that also carry a unique claim (#151)
This commit is contained in:
parent
f3c462b5d3
commit
51ce999af0
3 changed files with 435 additions and 12 deletions
|
|
@ -477,6 +477,9 @@ class OfflineSyncDatabase implements Database {
|
|||
tx,
|
||||
projectionUnchanged: projection.projectionUnchanged,
|
||||
domainBeforeUpsert: projection.domain,
|
||||
upsertRows: updateColumns == null && projection.domain.isNotEmpty
|
||||
? prepared.rows
|
||||
: const [],
|
||||
);
|
||||
if (noReturn) return <T>[];
|
||||
_stripStampedRows(result, prepared);
|
||||
|
|
|
|||
|
|
@ -2120,12 +2120,24 @@ class CrdtForeignKeyProjector {
|
|||
if (state.pendingInsertKeys.contains(row.key)) continue;
|
||||
for (final column in _uniqueResolver.uniqueColumnNamesFor(row.key.$1)) {
|
||||
final key = (row.key.$1, row.key.$2, column);
|
||||
row.values[column] = canonicalDomainValue(
|
||||
final localValue = canonicalDomainValue(
|
||||
authoredOverlays.containsKey(key)
|
||||
? authoredOverlays[key]
|
||||
: state.originalDomain[row.key]?[column],
|
||||
_context.columnsByTableAndName[row.key.$1]?[column],
|
||||
);
|
||||
// An authored unique claim still needs its FK repair when a local
|
||||
// edit makes the parent hidden. Only a released value may replace it.
|
||||
if (foreignKeys.reasons.containsKey(key) &&
|
||||
!authoredOverlays.containsKey(key) &&
|
||||
projectionValuesEqual(localValue, authoredByField[key])) {
|
||||
continue;
|
||||
}
|
||||
if (!projectionValuesEqual(localValue, row.values[column])) {
|
||||
// Retaining a unique release supersedes the FK candidate's reason.
|
||||
foreignKeys.reasons.remove(key);
|
||||
}
|
||||
row.values[column] = localValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -851,6 +851,7 @@ class CrdtMutationRecorder {
|
|||
bool projectionUnchanged = false,
|
||||
bool authoredColumnValues = false,
|
||||
Map<MergeRowKey, Map<String, Object?>> domainBeforeUpsert = const {},
|
||||
List<TableRow> upsertRows = const [],
|
||||
}) async {
|
||||
await _foreignKeyProjector.assertVisibleTargets(updatedRows, columns, transaction);
|
||||
|
||||
|
|
@ -865,6 +866,33 @@ class CrdtMutationRecorder {
|
|||
'updated',
|
||||
transaction,
|
||||
);
|
||||
final implicitForeignKeyRepairFields = columns == null
|
||||
? await _foreignKeyProjector.findImplicitRepairFields(
|
||||
tableName: tableName,
|
||||
rowIds: rowIds,
|
||||
transaction: transaction,
|
||||
)
|
||||
: const <MergeFieldKey>{};
|
||||
final upsertInputs = upsertRows.isEmpty || implicitForeignKeyRepairFields.isEmpty
|
||||
? const <MergeRowKey, Map<String, Object?>>{}
|
||||
: {
|
||||
for (final row in upsertRows)
|
||||
if (row.id case final UuidValue rowId)
|
||||
(row.table.tableName, rowId):
|
||||
row.toJsonForDatabase() as Map<String, dynamic>,
|
||||
};
|
||||
// Insert defaults must not author over an echoed null that is holding
|
||||
// a projected FK claim.
|
||||
bool echoesProjectedNull(UuidValue rowId, String columnName) {
|
||||
if (!implicitForeignKeyRepairFields.contains((tableName, rowId, columnName))) {
|
||||
return false;
|
||||
}
|
||||
final input = upsertInputs[(tableName, rowId)];
|
||||
return input != null &&
|
||||
input[columnName] == null &&
|
||||
domainBeforeUpsert[(tableName, rowId)]?[columnName] == null;
|
||||
}
|
||||
|
||||
// Explicit columns author even an unchanged null; full-row passthrough
|
||||
// keeps the claim behind an unchanged displayed alternative.
|
||||
final authoredOverlays = <MergeFieldKey, Object?>{
|
||||
|
|
@ -883,20 +911,14 @@ class CrdtMutationRecorder {
|
|||
if ((domainBeforeUpsert[(tableName, row.id)]?.containsKey(columnName) ??
|
||||
false) &&
|
||||
(columns != null ||
|
||||
!projectionValuesEqual(
|
||||
domainBeforeUpsert[(tableName, row.id)]![columnName],
|
||||
value,
|
||||
)))
|
||||
(!projectionValuesEqual(
|
||||
domainBeforeUpsert[(tableName, row.id)]![columnName],
|
||||
value,
|
||||
) &&
|
||||
!echoesProjectedNull(row.id as UuidValue, columnName))))
|
||||
(tableName, row.id as UuidValue, columnName): value,
|
||||
};
|
||||
_uniqueResolver.validateAuthoredFields(authoredOverlays);
|
||||
final implicitForeignKeyRepairFields = columns == null
|
||||
? await _foreignKeyProjector.findImplicitRepairFields(
|
||||
tableName: tableName,
|
||||
rowIds: rowIds,
|
||||
transaction: transaction,
|
||||
)
|
||||
: const <MergeFieldKey>{};
|
||||
await _recordUpdatedFields(
|
||||
updatedRows,
|
||||
crdtDataRows,
|
||||
|
|
|
|||
Loading…
Reference in a new issue