slatedb 0.2.0 → 0.4.4
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 +140 -8
- data/ext/slatedb/Cargo.toml +8 -7
- data/ext/slatedb/src/admin.rs +22 -3
- data/ext/slatedb/src/database.rs +182 -41
- 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 +85 -25
- data/ext/slatedb/src/snapshot.rs +34 -3
- data/ext/slatedb/src/transaction.rs +50 -9
- data/ext/slatedb/src/utils.rs +46 -4
- data/ext/slatedb/src/write_batch.rs +6 -1
- data/lib/slatedb/database.rb +116 -24
- data/lib/slatedb/metrics.rb +20 -0
- data/lib/slatedb/reader.rb +45 -10
- data/lib/slatedb/snapshot.rb +15 -7
- data/lib/slatedb/transaction.rb +44 -7
- data/lib/slatedb/version.rb +1 -1
- data/lib/slatedb.rb +1 -0
- metadata +15 -13
data/ext/slatedb/src/database.rs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
use std::
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::sync::{Arc, Mutex};
|
|
2
3
|
|
|
3
4
|
use magnus::prelude::*;
|
|
4
5
|
use magnus::{function, method, Error, RHash, Ruby};
|
|
@@ -6,15 +7,16 @@ use slatedb::config::{
|
|
|
6
7
|
DurabilityLevel, MergeOptions, PutOptions, ReadOptions, ScanOptions, Ttl, WriteOptions,
|
|
7
8
|
};
|
|
8
9
|
use slatedb::object_store::memory::InMemory;
|
|
9
|
-
use slatedb::{Db, IsolationLevel};
|
|
10
|
+
use slatedb::{Db, IsolationLevel, IterationOrder, KeyValue};
|
|
10
11
|
|
|
11
12
|
use crate::errors::invalid_argument_error;
|
|
12
13
|
use crate::iterator::Iterator;
|
|
13
14
|
use crate::merge_ops::{parse_merge_operator, parse_merge_operator_proc};
|
|
15
|
+
use crate::metrics::Metrics;
|
|
14
16
|
use crate::runtime::block_on_result;
|
|
15
17
|
use crate::snapshot::Snapshot;
|
|
16
18
|
use crate::transaction::Transaction;
|
|
17
|
-
use crate::utils::{get_optional, resolve_object_store};
|
|
19
|
+
use crate::utils::{get_optional, prefix_subrange_from_kwargs, resolve_object_store};
|
|
18
20
|
use crate::write_batch::WriteBatch;
|
|
19
21
|
|
|
20
22
|
/// Ruby wrapper for SlateDB database.
|
|
@@ -23,9 +25,69 @@ use crate::write_batch::WriteBatch;
|
|
|
23
25
|
#[magnus::wrap(class = "SlateDb::Database", free_immediately, size)]
|
|
24
26
|
pub struct Database {
|
|
25
27
|
inner: Arc<Db>,
|
|
28
|
+
metrics: Arc<Mutex<HashMap<String, i64>>>,
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
impl Database {
|
|
32
|
+
fn increment_metric(&self, name: &str) {
|
|
33
|
+
let mut metrics = self.metrics.lock().expect("metrics mutex poisoned");
|
|
34
|
+
*metrics.entry(name.to_string()).or_insert(0) += 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn key_value_to_hash(kv: KeyValue) -> Result<RHash, Error> {
|
|
38
|
+
let ruby = Ruby::get().expect("Ruby runtime not available");
|
|
39
|
+
let hash = ruby.hash_new();
|
|
40
|
+
hash.aset(
|
|
41
|
+
ruby.to_symbol("key"),
|
|
42
|
+
String::from_utf8_lossy(&kv.key).to_string(),
|
|
43
|
+
)?;
|
|
44
|
+
hash.aset(
|
|
45
|
+
ruby.to_symbol("value"),
|
|
46
|
+
String::from_utf8_lossy(&kv.value).to_string(),
|
|
47
|
+
)?;
|
|
48
|
+
hash.aset(ruby.to_symbol("seq"), kv.seq)?;
|
|
49
|
+
hash.aset(ruby.to_symbol("create_ts"), kv.create_ts)?;
|
|
50
|
+
hash.aset(ruby.to_symbol("expire_ts"), kv.expire_ts)?;
|
|
51
|
+
Ok(hash)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn read_options_from_kwargs(kwargs: &RHash) -> Result<ReadOptions, Error> {
|
|
55
|
+
let mut opts = ReadOptions::default();
|
|
56
|
+
|
|
57
|
+
if let Some(df) = get_optional::<String>(kwargs, "durability_filter")? {
|
|
58
|
+
opts.durability_filter = match df.as_str() {
|
|
59
|
+
"remote" => DurabilityLevel::Remote,
|
|
60
|
+
"memory" => DurabilityLevel::Memory,
|
|
61
|
+
other => {
|
|
62
|
+
return Err(invalid_argument_error(&format!(
|
|
63
|
+
"invalid durability_filter: {} (expected 'remote' or 'memory')",
|
|
64
|
+
other
|
|
65
|
+
)))
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if let Some(dirty) = get_optional::<bool>(kwargs, "dirty")? {
|
|
71
|
+
opts.dirty = dirty;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if let Some(cb) = get_optional::<bool>(kwargs, "cache_blocks")? {
|
|
75
|
+
opts.cache_blocks = cb;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Ok(opts)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn write_options_from_kwargs(kwargs: &RHash) -> Result<WriteOptions, Error> {
|
|
82
|
+
let await_durable = get_optional::<bool>(kwargs, "await_durable")?.unwrap_or(true);
|
|
83
|
+
let seqnum = get_optional::<u64>(kwargs, "seqnum")?.unwrap_or(0);
|
|
84
|
+
|
|
85
|
+
Ok(WriteOptions {
|
|
86
|
+
await_durable,
|
|
87
|
+
seqnum,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
29
91
|
/// Open a database at the given path.
|
|
30
92
|
///
|
|
31
93
|
/// # Arguments
|
|
@@ -37,8 +99,7 @@ impl Database {
|
|
|
37
99
|
/// A new Database instance
|
|
38
100
|
pub fn open(path: String, url: Option<String>, kwargs: RHash) -> Result<Self, Error> {
|
|
39
101
|
// Try string-based merge operator first, then proc-based
|
|
40
|
-
let merge_operator = parse_merge_operator(&kwargs)?
|
|
41
|
-
.or(parse_merge_operator_proc(&kwargs)?);
|
|
102
|
+
let merge_operator = parse_merge_operator(&kwargs)?.or(parse_merge_operator_proc(&kwargs)?);
|
|
42
103
|
|
|
43
104
|
let db = block_on_result(async {
|
|
44
105
|
let object_store: Arc<dyn slatedb::object_store::ObjectStore> =
|
|
@@ -58,6 +119,7 @@ impl Database {
|
|
|
58
119
|
|
|
59
120
|
Ok(Self {
|
|
60
121
|
inner: Arc::new(db),
|
|
122
|
+
metrics: Arc::new(Mutex::new(HashMap::new())),
|
|
61
123
|
})
|
|
62
124
|
}
|
|
63
125
|
|
|
@@ -77,6 +139,7 @@ impl Database {
|
|
|
77
139
|
|
|
78
140
|
let result =
|
|
79
141
|
block_on_result(async { self.inner.get_with_options(key.as_bytes(), &opts).await })?;
|
|
142
|
+
self.increment_metric("db.get.count");
|
|
80
143
|
|
|
81
144
|
Ok(result.map(|b| String::from_utf8_lossy(&b).to_string()))
|
|
82
145
|
}
|
|
@@ -94,31 +157,64 @@ impl Database {
|
|
|
94
157
|
return Err(invalid_argument_error("key cannot be empty"));
|
|
95
158
|
}
|
|
96
159
|
|
|
97
|
-
let
|
|
160
|
+
let opts = Self::read_options_from_kwargs(&kwargs)?;
|
|
98
161
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
let result =
|
|
163
|
+
block_on_result(async { self.inner.get_with_options(key.as_bytes(), &opts).await })?;
|
|
164
|
+
self.increment_metric("db.get_with_options.count");
|
|
165
|
+
|
|
166
|
+
Ok(result.map(|b| String::from_utf8_lossy(&b).to_string()))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// Get a key-value pair with metadata by key.
|
|
170
|
+
///
|
|
171
|
+
/// # Arguments
|
|
172
|
+
/// * `key` - The key to look up
|
|
173
|
+
///
|
|
174
|
+
/// # Returns
|
|
175
|
+
/// A Hash with key, value, seq, create_ts, and expire_ts, or nil if not found
|
|
176
|
+
pub fn get_key_value(&self, key: String) -> Result<Option<RHash>, Error> {
|
|
177
|
+
if key.is_empty() {
|
|
178
|
+
return Err(invalid_argument_error("key cannot be empty"));
|
|
111
179
|
}
|
|
112
180
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
181
|
+
let opts = ReadOptions::default();
|
|
182
|
+
let result = block_on_result(async {
|
|
183
|
+
self.inner
|
|
184
|
+
.get_key_value_with_options(key.as_bytes(), &opts)
|
|
185
|
+
.await
|
|
186
|
+
})?;
|
|
187
|
+
self.increment_metric("db.get_key_value.count");
|
|
188
|
+
|
|
189
|
+
result.map(Self::key_value_to_hash).transpose()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Get a key-value pair with metadata by key with options.
|
|
193
|
+
///
|
|
194
|
+
/// # Arguments
|
|
195
|
+
/// * `key` - The key to look up
|
|
196
|
+
/// * `kwargs` - Keyword arguments (durability_filter, dirty, cache_blocks)
|
|
197
|
+
///
|
|
198
|
+
/// # Returns
|
|
199
|
+
/// A Hash with key, value, seq, create_ts, and expire_ts, or nil if not found
|
|
200
|
+
pub fn get_key_value_with_options(
|
|
201
|
+
&self,
|
|
202
|
+
key: String,
|
|
203
|
+
kwargs: RHash,
|
|
204
|
+
) -> Result<Option<RHash>, Error> {
|
|
205
|
+
if key.is_empty() {
|
|
206
|
+
return Err(invalid_argument_error("key cannot be empty"));
|
|
116
207
|
}
|
|
117
208
|
|
|
118
|
-
let
|
|
119
|
-
|
|
209
|
+
let opts = Self::read_options_from_kwargs(&kwargs)?;
|
|
210
|
+
let result = block_on_result(async {
|
|
211
|
+
self.inner
|
|
212
|
+
.get_key_value_with_options(key.as_bytes(), &opts)
|
|
213
|
+
.await
|
|
214
|
+
})?;
|
|
215
|
+
self.increment_metric("db.get_key_value_with_options.count");
|
|
120
216
|
|
|
121
|
-
|
|
217
|
+
result.map(Self::key_value_to_hash).transpose()
|
|
122
218
|
}
|
|
123
219
|
|
|
124
220
|
/// Get a value by key as raw bytes.
|
|
@@ -137,6 +233,7 @@ impl Database {
|
|
|
137
233
|
|
|
138
234
|
let result =
|
|
139
235
|
block_on_result(async { self.inner.get_with_options(key.as_bytes(), &opts).await })?;
|
|
236
|
+
self.increment_metric("db.get_bytes.count");
|
|
140
237
|
|
|
141
238
|
Ok(result.map(|b| b.to_vec()))
|
|
142
239
|
}
|
|
@@ -155,6 +252,7 @@ impl Database {
|
|
|
155
252
|
|
|
156
253
|
let write_opts = WriteOptions {
|
|
157
254
|
await_durable: true,
|
|
255
|
+
seqnum: 0,
|
|
158
256
|
};
|
|
159
257
|
|
|
160
258
|
block_on_result(async {
|
|
@@ -162,6 +260,7 @@ impl Database {
|
|
|
162
260
|
.put_with_options(key.as_bytes(), value.as_bytes(), &put_opts, &write_opts)
|
|
163
261
|
.await
|
|
164
262
|
})?;
|
|
263
|
+
self.increment_metric("db.put.count");
|
|
165
264
|
|
|
166
265
|
Ok(())
|
|
167
266
|
}
|
|
@@ -171,7 +270,7 @@ impl Database {
|
|
|
171
270
|
/// # Arguments
|
|
172
271
|
/// * `key` - The key to store
|
|
173
272
|
/// * `value` - The value to store
|
|
174
|
-
/// * `kwargs` - Keyword arguments (ttl, await_durable)
|
|
273
|
+
/// * `kwargs` - Keyword arguments (ttl, await_durable, seqnum)
|
|
175
274
|
pub fn put_with_options(&self, key: String, value: String, kwargs: RHash) -> Result<(), Error> {
|
|
176
275
|
if key.is_empty() {
|
|
177
276
|
return Err(invalid_argument_error("key cannot be empty"));
|
|
@@ -187,14 +286,14 @@ impl Database {
|
|
|
187
286
|
};
|
|
188
287
|
|
|
189
288
|
// Parse await_durable
|
|
190
|
-
let
|
|
191
|
-
let write_opts = WriteOptions { await_durable };
|
|
289
|
+
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
192
290
|
|
|
193
291
|
block_on_result(async {
|
|
194
292
|
self.inner
|
|
195
293
|
.put_with_options(key.as_bytes(), value.as_bytes(), &put_opts, &write_opts)
|
|
196
294
|
.await
|
|
197
295
|
})?;
|
|
296
|
+
self.increment_metric("db.put_with_options.count");
|
|
198
297
|
|
|
199
298
|
Ok(())
|
|
200
299
|
}
|
|
@@ -210,6 +309,7 @@ impl Database {
|
|
|
210
309
|
|
|
211
310
|
let write_opts = WriteOptions {
|
|
212
311
|
await_durable: true,
|
|
312
|
+
seqnum: 0,
|
|
213
313
|
};
|
|
214
314
|
|
|
215
315
|
block_on_result(async {
|
|
@@ -217,6 +317,7 @@ impl Database {
|
|
|
217
317
|
.delete_with_options(key.as_bytes(), &write_opts)
|
|
218
318
|
.await
|
|
219
319
|
})?;
|
|
320
|
+
self.increment_metric("db.delete.count");
|
|
220
321
|
|
|
221
322
|
Ok(())
|
|
222
323
|
}
|
|
@@ -225,20 +326,20 @@ impl Database {
|
|
|
225
326
|
///
|
|
226
327
|
/// # Arguments
|
|
227
328
|
/// * `key` - The key to delete
|
|
228
|
-
/// * `kwargs` - Keyword arguments (await_durable)
|
|
329
|
+
/// * `kwargs` - Keyword arguments (await_durable, seqnum)
|
|
229
330
|
pub fn delete_with_options(&self, key: String, kwargs: RHash) -> Result<(), Error> {
|
|
230
331
|
if key.is_empty() {
|
|
231
332
|
return Err(invalid_argument_error("key cannot be empty"));
|
|
232
333
|
}
|
|
233
334
|
|
|
234
|
-
let
|
|
235
|
-
let write_opts = WriteOptions { await_durable };
|
|
335
|
+
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
236
336
|
|
|
237
337
|
block_on_result(async {
|
|
238
338
|
self.inner
|
|
239
339
|
.delete_with_options(key.as_bytes(), &write_opts)
|
|
240
340
|
.await
|
|
241
341
|
})?;
|
|
342
|
+
self.increment_metric("db.delete_with_options.count");
|
|
242
343
|
|
|
243
344
|
Ok(())
|
|
244
345
|
}
|
|
@@ -325,6 +426,18 @@ impl Database {
|
|
|
325
426
|
if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
|
|
326
427
|
opts.max_fetch_tasks = mft;
|
|
327
428
|
}
|
|
429
|
+
if let Some(order) = get_optional::<String>(&kwargs, "order")? {
|
|
430
|
+
opts.order = match order.as_str() {
|
|
431
|
+
"ascending" | "asc" => IterationOrder::Ascending,
|
|
432
|
+
"descending" | "desc" => IterationOrder::Descending,
|
|
433
|
+
other => {
|
|
434
|
+
return Err(invalid_argument_error(&format!(
|
|
435
|
+
"invalid order: {} (expected 'asc' or 'desc')",
|
|
436
|
+
other
|
|
437
|
+
)))
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
328
441
|
|
|
329
442
|
let start_bytes = start.into_bytes();
|
|
330
443
|
let end_bytes = end_key.map(|e| e.into_bytes());
|
|
@@ -354,7 +467,7 @@ impl Database {
|
|
|
354
467
|
let opts = ScanOptions::default();
|
|
355
468
|
let iter = block_on_result(async {
|
|
356
469
|
self.inner
|
|
357
|
-
.scan_prefix_with_options(prefix.as_bytes(), &opts)
|
|
470
|
+
.scan_prefix_with_options(prefix.as_bytes(), .., &opts)
|
|
358
471
|
.await
|
|
359
472
|
})?;
|
|
360
473
|
|
|
@@ -408,10 +521,23 @@ impl Database {
|
|
|
408
521
|
if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
|
|
409
522
|
opts.max_fetch_tasks = mft;
|
|
410
523
|
}
|
|
524
|
+
if let Some(order) = get_optional::<String>(&kwargs, "order")? {
|
|
525
|
+
opts.order = match order.as_str() {
|
|
526
|
+
"ascending" | "asc" => IterationOrder::Ascending,
|
|
527
|
+
"descending" | "desc" => IterationOrder::Descending,
|
|
528
|
+
other => {
|
|
529
|
+
return Err(invalid_argument_error(&format!(
|
|
530
|
+
"invalid order: {} (expected 'asc' or 'desc')",
|
|
531
|
+
other
|
|
532
|
+
)))
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
411
536
|
|
|
537
|
+
let subrange = prefix_subrange_from_kwargs(&kwargs)?;
|
|
412
538
|
let iter = block_on_result(async {
|
|
413
539
|
self.inner
|
|
414
|
-
.scan_prefix_with_options(prefix.as_bytes(), &opts)
|
|
540
|
+
.scan_prefix_with_options(prefix.as_bytes(), subrange, &opts)
|
|
415
541
|
.await
|
|
416
542
|
})?;
|
|
417
543
|
|
|
@@ -432,10 +558,9 @@ impl Database {
|
|
|
432
558
|
///
|
|
433
559
|
/// # Arguments
|
|
434
560
|
/// * `batch` - The WriteBatch to write
|
|
435
|
-
/// * `kwargs` - Keyword arguments (await_durable)
|
|
561
|
+
/// * `kwargs` - Keyword arguments (await_durable, seqnum)
|
|
436
562
|
pub fn write_with_options(&self, batch: &WriteBatch, kwargs: RHash) -> Result<(), Error> {
|
|
437
|
-
let
|
|
438
|
-
let write_opts = WriteOptions { await_durable };
|
|
563
|
+
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
439
564
|
|
|
440
565
|
let batch_inner = batch.take()?;
|
|
441
566
|
|
|
@@ -462,6 +587,7 @@ impl Database {
|
|
|
462
587
|
|
|
463
588
|
let write_opts = WriteOptions {
|
|
464
589
|
await_durable: true,
|
|
590
|
+
seqnum: 0,
|
|
465
591
|
};
|
|
466
592
|
|
|
467
593
|
block_on_result(async {
|
|
@@ -478,8 +604,13 @@ impl Database {
|
|
|
478
604
|
/// # Arguments
|
|
479
605
|
/// * `key` - The key to merge into
|
|
480
606
|
/// * `value` - The merge operand to apply
|
|
481
|
-
/// * `kwargs` - Keyword arguments (ttl, await_durable)
|
|
482
|
-
pub fn merge_with_options(
|
|
607
|
+
/// * `kwargs` - Keyword arguments (ttl, await_durable, seqnum)
|
|
608
|
+
pub fn merge_with_options(
|
|
609
|
+
&self,
|
|
610
|
+
key: String,
|
|
611
|
+
value: String,
|
|
612
|
+
kwargs: RHash,
|
|
613
|
+
) -> Result<(), Error> {
|
|
483
614
|
if key.is_empty() {
|
|
484
615
|
return Err(invalid_argument_error("key cannot be empty"));
|
|
485
616
|
}
|
|
@@ -492,8 +623,7 @@ impl Database {
|
|
|
492
623
|
},
|
|
493
624
|
};
|
|
494
625
|
|
|
495
|
-
let
|
|
496
|
-
let write_opts = WriteOptions { await_durable };
|
|
626
|
+
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
497
627
|
|
|
498
628
|
block_on_result(async {
|
|
499
629
|
self.inner
|
|
@@ -548,8 +678,8 @@ impl Database {
|
|
|
548
678
|
pub fn create_checkpoint(&self, kwargs: RHash) -> Result<RHash, Error> {
|
|
549
679
|
use slatedb::config::{CheckpointOptions, CheckpointScope};
|
|
550
680
|
|
|
551
|
-
let lifetime =
|
|
552
|
-
|
|
681
|
+
let lifetime =
|
|
682
|
+
get_optional::<u64>(&kwargs, "lifetime")?.map(std::time::Duration::from_millis);
|
|
553
683
|
let name = get_optional::<String>(&kwargs, "name")?;
|
|
554
684
|
|
|
555
685
|
let options = CheckpointOptions {
|
|
@@ -578,6 +708,11 @@ impl Database {
|
|
|
578
708
|
Ok(())
|
|
579
709
|
}
|
|
580
710
|
|
|
711
|
+
/// Return the database metrics registry.
|
|
712
|
+
pub fn metrics(&self) -> Result<Metrics, Error> {
|
|
713
|
+
Ok(Metrics::new(self.metrics.clone()))
|
|
714
|
+
}
|
|
715
|
+
|
|
581
716
|
/// Close the database.
|
|
582
717
|
pub fn close(&self) -> Result<(), Error> {
|
|
583
718
|
block_on_result(async { self.inner.close().await })?;
|
|
@@ -595,6 +730,11 @@ pub fn define_database_class(ruby: &Ruby, module: &magnus::RModule) -> Result<()
|
|
|
595
730
|
// Instance methods - simple versions
|
|
596
731
|
class.define_method("_get", method!(Database::get, 1))?;
|
|
597
732
|
class.define_method("_get_with_options", method!(Database::get_with_options, 2))?;
|
|
733
|
+
class.define_method("_get_key_value", method!(Database::get_key_value, 1))?;
|
|
734
|
+
class.define_method(
|
|
735
|
+
"_get_key_value_with_options",
|
|
736
|
+
method!(Database::get_key_value_with_options, 2),
|
|
737
|
+
)?;
|
|
598
738
|
class.define_method("get_bytes", method!(Database::get_bytes, 1))?;
|
|
599
739
|
class.define_method("_put", method!(Database::put, 2))?;
|
|
600
740
|
class.define_method("_put_with_options", method!(Database::put_with_options, 3))?;
|
|
@@ -633,6 +773,7 @@ pub fn define_database_class(ruby: &Ruby, module: &magnus::RModule) -> Result<()
|
|
|
633
773
|
method!(Database::create_checkpoint, 1),
|
|
634
774
|
)?;
|
|
635
775
|
class.define_method("flush", method!(Database::flush, 0))?;
|
|
776
|
+
class.define_method("_metrics", method!(Database::metrics, 0))?;
|
|
636
777
|
class.define_method("close", method!(Database::close, 0))?;
|
|
637
778
|
|
|
638
779
|
Ok(())
|
data/ext/slatedb/src/iterator.rs
CHANGED
|
@@ -98,7 +98,10 @@ impl Iterator {
|
|
|
98
98
|
let result = block_on(async {
|
|
99
99
|
let mut guard = inner.lock().await;
|
|
100
100
|
match guard.as_mut() {
|
|
101
|
-
Some(iter) => iter
|
|
101
|
+
Some(iter) => iter
|
|
102
|
+
.seek(key.as_bytes())
|
|
103
|
+
.await
|
|
104
|
+
.map_err(IteratorError::Slate),
|
|
102
105
|
None => Err(IteratorError::Closed),
|
|
103
106
|
}
|
|
104
107
|
});
|
data/ext/slatedb/src/lib.rs
CHANGED
|
@@ -21,6 +21,7 @@ mod database;
|
|
|
21
21
|
mod errors;
|
|
22
22
|
mod iterator;
|
|
23
23
|
mod merge_ops;
|
|
24
|
+
mod metrics;
|
|
24
25
|
mod reader;
|
|
25
26
|
mod runtime;
|
|
26
27
|
mod snapshot;
|
|
@@ -46,6 +47,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
46
47
|
snapshot::define_snapshot_class(ruby, &module)?;
|
|
47
48
|
reader::define_reader_class(ruby, &module)?;
|
|
48
49
|
admin::define_admin_class(ruby, &module)?;
|
|
50
|
+
metrics::define_metrics_class(ruby, &module)?;
|
|
49
51
|
|
|
50
52
|
Ok(())
|
|
51
53
|
}
|
|
@@ -149,9 +149,7 @@ impl RubyProcMergeOperator {
|
|
|
149
149
|
"Ruby merge operator called from non-Ruby thread, using fallback concatenation. \
|
|
150
150
|
This can happen during background compaction."
|
|
151
151
|
);
|
|
152
|
-
let mut result = existing_value
|
|
153
|
-
.map(|v| v.to_vec())
|
|
154
|
-
.unwrap_or_default();
|
|
152
|
+
let mut result = existing_value.map(|v| v.to_vec()).unwrap_or_default();
|
|
155
153
|
result.extend_from_slice(new_value);
|
|
156
154
|
Ok(Bytes::from(result))
|
|
157
155
|
}
|
|
@@ -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
|
+
}
|