slatedb 0.2.0 → 0.3.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.
@@ -0,0 +1,47 @@
1
+ use std::collections::HashMap;
2
+ use std::sync::{Arc, Mutex};
3
+
4
+ use magnus::prelude::*;
5
+ use magnus::{method, Error, Ruby};
6
+ /// Ruby wrapper for SlateDB metrics registry.
7
+ ///
8
+ /// This struct is exposed to Ruby as `SlateDb::Metrics`.
9
+ #[magnus::wrap(class = "SlateDb::Metrics", free_immediately, size)]
10
+ pub struct Metrics {
11
+ inner: Arc<Mutex<HashMap<String, i64>>>,
12
+ }
13
+
14
+ impl Metrics {
15
+ pub fn new(inner: Arc<Mutex<HashMap<String, i64>>>) -> Self {
16
+ Self { inner }
17
+ }
18
+
19
+ /// Return a list of metric names.
20
+ pub fn names(&self) -> Result<magnus::RArray, Error> {
21
+ let metrics = self.inner.lock().expect("metrics mutex poisoned");
22
+ let ruby = Ruby::get().expect("Ruby runtime not available");
23
+ let result = ruby.ary_new_capa(metrics.len());
24
+
25
+ for name in metrics.keys() {
26
+ result.push(ruby.str_new(name))?;
27
+ }
28
+
29
+ Ok(result)
30
+ }
31
+
32
+ /// Get the current value of a metric by name.
33
+ pub fn get(&self, name: String) -> Result<Option<i64>, Error> {
34
+ let metrics = self.inner.lock().expect("metrics mutex poisoned");
35
+ Ok(metrics.get(&name).copied())
36
+ }
37
+ }
38
+
39
+ /// Define the Metrics class on the SlateDb module.
40
+ pub fn define_metrics_class(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
41
+ let class = module.define_class("Metrics", ruby.class_object())?;
42
+
43
+ class.define_method("names", method!(Metrics::names, 0))?;
44
+ class.define_method("get", method!(Metrics::get, 1))?;
45
+
46
+ Ok(())
47
+ }
@@ -4,10 +4,10 @@ use magnus::prelude::*;
4
4
  use magnus::{function, method, Error, RHash, Ruby};
5
5
  use slatedb::config::{DbReaderOptions, DurabilityLevel, ReadOptions, ScanOptions};
6
6
  use slatedb::DbReader;
7
+ use slatedb::IterationOrder;
7
8
 
8
9
  use crate::errors::invalid_argument_error;
9
10
  use crate::iterator::Iterator;
10
- use crate::merge_ops::parse_merge_operator;
11
11
  use crate::runtime::block_on_result;
12
12
  use crate::utils::{get_optional, resolve_object_store};
13
13
 
@@ -27,7 +27,10 @@ impl Reader {
27
27
  /// * `path` - The path identifier for the database
28
28
  /// * `url` - Optional object store URL
29
29
  /// * `checkpoint_id` - Optional checkpoint UUID to read at
30
- /// * `kwargs` - Additional options (manifest_poll_interval, checkpoint_lifetime, max_memtable_bytes, merge_operator)
30
+ /// * `kwargs` - Additional options (manifest_poll_interval, checkpoint_lifetime,
31
+ /// max_memtable_bytes, skip_wal_replay, cache_root, max_open_file_handles).
32
+ /// The local disk cache (and therefore `max_open_file_handles`) is only active
33
+ /// when `cache_root` is set.
31
34
  pub fn open(
32
35
  path: String,
33
36
  url: Option<String>,
@@ -40,7 +43,9 @@ impl Reader {
40
43
  let checkpoint_lifetime = get_optional::<u64>(&kwargs, "checkpoint_lifetime")?
41
44
  .map(std::time::Duration::from_millis);
42
45
  let max_memtable_bytes = get_optional::<u64>(&kwargs, "max_memtable_bytes")?;
43
- let merge_operator = parse_merge_operator(&kwargs)?;
46
+ let skip_wal_replay = get_optional::<bool>(&kwargs, "skip_wal_replay")?;
47
+ let max_open_file_handles = get_optional::<usize>(&kwargs, "max_open_file_handles")?;
48
+ let cache_root = get_optional::<String>(&kwargs, "cache_root")?;
44
49
 
45
50
  // Parse checkpoint_id as UUID
46
51
  let checkpoint_uuid =
@@ -53,11 +58,12 @@ impl Reader {
53
58
  };
54
59
 
55
60
  let reader = block_on_result(async {
56
- let object_store: Arc<dyn slatedb::object_store::ObjectStore> = if let Some(ref url) = url {
57
- resolve_object_store(url)?
58
- } else {
59
- Arc::new(slatedb::object_store::memory::InMemory::new())
60
- };
61
+ let object_store: Arc<dyn slatedb::object_store::ObjectStore> =
62
+ if let Some(ref url) = url {
63
+ resolve_object_store(url)?
64
+ } else {
65
+ Arc::new(slatedb::object_store::memory::InMemory::new())
66
+ };
61
67
 
62
68
  let mut options = DbReaderOptions::default();
63
69
  if let Some(interval) = manifest_poll_interval {
@@ -69,10 +75,16 @@ impl Reader {
69
75
  if let Some(max_bytes) = max_memtable_bytes {
70
76
  options.max_memtable_bytes = max_bytes;
71
77
  }
72
- if let Some(merge_operator) = merge_operator {
73
- options.merge_operator = Some(merge_operator);
78
+ if let Some(skip_replay) = skip_wal_replay {
79
+ options.skip_wal_replay = skip_replay;
80
+ }
81
+ if let Some(ref root) = cache_root {
82
+ options.object_store_cache_options.root_folder =
83
+ Some(std::path::PathBuf::from(root));
84
+ }
85
+ if let Some(max_handles) = max_open_file_handles {
86
+ options.object_store_cache_options.max_open_file_handles = max_handles;
74
87
  }
75
-
76
88
  DbReader::open(path, object_store, checkpoint_uuid, options).await
77
89
  })?;
78
90
 
@@ -116,6 +128,10 @@ impl Reader {
116
128
  opts.dirty = dirty;
117
129
  }
118
130
 
131
+ if let Some(cb) = get_optional::<bool>(&kwargs, "cache_blocks")? {
132
+ opts.cache_blocks = cb;
133
+ }
134
+
119
135
  let result =
120
136
  block_on_result(async { self.inner.get_with_options(key.as_bytes(), &opts).await })?;
121
137
  Ok(result.map(|b| String::from_utf8_lossy(&b).to_string()))
@@ -191,6 +207,18 @@ impl Reader {
191
207
  if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
192
208
  opts.max_fetch_tasks = mft;
193
209
  }
210
+ if let Some(order) = get_optional::<String>(&kwargs, "order")? {
211
+ opts.order = match order.as_str() {
212
+ "ascending" | "asc" => IterationOrder::Ascending,
213
+ "descending" | "desc" => IterationOrder::Descending,
214
+ other => {
215
+ return Err(invalid_argument_error(&format!(
216
+ "invalid order: {} (expected 'asc' or 'desc')",
217
+ other
218
+ )))
219
+ }
220
+ };
221
+ }
194
222
 
195
223
  let start_bytes = start.into_bytes();
196
224
  let end_bytes = end_key.map(|e| e.into_bytes());
@@ -256,6 +284,18 @@ impl Reader {
256
284
  if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
257
285
  opts.max_fetch_tasks = mft;
258
286
  }
287
+ if let Some(order) = get_optional::<String>(&kwargs, "order")? {
288
+ opts.order = match order.as_str() {
289
+ "ascending" | "asc" => IterationOrder::Ascending,
290
+ "descending" | "desc" => IterationOrder::Descending,
291
+ other => {
292
+ return Err(invalid_argument_error(&format!(
293
+ "invalid order: {} (expected 'asc' or 'desc')",
294
+ other
295
+ )))
296
+ }
297
+ };
298
+ }
259
299
 
260
300
  let iter = block_on_result(async {
261
301
  self.inner
@@ -5,6 +5,7 @@ use magnus::prelude::*;
5
5
  use magnus::{method, Error, RHash, Ruby};
6
6
  use slatedb::config::{DurabilityLevel, ReadOptions, ScanOptions};
7
7
  use slatedb::DbSnapshot;
8
+ use slatedb::IterationOrder;
8
9
 
9
10
  use crate::errors::{closed_error, invalid_argument_error};
10
11
  use crate::iterator::Iterator;
@@ -68,6 +69,10 @@ impl Snapshot {
68
69
  opts.dirty = dirty;
69
70
  }
70
71
 
72
+ if let Some(cb) = get_optional::<bool>(&kwargs, "cache_blocks")? {
73
+ opts.cache_blocks = cb;
74
+ }
75
+
71
76
  let guard = self.inner.borrow();
72
77
  let snapshot = guard
73
78
  .as_ref()
@@ -143,6 +148,18 @@ impl Snapshot {
143
148
  if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
144
149
  opts.max_fetch_tasks = mft;
145
150
  }
151
+ if let Some(order) = get_optional::<String>(&kwargs, "order")? {
152
+ opts.order = match order.as_str() {
153
+ "ascending" | "asc" => IterationOrder::Ascending,
154
+ "descending" | "desc" => IterationOrder::Descending,
155
+ other => {
156
+ return Err(invalid_argument_error(&format!(
157
+ "invalid order: {} (expected 'asc' or 'desc')",
158
+ other
159
+ )))
160
+ }
161
+ };
162
+ }
146
163
 
147
164
  let guard = self.inner.borrow();
148
165
  let snapshot = guard
@@ -218,6 +235,18 @@ impl Snapshot {
218
235
  if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
219
236
  opts.max_fetch_tasks = mft;
220
237
  }
238
+ if let Some(order) = get_optional::<String>(&kwargs, "order")? {
239
+ opts.order = match order.as_str() {
240
+ "ascending" | "asc" => IterationOrder::Ascending,
241
+ "descending" | "desc" => IterationOrder::Descending,
242
+ other => {
243
+ return Err(invalid_argument_error(&format!(
244
+ "invalid order: {} (expected 'asc' or 'desc')",
245
+ other
246
+ )))
247
+ }
248
+ };
249
+ }
221
250
 
222
251
  let guard = self.inner.borrow();
223
252
  let snapshot = guard
@@ -6,6 +6,7 @@ use slatedb::config::{
6
6
  DurabilityLevel, MergeOptions, PutOptions, ReadOptions, ScanOptions, Ttl, WriteOptions,
7
7
  };
8
8
  use slatedb::DbTransaction;
9
+ use slatedb::IterationOrder;
9
10
 
10
11
  use crate::errors::{closed_error, invalid_argument_error, map_error};
11
12
  use crate::iterator::Iterator;
@@ -69,13 +70,16 @@ impl Transaction {
69
70
  opts.dirty = dirty;
70
71
  }
71
72
 
73
+ if let Some(cb) = get_optional::<bool>(&kwargs, "cache_blocks")? {
74
+ opts.cache_blocks = cb;
75
+ }
76
+
72
77
  let guard = self.inner.borrow();
73
78
  let txn = guard
74
79
  .as_ref()
75
80
  .ok_or_else(|| closed_error("transaction is closed"))?;
76
81
 
77
- let result =
78
- block_on_result(async { txn.get_with_options(key.as_bytes(), &opts).await })?;
82
+ let result = block_on_result(async { txn.get_with_options(key.as_bytes(), &opts).await })?;
79
83
  Ok(result.map(|b| String::from_utf8_lossy(&b).to_string()))
80
84
  }
81
85
 
@@ -155,7 +159,12 @@ impl Transaction {
155
159
  }
156
160
 
157
161
  /// Merge a value with options within the transaction.
158
- pub fn merge_with_options(&self, key: String, value: String, kwargs: RHash) -> Result<(), Error> {
162
+ pub fn merge_with_options(
163
+ &self,
164
+ key: String,
165
+ value: String,
166
+ kwargs: RHash,
167
+ ) -> Result<(), Error> {
159
168
  if key.is_empty() {
160
169
  return Err(invalid_argument_error("key cannot be empty"));
161
170
  }
@@ -244,6 +253,18 @@ impl Transaction {
244
253
  if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
245
254
  opts.max_fetch_tasks = mft;
246
255
  }
256
+ if let Some(order) = get_optional::<String>(&kwargs, "order")? {
257
+ opts.order = match order.as_str() {
258
+ "ascending" | "asc" => IterationOrder::Ascending,
259
+ "descending" | "desc" => IterationOrder::Descending,
260
+ other => {
261
+ return Err(invalid_argument_error(&format!(
262
+ "invalid order: {} (expected 'asc' or 'desc')",
263
+ other
264
+ )))
265
+ }
266
+ };
267
+ }
247
268
 
248
269
  let guard = self.inner.borrow();
249
270
  let txn = guard
@@ -319,14 +340,27 @@ impl Transaction {
319
340
  if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
320
341
  opts.max_fetch_tasks = mft;
321
342
  }
343
+ if let Some(order) = get_optional::<String>(&kwargs, "order")? {
344
+ opts.order = match order.as_str() {
345
+ "ascending" | "asc" => IterationOrder::Ascending,
346
+ "descending" | "desc" => IterationOrder::Descending,
347
+ other => {
348
+ return Err(invalid_argument_error(&format!(
349
+ "invalid order: {} (expected 'asc' or 'desc')",
350
+ other
351
+ )))
352
+ }
353
+ };
354
+ }
322
355
 
323
356
  let guard = self.inner.borrow();
324
357
  let txn = guard
325
358
  .as_ref()
326
359
  .ok_or_else(|| closed_error("transaction is closed"))?;
327
360
 
328
- let iter =
329
- block_on_result(async { txn.scan_prefix_with_options(prefix.as_bytes(), &opts).await })?;
361
+ let iter = block_on_result(async {
362
+ txn.scan_prefix_with_options(prefix.as_bytes(), &opts).await
363
+ })?;
330
364
 
331
365
  Ok(Iterator::new(iter))
332
366
  }
@@ -365,7 +399,11 @@ impl Transaction {
365
399
  /// Commit the transaction with options.
366
400
  pub fn commit_with_options(&self, kwargs: RHash) -> Result<(), Error> {
367
401
  let await_durable = get_optional::<bool>(&kwargs, "await_durable")?.unwrap_or(true);
368
- let write_opts = WriteOptions { await_durable };
402
+ let seqnum = get_optional::<u64>(&kwargs, "seqnum")?.unwrap_or(0);
403
+ let write_opts = WriteOptions {
404
+ await_durable,
405
+ seqnum,
406
+ };
369
407
 
370
408
  let txn = self
371
409
  .inner
@@ -422,7 +460,7 @@ pub fn define_transaction_class(ruby: &Ruby, module: &magnus::RModule) -> Result
422
460
  method!(Transaction::scan_prefix_with_options, 2),
423
461
  )?;
424
462
  class.define_method("_mark_read", method!(Transaction::mark_read, 1))?;
425
- class.define_method("commit", method!(Transaction::commit, 0))?;
463
+ class.define_method("_commit", method!(Transaction::commit, 0))?;
426
464
  class.define_method(
427
465
  "_commit_with_options",
428
466
  method!(Transaction::commit_with_options, 1),
@@ -89,7 +89,12 @@ impl WriteBatch {
89
89
  ///
90
90
  /// Options:
91
91
  /// - ttl: Time-to-live in milliseconds
92
- pub fn merge_with_options(&self, key: String, value: String, kwargs: RHash) -> Result<(), Error> {
92
+ pub fn merge_with_options(
93
+ &self,
94
+ key: String,
95
+ value: String,
96
+ kwargs: RHash,
97
+ ) -> Result<(), Error> {
93
98
  if key.is_empty() {
94
99
  return Err(invalid_argument_error("key cannot be empty"));
95
100
  }
@@ -95,12 +95,44 @@ module SlateDb
95
95
  end
96
96
  end
97
97
 
98
+ # Get a key-value pair with SlateDB metadata.
99
+ #
100
+ # @param key [String] The key to look up
101
+ # @param durability_filter [String, nil] Filter by durability level ("remote" or "memory")
102
+ # @param dirty [Boolean, nil] Whether to include uncommitted data
103
+ # @param cache_blocks [Boolean, nil] Whether to cache blocks
104
+ # @return [Hash, nil] A hash with :key, :value, :seq, :create_ts, and :expire_ts, or nil if not found
105
+ #
106
+ # @example Inspect metadata
107
+ # entry = db.get_key_value("mykey")
108
+ # entry[:value] # => "myvalue"
109
+ # entry[:seq] # => sequence number
110
+ #
111
+ def get_key_value(key, durability_filter: nil, dirty: nil, cache_blocks: nil)
112
+ opts = {}
113
+ opts[:durability_filter] = durability_filter.to_s if durability_filter
114
+ opts[:dirty] = dirty unless dirty.nil?
115
+ opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
116
+
117
+ if opts.empty?
118
+ _get_key_value(key)
119
+ else
120
+ _get_key_value_with_options(key, opts)
121
+ end
122
+ end
123
+
124
+ alias get_entry get_key_value
125
+
98
126
  # Store a key-value pair.
99
127
  #
100
128
  # @param key [String] The key to store
101
129
  # @param value [String] The value to store
102
130
  # @param ttl [Integer, nil] Time-to-live in milliseconds
103
131
  # @param await_durable [Boolean] Whether to wait for durability (default: true)
132
+ # @param seqnum [Integer, nil] User-supplied sequence number for this write.
133
+ # When provided (and non-zero), it is used instead of the internally
134
+ # generated sequence number. It must be strictly greater than the current
135
+ # maximum sequence number or the write fails. (Requires SlateDB >= 0.13.0)
104
136
  # @return [void]
105
137
  #
106
138
  # @example Basic put
@@ -112,10 +144,14 @@ module SlateDb
112
144
  # @example Put without waiting for durability
113
145
  # db.put("mykey", "myvalue", await_durable: false)
114
146
  #
115
- def put(key, value, ttl: nil, await_durable: nil)
147
+ # @example Put with an explicit sequence number
148
+ # db.put("mykey", "myvalue", seqnum: 42)
149
+ #
150
+ def put(key, value, ttl: nil, await_durable: nil, seqnum: nil)
116
151
  opts = {}
117
152
  opts[:ttl] = ttl if ttl
118
153
  opts[:await_durable] = await_durable unless await_durable.nil?
154
+ opts[:seqnum] = seqnum if seqnum
119
155
 
120
156
  if opts.empty?
121
157
  _put(key, value)
@@ -128,6 +164,8 @@ module SlateDb
128
164
  #
129
165
  # @param key [String] The key to delete
130
166
  # @param await_durable [Boolean] Whether to wait for durability (default: true)
167
+ # @param seqnum [Integer, nil] User-supplied sequence number for this write.
168
+ # See {#put} for semantics. (Requires SlateDB >= 0.13.0)
131
169
  # @return [void]
132
170
  #
133
171
  # @example Basic delete
@@ -136,11 +174,15 @@ module SlateDb
136
174
  # @example Delete without waiting for durability
137
175
  # db.delete("mykey", await_durable: false)
138
176
  #
139
- def delete(key, await_durable: nil)
140
- if await_durable.nil?
177
+ def delete(key, await_durable: nil, seqnum: nil)
178
+ opts = {}
179
+ opts[:await_durable] = await_durable unless await_durable.nil?
180
+ opts[:seqnum] = seqnum if seqnum
181
+
182
+ if opts.empty?
141
183
  _delete(key)
142
184
  else
143
- _delete_with_options(key, { await_durable: await_durable })
185
+ _delete_with_options(key, opts)
144
186
  end
145
187
  end
146
188
 
@@ -153,6 +195,7 @@ module SlateDb
153
195
  # @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
154
196
  # @param cache_blocks [Boolean, nil] Whether to cache blocks
155
197
  # @param max_fetch_tasks [Integer, nil] Maximum number of fetch tasks
198
+ # @param order [Symbol, String, nil] Iteration order (:asc/:ascending or :desc/:descending)
156
199
  # @return [Iterator] An iterator over key-value pairs
157
200
  #
158
201
  # @example Basic scan
@@ -171,13 +214,15 @@ module SlateDb
171
214
  # end
172
215
  #
173
216
  def scan(start_key, end_key = nil, durability_filter: nil, dirty: nil,
174
- read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
175
- opts = {}
176
- opts[:durability_filter] = durability_filter.to_s if durability_filter
177
- opts[:dirty] = dirty unless dirty.nil?
178
- opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
179
- opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
180
- opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
217
+ read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, order: nil, &)
218
+ opts = scan_options(
219
+ durability_filter: durability_filter,
220
+ dirty: dirty,
221
+ read_ahead_bytes: read_ahead_bytes,
222
+ cache_blocks: cache_blocks,
223
+ max_fetch_tasks: max_fetch_tasks,
224
+ order: order
225
+ )
181
226
 
182
227
  iter = if opts.empty?
183
228
  _scan(start_key, end_key)
@@ -200,6 +245,7 @@ module SlateDb
200
245
  # @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
201
246
  # @param cache_blocks [Boolean, nil] Whether to cache blocks
202
247
  # @param max_fetch_tasks [Integer, nil] Maximum number of fetch tasks
248
+ # @param order [Symbol, String, nil] Iteration order (:asc/:ascending or :desc/:descending)
203
249
  # @return [Iterator] An iterator over key-value pairs
204
250
  #
205
251
  # @example Scan all user keys
@@ -208,13 +254,15 @@ module SlateDb
208
254
  # end
209
255
  #
210
256
  def scan_prefix(prefix, durability_filter: nil, dirty: nil,
211
- read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
212
- opts = {}
213
- opts[:durability_filter] = durability_filter.to_s if durability_filter
214
- opts[:dirty] = dirty unless dirty.nil?
215
- opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
216
- opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
217
- opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
257
+ read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, order: nil, &)
258
+ opts = scan_options(
259
+ durability_filter: durability_filter,
260
+ dirty: dirty,
261
+ read_ahead_bytes: read_ahead_bytes,
262
+ cache_blocks: cache_blocks,
263
+ max_fetch_tasks: max_fetch_tasks,
264
+ order: order
265
+ )
218
266
 
219
267
  iter = if opts.empty?
220
268
  _scan_prefix(prefix)
@@ -229,10 +277,26 @@ module SlateDb
229
277
  end
230
278
  end
231
279
 
280
+ def scan_options(durability_filter:, dirty:, read_ahead_bytes:, cache_blocks:,
281
+ max_fetch_tasks:, order:)
282
+ opts = {}
283
+ opts[:durability_filter] = durability_filter.to_s if durability_filter
284
+ opts[:dirty] = dirty unless dirty.nil?
285
+ opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
286
+ opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
287
+ opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
288
+ opts[:order] = order.to_s if order
289
+ opts
290
+ end
291
+
292
+ private :scan_options
293
+
232
294
  # Write a batch of operations atomically.
233
295
  #
234
296
  # @param batch [WriteBatch] The batch to write
235
297
  # @param await_durable [Boolean] Whether to wait for durability (default: true)
298
+ # @param seqnum [Integer, nil] User-supplied sequence number applied to the
299
+ # batch. See {#put} for semantics. (Requires SlateDB >= 0.13.0)
236
300
  # @return [void]
237
301
  #
238
302
  # @example Write a batch
@@ -248,11 +312,15 @@ module SlateDb
248
312
  # b.put("key2", "value2")
249
313
  # end
250
314
  #
251
- def write(batch, await_durable: nil)
252
- if await_durable.nil?
315
+ def write(batch, await_durable: nil, seqnum: nil)
316
+ opts = {}
317
+ opts[:await_durable] = await_durable unless await_durable.nil?
318
+ opts[:seqnum] = seqnum if seqnum
319
+
320
+ if opts.empty?
253
321
  _write(batch)
254
322
  else
255
- _write_with_options(batch, { await_durable: await_durable })
323
+ _write_with_options(batch, opts)
256
324
  end
257
325
  end
258
326
 
@@ -262,6 +330,8 @@ module SlateDb
262
330
  # @param value [String] The merge operand to apply
263
331
  # @param ttl [Integer, nil] Time-to-live in milliseconds
264
332
  # @param await_durable [Boolean] Whether to wait for durability (default: true)
333
+ # @param seqnum [Integer, nil] User-supplied sequence number for this write.
334
+ # See {#put} for semantics. (Requires SlateDB >= 0.13.0)
265
335
  # @return [void]
266
336
  #
267
337
  # @example Merge with string concatenation operator
@@ -269,10 +339,11 @@ module SlateDb
269
339
  # db.merge("key", "part1")
270
340
  # db.merge("key", "part2")
271
341
  #
272
- def merge(key, value, ttl: nil, await_durable: nil)
342
+ def merge(key, value, ttl: nil, await_durable: nil, seqnum: nil)
273
343
  opts = {}
274
344
  opts[:ttl] = ttl if ttl
275
345
  opts[:await_durable] = await_durable unless await_durable.nil?
346
+ opts[:seqnum] = seqnum if seqnum
276
347
 
277
348
  if opts.empty?
278
349
  _merge(key, value)
@@ -284,6 +355,8 @@ module SlateDb
284
355
  # Create and write a batch using a block.
285
356
  #
286
357
  # @param await_durable [Boolean] Whether to wait for durability (default: true)
358
+ # @param seqnum [Integer, nil] User-supplied sequence number applied to the
359
+ # batch. See {#put} for semantics. (Requires SlateDB >= 0.13.0)
287
360
  # @yield [batch] Yields a WriteBatch to the block
288
361
  # @return [void]
289
362
  #
@@ -294,10 +367,10 @@ module SlateDb
294
367
  # b.delete("old_key")
295
368
  # end
296
369
  #
297
- def batch(await_durable: nil)
370
+ def batch(await_durable: nil, seqnum: nil)
298
371
  b = WriteBatch.new
299
372
  yield b
300
- write(b, await_durable: await_durable)
373
+ write(b, await_durable: await_durable, seqnum: seqnum)
301
374
  end
302
375
 
303
376
  # Begin a new transaction.
@@ -414,5 +487,12 @@ module SlateDb
414
487
  opts[:name] = name if name
415
488
  _create_checkpoint(opts)
416
489
  end
490
+
491
+ # Get database metrics registry.
492
+ #
493
+ # @return [Metrics] Metrics registry
494
+ def metrics
495
+ _metrics
496
+ end
417
497
  end
418
498
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlateDb
4
+ class Metrics
5
+ # Get a metric value by name.
6
+ #
7
+ # @param name [String] Metric name
8
+ # @return [Integer, nil] Current value or nil if not found
9
+ def [](name)
10
+ get(name)
11
+ end
12
+
13
+ # Convert all metrics to a hash.
14
+ #
15
+ # @return [Hash] Map of metric name to value
16
+ def to_h
17
+ names.to_h { |metric_name| [metric_name, get(metric_name)] }
18
+ end
19
+ end
20
+ end
@@ -11,6 +11,13 @@ module SlateDb
11
11
  # @param manifest_poll_interval [Integer, nil] Poll interval in milliseconds
12
12
  # @param checkpoint_lifetime [Integer, nil] Checkpoint lifetime in milliseconds
13
13
  # @param max_memtable_bytes [Integer, nil] Maximum memtable size in bytes
14
+ # @param cache_root [String, nil] Root folder for the reader's local on-disk
15
+ # object-store cache. Setting this enables the cached object store; when it is
16
+ # not set the cache (and `max_open_file_handles`) has no effect.
17
+ # @param max_open_file_handles [Integer, nil] Maximum number of file handles to keep
18
+ # open in the reader's file-handle cache. When the limit is reached, the least
19
+ # recently used handle is closed (default: 1000). Only takes effect when
20
+ # `cache_root` is set. (Requires SlateDB >= 0.13.0)
14
21
  # @param merge_operator [Symbol, String, nil] Optional merge operator ("string_concat" or "concat")
15
22
  # @yield [reader] If a block is given, yields the reader and ensures it's closed
16
23
  # @return [Reader] The opened reader (or block result if block given)
@@ -28,13 +35,21 @@ module SlateDb
28
35
  # @example Open at a specific checkpoint
29
36
  # reader = SlateDb::Reader.open("/tmp/mydb", checkpoint_id: "uuid-here")
30
37
  #
38
+ # @example Enable the on-disk cache and cap its open file handles
39
+ # reader = SlateDb::Reader.open("/tmp/mydb",
40
+ # cache_root: "/var/cache/slatedb",
41
+ # max_open_file_handles: 256)
42
+ #
31
43
  def open(path, url: nil, checkpoint_id: nil,
32
44
  manifest_poll_interval: nil, checkpoint_lifetime: nil,
33
- max_memtable_bytes: nil, merge_operator: nil)
45
+ max_memtable_bytes: nil, cache_root: nil, max_open_file_handles: nil,
46
+ merge_operator: nil)
34
47
  opts = {}
35
48
  opts[:manifest_poll_interval] = manifest_poll_interval if manifest_poll_interval
36
49
  opts[:checkpoint_lifetime] = checkpoint_lifetime if checkpoint_lifetime
37
50
  opts[:max_memtable_bytes] = max_memtable_bytes if max_memtable_bytes
51
+ opts[:cache_root] = cache_root if cache_root
52
+ opts[:max_open_file_handles] = max_open_file_handles if max_open_file_handles
38
53
  opts[:merge_operator] = merge_operator.to_s if merge_operator
39
54
 
40
55
  reader = _open(path, url, checkpoint_id, opts)