fix: Preserve space state across export round trips (#148)

This commit is contained in:
Marcelo Mendonça Soares 2026-09-18 15:33:24 -03:00 • committed by GitHub
commit fc6aaa7b4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 448 additions and 0 deletions

View file

@ -159,6 +159,7 @@ extension CrdtMergeRecorderExtension on CrdtMutationRecorder {
// Field metadata for merged inserts is recorded per table and node rather
// than per row: every query behind it already takes a set of row ids.
final pendingAttempts = _PendingInsertAttempts();
final deferredDeletes = <CrdtMergeDelete>[];
for (final operation in operations) {
if (!_context.isCrdtTrackedTableName(operation.tableName)) {
@ -191,6 +192,13 @@ extension CrdtMergeRecorderExtension on CrdtMutationRecorder {
transaction,
);
case final CrdtMergeDelete delete:
// A higher delete/restore generation can have an older HLC than a
// concurrent reinsertion. Bootstrap must retain it until this batch
// has inserted the row it belongs to.
if (!context.rows.containsKey((delete.tableName, delete.uuidRowId))) {
deferredDeletes.add(delete);
continue;
}
await _applyMergeDelete(
delete,
nodesByUuid,
@ -204,6 +212,9 @@ extension CrdtMergeRecorderExtension on CrdtMutationRecorder {
}
await _flushInsertAttempts(pendingAttempts, context, transaction);
for (final delete in deferredDeletes) {
await _applyMergeDelete(delete, nodesByUuid, context, transaction);
}
if (mayAffectProjection) {
await _foreignKeyProjector.project(

View file

@ -162,6 +162,21 @@ class CrdtUniqueConflictResolver {
if (rowKey.$1 == tableName) rowKey,
]..sort(compareMergeRowKeys);
// A null tuple makes no unique claim, but still needs to shed any old
// release. Restore candidates first; per-index winners then retain their
// canonical tuples so they remain identifiable through those indexes.
for (final rowKey in tableRowKeys) {
if (!resolveVisibleConflicts && !hidden.contains(rowKey)) continue;
for (final uniqueIndex in uniqueIndexes) {
_materializeCanonicalClaim(
rowKey: rowKey,
uniqueIndex: uniqueIndex,
claimByField: claimByField,
valuesByRow: valuesByRow,
);
}
}
for (final uniqueIndex in uniqueIndexes) {
for (final rowKey in tableRowKeys) {
if (!hidden.contains(rowKey)) continue;