1 // Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 
6 package org.rocksdb;
7 
8 import org.junit.Rule;
9 import org.junit.Test;
10 import org.junit.rules.TemporaryFolder;
11 
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.List;
15 import java.util.Random;
16 
17 import static java.nio.charset.StandardCharsets.UTF_8;
18 import static org.assertj.core.api.Assertions.assertThat;
19 import static org.assertj.core.api.Assertions.fail;
20 
21 /**
22  * Base class of {@link TransactionTest} and {@link OptimisticTransactionTest}
23  */
24 public abstract class AbstractTransactionTest {
25 
26   protected final static byte[] TXN_TEST_COLUMN_FAMILY = "txn_test_cf"
27       .getBytes();
28 
29   protected static final Random rand = PlatformRandomHelper.
30       getPlatformSpecificRandomFactory();
31 
32   @Rule
33   public TemporaryFolder dbFolder = new TemporaryFolder();
34 
35   public abstract DBContainer startDb()
36       throws RocksDBException;
37 
38   @Test
39   public void setSnapshot() throws RocksDBException {
40     try(final DBContainer dbContainer = startDb();
41         final Transaction txn = dbContainer.beginTransaction()) {
42       txn.setSnapshot();
43     }
44   }
45 
46   @Test
47   public void setSnapshotOnNextOperation() throws RocksDBException {
48     try(final DBContainer dbContainer = startDb();
49         final Transaction txn = dbContainer.beginTransaction()) {
50       txn.setSnapshotOnNextOperation();
51       txn.put("key1".getBytes(), "value1".getBytes());
52     }
53   }
54 
55   @Test
56   public void setSnapshotOnNextOperation_transactionNotifier() throws RocksDBException {
57     try(final DBContainer dbContainer = startDb();
58         final Transaction txn = dbContainer.beginTransaction()) {
59 
60       try(final TestTransactionNotifier notifier = new TestTransactionNotifier()) {
61         txn.setSnapshotOnNextOperation(notifier);
62         txn.put("key1".getBytes(), "value1".getBytes());
63 
64         txn.setSnapshotOnNextOperation(notifier);
65         txn.put("key2".getBytes(), "value2".getBytes());
66 
67         assertThat(notifier.getCreatedSnapshots().size()).isEqualTo(2);
68       }
69     }
70   }
71 
72   @Test
73   public void getSnapshot() throws RocksDBException {
74     try(final DBContainer dbContainer = startDb();
75         final Transaction txn = dbContainer.beginTransaction()) {
76       txn.setSnapshot();
77       final Snapshot snapshot = txn.getSnapshot();
78       assertThat(snapshot.isOwningHandle()).isFalse();
79     }
80   }
81 
82   @Test
83   public void getSnapshot_null() throws RocksDBException {
84     try(final DBContainer dbContainer = startDb();
85         final Transaction txn = dbContainer.beginTransaction()) {
86       final Snapshot snapshot = txn.getSnapshot();
87       assertThat(snapshot).isNull();
88     }
89   }
90 
91   @Test
92   public void clearSnapshot() throws RocksDBException {
93     try(final DBContainer dbContainer = startDb();
94         final Transaction txn = dbContainer.beginTransaction()) {
95       txn.setSnapshot();
96       txn.clearSnapshot();
97     }
98   }
99 
100   @Test
101   public void clearSnapshot_none() throws RocksDBException {
102     try(final DBContainer dbContainer = startDb();
103         final Transaction txn = dbContainer.beginTransaction()) {
104       txn.clearSnapshot();
105     }
106   }
107 
108   @Test
109   public void commit() throws RocksDBException {
110     final byte k1[] = "rollback-key1".getBytes(UTF_8);
111     final byte v1[] = "rollback-value1".getBytes(UTF_8);
112     try(final DBContainer dbContainer = startDb()) {
113       try(final Transaction txn = dbContainer.beginTransaction()) {
114         txn.put(k1, v1);
115         txn.commit();
116       }
117 
118       try(final ReadOptions readOptions = new ReadOptions();
119           final Transaction txn2 = dbContainer.beginTransaction()) {
120         assertThat(txn2.get(readOptions, k1)).isEqualTo(v1);
121       }
122     }
123   }
124 
125   @Test
126   public void rollback() throws RocksDBException {
127     final byte k1[] = "rollback-key1".getBytes(UTF_8);
128     final byte v1[] = "rollback-value1".getBytes(UTF_8);
129     try(final DBContainer dbContainer = startDb()) {
130       try(final Transaction txn = dbContainer.beginTransaction()) {
131         txn.put(k1, v1);
132         txn.rollback();
133       }
134 
135       try(final ReadOptions readOptions = new ReadOptions();
136           final Transaction txn2 = dbContainer.beginTransaction()) {
137         assertThat(txn2.get(readOptions, k1)).isNull();
138       }
139     }
140   }
141 
142   @Test
143   public void savePoint() throws RocksDBException {
144     final byte k1[] = "savePoint-key1".getBytes(UTF_8);
145     final byte v1[] = "savePoint-value1".getBytes(UTF_8);
146     final byte k2[] = "savePoint-key2".getBytes(UTF_8);
147     final byte v2[] = "savePoint-value2".getBytes(UTF_8);
148 
149     try(final DBContainer dbContainer = startDb();
150         final ReadOptions readOptions = new ReadOptions()) {
151 
152 
153       try(final Transaction txn = dbContainer.beginTransaction()) {
154         txn.put(k1, v1);
155 
156         assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
157 
158         txn.setSavePoint();
159 
160         txn.put(k2, v2);
161 
162         assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
163         assertThat(txn.get(readOptions, k2)).isEqualTo(v2);
164 
165         txn.rollbackToSavePoint();
166 
167         assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
168         assertThat(txn.get(readOptions, k2)).isNull();
169 
170         txn.commit();
171       }
172 
173       try(final Transaction txn2 = dbContainer.beginTransaction()) {
174         assertThat(txn2.get(readOptions, k1)).isEqualTo(v1);
175         assertThat(txn2.get(readOptions, k2)).isNull();
176       }
177     }
178   }
179 
180   @Test
181   public void getPut_cf() throws RocksDBException {
182     final byte k1[] = "key1".getBytes(UTF_8);
183     final byte v1[] = "value1".getBytes(UTF_8);
184     try(final DBContainer dbContainer = startDb();
185         final ReadOptions readOptions = new ReadOptions();
186         final Transaction txn = dbContainer.beginTransaction()) {
187       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
188       assertThat(txn.get(testCf, readOptions, k1)).isNull();
189       txn.put(testCf, k1, v1);
190       assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1);
191     }
192   }
193 
194   @Test
195   public void getPut() throws RocksDBException {
196     final byte k1[] = "key1".getBytes(UTF_8);
197     final byte v1[] = "value1".getBytes(UTF_8);
198     try(final DBContainer dbContainer = startDb();
199         final ReadOptions readOptions = new ReadOptions();
200         final Transaction txn = dbContainer.beginTransaction()) {
201       assertThat(txn.get(readOptions, k1)).isNull();
202       txn.put(k1, v1);
203       assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
204     }
205   }
206 
207   @Test
208   public void multiGetPut_cf() throws RocksDBException {
209     final byte keys[][] = new byte[][] {
210         "key1".getBytes(UTF_8),
211         "key2".getBytes(UTF_8)};
212     final byte values[][] = new byte[][] {
213         "value1".getBytes(UTF_8),
214         "value2".getBytes(UTF_8)};
215 
216     try(final DBContainer dbContainer = startDb();
217         final ReadOptions readOptions = new ReadOptions();
218         final Transaction txn = dbContainer.beginTransaction()) {
219       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
220       final List<ColumnFamilyHandle> cfList = Arrays.asList(testCf, testCf);
221 
222       assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(new byte[][] { null, null });
223 
224       txn.put(testCf, keys[0], values[0]);
225       txn.put(testCf, keys[1], values[1]);
226       assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values);
227     }
228   }
229 
230   @Test
231   public void multiGetPut() throws RocksDBException {
232     final byte keys[][] = new byte[][] {
233         "key1".getBytes(UTF_8),
234         "key2".getBytes(UTF_8)};
235     final byte values[][] = new byte[][] {
236         "value1".getBytes(UTF_8),
237         "value2".getBytes(UTF_8)};
238 
239     try(final DBContainer dbContainer = startDb();
240         final ReadOptions readOptions = new ReadOptions();
241         final Transaction txn = dbContainer.beginTransaction()) {
242 
243       assertThat(txn.multiGet(readOptions, keys)).isEqualTo(new byte[][] { null, null });
244 
245       txn.put(keys[0], values[0]);
246       txn.put(keys[1], values[1]);
247       assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values);
248     }
249   }
250 
251   @Test
252   public void getForUpdate_cf() throws RocksDBException {
253     final byte k1[] = "key1".getBytes(UTF_8);
254     final byte v1[] = "value1".getBytes(UTF_8);
255     try(final DBContainer dbContainer = startDb();
256         final ReadOptions readOptions = new ReadOptions();
257         final Transaction txn = dbContainer.beginTransaction()) {
258       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
259       assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isNull();
260       txn.put(testCf, k1, v1);
261       assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1);
262     }
263   }
264 
265   @Test
266   public void getForUpdate() throws RocksDBException {
267     final byte k1[] = "key1".getBytes(UTF_8);
268     final byte v1[] = "value1".getBytes(UTF_8);
269     try(final DBContainer dbContainer = startDb();
270         final ReadOptions readOptions = new ReadOptions();
271         final Transaction txn = dbContainer.beginTransaction()) {
272       assertThat(txn.getForUpdate(readOptions, k1, true)).isNull();
273       txn.put(k1, v1);
274       assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1);
275     }
276   }
277 
278   @Test
279   public void multiGetForUpdate_cf() throws RocksDBException {
280     final byte keys[][] = new byte[][] {
281         "key1".getBytes(UTF_8),
282         "key2".getBytes(UTF_8)};
283     final byte values[][] = new byte[][] {
284         "value1".getBytes(UTF_8),
285         "value2".getBytes(UTF_8)};
286 
287     try(final DBContainer dbContainer = startDb();
288         final ReadOptions readOptions = new ReadOptions();
289         final Transaction txn = dbContainer.beginTransaction()) {
290       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
291       final List<ColumnFamilyHandle> cfList = Arrays.asList(testCf, testCf);
292 
293       assertThat(txn.multiGetForUpdate(readOptions, cfList, keys))
294           .isEqualTo(new byte[][] { null, null });
295 
296       txn.put(testCf, keys[0], values[0]);
297       txn.put(testCf, keys[1], values[1]);
298       assertThat(txn.multiGetForUpdate(readOptions, cfList, keys))
299           .isEqualTo(values);
300     }
301   }
302 
303   @Test
304   public void multiGetForUpdate() throws RocksDBException {
305     final byte keys[][] = new byte[][]{
306         "key1".getBytes(UTF_8),
307         "key2".getBytes(UTF_8)};
308     final byte values[][] = new byte[][]{
309         "value1".getBytes(UTF_8),
310         "value2".getBytes(UTF_8)};
311 
312     try (final DBContainer dbContainer = startDb();
313          final ReadOptions readOptions = new ReadOptions();
314          final Transaction txn = dbContainer.beginTransaction()) {
315       assertThat(txn.multiGetForUpdate(readOptions, keys)).isEqualTo(new byte[][]{null, null});
316 
317       txn.put(keys[0], values[0]);
318       txn.put(keys[1], values[1]);
319       assertThat(txn.multiGetForUpdate(readOptions, keys)).isEqualTo(values);
320     }
321   }
322 
323   @Test
324   public void getIterator() throws RocksDBException {
325     try(final DBContainer dbContainer = startDb();
326         final ReadOptions readOptions = new ReadOptions();
327         final Transaction txn = dbContainer.beginTransaction()) {
328 
329       final byte[] k1 = "key1".getBytes(UTF_8);
330       final byte[] v1 = "value1".getBytes(UTF_8);
331 
332       txn.put(k1, v1);
333 
334       try(final RocksIterator iterator = txn.getIterator(readOptions)) {
335         iterator.seek(k1);
336         assertThat(iterator.isValid()).isTrue();
337         assertThat(iterator.key()).isEqualTo(k1);
338         assertThat(iterator.value()).isEqualTo(v1);
339       }
340     }
341   }
342 
343   @Test
344   public void getIterator_cf() throws RocksDBException {
345     try(final DBContainer dbContainer = startDb();
346         final ReadOptions readOptions = new ReadOptions();
347         final Transaction txn = dbContainer.beginTransaction()) {
348       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
349 
350       final byte[] k1 = "key1".getBytes(UTF_8);
351       final byte[] v1 = "value1".getBytes(UTF_8);
352 
353       txn.put(testCf, k1, v1);
354 
355       try(final RocksIterator iterator = txn.getIterator(readOptions, testCf)) {
356         iterator.seek(k1);
357         assertThat(iterator.isValid()).isTrue();
358         assertThat(iterator.key()).isEqualTo(k1);
359         assertThat(iterator.value()).isEqualTo(v1);
360       }
361     }
362   }
363 
364   @Test
365   public void merge_cf() throws RocksDBException {
366     final byte[] k1 = "key1".getBytes(UTF_8);
367     final byte[] v1 = "value1".getBytes(UTF_8);
368 
369     try(final DBContainer dbContainer = startDb();
370         final Transaction txn = dbContainer.beginTransaction()) {
371       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
372       txn.merge(testCf, k1, v1);
373     }
374   }
375 
376   @Test
377   public void merge() throws RocksDBException {
378     final byte[] k1 = "key1".getBytes(UTF_8);
379     final byte[] v1 = "value1".getBytes(UTF_8);
380 
381     try(final DBContainer dbContainer = startDb();
382         final Transaction txn = dbContainer.beginTransaction()) {
383       txn.merge(k1, v1);
384     }
385   }
386 
387 
388   @Test
389   public void delete_cf() throws RocksDBException {
390     final byte[] k1 = "key1".getBytes(UTF_8);
391     final byte[] v1 = "value1".getBytes(UTF_8);
392 
393     try(final DBContainer dbContainer = startDb();
394         final ReadOptions readOptions = new ReadOptions();
395         final Transaction txn = dbContainer.beginTransaction()) {
396       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
397       txn.put(testCf, k1, v1);
398       assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1);
399 
400       txn.delete(testCf, k1);
401       assertThat(txn.get(testCf, readOptions, k1)).isNull();
402     }
403   }
404 
405   @Test
406   public void delete() throws RocksDBException {
407     final byte[] k1 = "key1".getBytes(UTF_8);
408     final byte[] v1 = "value1".getBytes(UTF_8);
409 
410     try(final DBContainer dbContainer = startDb();
411         final ReadOptions readOptions = new ReadOptions();
412         final Transaction txn = dbContainer.beginTransaction()) {
413       txn.put(k1, v1);
414       assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
415 
416       txn.delete(k1);
417       assertThat(txn.get(readOptions, k1)).isNull();
418     }
419   }
420 
421   @Test
422   public void delete_parts_cf() throws RocksDBException {
423     final byte keyParts[][] = new byte[][] {
424         "ke".getBytes(UTF_8),
425         "y1".getBytes(UTF_8)};
426     final byte valueParts[][] = new byte[][] {
427         "val".getBytes(UTF_8),
428         "ue1".getBytes(UTF_8)};
429     final byte[] key = concat(keyParts);
430     final byte[] value = concat(valueParts);
431 
432     try(final DBContainer dbContainer = startDb();
433         final ReadOptions readOptions = new ReadOptions();
434         final Transaction txn = dbContainer.beginTransaction()) {
435       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
436       txn.put(testCf, keyParts, valueParts);
437       assertThat(txn.get(testCf, readOptions, key)).isEqualTo(value);
438 
439       txn.delete(testCf, keyParts);
440 
441       assertThat(txn.get(testCf, readOptions, key))
442           .isNull();
443     }
444   }
445 
446   @Test
447   public void delete_parts() throws RocksDBException {
448     final byte keyParts[][] = new byte[][] {
449         "ke".getBytes(UTF_8),
450         "y1".getBytes(UTF_8)};
451     final byte valueParts[][] = new byte[][] {
452         "val".getBytes(UTF_8),
453         "ue1".getBytes(UTF_8)};
454     final byte[] key = concat(keyParts);
455     final byte[] value = concat(valueParts);
456 
457     try(final DBContainer dbContainer = startDb();
458         final ReadOptions readOptions = new ReadOptions();
459         final Transaction txn = dbContainer.beginTransaction()) {
460 
461       txn.put(keyParts, valueParts);
462 
463       assertThat(txn.get(readOptions, key)).isEqualTo(value);
464 
465       txn.delete(keyParts);
466 
467       assertThat(txn.get(readOptions, key)).isNull();
468     }
469   }
470 
471   @Test
472   public void getPutUntracked_cf() throws RocksDBException {
473     final byte k1[] = "key1".getBytes(UTF_8);
474     final byte v1[] = "value1".getBytes(UTF_8);
475     try(final DBContainer dbContainer = startDb();
476         final ReadOptions readOptions = new ReadOptions();
477         final Transaction txn = dbContainer.beginTransaction()) {
478       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
479       assertThat(txn.get(testCf, readOptions, k1)).isNull();
480       txn.putUntracked(testCf, k1, v1);
481       assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1);
482     }
483   }
484 
485   @Test
486   public void getPutUntracked() throws RocksDBException {
487     final byte k1[] = "key1".getBytes(UTF_8);
488     final byte v1[] = "value1".getBytes(UTF_8);
489     try(final DBContainer dbContainer = startDb();
490         final ReadOptions readOptions = new ReadOptions();
491         final Transaction txn = dbContainer.beginTransaction()) {
492       assertThat(txn.get(readOptions, k1)).isNull();
493       txn.putUntracked(k1, v1);
494       assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
495     }
496   }
497 
498   @Test
499   public void multiGetPutUntracked_cf() throws RocksDBException {
500     final byte keys[][] = new byte[][] {
501         "key1".getBytes(UTF_8),
502         "key2".getBytes(UTF_8)};
503     final byte values[][] = new byte[][] {
504         "value1".getBytes(UTF_8),
505         "value2".getBytes(UTF_8)};
506 
507     try(final DBContainer dbContainer = startDb();
508         final ReadOptions readOptions = new ReadOptions();
509         final Transaction txn = dbContainer.beginTransaction()) {
510       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
511 
512       final List<ColumnFamilyHandle> cfList = Arrays.asList(testCf, testCf);
513 
514       assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(new byte[][] { null, null });
515       txn.putUntracked(testCf, keys[0], values[0]);
516       txn.putUntracked(testCf, keys[1], values[1]);
517       assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values);
518     }
519   }
520 
521   @Test
522   public void multiGetPutUntracked() throws RocksDBException {
523     final byte keys[][] = new byte[][] {
524         "key1".getBytes(UTF_8),
525         "key2".getBytes(UTF_8)};
526     final byte values[][] = new byte[][] {
527         "value1".getBytes(UTF_8),
528         "value2".getBytes(UTF_8)};
529 
530     try(final DBContainer dbContainer = startDb();
531         final ReadOptions readOptions = new ReadOptions();
532         final Transaction txn = dbContainer.beginTransaction()) {
533 
534       assertThat(txn.multiGet(readOptions, keys)).isEqualTo(new byte[][] { null, null });
535       txn.putUntracked(keys[0], values[0]);
536       txn.putUntracked(keys[1], values[1]);
537       assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values);
538     }
539   }
540 
541   @Test
542   public void mergeUntracked_cf() throws RocksDBException {
543     final byte[] k1 = "key1".getBytes(UTF_8);
544     final byte[] v1 = "value1".getBytes(UTF_8);
545 
546     try(final DBContainer dbContainer = startDb();
547         final Transaction txn = dbContainer.beginTransaction()) {
548       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
549       txn.mergeUntracked(testCf, k1, v1);
550     }
551   }
552 
553   @Test
554   public void mergeUntracked() throws RocksDBException {
555     final byte[] k1 = "key1".getBytes(UTF_8);
556     final byte[] v1 = "value1".getBytes(UTF_8);
557 
558     try(final DBContainer dbContainer = startDb();
559         final Transaction txn = dbContainer.beginTransaction()) {
560       txn.mergeUntracked(k1, v1);
561     }
562   }
563 
564   @Test
565   public void deleteUntracked_cf() throws RocksDBException {
566     final byte[] k1 = "key1".getBytes(UTF_8);
567     final byte[] v1 = "value1".getBytes(UTF_8);
568 
569     try(final DBContainer dbContainer = startDb();
570         final ReadOptions readOptions = new ReadOptions();
571         final Transaction txn = dbContainer.beginTransaction()) {
572       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
573       txn.put(testCf, k1, v1);
574       assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1);
575 
576       txn.deleteUntracked(testCf, k1);
577       assertThat(txn.get(testCf, readOptions, k1)).isNull();
578     }
579   }
580 
581   @Test
582   public void deleteUntracked() throws RocksDBException {
583     final byte[] k1 = "key1".getBytes(UTF_8);
584     final byte[] v1 = "value1".getBytes(UTF_8);
585 
586     try(final DBContainer dbContainer = startDb();
587         final ReadOptions readOptions = new ReadOptions();
588         final Transaction txn = dbContainer.beginTransaction()) {
589       txn.put(k1, v1);
590       assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
591 
592       txn.deleteUntracked(k1);
593       assertThat(txn.get(readOptions, k1)).isNull();
594     }
595   }
596 
597   @Test
598   public void deleteUntracked_parts_cf() throws RocksDBException {
599     final byte keyParts[][] = new byte[][] {
600         "ke".getBytes(UTF_8),
601         "y1".getBytes(UTF_8)};
602     final byte valueParts[][] = new byte[][] {
603         "val".getBytes(UTF_8),
604         "ue1".getBytes(UTF_8)};
605     final byte[] key = concat(keyParts);
606     final byte[] value = concat(valueParts);
607 
608     try(final DBContainer dbContainer = startDb();
609         final ReadOptions readOptions = new ReadOptions();
610         final Transaction txn = dbContainer.beginTransaction()) {
611       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
612       txn.put(testCf, keyParts, valueParts);
613       assertThat(txn.get(testCf, readOptions, key)).isEqualTo(value);
614 
615       txn.deleteUntracked(testCf, keyParts);
616       assertThat(txn.get(testCf, readOptions, key)).isNull();
617     }
618   }
619 
620   @Test
621   public void deleteUntracked_parts() throws RocksDBException {
622     final byte keyParts[][] = new byte[][] {
623         "ke".getBytes(UTF_8),
624         "y1".getBytes(UTF_8)};
625     final byte valueParts[][] = new byte[][] {
626         "val".getBytes(UTF_8),
627         "ue1".getBytes(UTF_8)};
628     final byte[] key = concat(keyParts);
629     final byte[] value = concat(valueParts);
630 
631     try(final DBContainer dbContainer = startDb();
632         final ReadOptions readOptions = new ReadOptions();
633         final Transaction txn = dbContainer.beginTransaction()) {
634       txn.put(keyParts, valueParts);
635       assertThat(txn.get(readOptions, key)).isEqualTo(value);
636 
637       txn.deleteUntracked(keyParts);
638       assertThat(txn.get(readOptions, key)).isNull();
639     }
640   }
641 
642   @Test
643   public void putLogData() throws RocksDBException {
644     final byte[] blob = "blobby".getBytes(UTF_8);
645     try(final DBContainer dbContainer = startDb();
646         final Transaction txn = dbContainer.beginTransaction()) {
647       txn.putLogData(blob);
648     }
649   }
650 
651   @Test
652   public void enabledDisableIndexing() throws RocksDBException {
653     try(final DBContainer dbContainer = startDb();
654         final Transaction txn = dbContainer.beginTransaction()) {
655       txn.disableIndexing();
656       txn.enableIndexing();
657       txn.disableIndexing();
658       txn.enableIndexing();
659     }
660   }
661 
662   @Test
663   public void numKeys() throws RocksDBException {
664     final byte k1[] = "key1".getBytes(UTF_8);
665     final byte v1[] = "value1".getBytes(UTF_8);
666     final byte k2[] = "key2".getBytes(UTF_8);
667     final byte v2[] = "value2".getBytes(UTF_8);
668     final byte k3[] = "key3".getBytes(UTF_8);
669     final byte v3[] = "value3".getBytes(UTF_8);
670 
671     try(final DBContainer dbContainer = startDb();
672         final Transaction txn = dbContainer.beginTransaction()) {
673       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
674       txn.put(k1, v1);
675       txn.put(testCf, k2, v2);
676       txn.merge(k3, v3);
677       txn.delete(testCf, k2);
678 
679       assertThat(txn.getNumKeys()).isEqualTo(3);
680       assertThat(txn.getNumPuts()).isEqualTo(2);
681       assertThat(txn.getNumMerges()).isEqualTo(1);
682       assertThat(txn.getNumDeletes()).isEqualTo(1);
683     }
684   }
685 
686   @Test
687   public void elapsedTime() throws RocksDBException, InterruptedException {
688     final long preStartTxnTime = System.currentTimeMillis();
689     try (final DBContainer dbContainer = startDb();
690          final Transaction txn = dbContainer.beginTransaction()) {
691       Thread.sleep(2);
692 
693       final long txnElapsedTime = txn.getElapsedTime();
694       assertThat(txnElapsedTime).isLessThan(System.currentTimeMillis() - preStartTxnTime);
695       assertThat(txnElapsedTime).isGreaterThan(0);
696     }
697   }
698 
699   @Test
700   public void getWriteBatch() throws RocksDBException {
701     final byte k1[] = "key1".getBytes(UTF_8);
702     final byte v1[] = "value1".getBytes(UTF_8);
703 
704     try(final DBContainer dbContainer = startDb();
705         final Transaction txn = dbContainer.beginTransaction()) {
706 
707       txn.put(k1, v1);
708 
709       final WriteBatchWithIndex writeBatch = txn.getWriteBatch();
710       assertThat(writeBatch).isNotNull();
711       assertThat(writeBatch.isOwningHandle()).isFalse();
712       assertThat(writeBatch.count()).isEqualTo(1);
713     }
714   }
715 
716   @Test
717   public void setLockTimeout() throws RocksDBException {
718     try(final DBContainer dbContainer = startDb();
719         final Transaction txn = dbContainer.beginTransaction()) {
720       txn.setLockTimeout(1000);
721     }
722   }
723 
724   @Test
725   public void writeOptions() throws RocksDBException {
726     final byte k1[] = "key1".getBytes(UTF_8);
727     final byte v1[] = "value1".getBytes(UTF_8);
728 
729     try(final DBContainer dbContainer = startDb();
730         final WriteOptions writeOptions = new WriteOptions()
731         .setDisableWAL(true)
732         .setSync(true);
733         final Transaction txn = dbContainer.beginTransaction(writeOptions)) {
734 
735       txn.put(k1, v1);
736 
737       WriteOptions txnWriteOptions = txn.getWriteOptions();
738       assertThat(txnWriteOptions).isNotNull();
739       assertThat(txnWriteOptions.isOwningHandle()).isFalse();
740       assertThat(txnWriteOptions).isNotSameAs(writeOptions);
741       assertThat(txnWriteOptions.disableWAL()).isTrue();
742       assertThat(txnWriteOptions.sync()).isTrue();
743 
744       txn.setWriteOptions(txnWriteOptions.setSync(false));
745       txnWriteOptions = txn.getWriteOptions();
746       assertThat(txnWriteOptions).isNotNull();
747       assertThat(txnWriteOptions.isOwningHandle()).isFalse();
748       assertThat(txnWriteOptions).isNotSameAs(writeOptions);
749       assertThat(txnWriteOptions.disableWAL()).isTrue();
750       assertThat(txnWriteOptions.sync()).isFalse();
751     }
752   }
753 
754   @Test
755   public void undoGetForUpdate_cf() throws RocksDBException {
756     final byte k1[] = "key1".getBytes(UTF_8);
757     final byte v1[] = "value1".getBytes(UTF_8);
758     try(final DBContainer dbContainer = startDb();
759         final ReadOptions readOptions = new ReadOptions();
760         final Transaction txn = dbContainer.beginTransaction()) {
761       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
762       assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isNull();
763       txn.put(testCf, k1, v1);
764       assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1);
765       txn.undoGetForUpdate(testCf, k1);
766     }
767   }
768 
769   @Test
770   public void undoGetForUpdate() throws RocksDBException {
771     final byte k1[] = "key1".getBytes(UTF_8);
772     final byte v1[] = "value1".getBytes(UTF_8);
773     try(final DBContainer dbContainer = startDb();
774         final ReadOptions readOptions = new ReadOptions();
775         final Transaction txn = dbContainer.beginTransaction()) {
776       assertThat(txn.getForUpdate(readOptions, k1, true)).isNull();
777       txn.put(k1, v1);
778       assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1);
779       txn.undoGetForUpdate(k1);
780     }
781   }
782 
783   @Test
784   public void rebuildFromWriteBatch() throws RocksDBException {
785     final byte k1[] = "key1".getBytes(UTF_8);
786     final byte v1[] = "value1".getBytes(UTF_8);
787     final byte k2[] = "key2".getBytes(UTF_8);
788     final byte v2[] = "value2".getBytes(UTF_8);
789     final byte k3[] = "key3".getBytes(UTF_8);
790     final byte v3[] = "value3".getBytes(UTF_8);
791 
792     try(final DBContainer dbContainer = startDb();
793         final ReadOptions readOptions = new ReadOptions();
794         final Transaction txn = dbContainer.beginTransaction()) {
795 
796       txn.put(k1, v1);
797 
798       assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
799       assertThat(txn.getNumKeys()).isEqualTo(1);
800 
801       try(final WriteBatch writeBatch = new WriteBatch()) {
802         writeBatch.put(k2, v2);
803         writeBatch.put(k3, v3);
804         txn.rebuildFromWriteBatch(writeBatch);
805 
806         assertThat(txn.get(readOptions, k1)).isEqualTo(v1);
807         assertThat(txn.get(readOptions, k2)).isEqualTo(v2);
808         assertThat(txn.get(readOptions, k3)).isEqualTo(v3);
809         assertThat(txn.getNumKeys()).isEqualTo(3);
810       }
811     }
812   }
813 
814   @Test
815   public void getCommitTimeWriteBatch() throws RocksDBException {
816     final byte k1[] = "key1".getBytes(UTF_8);
817     final byte v1[] = "value1".getBytes(UTF_8);
818 
819     try(final DBContainer dbContainer = startDb();
820         final Transaction txn = dbContainer.beginTransaction()) {
821 
822       txn.put(k1, v1);
823       final WriteBatch writeBatch = txn.getCommitTimeWriteBatch();
824 
825       assertThat(writeBatch).isNotNull();
826       assertThat(writeBatch.isOwningHandle()).isFalse();
827       assertThat(writeBatch.count()).isEqualTo(0);
828     }
829   }
830 
831   @Test
832   public void logNumber() throws RocksDBException {
833     try(final DBContainer dbContainer = startDb();
834         final Transaction txn = dbContainer.beginTransaction()) {
835       assertThat(txn.getLogNumber()).isEqualTo(0);
836       final long logNumber = rand.nextLong();
837       txn.setLogNumber(logNumber);
838       assertThat(txn.getLogNumber()).isEqualTo(logNumber);
839     }
840   }
841 
842   private static byte[] concat(final byte[][] bufs) {
843     int resultLength = 0;
844     for(final byte[] buf : bufs) {
845       resultLength += buf.length;
846     }
847 
848     final byte[] result = new byte[resultLength];
849     int resultOffset = 0;
850     for(final byte[] buf : bufs) {
851       final int srcLength = buf.length;
852       System.arraycopy(buf, 0, result, resultOffset, srcLength);
853       resultOffset += srcLength;
854     }
855 
856     return result;
857   }
858 
859   private static class TestTransactionNotifier
860       extends AbstractTransactionNotifier {
861     private final List<Snapshot> createdSnapshots = new ArrayList<>();
862 
863     @Override
864     public void snapshotCreated(final Snapshot newSnapshot) {
865       createdSnapshots.add(newSnapshot);
866     }
867 
868     public List<Snapshot> getCreatedSnapshots() {
869       return createdSnapshots;
870     }
871   }
872 
873   protected static abstract class DBContainer
874       implements AutoCloseable {
875     protected final WriteOptions writeOptions;
876     protected final List<ColumnFamilyHandle> columnFamilyHandles;
877     protected final ColumnFamilyOptions columnFamilyOptions;
878     protected final DBOptions options;
879 
880     public DBContainer(final WriteOptions writeOptions,
881         final List<ColumnFamilyHandle> columnFamilyHandles,
882         final ColumnFamilyOptions columnFamilyOptions,
883         final DBOptions options) {
884       this.writeOptions = writeOptions;
885       this.columnFamilyHandles = columnFamilyHandles;
886       this.columnFamilyOptions = columnFamilyOptions;
887       this.options = options;
888     }
889 
890     public abstract Transaction beginTransaction();
891 
892     public abstract Transaction beginTransaction(
893         final WriteOptions writeOptions);
894 
895     public ColumnFamilyHandle getTestColumnFamily() {
896       return columnFamilyHandles.get(1);
897     }
898 
899     @Override
900     public abstract void close();
901   }
902 }
903