diff --git a/.changeset/quiet-pianos-fix.md b/.changeset/quiet-pianos-fix.md new file mode 100644 index 0000000..d16144e --- /dev/null +++ b/.changeset/quiet-pianos-fix.md @@ -0,0 +1,5 @@ +--- +'@electric-sql/d2ts': patch +--- + +Fix SQLite index compaction dropping data when a version advances to itself under an incomparable frontier diff --git a/packages/d2ts/src/sqlite/version-index.ts b/packages/d2ts/src/sqlite/version-index.ts index 4729b50..dba9891 100644 --- a/packages/d2ts/src/sqlite/version-index.ts +++ b/packages/d2ts/src/sqlite/version-index.ts @@ -542,6 +542,12 @@ export class SQLIndex { const newVersion = oldVersion.advanceBy(compactionFrontier) const newVersionJson = newVersion.toJSON() + // Incomparable versions can advance to themselves; moving those rows + // would insert into the destination and then delete it. + if (oldVersion.equals(newVersion)) { + continue + } + if (!versionGroups.has(newVersionJson)) { versionGroups.set(newVersionJson, []) } diff --git a/packages/d2ts/tests/version-index.test.ts b/packages/d2ts/tests/version-index.test.ts index cb100fb..d2835ef 100644 --- a/packages/d2ts/tests/version-index.test.ts +++ b/packages/d2ts/tests/version-index.test.ts @@ -330,6 +330,28 @@ function createIndexTests< expect(result).toEqual([[10, 2]]) }) + test('should handle compaction with incomparable versions', () => { + const version1 = v([1, 0]) + const version2 = v([1, 1]) + const version3 = v([2, 0]) + + index.addValue('key1', version1, [10, 1]) + index.addValue('key1', version2, [10, 2]) + index.addValue('key1', version3, [10, -1]) + + const frontier = new Antichain([v([1, 2]), v([2, 1])]) + index.compact(frontier) // [1, 1] -> [10, 3] and [2, 1] -> [10, -1] + + const result1 = index.reconstructAt('key1', v([1, 2])) + expect(result1).toEqual([[10, 3]]) + + const result2 = index.reconstructAt('key1', v([2, 1])) + expect(result2).toEqual([ + [10, 3], + [10, -1], + ]) + }) + test('should throw error for invalid compaction frontier', () => { const version = v([1]) const frontier1 = new Antichain([v([2])])