/ tests / test_transactiondb.nim
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()