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.Test;
9 
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.List;
13 
14 import static java.nio.charset.StandardCharsets.UTF_8;
15 import static org.assertj.core.api.Assertions.assertThat;
16 import static org.assertj.core.api.Assertions.fail;
17 
18 public class TransactionTest extends AbstractTransactionTest {
19 
20   @Test
getForUpdate_cf_conflict()21   public void getForUpdate_cf_conflict() throws RocksDBException {
22     final byte k1[] = "key1".getBytes(UTF_8);
23     final byte v1[] = "value1".getBytes(UTF_8);
24     final byte v12[] = "value12".getBytes(UTF_8);
25     try(final DBContainer dbContainer = startDb();
26         final ReadOptions readOptions = new ReadOptions()) {
27       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
28 
29       try(final Transaction txn = dbContainer.beginTransaction()) {
30         txn.put(testCf, k1, v1);
31         assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1);
32         txn.commit();
33       }
34 
35       try(final Transaction txn2 = dbContainer.beginTransaction()) {
36         try(final Transaction txn3 = dbContainer.beginTransaction()) {
37           assertThat(txn3.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1);
38 
39           // NOTE: txn2 updates k1, during txn3
40           try {
41             txn2.put(testCf, k1, v12); // should cause an exception!
42           } catch(final RocksDBException e) {
43             assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut);
44             return;
45           }
46         }
47       }
48 
49       fail("Expected an exception for put after getForUpdate from conflicting" +
50           "transactions");
51     }
52   }
53 
54   @Test
getForUpdate_conflict()55   public void getForUpdate_conflict() throws RocksDBException {
56     final byte k1[] = "key1".getBytes(UTF_8);
57     final byte v1[] = "value1".getBytes(UTF_8);
58     final byte v12[] = "value12".getBytes(UTF_8);
59     try(final DBContainer dbContainer = startDb();
60         final ReadOptions readOptions = new ReadOptions()) {
61 
62       try(final Transaction txn = dbContainer.beginTransaction()) {
63         txn.put(k1, v1);
64         assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1);
65         txn.commit();
66       }
67 
68       try(final Transaction txn2 = dbContainer.beginTransaction()) {
69         try(final Transaction txn3 = dbContainer.beginTransaction()) {
70           assertThat(txn3.getForUpdate(readOptions, k1, true)).isEqualTo(v1);
71 
72           // NOTE: txn2 updates k1, during txn3
73           try {
74             txn2.put(k1, v12); // should cause an exception!
75           } catch(final RocksDBException e) {
76             assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut);
77             return;
78           }
79         }
80       }
81 
82       fail("Expected an exception for put after getForUpdate from conflicting" +
83           "transactions");
84     }
85   }
86 
87   @Test
multiGetForUpdate_cf_conflict()88   public void multiGetForUpdate_cf_conflict() throws RocksDBException {
89     final byte keys[][] = new byte[][] {
90         "key1".getBytes(UTF_8),
91         "key2".getBytes(UTF_8)};
92     final byte values[][] = new byte[][] {
93         "value1".getBytes(UTF_8),
94         "value2".getBytes(UTF_8)};
95     final byte[] otherValue = "otherValue".getBytes(UTF_8);
96 
97     try(final DBContainer dbContainer = startDb();
98         final ReadOptions readOptions = new ReadOptions()) {
99       final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily();
100       final List<ColumnFamilyHandle> cfList = Arrays.asList(testCf, testCf);
101 
102       try(final Transaction txn = dbContainer.beginTransaction()) {
103         txn.put(testCf, keys[0], values[0]);
104         txn.put(testCf, keys[1], values[1]);
105         assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values);
106         txn.commit();
107       }
108 
109       try(final Transaction txn2 = dbContainer.beginTransaction()) {
110         try(final Transaction txn3 = dbContainer.beginTransaction()) {
111           assertThat(txn3.multiGetForUpdate(readOptions, cfList, keys))
112               .isEqualTo(values);
113 
114           // NOTE: txn2 updates k1, during txn3
115           try {
116             txn2.put(testCf, keys[0], otherValue); // should cause an exception!
117           } catch(final RocksDBException e) {
118             assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut);
119             return;
120           }
121         }
122       }
123 
124       fail("Expected an exception for put after getForUpdate from conflicting" +
125           "transactions");
126     }
127   }
128 
129   @Test
multiGetForUpdate_conflict()130   public void multiGetForUpdate_conflict() throws RocksDBException {
131     final byte keys[][] = new byte[][] {
132         "key1".getBytes(UTF_8),
133         "key2".getBytes(UTF_8)};
134     final byte values[][] = new byte[][] {
135         "value1".getBytes(UTF_8),
136         "value2".getBytes(UTF_8)};
137     final byte[] otherValue = "otherValue".getBytes(UTF_8);
138 
139     try(final DBContainer dbContainer = startDb();
140         final ReadOptions readOptions = new ReadOptions()) {
141       try(final Transaction txn = dbContainer.beginTransaction()) {
142         txn.put(keys[0], values[0]);
143         txn.put(keys[1], values[1]);
144         assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values);
145         txn.commit();
146       }
147 
148       try(final Transaction txn2 = dbContainer.beginTransaction()) {
149         try(final Transaction txn3 = dbContainer.beginTransaction()) {
150           assertThat(txn3.multiGetForUpdate(readOptions, keys))
151               .isEqualTo(values);
152 
153           // NOTE: txn2 updates k1, during txn3
154           try {
155             txn2.put(keys[0], otherValue); // should cause an exception!
156           } catch(final RocksDBException e) {
157             assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut);
158             return;
159           }
160         }
161       }
162 
163       fail("Expected an exception for put after getForUpdate from conflicting" +
164           "transactions");
165     }
166   }
167 
168   @Test
name()169   public void name() throws RocksDBException {
170     try(final DBContainer dbContainer = startDb();
171         final Transaction txn = dbContainer.beginTransaction()) {
172       assertThat(txn.getName()).isEmpty();
173       final String name = "my-transaction-" + rand.nextLong();
174       txn.setName(name);
175       assertThat(txn.getName()).isEqualTo(name);
176     }
177   }
178 
179   @Test
ID()180   public void ID() throws RocksDBException {
181     try(final DBContainer dbContainer = startDb();
182         final Transaction txn = dbContainer.beginTransaction()) {
183       assertThat(txn.getID()).isGreaterThan(0);
184     }
185   }
186 
187   @Test
deadlockDetect()188   public void deadlockDetect() throws RocksDBException {
189     try(final DBContainer dbContainer = startDb();
190         final Transaction txn = dbContainer.beginTransaction()) {
191       assertThat(txn.isDeadlockDetect()).isFalse();
192     }
193   }
194 
195   @Test
waitingTxns()196   public void waitingTxns() throws RocksDBException {
197     try(final DBContainer dbContainer = startDb();
198         final Transaction txn = dbContainer.beginTransaction()) {
199       assertThat(txn.getWaitingTxns().getTransactionIds().length).isEqualTo(0);
200     }
201   }
202 
203   @Test
state()204   public void state() throws RocksDBException {
205     try(final DBContainer dbContainer = startDb()) {
206 
207       try(final Transaction txn = dbContainer.beginTransaction()) {
208         assertThat(txn.getState())
209             .isSameAs(Transaction.TransactionState.STARTED);
210         txn.commit();
211         assertThat(txn.getState())
212             .isSameAs(Transaction.TransactionState.COMMITED);
213       }
214 
215       try(final Transaction txn = dbContainer.beginTransaction()) {
216         assertThat(txn.getState())
217             .isSameAs(Transaction.TransactionState.STARTED);
218         txn.rollback();
219         assertThat(txn.getState())
220             .isSameAs(Transaction.TransactionState.STARTED);
221       }
222     }
223   }
224 
225   @Test
Id()226   public void Id() throws RocksDBException {
227     try(final DBContainer dbContainer = startDb();
228         final Transaction txn = dbContainer.beginTransaction()) {
229       assertThat(txn.getId()).isNotNull();
230     }
231   }
232 
233   @Override
startDb()234   public TransactionDBContainer startDb() throws RocksDBException {
235     final DBOptions options = new DBOptions()
236         .setCreateIfMissing(true)
237         .setCreateMissingColumnFamilies(true);
238     final TransactionDBOptions txnDbOptions = new TransactionDBOptions();
239     final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions();
240     final List<ColumnFamilyDescriptor> columnFamilyDescriptors =
241         Arrays.asList(
242             new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
243             new ColumnFamilyDescriptor(TXN_TEST_COLUMN_FAMILY,
244                 columnFamilyOptions));
245     final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
246 
247     final TransactionDB txnDb;
248     try {
249       txnDb = TransactionDB.open(options, txnDbOptions,
250           dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors,
251               columnFamilyHandles);
252     } catch(final RocksDBException e) {
253       columnFamilyOptions.close();
254       txnDbOptions.close();
255       options.close();
256       throw e;
257     }
258 
259     final WriteOptions writeOptions = new WriteOptions();
260     final TransactionOptions txnOptions = new TransactionOptions();
261 
262     return new TransactionDBContainer(txnOptions, writeOptions,
263         columnFamilyHandles, txnDb, txnDbOptions, columnFamilyOptions, options);
264   }
265 
266   private static class TransactionDBContainer
267       extends DBContainer {
268     private final TransactionOptions txnOptions;
269     private final TransactionDB txnDb;
270     private final TransactionDBOptions txnDbOptions;
271 
TransactionDBContainer( final TransactionOptions txnOptions, final WriteOptions writeOptions, final List<ColumnFamilyHandle> columnFamilyHandles, final TransactionDB txnDb, final TransactionDBOptions txnDbOptions, final ColumnFamilyOptions columnFamilyOptions, final DBOptions options)272     public TransactionDBContainer(
273         final TransactionOptions txnOptions, final WriteOptions writeOptions,
274         final List<ColumnFamilyHandle> columnFamilyHandles,
275         final TransactionDB txnDb, final TransactionDBOptions txnDbOptions,
276         final ColumnFamilyOptions columnFamilyOptions,
277         final DBOptions options) {
278       super(writeOptions, columnFamilyHandles, columnFamilyOptions,
279           options);
280       this.txnOptions = txnOptions;
281       this.txnDb = txnDb;
282       this.txnDbOptions = txnDbOptions;
283     }
284 
285     @Override
beginTransaction()286     public Transaction beginTransaction() {
287       return txnDb.beginTransaction(writeOptions, txnOptions);
288     }
289 
290     @Override
beginTransaction(final WriteOptions writeOptions)291     public Transaction beginTransaction(final WriteOptions writeOptions) {
292       return txnDb.beginTransaction(writeOptions, txnOptions);
293     }
294 
295     @Override
close()296     public void close() {
297       txnOptions.close();
298       writeOptions.close();
299       for(final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) {
300         columnFamilyHandle.close();
301       }
302       txnDb.close();
303       txnDbOptions.close();
304       options.close();
305     }
306   }
307 
308 }
309