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