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/reader.rs
CHANGED
|
@@ -4,12 +4,13 @@ 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::DbReaderMode;
|
|
8
|
+
use slatedb::IterationOrder;
|
|
7
9
|
|
|
8
10
|
use crate::errors::invalid_argument_error;
|
|
9
11
|
use crate::iterator::Iterator;
|
|
10
|
-
use crate::merge_ops::parse_merge_operator;
|
|
11
12
|
use crate::runtime::block_on_result;
|
|
12
|
-
use crate::utils::{get_optional, resolve_object_store};
|
|
13
|
+
use crate::utils::{get_optional, prefix_subrange_from_kwargs, resolve_object_store};
|
|
13
14
|
|
|
14
15
|
/// Ruby wrapper for SlateDB Reader.
|
|
15
16
|
///
|
|
@@ -26,8 +27,20 @@ impl Reader {
|
|
|
26
27
|
/// # Arguments
|
|
27
28
|
/// * `path` - The path identifier for the database
|
|
28
29
|
/// * `url` - Optional object store URL
|
|
29
|
-
/// * `checkpoint_id` - Optional checkpoint UUID to read at
|
|
30
|
-
///
|
|
30
|
+
/// * `checkpoint_id` - Optional checkpoint UUID to read at. When set, the reader
|
|
31
|
+
/// is pinned to that checkpoint ([`DbReaderMode::Checkpoint`]).
|
|
32
|
+
/// * `kwargs` - Additional options (manifest_poll_interval, checkpoint_lifetime,
|
|
33
|
+
/// max_memtable_bytes, skip_wal_replay, cache_root, max_open_file_handles,
|
|
34
|
+
/// follow_latest).
|
|
35
|
+
/// The local disk cache (and therefore `max_open_file_handles`) is only active
|
|
36
|
+
/// when `cache_root` is set.
|
|
37
|
+
///
|
|
38
|
+
/// When neither `checkpoint_id` nor `follow_latest` is given the reader defaults
|
|
39
|
+
/// to [`DbReaderMode::ManagedCheckpoint`], creating and periodically refreshing
|
|
40
|
+
/// its own checkpoint so garbage collection cannot delete objects out from under
|
|
41
|
+
/// it. Setting `follow_latest: true` selects [`DbReaderMode::FollowLatest`], which
|
|
42
|
+
/// tails the latest manifest without writing any checkpoint — useful for read-only
|
|
43
|
+
/// or mirrored databases, at the cost of no protection from garbage collection.
|
|
31
44
|
pub fn open(
|
|
32
45
|
path: String,
|
|
33
46
|
url: Option<String>,
|
|
@@ -40,24 +53,35 @@ impl Reader {
|
|
|
40
53
|
let checkpoint_lifetime = get_optional::<u64>(&kwargs, "checkpoint_lifetime")?
|
|
41
54
|
.map(std::time::Duration::from_millis);
|
|
42
55
|
let max_memtable_bytes = get_optional::<u64>(&kwargs, "max_memtable_bytes")?;
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
let
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
let skip_wal_replay = get_optional::<bool>(&kwargs, "skip_wal_replay")?;
|
|
57
|
+
let max_open_file_handles = get_optional::<usize>(&kwargs, "max_open_file_handles")?;
|
|
58
|
+
let cache_root = get_optional::<String>(&kwargs, "cache_root")?;
|
|
59
|
+
let follow_latest = get_optional::<bool>(&kwargs, "follow_latest")?.unwrap_or(false);
|
|
60
|
+
|
|
61
|
+
// Resolve the reader mode from checkpoint_id / follow_latest.
|
|
62
|
+
let mode = match checkpoint_id {
|
|
63
|
+
Some(id_str) => {
|
|
64
|
+
if follow_latest {
|
|
65
|
+
return Err(invalid_argument_error(
|
|
66
|
+
"checkpoint_id and follow_latest are mutually exclusive",
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
let uuid = uuid::Uuid::parse_str(&id_str).map_err(|e| {
|
|
49
70
|
invalid_argument_error(&format!("invalid checkpoint_id: {}", e))
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
})?;
|
|
72
|
+
DbReaderMode::Checkpoint(uuid)
|
|
73
|
+
}
|
|
74
|
+
None if follow_latest => DbReaderMode::FollowLatest,
|
|
75
|
+
None => DbReaderMode::ManagedCheckpoint,
|
|
76
|
+
};
|
|
54
77
|
|
|
55
78
|
let reader = block_on_result(async {
|
|
56
|
-
let object_store: Arc<dyn slatedb::object_store::ObjectStore> =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
let object_store: Arc<dyn slatedb::object_store::ObjectStore> =
|
|
80
|
+
if let Some(ref url) = url {
|
|
81
|
+
resolve_object_store(url)?
|
|
82
|
+
} else {
|
|
83
|
+
Arc::new(slatedb::object_store::memory::InMemory::new())
|
|
84
|
+
};
|
|
61
85
|
|
|
62
86
|
let mut options = DbReaderOptions::default();
|
|
63
87
|
if let Some(interval) = manifest_poll_interval {
|
|
@@ -69,11 +93,17 @@ impl Reader {
|
|
|
69
93
|
if let Some(max_bytes) = max_memtable_bytes {
|
|
70
94
|
options.max_memtable_bytes = max_bytes;
|
|
71
95
|
}
|
|
72
|
-
if let Some(
|
|
73
|
-
options.
|
|
96
|
+
if let Some(skip_replay) = skip_wal_replay {
|
|
97
|
+
options.skip_wal_replay = skip_replay;
|
|
74
98
|
}
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
if let Some(ref root) = cache_root {
|
|
100
|
+
options.object_store_cache_options.root_folder =
|
|
101
|
+
Some(std::path::PathBuf::from(root));
|
|
102
|
+
}
|
|
103
|
+
if let Some(max_handles) = max_open_file_handles {
|
|
104
|
+
options.object_store_cache_options.max_open_file_handles = max_handles;
|
|
105
|
+
}
|
|
106
|
+
DbReader::open(path, object_store, mode, options).await
|
|
77
107
|
})?;
|
|
78
108
|
|
|
79
109
|
Ok(Self {
|
|
@@ -116,6 +146,10 @@ impl Reader {
|
|
|
116
146
|
opts.dirty = dirty;
|
|
117
147
|
}
|
|
118
148
|
|
|
149
|
+
if let Some(cb) = get_optional::<bool>(&kwargs, "cache_blocks")? {
|
|
150
|
+
opts.cache_blocks = cb;
|
|
151
|
+
}
|
|
152
|
+
|
|
119
153
|
let result =
|
|
120
154
|
block_on_result(async { self.inner.get_with_options(key.as_bytes(), &opts).await })?;
|
|
121
155
|
Ok(result.map(|b| String::from_utf8_lossy(&b).to_string()))
|
|
@@ -191,6 +225,18 @@ impl Reader {
|
|
|
191
225
|
if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
|
|
192
226
|
opts.max_fetch_tasks = mft;
|
|
193
227
|
}
|
|
228
|
+
if let Some(order) = get_optional::<String>(&kwargs, "order")? {
|
|
229
|
+
opts.order = match order.as_str() {
|
|
230
|
+
"ascending" | "asc" => IterationOrder::Ascending,
|
|
231
|
+
"descending" | "desc" => IterationOrder::Descending,
|
|
232
|
+
other => {
|
|
233
|
+
return Err(invalid_argument_error(&format!(
|
|
234
|
+
"invalid order: {} (expected 'asc' or 'desc')",
|
|
235
|
+
other
|
|
236
|
+
)))
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
194
240
|
|
|
195
241
|
let start_bytes = start.into_bytes();
|
|
196
242
|
let end_bytes = end_key.map(|e| e.into_bytes());
|
|
@@ -211,7 +257,8 @@ impl Reader {
|
|
|
211
257
|
return Err(invalid_argument_error("prefix cannot be empty"));
|
|
212
258
|
}
|
|
213
259
|
|
|
214
|
-
let iter =
|
|
260
|
+
let iter =
|
|
261
|
+
block_on_result(async { self.inner.scan_prefix(prefix.as_bytes(), ..).await })?;
|
|
215
262
|
|
|
216
263
|
Ok(Iterator::new(iter))
|
|
217
264
|
}
|
|
@@ -256,10 +303,23 @@ impl Reader {
|
|
|
256
303
|
if let Some(mft) = get_optional::<usize>(&kwargs, "max_fetch_tasks")? {
|
|
257
304
|
opts.max_fetch_tasks = mft;
|
|
258
305
|
}
|
|
306
|
+
if let Some(order) = get_optional::<String>(&kwargs, "order")? {
|
|
307
|
+
opts.order = match order.as_str() {
|
|
308
|
+
"ascending" | "asc" => IterationOrder::Ascending,
|
|
309
|
+
"descending" | "desc" => IterationOrder::Descending,
|
|
310
|
+
other => {
|
|
311
|
+
return Err(invalid_argument_error(&format!(
|
|
312
|
+
"invalid order: {} (expected 'asc' or 'desc')",
|
|
313
|
+
other
|
|
314
|
+
)))
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
259
318
|
|
|
319
|
+
let subrange = prefix_subrange_from_kwargs(&kwargs)?;
|
|
260
320
|
let iter = block_on_result(async {
|
|
261
321
|
self.inner
|
|
262
|
-
.scan_prefix_with_options(prefix.as_bytes(), &opts)
|
|
322
|
+
.scan_prefix_with_options(prefix.as_bytes(), subrange, &opts)
|
|
263
323
|
.await
|
|
264
324
|
})?;
|
|
265
325
|
|
data/ext/slatedb/src/snapshot.rs
CHANGED
|
@@ -5,11 +5,12 @@ 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;
|
|
11
12
|
use crate::runtime::block_on_result;
|
|
12
|
-
use crate::utils::get_optional;
|
|
13
|
+
use crate::utils::{get_optional, prefix_subrange_from_kwargs};
|
|
13
14
|
|
|
14
15
|
/// Ruby wrapper for SlateDB Snapshot.
|
|
15
16
|
///
|
|
@@ -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
|
|
@@ -173,7 +190,7 @@ impl Snapshot {
|
|
|
173
190
|
.as_ref()
|
|
174
191
|
.ok_or_else(|| closed_error("snapshot is closed"))?;
|
|
175
192
|
|
|
176
|
-
let iter = block_on_result(async { snapshot.scan_prefix(prefix.as_bytes()).await })?;
|
|
193
|
+
let iter = block_on_result(async { snapshot.scan_prefix(prefix.as_bytes(), ..).await })?;
|
|
177
194
|
|
|
178
195
|
Ok(Iterator::new(iter))
|
|
179
196
|
}
|
|
@@ -218,6 +235,20 @@ 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
|
+
}
|
|
250
|
+
|
|
251
|
+
let subrange = prefix_subrange_from_kwargs(&kwargs)?;
|
|
221
252
|
|
|
222
253
|
let guard = self.inner.borrow();
|
|
223
254
|
let snapshot = guard
|
|
@@ -226,7 +257,7 @@ impl Snapshot {
|
|
|
226
257
|
|
|
227
258
|
let iter = block_on_result(async {
|
|
228
259
|
snapshot
|
|
229
|
-
.scan_prefix_with_options(prefix.as_bytes(), &opts)
|
|
260
|
+
.scan_prefix_with_options(prefix.as_bytes(), subrange, &opts)
|
|
230
261
|
.await
|
|
231
262
|
})?;
|
|
232
263
|
|
|
@@ -6,11 +6,12 @@ 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;
|
|
12
13
|
use crate::runtime::block_on_result;
|
|
13
|
-
use crate::utils::get_optional;
|
|
14
|
+
use crate::utils::{get_optional, prefix_subrange_from_kwargs};
|
|
14
15
|
|
|
15
16
|
/// Ruby wrapper for SlateDB Transaction.
|
|
16
17
|
///
|
|
@@ -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
|
|
@@ -274,7 +295,7 @@ impl Transaction {
|
|
|
274
295
|
.as_ref()
|
|
275
296
|
.ok_or_else(|| closed_error("transaction is closed"))?;
|
|
276
297
|
|
|
277
|
-
let iter = block_on_result(async { txn.scan_prefix(prefix.as_bytes()).await })?;
|
|
298
|
+
let iter = block_on_result(async { txn.scan_prefix(prefix.as_bytes(), ..).await })?;
|
|
278
299
|
|
|
279
300
|
Ok(Iterator::new(iter))
|
|
280
301
|
}
|
|
@@ -319,14 +340,30 @@ 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
|
+
}
|
|
355
|
+
|
|
356
|
+
let subrange = prefix_subrange_from_kwargs(&kwargs)?;
|
|
322
357
|
|
|
323
358
|
let guard = self.inner.borrow();
|
|
324
359
|
let txn = guard
|
|
325
360
|
.as_ref()
|
|
326
361
|
.ok_or_else(|| closed_error("transaction is closed"))?;
|
|
327
362
|
|
|
328
|
-
let iter =
|
|
329
|
-
|
|
363
|
+
let iter = block_on_result(async {
|
|
364
|
+
txn.scan_prefix_with_options(prefix.as_bytes(), subrange, &opts)
|
|
365
|
+
.await
|
|
366
|
+
})?;
|
|
330
367
|
|
|
331
368
|
Ok(Iterator::new(iter))
|
|
332
369
|
}
|
|
@@ -365,7 +402,11 @@ impl Transaction {
|
|
|
365
402
|
/// Commit the transaction with options.
|
|
366
403
|
pub fn commit_with_options(&self, kwargs: RHash) -> Result<(), Error> {
|
|
367
404
|
let await_durable = get_optional::<bool>(&kwargs, "await_durable")?.unwrap_or(true);
|
|
368
|
-
let
|
|
405
|
+
let seqnum = get_optional::<u64>(&kwargs, "seqnum")?.unwrap_or(0);
|
|
406
|
+
let write_opts = WriteOptions {
|
|
407
|
+
await_durable,
|
|
408
|
+
seqnum,
|
|
409
|
+
};
|
|
369
410
|
|
|
370
411
|
let txn = self
|
|
371
412
|
.inner
|
|
@@ -422,7 +463,7 @@ pub fn define_transaction_class(ruby: &Ruby, module: &magnus::RModule) -> Result
|
|
|
422
463
|
method!(Transaction::scan_prefix_with_options, 2),
|
|
423
464
|
)?;
|
|
424
465
|
class.define_method("_mark_read", method!(Transaction::mark_read, 1))?;
|
|
425
|
-
class.define_method("
|
|
466
|
+
class.define_method("_commit", method!(Transaction::commit, 0))?;
|
|
426
467
|
class.define_method(
|
|
427
468
|
"_commit_with_options",
|
|
428
469
|
method!(Transaction::commit_with_options, 1),
|
data/ext/slatedb/src/utils.rs
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
use std::ops::Bound;
|
|
1
2
|
use std::sync::Arc;
|
|
2
3
|
|
|
3
4
|
use magnus::value::ReprValue;
|
|
4
5
|
use magnus::{Error, RHash, Ruby, TryConvert};
|
|
5
6
|
use slatedb::object_store::aws::AmazonS3Builder;
|
|
6
|
-
use slatedb::object_store::
|
|
7
|
-
use slatedb::{
|
|
7
|
+
use slatedb::object_store::prefix::PrefixStore;
|
|
8
|
+
use slatedb::object_store::{
|
|
9
|
+
parse_url_opts, Error as ObjectStoreError, ObjectStore, ObjectStoreScheme,
|
|
10
|
+
};
|
|
11
|
+
use slatedb::Error as SlateError;
|
|
8
12
|
use url::Url;
|
|
9
13
|
|
|
10
14
|
/// Helper to extract an optional value from an RHash
|
|
@@ -23,6 +27,30 @@ pub fn get_optional<T: TryConvert>(hash: &RHash, key: &str) -> Result<Option<T>,
|
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
/// A key sub-range for a prefix scan, expressed as owned byte bounds. This
|
|
31
|
+
/// implements `slatedb::bytes_range::ByteRangeBounds`, so it can be passed
|
|
32
|
+
/// straight to `scan_prefix`/`scan_prefix_with_options`.
|
|
33
|
+
pub type PrefixSubrange = (Bound<Vec<u8>>, Bound<Vec<u8>>);
|
|
34
|
+
|
|
35
|
+
/// Build a suffix sub-range (relative to a scan prefix) from optional
|
|
36
|
+
/// `subrange_from` / `subrange_to` keyword arguments.
|
|
37
|
+
///
|
|
38
|
+
/// The bounds are key *suffixes* appended to the prefix: `subrange_from` is an
|
|
39
|
+
/// inclusive lower bound and `subrange_to` is an exclusive upper bound. A
|
|
40
|
+
/// missing bound is unbounded, so an empty hash yields a full-prefix scan
|
|
41
|
+
/// (equivalent to `..`).
|
|
42
|
+
pub fn prefix_subrange_from_kwargs(hash: &RHash) -> Result<PrefixSubrange, Error> {
|
|
43
|
+
let start = match get_optional::<String>(hash, "subrange_from")? {
|
|
44
|
+
Some(s) => Bound::Included(s.into_bytes()),
|
|
45
|
+
None => Bound::Unbounded,
|
|
46
|
+
};
|
|
47
|
+
let end = match get_optional::<String>(hash, "subrange_to")? {
|
|
48
|
+
Some(s) => Bound::Excluded(s.into_bytes()),
|
|
49
|
+
None => Bound::Unbounded,
|
|
50
|
+
};
|
|
51
|
+
Ok((start, end))
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
/// Convert an object_store error to a SlateDB error
|
|
27
55
|
fn to_slate_error(e: ObjectStoreError) -> SlateError {
|
|
28
56
|
SlateError::unavailable(e.to_string())
|
|
@@ -52,8 +80,22 @@ pub fn resolve_object_store(url: &str) -> Result<Arc<dyn ObjectStore>, SlateErro
|
|
|
52
80
|
Ok(Arc::new(store))
|
|
53
81
|
}
|
|
54
82
|
_ => {
|
|
55
|
-
//
|
|
56
|
-
|
|
83
|
+
// SlateDB 0.14 changed `Db::resolve_object_store` to reject any URL
|
|
84
|
+
// that carries a path component (returning `InvalidObjectStorePath`),
|
|
85
|
+
// whereas 0.13 transparently wrapped it in a `PrefixStore`. Preserve
|
|
86
|
+
// the old, more forgiving behavior here so callers can keep passing a
|
|
87
|
+
// full location such as "file:///data/mydb" or "gs://bucket/prefix".
|
|
88
|
+
//
|
|
89
|
+
// Env keys are lowercased because `parse_url_opts` only recognizes
|
|
90
|
+
// lower-case option keys.
|
|
91
|
+
let env_vars = std::env::vars().map(|(k, v)| (k.to_ascii_lowercase(), v));
|
|
92
|
+
let (store, path) = parse_url_opts(&parsed_url, env_vars).map_err(to_slate_error)?;
|
|
93
|
+
let store: Arc<dyn ObjectStore> = Arc::from(store);
|
|
94
|
+
if path.as_ref().is_empty() {
|
|
95
|
+
Ok(store)
|
|
96
|
+
} else {
|
|
97
|
+
Ok(Arc::new(PrefixStore::new(store, path)))
|
|
98
|
+
}
|
|
57
99
|
}
|
|
58
100
|
}
|
|
59
101
|
}
|
|
@@ -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
|
}
|