@@ -101,7 +101,126 @@ func TestCompare(t *testing.T) {
101101 compareWith pgSnapshot
102102 result comparisonResult
103103 }{
104+ // === Identity / equality ===
105+ {snap (0 , 0 ), snap (0 , 0 ), equal },
106+ {snap (1 , 1 ), snap (1 , 1 ), equal },
107+ {snap (5 , 5 ), snap (5 , 5 ), equal },
108+ {snap (100 , 100 ), snap (100 , 100 ), equal },
109+ {snap (0 , 4 , 2 ), snap (0 , 4 , 2 ), equal },
110+ {snap (10 , 20 , 12 , 15 , 18 ), snap (10 , 20 , 12 , 15 , 18 ), equal },
111+
112+ // Same visible set expressed differently: all xipList entries fill [xmin,xmax)
113+ {snap (1 , 1 ), snap (1 , 5 , 1 , 2 , 3 , 4 ), equal },
114+ {snap (1 , 5 , 1 , 2 , 3 , 4 ), snap (1 , 1 ), equal },
115+ {snap (5 , 5 ), snap (5 , 8 , 5 , 6 , 7 ), equal },
116+ {snap (5 , 8 , 5 , 6 , 7 ), snap (5 , 5 ), equal },
117+
118+ // === Strict ordering: one has more info, no conflict ===
119+ // RHS committed one more tx (tx 3)
104120 {snap (0 , 4 , 2 ), snap (0 , 4 , 2 , 3 ), gt },
121+ {snap (0 , 4 , 2 , 3 ), snap (0 , 4 , 2 ), lt },
122+
123+ // Higher xmin means more committed
124+ {snap (5 , 10 , 7 ), snap (3 , 10 , 3 , 5 , 7 ), gt },
125+ {snap (3 , 10 , 3 , 5 , 7 ), snap (5 , 10 , 7 ), lt },
126+
127+ // Higher xmax with no new in-progress means strictly more info
128+ {snap (1 , 1 ), snap (1 , 3 ), lt },
129+ {snap (1 , 3 ), snap (1 , 1 ), gt },
130+ {snap (5 , 5 ), snap (5 , 10 ), lt },
131+ {snap (5 , 10 ), snap (5 , 5 ), gt },
132+
133+ // One snapshot is a strict superset of the other's knowledge
134+ {snap (10 , 10 ), snap (10 , 15 , 12 ), lt },
135+ {snap (10 , 15 , 12 ), snap (10 , 10 ), gt },
136+ {snap (10 , 10 ), snap (10 , 15 ), lt },
137+ {snap (10 , 15 ), snap (10 , 10 ), gt },
138+
139+ // xmin advanced past the other's in-progress
140+ {snap (10 , 10 ), snap (8 , 10 , 8 , 9 ), gt },
141+ {snap (8 , 10 , 8 , 9 ), snap (10 , 10 ), lt },
142+
143+ // Both see same xmax but one has fewer in-progress
144+ {snap (5 , 10 , 5 , 7 ), snap (5 , 10 , 5 ), lt },
145+ {snap (5 , 10 , 5 ), snap (5 , 10 , 5 , 7 ), gt },
146+ {snap (5 , 10 , 5 , 6 , 7 , 8 , 9 ), snap (5 , 10 , 5 , 6 , 7 ), lt },
147+ {snap (5 , 10 , 5 , 6 , 7 ), snap (5 , 10 , 5 , 6 , 7 , 8 , 9 ), gt },
148+
149+ // One sees everything the other does plus a higher xmax range
150+ {snap (0 , 5 ), snap (0 , 10 ), lt },
151+ {snap (0 , 10 ), snap (0 , 5 ), gt },
152+
153+ // === Concurrent: each knows something the other doesn't ===
154+ // Original bug case: LHS knows tx 100 done, RHS knows tx 102 done
155+ {snap (101 , 101 ), snap (100 , 104 , 100 , 101 ), concurrent },
156+ {snap (100 , 104 , 100 , 101 ), snap (101 , 101 ), concurrent },
157+
158+ // LHS knows tx 5 committed, RHS knows tx 7 committed
159+ {snap (6 , 8 , 6 , 7 ), snap (5 , 9 , 5 , 8 ), concurrent },
160+ {snap (5 , 9 , 5 , 8 ), snap (6 , 8 , 6 , 7 ), concurrent },
161+
162+ // Disjoint xmax ranges with each having committed txs the other hasn't seen
163+ {snap (3 , 5 , 4 ), snap (2 , 7 , 2 , 3 , 6 ), concurrent },
164+ {snap (2 , 7 , 2 , 3 , 6 ), snap (3 , 5 , 4 ), concurrent },
165+
166+ // Each has one committed tx the other considers in-progress
167+ {snap (10 , 14 , 10 , 12 ), snap (10 , 14 , 11 , 13 ), concurrent },
168+ {snap (10 , 14 , 11 , 13 ), snap (10 , 14 , 10 , 12 ), concurrent },
169+
170+ // LHS advanced xmin past RHS's in-progress, but RHS has higher xmax with committed txs
171+ {snap (20 , 20 ), snap (18 , 25 , 18 , 19 ), concurrent },
172+ {snap (18 , 25 , 18 , 19 ), snap (20 , 20 ), concurrent },
173+
174+ // Both have different in-progress sets that overlap in complex ways
175+ {snap (5 , 12 , 5 , 8 , 10 ), snap (5 , 12 , 6 , 9 , 11 ), concurrent },
176+ {snap (5 , 12 , 6 , 9 , 11 ), snap (5 , 12 , 5 , 8 , 10 ), concurrent },
177+
178+ // Wide gap: LHS committed low txs, RHS committed high txs
179+ {snap (50 , 50 ), snap (40 , 60 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 ), concurrent },
180+ {snap (40 , 60 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 ), snap (50 , 50 ), concurrent },
181+
182+ // === Large xid gaps: must not iterate the range ===
183+ {snap (0 , 4 , 2 ), snap (1 << 63 , 1 << 63 ), lt },
184+ {snap (1 << 63 , 1 << 63 ), snap (0 , 4 , 2 ), gt },
185+ {snap (1 , 1 ), snap (2 , 1 << 63 ), lt },
186+ {snap (2 , 1 << 63 ), snap (1 , 1 ), gt },
187+ {snap (1 << 62 , 1 << 62 ), snap (1 << 63 , 1 << 63 ), lt },
188+ {snap (1 << 63 , 1 << 63 ), snap (1 << 62 , 1 << 62 ), gt },
189+
190+ // Large gap concurrent: each has info the other doesn't
191+ {snap (1 << 62 , 1 << 62 ), snap (1 << 61 , 1 << 63 , 1 << 61 ), concurrent },
192+ {snap (1 << 61 , 1 << 63 , 1 << 61 ), snap (1 << 62 , 1 << 62 ), concurrent },
193+
194+ // === Edge cases ===
195+ // xmin=0, xmax=0: empty snapshot
196+ {snap (0 , 0 ), snap (0 , 1 ), lt },
197+ {snap (0 , 1 ), snap (0 , 0 ), gt },
198+
199+ // Single tx difference
200+ {snap (0 , 1 ), snap (0 , 2 ), lt },
201+ {snap (0 , 2 ), snap (0 , 1 ), gt },
202+ {snap (0 , 2 , 1 ), snap (0 , 2 ), lt },
203+ {snap (0 , 2 ), snap (0 , 2 , 1 ), gt },
204+
205+ // Adjacent xmax values
206+ {snap (5 , 6 ), snap (5 , 7 ), lt },
207+ {snap (5 , 7 ), snap (5 , 6 ), gt },
208+
209+ // All txs in-progress in range = same knowledge as lower xmax
210+ {snap (3 , 3 ), snap (3 , 6 , 3 , 4 , 5 ), equal },
211+ {snap (3 , 6 , 3 , 4 , 5 ), snap (3 , 3 ), equal },
212+
213+ // One committed tx among many in-progress
214+ {snap (0 , 10 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ), snap (0 , 10 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ), gt },
215+ {snap (0 , 10 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ), snap (0 , 10 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ), lt },
216+
217+ // markComplete scenario: snapshot after marking own tx complete
218+ {snap (100 , 100 ).markComplete (100 ), snap (100 , 100 ), gt },
219+ {snap (100 , 100 ), snap (100 , 100 ).markComplete (100 ), lt },
220+
221+ // markComplete advances past other's in-progress: strictly greater
222+ {snap (100 , 102 , 100 ).markComplete (100 ), snap (100 , 102 , 101 ), gt },
223+ {snap (100 , 102 , 101 ), snap (100 , 102 , 100 ).markComplete (100 ), lt },
105224 }
106225
107226 for _ , tc := range testCases {
0 commit comments