11import { storage , updateSyncRulesFromYaml } from '@powersync/service-core' ;
22import { bucketRequest , bucketRequestMap , register , test_utils } from '@powersync/service-core-tests' ;
33import { describe , expect , test } from 'vitest' ;
4+ import { PostgresCompactor } from '../../src/storage/PostgresCompactor.js' ;
45import { POSTGRES_STORAGE_FACTORY } from './util.js' ;
56
67describe ( 'Postgres Sync Bucket Storage Compact' , ( ) => register . registerCompactTests ( POSTGRES_STORAGE_FACTORY ) ) ;
@@ -31,6 +32,7 @@ bucket_definitions:
3132 after : { id : 't1' } ,
3233 afterReplicaId : test_utils . rid ( 't1' )
3334 } ) ;
35+ await batch . markAllSnapshotDone ( '1/1' ) ;
3436 await batch . commit ( '1/1' ) ;
3537 } ) ;
3638
@@ -52,4 +54,65 @@ bucket_definitions:
5254 { op_id : '2' , op : 'PUT' , object_id : 't1' }
5355 ] ) ;
5456 } ) ;
57+
58+ test ( 'clearBucket fails fast when prefix includes PUT' , async ( ) => {
59+ // This tests the specific implementation, to check that our operation type guard is working
60+ // for CLEAR compacting.
61+ await using factory = await POSTGRES_STORAGE_FACTORY . factory ( ) ;
62+ const syncRules = await factory . updateSyncRules (
63+ updateSyncRulesFromYaml ( `
64+ bucket_definitions:
65+ global:
66+ data: [select * from test]
67+ ` )
68+ ) ;
69+ const bucketStorage = factory . getInstance ( syncRules ) ;
70+ const bucket = bucketRequest ( syncRules , 'global[]' ) ;
71+
72+ const result = await bucketStorage . startBatch ( test_utils . BATCH_OPTIONS , async ( batch ) => {
73+ await batch . markAllSnapshotDone ( '1/1' ) ;
74+ await batch . save ( {
75+ sourceTable : TEST_TABLE ,
76+ tag : storage . SaveOperationTag . INSERT ,
77+ after : { id : 't1' } ,
78+ afterReplicaId : test_utils . rid ( 't1' )
79+ } ) ;
80+ await batch . save ( {
81+ sourceTable : TEST_TABLE ,
82+ tag : storage . SaveOperationTag . DELETE ,
83+ before : { id : 't1' } ,
84+ beforeReplicaId : test_utils . rid ( 't1' )
85+ } ) ;
86+ await batch . save ( {
87+ sourceTable : TEST_TABLE ,
88+ tag : storage . SaveOperationTag . INSERT ,
89+ after : { id : 't2' } ,
90+ afterReplicaId : test_utils . rid ( 't2' )
91+ } ) ;
92+ await batch . save ( {
93+ sourceTable : TEST_TABLE ,
94+ tag : storage . SaveOperationTag . DELETE ,
95+ before : { id : 't2' } ,
96+ beforeReplicaId : test_utils . rid ( 't2' )
97+ } ) ;
98+ await batch . commit ( '1/1' ) ;
99+ } ) ;
100+
101+ const checkpoint = result ! . flushed_op ;
102+ const rowsBefore = await test_utils . oneFromAsync (
103+ bucketStorage . getBucketDataBatch ( checkpoint , bucketRequestMap ( syncRules , [ [ 'global[]' , 0n ] ] ) )
104+ ) ;
105+ const dataBefore = test_utils . getBatchData ( rowsBefore ) ;
106+ const clearToOpId = BigInt ( dataBefore [ 2 ] . op_id ) ;
107+
108+ const compactor = new PostgresCompactor ( factory . db , bucketStorage . group_id , { } ) ;
109+ // Trigger the private method directly
110+ await expect ( compactor . clearBucketForTests ( bucket , clearToOpId ) ) . rejects . toThrow ( / U n e x p e c t e d P U T o p e r a t i o n / ) ;
111+
112+ // The method wraps in a transaction; on assertion error the bucket must remain unchanged.
113+ const rowsAfter = await test_utils . oneFromAsync (
114+ bucketStorage . getBucketDataBatch ( checkpoint , bucketRequestMap ( syncRules , [ [ 'global[]' , 0n ] ] ) )
115+ ) ;
116+ expect ( test_utils . getBatchData ( rowsAfter ) ) . toEqual ( dataBefore ) ;
117+ } ) ;
55118} ) ;
0 commit comments