test_transactiondb.nim
1 # Nim-RocksDB 2 # Copyright 2024 Status Research & Development GmbH 3 # Licensed under either of 4 # 5 # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 # * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 7 # 8 # at your option. This file may not be copied, modified, or distributed except according to those terms. 9 10 {.used.} 11 12 import std/os, tempfile, unittest2, ../rocksdb/[transactiondb], ./test_helper 13 14 suite "TransactionDbRef Tests": 15 const 16 CF_DEFAULT = "default" 17 CF_OTHER = "other" 18 19 let 20 key1 = @[byte(1)] 21 val1 = @[byte(1)] 22 key2 = @[byte(2)] 23 val2 = @[byte(2)] 24 key3 = @[byte(3)] 25 val3 = @[byte(3)] 26 27 setup: 28 let 29 dbPath = mkdtemp() / "data" 30 db = initTransactionDb(dbPath, columnFamilyNames = @[CF_OTHER]) 31 defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get() 32 otherCfHandle = db.getColFamilyHandle(CF_OTHER).get() 33 34 teardown: 35 db.close() 36 removeDir($dbPath) 37 38 # test multiple transactions 39 test "Test rollback using default column family": 40 var tx = db.beginTransaction() 41 defer: 42 tx.close() 43 check not tx.isClosed() 44 45 check: 46 tx.put(key1, val1).isOk() 47 tx.put(key2, val2).isOk() 48 tx.put(key3, val3).isOk() 49 50 tx.delete(key2).isOk() 51 not tx.isClosed() 52 53 check: 54 tx.get(key1).get() == val1 55 tx.get(key2).error() == "" 56 tx.get(key3).get() == val3 57 58 let res = tx.rollback() 59 check: 60 res.isOk() 61 tx.get(key1).error() == "" 62 tx.get(key2).error() == "" 63 tx.get(key3).error() == "" 64 65 test "Test commit using default column family": 66 var tx = db.beginTransaction() 67 defer: 68 tx.close() 69 check not tx.isClosed() 70 71 check: 72 tx.put(key1, val1).isOk() 73 tx.put(key2, val2).isOk() 74 tx.put(key3, val3).isOk() 75 76 tx.delete(key2).isOk() 77 not tx.isClosed() 78 79 check: 80 tx.get(key1).get() == val1 81 tx.get(key2).error() == "" 82 tx.get(key3).get() == val3 83 84 let res = tx.commit() 85 check: 86 res.isOk() 87 tx.get(key1).get() == val1 88 tx.get(key2).error() == "" 89 tx.get(key3).get() == val3 90 91 test "Test setting column family in beginTransaction": 92 var tx = db.beginTransaction(cfHandle = otherCfHandle) 93 defer: 94 tx.close() 95 check not tx.isClosed() 96 97 check: 98 tx.put(key1, val1).isOk() 99 tx.put(key2, val2).isOk() 100 tx.put(key3, val3).isOk() 101 102 tx.delete(key2).isOk() 103 not tx.isClosed() 104 105 check: 106 tx.get(key1, defaultCfHandle).error() == "" 107 tx.get(key2, defaultCfHandle).error() == "" 108 tx.get(key3, defaultCfHandle).error() == "" 109 tx.get(key1, otherCfHandle).get() == val1 110 tx.get(key2, otherCfHandle).error() == "" 111 tx.get(key3, otherCfHandle).get() == val3 112 113 test "Test rollback and commit with multiple transactions": 114 var tx1 = db.beginTransaction(cfHandle = defaultCfHandle) 115 defer: 116 tx1.close() 117 check not tx1.isClosed() 118 var tx2 = db.beginTransaction(cfHandle = otherCfHandle) 119 defer: 120 tx2.close() 121 check not tx2.isClosed() 122 123 check: 124 tx1.put(key1, val1).isOk() 125 tx1.put(key2, val2).isOk() 126 tx1.put(key3, val3).isOk() 127 tx1.delete(key2).isOk() 128 not tx1.isClosed() 129 tx2.put(key1, val1).isOk() 130 tx2.put(key2, val2).isOk() 131 tx2.put(key3, val3).isOk() 132 tx2.delete(key2).isOk() 133 not tx2.isClosed() 134 135 check: 136 tx1.get(key1, defaultCfHandle).get() == val1 137 tx1.get(key2, defaultCfHandle).error() == "" 138 tx1.get(key3, defaultCfHandle).get() == val3 139 tx1.get(key1, otherCfHandle).error() == "" 140 tx1.get(key2, otherCfHandle).error() == "" 141 tx1.get(key3, otherCfHandle).error() == "" 142 143 tx2.get(key1, defaultCfHandle).error() == "" 144 tx2.get(key2, defaultCfHandle).error() == "" 145 tx2.get(key3, defaultCfHandle).error() == "" 146 tx2.get(key1, otherCfHandle).get() == val1 147 tx2.get(key2, otherCfHandle).error() == "" 148 tx2.get(key3, otherCfHandle).get() == val3 149 150 block: 151 let res = tx1.rollback() 152 check: 153 res.isOk() 154 tx1.get(key1, defaultCfHandle).error() == "" 155 tx1.get(key2, defaultCfHandle).error() == "" 156 tx1.get(key3, defaultCfHandle).error() == "" 157 tx1.get(key1, otherCfHandle).error() == "" 158 tx1.get(key2, otherCfHandle).error() == "" 159 tx1.get(key3, otherCfHandle).error() == "" 160 161 block: 162 let res = tx2.commit() 163 check: 164 res.isOk() 165 tx2.get(key1, defaultCfHandle).error() == "" 166 tx2.get(key2, defaultCfHandle).error() == "" 167 tx2.get(key3, defaultCfHandle).error() == "" 168 tx2.get(key1, otherCfHandle).get() == val1 169 tx2.get(key2, otherCfHandle).error() == "" 170 tx2.get(key3, otherCfHandle).get() == val3 171 172 test "Put, get and delete empty key": 173 let tx = db.beginTransaction() 174 defer: 175 tx.close() 176 177 let empty: seq[byte] = @[] 178 check: 179 tx.put(empty, val1).isOk() 180 tx.get(empty).get() == val1 181 tx.delete(empty).isOk() 182 tx.get(empty).isErr() 183 184 test "Test close": 185 var tx = db.beginTransaction() 186 187 check not tx.isClosed() 188 tx.close() 189 check tx.isClosed() 190 tx.close() 191 check tx.isClosed() 192 193 check not db.isClosed() 194 db.close() 195 check db.isClosed() 196 db.close() 197 check db.isClosed() 198 199 test "Test close multiple tx": 200 var tx1 = db.beginTransaction() 201 var tx2 = db.beginTransaction() 202 203 check not db.isClosed() 204 check not tx1.isClosed() 205 tx1.close() 206 check tx1.isClosed() 207 tx1.close() 208 check tx1.isClosed() 209 210 check not db.isClosed() 211 check not tx2.isClosed() 212 tx2.close() 213 check tx2.isClosed() 214 tx2.close() 215 check tx2.isClosed() 216 217 test "Test auto close enabled": 218 let 219 dbPath = mkdtemp() / "autoclose-enabled" 220 dbOpts = defaultDbOptions(autoClose = true) 221 txDbOpts = defaultTransactionDbOptions(autoClose = true) 222 columnFamilies = @[ 223 initColFamilyDescriptor(CF_DEFAULT, defaultColFamilyOptions(autoClose = true)) 224 ] 225 db = openTransactionDb(dbPath, dbOpts, txDbOpts, columnFamilies).get() 226 227 check: 228 dbOpts.isClosed() == false 229 txDbOpts.isClosed() == false 230 columnFamilies[0].isClosed() == false 231 db.isClosed() == false 232 233 db.close() 234 235 check: 236 dbOpts.isClosed() == true 237 txDbOpts.isClosed() == true 238 columnFamilies[0].isClosed() == true 239 db.isClosed() == true 240 241 test "Test auto close disabled": 242 let 243 dbPath = mkdtemp() / "autoclose-disabled" 244 dbOpts = defaultDbOptions(autoClose = false) 245 txDbOpts = defaultTransactionDbOptions(autoClose = false) 246 columnFamilies = @[ 247 initColFamilyDescriptor(CF_DEFAULT, defaultColFamilyOptions(autoClose = false)) 248 ] 249 db = openTransactionDb(dbPath, dbOpts, txDbOpts, columnFamilies).get() 250 251 check: 252 dbOpts.isClosed() == false 253 txDbOpts.isClosed() == false 254 columnFamilies[0].isClosed() == false 255 db.isClosed() == false 256 257 db.close() 258 259 check: 260 dbOpts.isClosed() == false 261 txDbOpts.isClosed() == false 262 columnFamilies[0].isClosed() == false 263 db.isClosed() == true 264 265 test "Test auto close tx enabled": 266 let 267 readOpts = defaultReadOptions(autoClose = true) 268 writeOpts = defaultWriteOptions(autoClose = true) 269 txOpts = defaultTransactionOptions(autoClose = true) 270 tx = db.beginTransaction(readOpts, writeOpts, txOpts) 271 272 check: 273 readOpts.isClosed() == false 274 writeOpts.isClosed() == false 275 txOpts.isClosed() == false 276 tx.isClosed() == false 277 278 tx.close() 279 280 check: 281 readOpts.isClosed() == true 282 writeOpts.isClosed() == true 283 txOpts.isClosed() == true 284 tx.isClosed() == true 285 286 test "Test auto close tx disabled": 287 let 288 readOpts = defaultReadOptions(autoClose = false) 289 writeOpts = defaultWriteOptions(autoClose = false) 290 txOpts = defaultTransactionOptions(autoClose = false) 291 tx = db.beginTransaction(readOpts, writeOpts, txOpts) 292 293 check: 294 readOpts.isClosed() == false 295 writeOpts.isClosed() == false 296 txOpts.isClosed() == false 297 tx.isClosed() == false 298 299 tx.close() 300 301 check: 302 readOpts.isClosed() == false 303 writeOpts.isClosed() == false 304 txOpts.isClosed() == false 305 tx.isClosed() == true 306 307 test "Test iterator": 308 let tx1 = db.beginTransaction() 309 defer: 310 tx1.close() 311 check: 312 tx1.put(key1, val1).isOk() 313 tx1.commit().isOk() 314 315 block: 316 # test the db iterator 317 let iter = db.openIterator().get() 318 defer: 319 iter.close() 320 321 iter.seekToKey(key1) 322 check: 323 iter.isValid() == true 324 iter.key() == key1 325 iter.value() == val1 326 iter.seekToKey(key2) 327 check iter.isValid() == false 328 329 block: 330 # test the tx iterator 331 let iter = tx1.openIterator().get() 332 defer: 333 iter.close() 334 335 iter.seekToKey(key1) 336 check: 337 iter.isValid() == true 338 iter.key() == key1 339 iter.value() == val1 340 iter.seekToKey(key2) 341 check iter.isValid() == false 342 343 test "Create and restore snapshot": 344 let tx1 = db.beginTransaction() 345 defer: 346 tx1.close() 347 check: 348 tx1.put(key1, val1).isOk() 349 tx1.commit().isOk() 350 351 let snapshot = db.getSnapshot().get() 352 check: 353 snapshot.getSequenceNumber() > 0 354 not snapshot.isClosed() 355 356 # after taking snapshot, update the db 357 let tx2 = db.beginTransaction() 358 defer: 359 tx2.close() 360 check: 361 tx2.delete(key1).isOk() 362 tx2.put(key2, val2).isOk() 363 tx2.commit().isOk() 364 365 let readOpts = defaultReadOptions(autoClose = true) 366 readOpts.setSnapshot(snapshot) 367 368 # read from the snapshot using an iterator 369 let iter = db.openIterator(readOpts = readOpts).get() 370 defer: 371 iter.close() 372 iter.seekToKey(key1) 373 check: 374 iter.isValid() == true 375 iter.key() == key1 376 iter.value() == val1 377 iter.seekToKey(key2) 378 check iter.isValid() == false 379 380 # read from the snapshot using a transaction 381 let tx3 = db.beginTransaction(readOpts = readOpts) 382 defer: 383 tx3.close() 384 check: 385 tx3.get(key1).get() == val1 386 tx3.get(key2).isErr() 387 388 db.releaseSnapshot(snapshot) 389 check snapshot.isClosed()