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.
- checksums.yaml +4 -4
- data/README.md +75 -1
- data/ext/slatedb/Cargo.toml +8 -7
- data/ext/slatedb/src/admin.rs +17 -3
- data/ext/slatedb/src/database.rs +178 -38
- data/ext/slatedb/src/iterator.rs +4 -1
- data/ext/slatedb/src/lib.rs +2 -0
- data/ext/slatedb/src/merge_ops.rs +1 -3
- data/ext/slatedb/src/metrics.rs +47 -0
- data/ext/slatedb/src/reader.rs +51 -11
- data/ext/slatedb/src/snapshot.rs +29 -0
- data/ext/slatedb/src/transaction.rs +45 -7
- data/ext/slatedb/src/write_batch.rs +6 -1
- data/lib/slatedb/database.rb +104 -24
- data/lib/slatedb/metrics.rb +20 -0
- data/lib/slatedb/reader.rb +16 -1
- data/lib/slatedb/transaction.rb +29 -0
- data/lib/slatedb/version.rb +1 -1
- data/lib/slatedb.rb +1 -0
- metadata +15 -13
|
@@ -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
|
+
}
|
data/ext/slatedb/src/reader.rs
CHANGED
|
@@ -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,
|
|
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
|
|
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> =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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(
|
|
73
|
-
options.
|
|
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
|
data/ext/slatedb/src/snapshot.rs
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
|
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("
|
|
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(
|
|
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
|
}
|
data/lib/slatedb/database.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
data/lib/slatedb/reader.rb
CHANGED
|
@@ -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,
|
|
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)
|