slatedb 0.4.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ea58f5cc108fef4066004dce3db7641d979589c6afa56023845b4e568198630
4
- data.tar.gz: c5e52bded1c840e6d51ba420b673c5f03e3f9450dd78476dc129a45cab02b11b
3
+ metadata.gz: c46e1c81aa2f9f98834847d89ebe70a3caa18dd7307ec864d2d18fb448b8c366
4
+ data.tar.gz: 1e24f5bb9dc914c3f9f5fe7025d481b0eb93160ffd5834f78d039c333d66e434
5
5
  SHA512:
6
- metadata.gz: 715e35df65d25a1487f648718ec72e593fed5e22289f47c2017aa8eac24ccda5d5df00151ec618b1d732550be2f8b6ac5d8136b9358d18c197ae11b5e437e8a9
7
- data.tar.gz: 92780ae93284906f92a4e4550d885c281af32f7cd1f91642e6ca9ebbe1f114d574d20a17be3f0605b5caaaafe64e27e70f12a96b5fcc6255fd8f0e210f0fa9b1
6
+ metadata.gz: 3b5fce35dda024e1011917c41685c88eadf9537558bcc643c06e4db988fc99dd3819f4d0b5f5be1ff5f660a33053b3c4b6fc579e1723523edc940cef67682d39
7
+ data.tar.gz: e0025127be66530aa82e64bcc98141b20cf5e5fd0a66290f32bcd88b16b4b7143a144238f85b0f209c7f7fc56477d85cfa3556f5b1808940f31d55086bf7e99d
data/README.md CHANGED
@@ -64,6 +64,34 @@ SlateDb::Database.open("/tmp/mydb") do |db|
64
64
  end # automatically closed
65
65
  ```
66
66
 
67
+ ### Compaction Controls
68
+
69
+ SlateDB 0.17 adds controls for how long compaction inputs remain protected from
70
+ garbage collection and when to consolidate sorted runs:
71
+
72
+ ```ruby
73
+ SlateDb::Database.open("mydb",
74
+ url: "s3://mybucket/path",
75
+ compactor_checkpoint_lifetime: 30 * 60_000,
76
+ sorted_run_consolidation_threshold: 8) do |db|
77
+ db.put("key", "value")
78
+ end
79
+ ```
80
+
81
+ `compactor_checkpoint_lifetime` is in milliseconds and defaults to 15 minutes.
82
+ Reads, scans, snapshots, and transactions using old compaction inputs must finish
83
+ within that time; after it expires, garbage collection can remove those inputs.
84
+
85
+ `sorted_run_consolidation_threshold` starts fallback consolidation when the
86
+ projected sorted-run count exceeds the threshold. It is not a hard limit.
87
+ Zero disables it (the default); use at least two to enable it. SlateDB warns and
88
+ disables the trigger for one. Low thresholds can increase storage reads and writes.
89
+
90
+ ### Key Size
91
+
92
+ Keys can now exceed the previous 65,535-byte limit, up to SlateDB's new
93
+ 4,294,967,295-byte limit. Very large keys still require enough memory to process.
94
+
67
95
  ### Persistent Storage
68
96
 
69
97
  For persistent storage, provide an object store URL:
@@ -501,6 +529,14 @@ SlateDb::Reader.open("/tmp/mydb", url: "s3://bucket/path") do |reader|
501
529
  reader.scan("prefix:").each do |k, v|
502
530
  puts "#{k}: #{v}"
503
531
  end
532
+
533
+ # Range and prefix scans accept :asc/:ascending or :desc/:descending.
534
+ reader.scan("user:100", "user:200", order: :desc).each do |k, v|
535
+ puts "#{k}: #{v}"
536
+ end
537
+ reader.scan_prefix("user:", from: "100", to: "200", order: :desc).each do |k, v|
538
+ puts "#{k}: #{v}"
539
+ end
504
540
  end
505
541
 
506
542
  # Open at a specific checkpoint (pins the reader to that checkpoint's state)
@@ -561,8 +597,19 @@ admin.run_gc(min_age: 3600_000) # Set min age for all directorie
561
597
  admin.run_gc(manifest_min_age: 86400_000) # Custom age for manifest (1 day)
562
598
  admin.run_gc(wal_min_age: 60_000) # Custom age for WAL (1 minute)
563
599
  admin.run_gc(compacted_min_age: 60_000) # Custom age for compacted (1 minute)
600
+
601
+ # Delete a database (SlateDB >= 0.16.0)
602
+ would_delete = admin.delete_db # Dry run: returns the object paths
603
+ # that *would* be deleted, deleting nothing
604
+ deleted = admin.delete_db(confirm: true) # Actually delete; returns the removed paths
564
605
  ```
565
606
 
607
+ `delete_db` is idempotent, and a confirmed delete of a directory that is not a
608
+ SlateDB database (no manifest and no in-progress deletion marker) is refused, so
609
+ a mistyped path cannot wipe an unrelated directory. For a cloned database, any
610
+ checkpoints it pinned in parent databases are stripped before its own objects
611
+ are removed.
612
+
566
613
  ### Flushing
567
614
 
568
615
  Ensure all writes are persisted:
@@ -572,6 +619,26 @@ db.put("key", "value")
572
619
  db.flush
573
620
  ```
574
621
 
622
+ By default, `flush` flushes the write-ahead log (WAL) if enabled, otherwise the
623
+ memtable. You can choose explicitly with `flush_type:` (SlateDB >= 0.16.0):
624
+
625
+ ```ruby
626
+ db.flush(flush_type: :wal) # Flush the write-ahead log
627
+ db.flush(flush_type: :memtable) # Freeze and flush memtable entries to object storage
628
+ ```
629
+
630
+ ### Closing
631
+
632
+ Closing flushes the active memtable first by default, so durable writes are not
633
+ lost. You can control the final flush when closing (SlateDB >= 0.16.0):
634
+
635
+ ```ruby
636
+ db.close # Flush the memtable, then close (default)
637
+ db.close(flush_type: :wal) # Flush the WAL before closing
638
+ db.close(flush: false) # Close without a final flush; writes that are not
639
+ # yet durable may be lost
640
+ ```
641
+
575
642
  ## Thread Safety
576
643
 
577
644
  **SlateDB is fully thread-safe and optimized for concurrent access.**
@@ -11,13 +11,13 @@ name = "slatedb"
11
11
  crate-type = ["cdylib"]
12
12
 
13
13
  [dependencies]
14
- slatedb = "0.15.0"
15
- magnus = { version = "0.8.2", features = ["rb-sys"] }
16
- rb-sys = { version = "0.9.128", features = ["stable-api-compiled-fallback"] }
17
- tokio = { version = "1.52.3", features = ["rt-multi-thread", "sync"] }
18
- bytes = "1.11.1"
19
- serde_json = "1.0.145"
14
+ slatedb = "0.17.0"
15
+ magnus = { version = "0.9.3", features = ["rb-sys"] }
16
+ rb-sys = { version = "0.9.130", features = ["stable-api-compiled-fallback"] }
17
+ tokio = { version = "1.54.1", features = ["rt-multi-thread", "sync"] }
18
+ bytes = "1.12.1"
19
+ serde_json = "1.0.154"
20
20
  url = "2.5.8"
21
21
  once_cell = "1.21.4"
22
- log = "0.4.29"
23
- uuid = "1.23.1"
22
+ log = "0.4.34"
23
+ uuid = "1.29.0"
@@ -265,6 +265,26 @@ impl Admin {
265
265
 
266
266
  Ok(())
267
267
  }
268
+
269
+ /// Delete a database, stripping any checkpoints it pinned in parent
270
+ /// databases (for clones) before removing its own objects (SlateDB >= 0.16.0).
271
+ ///
272
+ /// # Arguments
273
+ /// * `confirm` - When `false` (the default), performs a dry run and returns
274
+ /// the list of object paths that *would* be deleted without touching
275
+ /// anything. When `true`, actually deletes and returns the object paths
276
+ /// that were removed.
277
+ ///
278
+ /// A `confirm` delete of a directory with neither a manifest nor a leftover
279
+ /// `.deleting` marker is refused, so an incorrect path cannot wipe an
280
+ /// unrelated directory. The operation is idempotent.
281
+ ///
282
+ /// # Returns
283
+ /// An array of object paths (deleted, or that would be deleted for a dry run).
284
+ pub fn delete_db(&self, confirm: bool) -> Result<Vec<String>, Error> {
285
+ let deleted = block_on_result(async { self.inner.delete_db(confirm).await })?;
286
+ Ok(deleted)
287
+ }
268
288
  }
269
289
 
270
290
  /// Define the Admin class on the SlateDb module.
@@ -282,6 +302,7 @@ pub fn define_admin_class(ruby: &Ruby, module: &magnus::RModule) -> Result<(), E
282
302
  class.define_method("_refresh_checkpoint", method!(Admin::refresh_checkpoint, 2))?;
283
303
  class.define_method("_delete_checkpoint", method!(Admin::delete_checkpoint, 1))?;
284
304
  class.define_method("_run_gc", method!(Admin::run_gc, 1))?;
305
+ class.define_method("_delete_db", method!(Admin::delete_db, 1))?;
285
306
 
286
307
  Ok(())
287
308
  }
@@ -4,7 +4,8 @@ use std::sync::{Arc, Mutex};
4
4
  use magnus::prelude::*;
5
5
  use magnus::{function, method, Error, RHash, Ruby};
6
6
  use slatedb::config::{
7
- DurabilityLevel, MergeOptions, PutOptions, ReadOptions, ScanOptions, Ttl, WriteOptions,
7
+ CloseOptions, DurabilityLevel, FlushOptions, FlushType, MergeOptions, PutOptions, ReadOptions,
8
+ ScanOptions, Settings, Ttl, WriteOptions,
8
9
  };
9
10
  use slatedb::object_store::memory::InMemory;
10
11
  use slatedb::{Db, IsolationLevel, IterationOrder, KeyValue};
@@ -79,13 +80,32 @@ impl Database {
79
80
  }
80
81
 
81
82
  fn write_options_from_kwargs(kwargs: &RHash) -> Result<WriteOptions, Error> {
82
- let await_durable = get_optional::<bool>(kwargs, "await_durable")?.unwrap_or(true);
83
83
  let seqnum = get_optional::<u64>(kwargs, "seqnum")?.unwrap_or(0);
84
84
 
85
- Ok(WriteOptions {
86
- await_durable,
87
- seqnum,
88
- })
85
+ Ok(WriteOptions { seqnum })
86
+ }
87
+
88
+ /// Parse the `await_durable` keyword argument (defaults to `true`).
89
+ ///
90
+ /// As of SlateDB 0.16.0, `await_durable` is no longer a `WriteOptions`
91
+ /// field. Write methods return immediately after updating the in-memory
92
+ /// WAL/MemTable and hand back a `WriteHandle`; blocking until the write is
93
+ /// durable in object storage is done by awaiting that handle. This helper
94
+ /// preserves the previous ergonomic `await_durable:` behavior for callers.
95
+ fn await_durable_from_kwargs(kwargs: &RHash) -> Result<bool, Error> {
96
+ Ok(get_optional::<bool>(kwargs, "await_durable")?.unwrap_or(true))
97
+ }
98
+
99
+ /// Parse a `flush_type` name into a SlateDB `FlushType` (SlateDB >= 0.16.0).
100
+ fn parse_flush_type(name: &str) -> Result<FlushType, Error> {
101
+ match name {
102
+ "wal" => Ok(FlushType::Wal),
103
+ "memtable" | "mem_table" => Ok(FlushType::MemTable),
104
+ other => Err(invalid_argument_error(&format!(
105
+ "invalid flush_type: {} (expected 'wal' or 'memtable')",
106
+ other
107
+ ))),
108
+ }
89
109
  }
90
110
 
91
111
  /// Open a database at the given path.
@@ -100,6 +120,23 @@ impl Database {
100
120
  pub fn open(path: String, url: Option<String>, kwargs: RHash) -> Result<Self, Error> {
101
121
  // Try string-based merge operator first, then proc-based
102
122
  let merge_operator = parse_merge_operator(&kwargs)?.or(parse_merge_operator_proc(&kwargs)?);
123
+ let checkpoint_lifetime = get_optional::<u64>(&kwargs, "compactor_checkpoint_lifetime")?
124
+ .map(std::time::Duration::from_millis);
125
+ let consolidation_threshold =
126
+ get_optional::<usize>(&kwargs, "sorted_run_consolidation_threshold")?;
127
+
128
+ let mut settings = Settings::default();
129
+ if let Some(compactor) = settings.compactor_options.as_mut() {
130
+ if let Some(lifetime) = checkpoint_lifetime {
131
+ compactor.checkpoint_lifetime = lifetime;
132
+ }
133
+ if let Some(threshold) = consolidation_threshold {
134
+ compactor.scheduler_options.insert(
135
+ "sorted_run_consolidation_threshold".to_string(),
136
+ threshold.to_string(),
137
+ );
138
+ }
139
+ }
103
140
 
104
141
  let db = block_on_result(async {
105
142
  let object_store: Arc<dyn slatedb::object_store::ObjectStore> =
@@ -109,7 +146,7 @@ impl Database {
109
146
  Arc::new(InMemory::new())
110
147
  };
111
148
 
112
- let mut builder = Db::builder(path, object_store);
149
+ let mut builder = Db::builder(path, object_store).with_settings(settings);
113
150
  if let Some(merge_operator) = merge_operator {
114
151
  builder = builder.with_merge_operator(merge_operator);
115
152
  }
@@ -249,16 +286,14 @@ impl Database {
249
286
  }
250
287
 
251
288
  let put_opts = PutOptions { ttl: Ttl::Default };
252
-
253
- let write_opts = WriteOptions {
254
- await_durable: true,
255
- seqnum: 0,
256
- };
289
+ let write_opts = WriteOptions::default();
257
290
 
258
291
  block_on_result(async {
259
- self.inner
292
+ let handle = self
293
+ .inner
260
294
  .put_with_options(key.as_bytes(), value.as_bytes(), &put_opts, &write_opts)
261
- .await
295
+ .await?;
296
+ handle.await_durable().await
262
297
  })?;
263
298
  self.increment_metric("db.put.count");
264
299
 
@@ -280,18 +315,23 @@ impl Database {
280
315
  let ttl = get_optional::<u64>(&kwargs, "ttl")?;
281
316
  let put_opts = PutOptions {
282
317
  ttl: match ttl {
283
- Some(ms) => Ttl::ExpireAfter(ms),
318
+ Some(ms) => Ttl::ExpireAfterMillis(ms),
284
319
  None => Ttl::Default,
285
320
  },
286
321
  };
287
322
 
288
- // Parse await_durable
289
323
  let write_opts = Self::write_options_from_kwargs(&kwargs)?;
324
+ let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
290
325
 
291
326
  block_on_result(async {
292
- self.inner
327
+ let handle = self
328
+ .inner
293
329
  .put_with_options(key.as_bytes(), value.as_bytes(), &put_opts, &write_opts)
294
- .await
330
+ .await?;
331
+ if await_durable {
332
+ handle.await_durable().await?;
333
+ }
334
+ Ok(())
295
335
  })?;
296
336
  self.increment_metric("db.put_with_options.count");
297
337
 
@@ -307,15 +347,14 @@ impl Database {
307
347
  return Err(invalid_argument_error("key cannot be empty"));
308
348
  }
309
349
 
310
- let write_opts = WriteOptions {
311
- await_durable: true,
312
- seqnum: 0,
313
- };
350
+ let write_opts = WriteOptions::default();
314
351
 
315
352
  block_on_result(async {
316
- self.inner
353
+ let handle = self
354
+ .inner
317
355
  .delete_with_options(key.as_bytes(), &write_opts)
318
- .await
356
+ .await?;
357
+ handle.await_durable().await
319
358
  })?;
320
359
  self.increment_metric("db.delete.count");
321
360
 
@@ -333,11 +372,17 @@ impl Database {
333
372
  }
334
373
 
335
374
  let write_opts = Self::write_options_from_kwargs(&kwargs)?;
375
+ let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
336
376
 
337
377
  block_on_result(async {
338
- self.inner
378
+ let handle = self
379
+ .inner
339
380
  .delete_with_options(key.as_bytes(), &write_opts)
340
- .await
381
+ .await?;
382
+ if await_durable {
383
+ handle.await_durable().await?;
384
+ }
385
+ Ok(())
341
386
  })?;
342
387
  self.increment_metric("db.delete_with_options.count");
343
388
 
@@ -550,7 +595,10 @@ impl Database {
550
595
  /// * `batch` - The WriteBatch to write
551
596
  pub fn write(&self, batch: &WriteBatch) -> Result<(), Error> {
552
597
  let batch_inner = batch.take()?;
553
- block_on_result(async { self.inner.write(batch_inner).await })?;
598
+ block_on_result(async {
599
+ let handle = self.inner.write(batch_inner).await?;
600
+ handle.await_durable().await
601
+ })?;
554
602
  Ok(())
555
603
  }
556
604
 
@@ -561,13 +609,19 @@ impl Database {
561
609
  /// * `kwargs` - Keyword arguments (await_durable, seqnum)
562
610
  pub fn write_with_options(&self, batch: &WriteBatch, kwargs: RHash) -> Result<(), Error> {
563
611
  let write_opts = Self::write_options_from_kwargs(&kwargs)?;
612
+ let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
564
613
 
565
614
  let batch_inner = batch.take()?;
566
615
 
567
616
  block_on_result(async {
568
- self.inner
617
+ let handle = self
618
+ .inner
569
619
  .write_with_options(batch_inner, &write_opts)
570
- .await
620
+ .await?;
621
+ if await_durable {
622
+ handle.await_durable().await?;
623
+ }
624
+ Ok(())
571
625
  })?;
572
626
 
573
627
  Ok(())
@@ -584,16 +638,14 @@ impl Database {
584
638
  }
585
639
 
586
640
  let merge_opts = MergeOptions { ttl: Ttl::Default };
587
-
588
- let write_opts = WriteOptions {
589
- await_durable: true,
590
- seqnum: 0,
591
- };
641
+ let write_opts = WriteOptions::default();
592
642
 
593
643
  block_on_result(async {
594
- self.inner
644
+ let handle = self
645
+ .inner
595
646
  .merge_with_options(key.as_bytes(), value.as_bytes(), &merge_opts, &write_opts)
596
- .await
647
+ .await?;
648
+ handle.await_durable().await
597
649
  })?;
598
650
 
599
651
  Ok(())
@@ -618,17 +670,23 @@ impl Database {
618
670
  let ttl = get_optional::<u64>(&kwargs, "ttl")?;
619
671
  let merge_opts = MergeOptions {
620
672
  ttl: match ttl {
621
- Some(ms) => Ttl::ExpireAfter(ms),
673
+ Some(ms) => Ttl::ExpireAfterMillis(ms),
622
674
  None => Ttl::Default,
623
675
  },
624
676
  };
625
677
 
626
678
  let write_opts = Self::write_options_from_kwargs(&kwargs)?;
679
+ let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
627
680
 
628
681
  block_on_result(async {
629
- self.inner
682
+ let handle = self
683
+ .inner
630
684
  .merge_with_options(key.as_bytes(), value.as_bytes(), &merge_opts, &write_opts)
631
- .await
685
+ .await?;
686
+ if await_durable {
687
+ handle.await_durable().await?;
688
+ }
689
+ Ok(())
632
690
  })?;
633
691
 
634
692
  Ok(())
@@ -708,6 +766,19 @@ impl Database {
708
766
  Ok(())
709
767
  }
710
768
 
769
+ /// Flush the database, choosing the flush type (SlateDB >= 0.16.0).
770
+ ///
771
+ /// # Arguments
772
+ /// * `flush_type` - "wal" to flush the WAL, or "memtable" to freeze and
773
+ /// flush memtable entries to object storage.
774
+ pub fn flush_with_options(&self, flush_type: String) -> Result<(), Error> {
775
+ let options = FlushOptions {
776
+ flush_type: Self::parse_flush_type(&flush_type)?,
777
+ };
778
+ block_on_result(async { self.inner.flush_with_options(options).await })?;
779
+ Ok(())
780
+ }
781
+
711
782
  /// Return the database metrics registry.
712
783
  pub fn metrics(&self) -> Result<Metrics, Error> {
713
784
  Ok(Metrics::new(self.metrics.clone()))
@@ -718,6 +789,22 @@ impl Database {
718
789
  block_on_result(async { self.inner.close().await })?;
719
790
  Ok(())
720
791
  }
792
+
793
+ /// Close the database, controlling the final flush (SlateDB >= 0.16.0).
794
+ ///
795
+ /// # Arguments
796
+ /// * `flush_type` - `Some("wal"|"memtable")` to flush before closing, or
797
+ /// `None` to close without a final flush (writes that are not yet durable
798
+ /// may be lost).
799
+ pub fn close_with_options(&self, flush_type: Option<String>) -> Result<(), Error> {
800
+ let flush_type = match flush_type {
801
+ Some(name) => Some(Self::parse_flush_type(&name)?),
802
+ None => None,
803
+ };
804
+ let options = CloseOptions { flush_type };
805
+ block_on_result(async { self.inner.close_with_options(options).await })?;
806
+ Ok(())
807
+ }
721
808
  }
722
809
 
723
810
  /// Define the Database class on the SlateDb module.
@@ -772,9 +859,17 @@ pub fn define_database_class(ruby: &Ruby, module: &magnus::RModule) -> Result<()
772
859
  "_create_checkpoint",
773
860
  method!(Database::create_checkpoint, 1),
774
861
  )?;
775
- class.define_method("flush", method!(Database::flush, 0))?;
862
+ class.define_method("_flush", method!(Database::flush, 0))?;
863
+ class.define_method(
864
+ "_flush_with_options",
865
+ method!(Database::flush_with_options, 1),
866
+ )?;
776
867
  class.define_method("_metrics", method!(Database::metrics, 0))?;
777
- class.define_method("close", method!(Database::close, 0))?;
868
+ class.define_method("_close", method!(Database::close, 0))?;
869
+ class.define_method(
870
+ "_close_with_options",
871
+ method!(Database::close_with_options, 1),
872
+ )?;
778
873
 
779
874
  Ok(())
780
875
  }
@@ -109,7 +109,7 @@ impl Transaction {
109
109
  let ttl = get_optional::<u64>(&kwargs, "ttl")?;
110
110
  let put_opts = PutOptions {
111
111
  ttl: match ttl {
112
- Some(ms) => Ttl::ExpireAfter(ms),
112
+ Some(ms) => Ttl::ExpireAfterMillis(ms),
113
113
  None => Ttl::Default,
114
114
  },
115
115
  };
@@ -172,7 +172,7 @@ impl Transaction {
172
172
  let ttl = get_optional::<u64>(&kwargs, "ttl")?;
173
173
  let merge_opts = MergeOptions {
174
174
  ttl: match ttl {
175
- Some(ms) => Ttl::ExpireAfter(ms),
175
+ Some(ms) => Ttl::ExpireAfterMillis(ms),
176
176
  None => Ttl::Default,
177
177
  },
178
178
  };
@@ -395,7 +395,16 @@ impl Transaction {
395
395
  .take()
396
396
  .ok_or_else(|| closed_error("transaction is closed"))?;
397
397
 
398
- block_on_result(async { txn.commit().await })?;
398
+ block_on_result(async {
399
+ // As of SlateDB 0.16.0, commit returns as soon as the write is in
400
+ // memory and hands back an optional WriteHandle (None for an empty
401
+ // or read-only transaction). Await durability to preserve the
402
+ // previous blocking-commit behavior.
403
+ if let Some(handle) = txn.commit().await? {
404
+ handle.await_durable().await?;
405
+ }
406
+ Ok(())
407
+ })?;
399
408
  Ok(())
400
409
  }
401
410
 
@@ -403,10 +412,7 @@ impl Transaction {
403
412
  pub fn commit_with_options(&self, kwargs: RHash) -> Result<(), Error> {
404
413
  let await_durable = get_optional::<bool>(&kwargs, "await_durable")?.unwrap_or(true);
405
414
  let seqnum = get_optional::<u64>(&kwargs, "seqnum")?.unwrap_or(0);
406
- let write_opts = WriteOptions {
407
- await_durable,
408
- seqnum,
409
- };
415
+ let write_opts = WriteOptions { seqnum };
410
416
 
411
417
  let txn = self
412
418
  .inner
@@ -414,7 +420,14 @@ impl Transaction {
414
420
  .take()
415
421
  .ok_or_else(|| closed_error("transaction is closed"))?;
416
422
 
417
- block_on_result(async { txn.commit_with_options(&write_opts).await })?;
423
+ block_on_result(async {
424
+ if let Some(handle) = txn.commit_with_options(&write_opts).await? {
425
+ if await_durable {
426
+ handle.await_durable().await?;
427
+ }
428
+ }
429
+ Ok(())
430
+ })?;
418
431
  Ok(())
419
432
  }
420
433
 
@@ -49,7 +49,7 @@ impl WriteBatch {
49
49
  let ttl = get_optional::<u64>(&kwargs, "ttl")?;
50
50
  let put_opts = PutOptions {
51
51
  ttl: match ttl {
52
- Some(ms) => Ttl::ExpireAfter(ms),
52
+ Some(ms) => Ttl::ExpireAfterMillis(ms),
53
53
  None => Ttl::Default,
54
54
  },
55
55
  };
@@ -102,7 +102,7 @@ impl WriteBatch {
102
102
  let ttl = get_optional::<u64>(&kwargs, "ttl")?;
103
103
  let merge_opts = MergeOptions {
104
104
  ttl: match ttl {
105
- Some(ms) => Ttl::ExpireAfter(ms),
105
+ Some(ms) => Ttl::ExpireAfterMillis(ms),
106
106
  None => Ttl::Default,
107
107
  },
108
108
  };
data/lib/slatedb/admin.rb CHANGED
@@ -118,5 +118,33 @@ module SlateDb
118
118
  opts[:min_age] = min_age if min_age
119
119
  _run_gc(opts)
120
120
  end
121
+
122
+ # Delete a database, removing its objects from the object store
123
+ # (SlateDB >= 0.16.0).
124
+ #
125
+ # For a cloned database, any checkpoints it pinned in parent databases are
126
+ # stripped before its own objects are removed.
127
+ #
128
+ # By default this performs a dry run: it returns the object paths that
129
+ # *would* be deleted without removing anything. Pass +confirm: true+ to
130
+ # actually delete. A confirmed delete of a directory that is not a SlateDB
131
+ # database (no manifest and no in-progress deletion marker) is refused, so a
132
+ # mistyped path cannot wipe an unrelated directory. The operation is
133
+ # idempotent.
134
+ #
135
+ # @param confirm [Boolean] When false (the default), performs a dry run and
136
+ # returns the paths that would be deleted. When true, deletes and returns
137
+ # the paths that were removed.
138
+ # @return [Array<String>] Object paths deleted (or that would be deleted)
139
+ #
140
+ # @example Preview what would be deleted
141
+ # admin.delete_db.each { |path| puts path }
142
+ #
143
+ # @example Actually delete the database
144
+ # deleted = admin.delete_db(confirm: true)
145
+ #
146
+ def delete_db(confirm: false)
147
+ _delete_db(confirm)
148
+ end
121
149
  end
122
150
  end
@@ -12,6 +12,12 @@ module SlateDb
12
12
  # @param merge_operator [Symbol, String, Proc, nil] Optional merge operator.
13
13
  # Can be a symbol/string ("string_concat" or "concat") or a Proc/lambda
14
14
  # that takes (key, existing_value, new_value) and returns the merged value.
15
+ # @param compactor_checkpoint_lifetime [Integer, nil] How long compaction inputs
16
+ # are protected from garbage collection, in milliseconds (default: 15 minutes).
17
+ # Reads using those inputs must finish within this time.
18
+ # @param sorted_run_consolidation_threshold [Integer, nil] Sorted-run count above
19
+ # which fallback consolidation starts. Zero disables it (default); use at least
20
+ # two to enable it. One is disabled by SlateDB with a warning. This is not a hard limit.
15
21
  # @yield [db] If a block is given, yields the database and ensures it's closed
16
22
  # @return [Database] The opened database (or block result if block given)
17
23
  #
@@ -38,8 +44,12 @@ module SlateDb
38
44
  # db.merge("counter", "3")
39
45
  # db.get("counter") # => "8"
40
46
  #
41
- def open(path, url: nil, merge_operator: nil)
42
- opts = {}
47
+ def open(path, url: nil, merge_operator: nil,
48
+ compactor_checkpoint_lifetime: nil, sorted_run_consolidation_threshold: nil)
49
+ opts = {
50
+ compactor_checkpoint_lifetime: compactor_checkpoint_lifetime,
51
+ sorted_run_consolidation_threshold: sorted_run_consolidation_threshold
52
+ }.compact
43
53
 
44
54
  case merge_operator
45
55
  when Symbol, String
@@ -506,5 +516,63 @@ module SlateDb
506
516
  def metrics
507
517
  _metrics
508
518
  end
519
+
520
+ # Flush in-memory writes to object storage.
521
+ #
522
+ # With no arguments this flushes pending writes durably, matching the
523
+ # previous behavior. Pass +flush_type+ to control what is flushed
524
+ # (SlateDB >= 0.16.0).
525
+ #
526
+ # @param flush_type [Symbol, String, nil] Which data to flush: :wal to flush
527
+ # the write-ahead log, or :memtable to freeze and flush memtable entries
528
+ # to object storage. When nil (the default), SlateDB flushes the WAL if
529
+ # enabled, otherwise the memtable.
530
+ # @return [void]
531
+ #
532
+ # @example Flush pending writes
533
+ # db.flush
534
+ #
535
+ # @example Explicitly flush the WAL
536
+ # db.flush(flush_type: :wal)
537
+ #
538
+ def flush(flush_type: nil)
539
+ if flush_type.nil?
540
+ _flush
541
+ else
542
+ _flush_with_options(flush_type.to_s)
543
+ end
544
+ end
545
+
546
+ # Close the database.
547
+ #
548
+ # By default the active memtable is flushed before closing (matching the
549
+ # previous behavior). Pass +flush: false+ to close without a final flush,
550
+ # in which case writes that are not yet durable may be lost
551
+ # (SlateDB >= 0.16.0).
552
+ #
553
+ # @param flush [Boolean] Whether to flush before closing (default: true)
554
+ # @param flush_type [Symbol, String, nil] When flushing, which data to
555
+ # flush: :wal or :memtable. Defaults to :memtable. Ignored when
556
+ # +flush: false+.
557
+ # @return [void]
558
+ #
559
+ # @example Close (flushing the memtable first)
560
+ # db.close
561
+ #
562
+ # @example Close without flushing
563
+ # db.close(flush: false)
564
+ #
565
+ # @example Flush the WAL before closing
566
+ # db.close(flush_type: :wal)
567
+ #
568
+ def close(flush: true, flush_type: nil)
569
+ if !flush
570
+ _close_with_options(nil)
571
+ elsif flush_type.nil?
572
+ _close
573
+ else
574
+ _close_with_options(flush_type.to_s)
575
+ end
576
+ end
509
577
  end
510
578
  end
@@ -106,16 +106,19 @@ module SlateDb
106
106
  #
107
107
  # @param start_key [String] The start key (inclusive)
108
108
  # @param end_key [String, nil] The end key (exclusive)
109
+ # @param order [Symbol, String, nil] Iteration order (:asc/:ascending or :desc/:descending)
109
110
  # @return [Iterator] An iterator over key-value pairs
110
111
  #
111
112
  def scan(start_key, end_key = nil, durability_filter: nil, dirty: nil,
112
- read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
113
- opts = {}
114
- opts[:durability_filter] = durability_filter.to_s if durability_filter
115
- opts[:dirty] = dirty unless dirty.nil?
116
- opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
117
- opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
118
- opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
113
+ read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, order: nil, &)
114
+ opts = {
115
+ durability_filter: durability_filter&.to_s,
116
+ dirty: dirty,
117
+ read_ahead_bytes: read_ahead_bytes,
118
+ cache_blocks: cache_blocks,
119
+ max_fetch_tasks: max_fetch_tasks,
120
+ order: order&.to_s
121
+ }.compact
119
122
 
120
123
  iter = if opts.empty?
121
124
  _scan(start_key, end_key)
@@ -138,6 +141,7 @@ module SlateDb
138
141
  # @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
139
142
  # @param cache_blocks [Boolean, nil] Whether to cache blocks
140
143
  # @param max_fetch_tasks [Integer, nil] Maximum number of fetch tasks
144
+ # @param order [Symbol, String, nil] Iteration order (:asc/:ascending or :desc/:descending)
141
145
  # @param from [String, nil] Inclusive lower bound suffix, appended to the
142
146
  # prefix, to start scanning from (e.g. prefix "user:" with from "100"
143
147
  # starts at "user:100"). Defaults to the start of the prefix.
@@ -146,7 +150,7 @@ module SlateDb
146
150
  # @return [Iterator] An iterator over key-value pairs
147
151
  #
148
152
  def scan_prefix(prefix, durability_filter: nil, dirty: nil,
149
- read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil,
153
+ read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, order: nil,
150
154
  from: nil, to: nil, &)
151
155
  opts = {
152
156
  durability_filter: durability_filter&.to_s,
@@ -154,6 +158,7 @@ module SlateDb
154
158
  read_ahead_bytes: read_ahead_bytes,
155
159
  cache_blocks: cache_blocks,
156
160
  max_fetch_tasks: max_fetch_tasks,
161
+ order: order&.to_s,
157
162
  subrange_from: from,
158
163
  subrange_to: to
159
164
  }.compact
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SlateDb
4
- VERSION = "0.4.4"
4
+ VERSION = "0.6.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slatedb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - SlateDB Contributors
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.9.128
18
+ version: 0.9.130
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.9.128
25
+ version: 0.9.130
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '1.86'
74
+ version: '1.92'
75
75
  type: :development
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '1.86'
81
+ version: '1.92'
82
82
  description: A cloud-native embedded key-value store built on object storage
83
83
  email:
84
84
  - slatedb@example.com
@@ -137,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  requirements: []
140
- rubygems_version: 4.0.16
140
+ rubygems_version: 4.0.20
141
141
  specification_version: 4
142
142
  summary: Ruby bindings for SlateDB
143
143
  test_files: []