slatedb 0.1.1 → 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 +219 -4
- data/ext/slatedb/Cargo.toml +10 -10
- data/ext/slatedb/src/admin.rs +20 -5
- data/ext/slatedb/src/database.rs +376 -39
- data/ext/slatedb/src/iterator.rs +4 -1
- data/ext/slatedb/src/lib.rs +3 -0
- data/ext/slatedb/src/merge_ops.rs +240 -0
- data/ext/slatedb/src/metrics.rs +47 -0
- data/ext/slatedb/src/reader.rs +118 -7
- data/ext/slatedb/src/runtime.rs +52 -1
- data/ext/slatedb/src/snapshot.rs +105 -0
- data/ext/slatedb/src/transaction.rs +189 -9
- data/ext/slatedb/src/utils.rs +5 -4
- data/ext/slatedb/src/write_batch.rs +48 -1
- data/lib/slatedb/database.rb +205 -19
- data/lib/slatedb/metrics.rb +20 -0
- data/lib/slatedb/reader.rb +50 -1
- data/lib/slatedb/snapshot.rb +32 -0
- data/lib/slatedb/transaction.rb +96 -0
- data/lib/slatedb/version.rb +1 -1
- data/lib/slatedb/write_batch.rb +20 -0
- data/lib/slatedb.rb +1 -0
- metadata +16 -13
data/lib/slatedb/database.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SlateDb
|
|
4
|
-
class Database
|
|
4
|
+
class Database # rubocop:disable Metrics/ClassLength
|
|
5
5
|
private_class_method :new
|
|
6
6
|
|
|
7
7
|
class << self
|
|
@@ -9,6 +9,9 @@ module SlateDb
|
|
|
9
9
|
#
|
|
10
10
|
# @param path [String] The path identifier for the database
|
|
11
11
|
# @param url [String, nil] Optional object store URL (e.g., "s3://bucket/path")
|
|
12
|
+
# @param merge_operator [Symbol, String, Proc, nil] Optional merge operator.
|
|
13
|
+
# Can be a symbol/string ("string_concat" or "concat") or a Proc/lambda
|
|
14
|
+
# that takes (key, existing_value, new_value) and returns the merged value.
|
|
12
15
|
# @yield [db] If a block is given, yields the database and ensures it's closed
|
|
13
16
|
# @return [Database] The opened database (or block result if block given)
|
|
14
17
|
#
|
|
@@ -25,8 +28,29 @@ module SlateDb
|
|
|
25
28
|
# @example Open with S3 backend
|
|
26
29
|
# db = SlateDb::Database.open("/tmp/mydb", url: "s3://mybucket/path")
|
|
27
30
|
#
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
# @example Open with a custom merge operator (Proc)
|
|
32
|
+
# # Custom merge that adds numbers
|
|
33
|
+
# db = SlateDb::Database.open("/tmp/mydb", merge_operator: ->(key, existing, new_val) {
|
|
34
|
+
# existing_num = existing ? existing.to_i : 0
|
|
35
|
+
# (existing_num + new_val.to_i).to_s
|
|
36
|
+
# })
|
|
37
|
+
# db.merge("counter", "5")
|
|
38
|
+
# db.merge("counter", "3")
|
|
39
|
+
# db.get("counter") # => "8"
|
|
40
|
+
#
|
|
41
|
+
def open(path, url: nil, merge_operator: nil)
|
|
42
|
+
opts = {}
|
|
43
|
+
|
|
44
|
+
case merge_operator
|
|
45
|
+
when Symbol, String
|
|
46
|
+
opts[:merge_operator] = merge_operator.to_s
|
|
47
|
+
when Proc
|
|
48
|
+
# Store the proc to prevent GC and pass to Rust
|
|
49
|
+
@_merge_operator_proc = merge_operator
|
|
50
|
+
opts[:merge_operator_proc] = merge_operator
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
db = _open(path, url, opts)
|
|
30
54
|
|
|
31
55
|
if block_given?
|
|
32
56
|
begin
|
|
@@ -71,12 +95,44 @@ module SlateDb
|
|
|
71
95
|
end
|
|
72
96
|
end
|
|
73
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
|
+
|
|
74
126
|
# Store a key-value pair.
|
|
75
127
|
#
|
|
76
128
|
# @param key [String] The key to store
|
|
77
129
|
# @param value [String] The value to store
|
|
78
130
|
# @param ttl [Integer, nil] Time-to-live in milliseconds
|
|
79
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)
|
|
80
136
|
# @return [void]
|
|
81
137
|
#
|
|
82
138
|
# @example Basic put
|
|
@@ -88,10 +144,14 @@ module SlateDb
|
|
|
88
144
|
# @example Put without waiting for durability
|
|
89
145
|
# db.put("mykey", "myvalue", await_durable: false)
|
|
90
146
|
#
|
|
91
|
-
|
|
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)
|
|
92
151
|
opts = {}
|
|
93
152
|
opts[:ttl] = ttl if ttl
|
|
94
153
|
opts[:await_durable] = await_durable unless await_durable.nil?
|
|
154
|
+
opts[:seqnum] = seqnum if seqnum
|
|
95
155
|
|
|
96
156
|
if opts.empty?
|
|
97
157
|
_put(key, value)
|
|
@@ -104,6 +164,8 @@ module SlateDb
|
|
|
104
164
|
#
|
|
105
165
|
# @param key [String] The key to delete
|
|
106
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)
|
|
107
169
|
# @return [void]
|
|
108
170
|
#
|
|
109
171
|
# @example Basic delete
|
|
@@ -112,11 +174,15 @@ module SlateDb
|
|
|
112
174
|
# @example Delete without waiting for durability
|
|
113
175
|
# db.delete("mykey", await_durable: false)
|
|
114
176
|
#
|
|
115
|
-
def delete(key, await_durable: nil)
|
|
116
|
-
|
|
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?
|
|
117
183
|
_delete(key)
|
|
118
184
|
else
|
|
119
|
-
_delete_with_options(key,
|
|
185
|
+
_delete_with_options(key, opts)
|
|
120
186
|
end
|
|
121
187
|
end
|
|
122
188
|
|
|
@@ -129,6 +195,7 @@ module SlateDb
|
|
|
129
195
|
# @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
|
|
130
196
|
# @param cache_blocks [Boolean, nil] Whether to cache blocks
|
|
131
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)
|
|
132
199
|
# @return [Iterator] An iterator over key-value pairs
|
|
133
200
|
#
|
|
134
201
|
# @example Basic scan
|
|
@@ -147,13 +214,15 @@ module SlateDb
|
|
|
147
214
|
# end
|
|
148
215
|
#
|
|
149
216
|
def scan(start_key, end_key = nil, durability_filter: nil, dirty: nil,
|
|
150
|
-
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
|
|
151
|
-
opts =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
)
|
|
157
226
|
|
|
158
227
|
iter = if opts.empty?
|
|
159
228
|
_scan(start_key, end_key)
|
|
@@ -168,10 +237,66 @@ module SlateDb
|
|
|
168
237
|
end
|
|
169
238
|
end
|
|
170
239
|
|
|
240
|
+
# Scan all keys with a given prefix.
|
|
241
|
+
#
|
|
242
|
+
# @param prefix [String] The key prefix to scan
|
|
243
|
+
# @param durability_filter [String, nil] Filter by durability level ("remote" or "memory")
|
|
244
|
+
# @param dirty [Boolean, nil] Whether to include uncommitted data
|
|
245
|
+
# @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
|
|
246
|
+
# @param cache_blocks [Boolean, nil] Whether to cache blocks
|
|
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)
|
|
249
|
+
# @return [Iterator] An iterator over key-value pairs
|
|
250
|
+
#
|
|
251
|
+
# @example Scan all user keys
|
|
252
|
+
# db.scan_prefix("user:") do |key, value|
|
|
253
|
+
# puts "#{key}: #{value}"
|
|
254
|
+
# end
|
|
255
|
+
#
|
|
256
|
+
def scan_prefix(prefix, durability_filter: nil, dirty: nil,
|
|
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
|
+
)
|
|
266
|
+
|
|
267
|
+
iter = if opts.empty?
|
|
268
|
+
_scan_prefix(prefix)
|
|
269
|
+
else
|
|
270
|
+
_scan_prefix_with_options(prefix, opts)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
if block_given?
|
|
274
|
+
iter.each(&)
|
|
275
|
+
else
|
|
276
|
+
iter
|
|
277
|
+
end
|
|
278
|
+
end
|
|
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
|
+
|
|
171
294
|
# Write a batch of operations atomically.
|
|
172
295
|
#
|
|
173
296
|
# @param batch [WriteBatch] The batch to write
|
|
174
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)
|
|
175
300
|
# @return [void]
|
|
176
301
|
#
|
|
177
302
|
# @example Write a batch
|
|
@@ -187,17 +312,51 @@ module SlateDb
|
|
|
187
312
|
# b.put("key2", "value2")
|
|
188
313
|
# end
|
|
189
314
|
#
|
|
190
|
-
def write(batch, await_durable: nil)
|
|
191
|
-
|
|
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?
|
|
192
321
|
_write(batch)
|
|
193
322
|
else
|
|
194
|
-
_write_with_options(batch,
|
|
323
|
+
_write_with_options(batch, opts)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Merge a value into the database.
|
|
328
|
+
#
|
|
329
|
+
# @param key [String] The key to merge into
|
|
330
|
+
# @param value [String] The merge operand to apply
|
|
331
|
+
# @param ttl [Integer, nil] Time-to-live in milliseconds
|
|
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)
|
|
335
|
+
# @return [void]
|
|
336
|
+
#
|
|
337
|
+
# @example Merge with string concatenation operator
|
|
338
|
+
# db = SlateDb::Database.open("/tmp/mydb", merge_operator: :string_concat)
|
|
339
|
+
# db.merge("key", "part1")
|
|
340
|
+
# db.merge("key", "part2")
|
|
341
|
+
#
|
|
342
|
+
def merge(key, value, ttl: nil, await_durable: nil, seqnum: nil)
|
|
343
|
+
opts = {}
|
|
344
|
+
opts[:ttl] = ttl if ttl
|
|
345
|
+
opts[:await_durable] = await_durable unless await_durable.nil?
|
|
346
|
+
opts[:seqnum] = seqnum if seqnum
|
|
347
|
+
|
|
348
|
+
if opts.empty?
|
|
349
|
+
_merge(key, value)
|
|
350
|
+
else
|
|
351
|
+
_merge_with_options(key, value, opts)
|
|
195
352
|
end
|
|
196
353
|
end
|
|
197
354
|
|
|
198
355
|
# Create and write a batch using a block.
|
|
199
356
|
#
|
|
200
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)
|
|
201
360
|
# @yield [batch] Yields a WriteBatch to the block
|
|
202
361
|
# @return [void]
|
|
203
362
|
#
|
|
@@ -208,10 +367,10 @@ module SlateDb
|
|
|
208
367
|
# b.delete("old_key")
|
|
209
368
|
# end
|
|
210
369
|
#
|
|
211
|
-
def batch(await_durable: nil)
|
|
370
|
+
def batch(await_durable: nil, seqnum: nil)
|
|
212
371
|
b = WriteBatch.new
|
|
213
372
|
yield b
|
|
214
|
-
write(b, await_durable: await_durable)
|
|
373
|
+
write(b, await_durable: await_durable, seqnum: seqnum)
|
|
215
374
|
end
|
|
216
375
|
|
|
217
376
|
# Begin a new transaction.
|
|
@@ -308,5 +467,32 @@ module SlateDb
|
|
|
308
467
|
snap
|
|
309
468
|
end
|
|
310
469
|
end
|
|
470
|
+
|
|
471
|
+
# Create a checkpoint of the database.
|
|
472
|
+
#
|
|
473
|
+
# @param lifetime [Integer, nil] Checkpoint lifetime in milliseconds
|
|
474
|
+
# @param name [String, nil] Optional name for the checkpoint
|
|
475
|
+
# @return [Hash] Hash with :id (UUID string) and :manifest_id (integer)
|
|
476
|
+
#
|
|
477
|
+
# @example Create a named checkpoint
|
|
478
|
+
# checkpoint = db.create_checkpoint(name: "before-migration")
|
|
479
|
+
# puts "Checkpoint ID: #{checkpoint[:id]}"
|
|
480
|
+
#
|
|
481
|
+
# @example Create a checkpoint with lifetime
|
|
482
|
+
# checkpoint = db.create_checkpoint(lifetime: 3600_000) # 1 hour
|
|
483
|
+
#
|
|
484
|
+
def create_checkpoint(lifetime: nil, name: nil)
|
|
485
|
+
opts = {}
|
|
486
|
+
opts[:lifetime] = lifetime if lifetime
|
|
487
|
+
opts[:name] = name if name
|
|
488
|
+
_create_checkpoint(opts)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Get database metrics registry.
|
|
492
|
+
#
|
|
493
|
+
# @return [Metrics] Metrics registry
|
|
494
|
+
def metrics
|
|
495
|
+
_metrics
|
|
496
|
+
end
|
|
311
497
|
end
|
|
312
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,14 @@ 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)
|
|
21
|
+
# @param merge_operator [Symbol, String, nil] Optional merge operator ("string_concat" or "concat")
|
|
14
22
|
# @yield [reader] If a block is given, yields the reader and ensures it's closed
|
|
15
23
|
# @return [Reader] The opened reader (or block result if block given)
|
|
16
24
|
#
|
|
@@ -27,13 +35,22 @@ module SlateDb
|
|
|
27
35
|
# @example Open at a specific checkpoint
|
|
28
36
|
# reader = SlateDb::Reader.open("/tmp/mydb", checkpoint_id: "uuid-here")
|
|
29
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
|
+
#
|
|
30
43
|
def open(path, url: nil, checkpoint_id: nil,
|
|
31
44
|
manifest_poll_interval: nil, checkpoint_lifetime: nil,
|
|
32
|
-
max_memtable_bytes: nil
|
|
45
|
+
max_memtable_bytes: nil, cache_root: nil, max_open_file_handles: nil,
|
|
46
|
+
merge_operator: nil)
|
|
33
47
|
opts = {}
|
|
34
48
|
opts[:manifest_poll_interval] = manifest_poll_interval if manifest_poll_interval
|
|
35
49
|
opts[:checkpoint_lifetime] = checkpoint_lifetime if checkpoint_lifetime
|
|
36
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
|
|
53
|
+
opts[:merge_operator] = merge_operator.to_s if merge_operator
|
|
37
54
|
|
|
38
55
|
reader = _open(path, url, checkpoint_id, opts)
|
|
39
56
|
|
|
@@ -101,5 +118,37 @@ module SlateDb
|
|
|
101
118
|
iter
|
|
102
119
|
end
|
|
103
120
|
end
|
|
121
|
+
|
|
122
|
+
# Scan all keys with a given prefix.
|
|
123
|
+
#
|
|
124
|
+
# @param prefix [String] The key prefix to scan
|
|
125
|
+
# @param durability_filter [String, nil] Filter by durability level
|
|
126
|
+
# @param dirty [Boolean, nil] Whether to include uncommitted data
|
|
127
|
+
# @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
|
|
128
|
+
# @param cache_blocks [Boolean, nil] Whether to cache blocks
|
|
129
|
+
# @param max_fetch_tasks [Integer, nil] Maximum number of fetch tasks
|
|
130
|
+
# @return [Iterator] An iterator over key-value pairs
|
|
131
|
+
#
|
|
132
|
+
def scan_prefix(prefix, durability_filter: nil, dirty: nil,
|
|
133
|
+
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
|
|
134
|
+
opts = {}
|
|
135
|
+
opts[:durability_filter] = durability_filter.to_s if durability_filter
|
|
136
|
+
opts[:dirty] = dirty unless dirty.nil?
|
|
137
|
+
opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
|
|
138
|
+
opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
|
|
139
|
+
opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
|
|
140
|
+
|
|
141
|
+
iter = if opts.empty?
|
|
142
|
+
_scan_prefix(prefix)
|
|
143
|
+
else
|
|
144
|
+
_scan_prefix_with_options(prefix, opts)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if block_given?
|
|
148
|
+
iter.each(&)
|
|
149
|
+
else
|
|
150
|
+
iter
|
|
151
|
+
end
|
|
152
|
+
end
|
|
104
153
|
end
|
|
105
154
|
end
|
data/lib/slatedb/snapshot.rb
CHANGED
|
@@ -50,5 +50,37 @@ module SlateDb
|
|
|
50
50
|
iter
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
+
|
|
54
|
+
# Scan all keys with a given prefix from the snapshot.
|
|
55
|
+
#
|
|
56
|
+
# @param prefix [String] The key prefix to scan
|
|
57
|
+
# @param durability_filter [String, nil] Filter by durability level
|
|
58
|
+
# @param dirty [Boolean, nil] Whether to include uncommitted data
|
|
59
|
+
# @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
|
|
60
|
+
# @param cache_blocks [Boolean, nil] Whether to cache blocks
|
|
61
|
+
# @param max_fetch_tasks [Integer, nil] Maximum number of fetch tasks
|
|
62
|
+
# @return [Iterator] An iterator over key-value pairs
|
|
63
|
+
#
|
|
64
|
+
def scan_prefix(prefix, durability_filter: nil, dirty: nil,
|
|
65
|
+
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
|
|
66
|
+
opts = {}
|
|
67
|
+
opts[:durability_filter] = durability_filter.to_s if durability_filter
|
|
68
|
+
opts[:dirty] = dirty unless dirty.nil?
|
|
69
|
+
opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
|
|
70
|
+
opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
|
|
71
|
+
opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
|
|
72
|
+
|
|
73
|
+
iter = if opts.empty?
|
|
74
|
+
_scan_prefix(prefix)
|
|
75
|
+
else
|
|
76
|
+
_scan_prefix_with_options(prefix, opts)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if block_given?
|
|
80
|
+
iter.each(&)
|
|
81
|
+
else
|
|
82
|
+
iter
|
|
83
|
+
end
|
|
84
|
+
end
|
|
53
85
|
end
|
|
54
86
|
end
|
data/lib/slatedb/transaction.rb
CHANGED
|
@@ -47,6 +47,21 @@ module SlateDb
|
|
|
47
47
|
_delete(key)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# Merge a value within the transaction.
|
|
51
|
+
#
|
|
52
|
+
# @param key [String] The key to merge into
|
|
53
|
+
# @param value [String] The merge operand to apply
|
|
54
|
+
# @param ttl [Integer, nil] Time-to-live in milliseconds
|
|
55
|
+
# @return [void]
|
|
56
|
+
#
|
|
57
|
+
def merge(key, value, ttl: nil)
|
|
58
|
+
if ttl
|
|
59
|
+
_merge_with_options(key, value, { ttl: ttl })
|
|
60
|
+
else
|
|
61
|
+
_merge(key, value)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
50
65
|
# Scan a range of keys within the transaction.
|
|
51
66
|
#
|
|
52
67
|
# @param start_key [String] The start key (inclusive)
|
|
@@ -74,5 +89,86 @@ module SlateDb
|
|
|
74
89
|
iter
|
|
75
90
|
end
|
|
76
91
|
end
|
|
92
|
+
|
|
93
|
+
# Scan all keys with a given prefix within the transaction.
|
|
94
|
+
#
|
|
95
|
+
# @param prefix [String] The key prefix to scan
|
|
96
|
+
# @param durability_filter [String, nil] Filter by durability level
|
|
97
|
+
# @param dirty [Boolean, nil] Whether to include uncommitted data
|
|
98
|
+
# @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
|
|
99
|
+
# @param cache_blocks [Boolean, nil] Whether to cache blocks
|
|
100
|
+
# @param max_fetch_tasks [Integer, nil] Maximum number of fetch tasks
|
|
101
|
+
# @return [Iterator] An iterator over key-value pairs
|
|
102
|
+
#
|
|
103
|
+
def scan_prefix(prefix, durability_filter: nil, dirty: nil,
|
|
104
|
+
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
|
|
105
|
+
opts = {}
|
|
106
|
+
opts[:durability_filter] = durability_filter.to_s if durability_filter
|
|
107
|
+
opts[:dirty] = dirty unless dirty.nil?
|
|
108
|
+
opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
|
|
109
|
+
opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
|
|
110
|
+
opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
|
|
111
|
+
|
|
112
|
+
iter = if opts.empty?
|
|
113
|
+
_scan_prefix(prefix)
|
|
114
|
+
else
|
|
115
|
+
_scan_prefix_with_options(prefix, opts)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if block_given?
|
|
119
|
+
iter.each(&)
|
|
120
|
+
else
|
|
121
|
+
iter
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Mark keys as read for conflict detection.
|
|
126
|
+
#
|
|
127
|
+
# This explicitly tracks reads for conflict checking in serializable isolation,
|
|
128
|
+
# allowing selective read-write conflict detection even when keys weren't
|
|
129
|
+
# actually read via get().
|
|
130
|
+
#
|
|
131
|
+
# @param keys [Array<String>] The keys to mark as read
|
|
132
|
+
# @return [void]
|
|
133
|
+
#
|
|
134
|
+
# @example Mark keys for conflict detection
|
|
135
|
+
# db.transaction(isolation: :serializable) do |txn|
|
|
136
|
+
# txn.mark_read(["key1", "key2"])
|
|
137
|
+
# # These keys will now be checked for conflicts on commit
|
|
138
|
+
# txn.put("key3", "value")
|
|
139
|
+
# end
|
|
140
|
+
#
|
|
141
|
+
def mark_read(keys)
|
|
142
|
+
_mark_read(Array(keys))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Commit the transaction.
|
|
146
|
+
#
|
|
147
|
+
# @param await_durable [Boolean, nil] Whether to wait for durability (default: true)
|
|
148
|
+
# @param seqnum [Integer, nil] User-supplied sequence number for the commit.
|
|
149
|
+
# When provided (and non-zero), it is used instead of the internally
|
|
150
|
+
# generated sequence number and must be strictly greater than the current
|
|
151
|
+
# maximum sequence number. (Requires SlateDB >= 0.13.0)
|
|
152
|
+
# @return [void]
|
|
153
|
+
#
|
|
154
|
+
# @example Commit a transaction
|
|
155
|
+
# txn = db.begin_transaction
|
|
156
|
+
# txn.put("key", "value")
|
|
157
|
+
# txn.commit
|
|
158
|
+
#
|
|
159
|
+
# @example Commit with an explicit sequence number
|
|
160
|
+
# txn.commit(seqnum: 99)
|
|
161
|
+
#
|
|
162
|
+
def commit(await_durable: nil, seqnum: nil)
|
|
163
|
+
opts = {}
|
|
164
|
+
opts[:await_durable] = await_durable unless await_durable.nil?
|
|
165
|
+
opts[:seqnum] = seqnum if seqnum
|
|
166
|
+
|
|
167
|
+
if opts.empty?
|
|
168
|
+
_commit
|
|
169
|
+
else
|
|
170
|
+
_commit_with_options(opts)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
77
173
|
end
|
|
78
174
|
end
|
data/lib/slatedb/version.rb
CHANGED
data/lib/slatedb/write_batch.rb
CHANGED
|
@@ -34,5 +34,25 @@ module SlateDb
|
|
|
34
34
|
_delete(key)
|
|
35
35
|
self
|
|
36
36
|
end
|
|
37
|
+
|
|
38
|
+
# Add a merge operation to the batch.
|
|
39
|
+
#
|
|
40
|
+
# @param key [String] The key to merge into
|
|
41
|
+
# @param value [String] The merge operand to apply
|
|
42
|
+
# @param ttl [Integer, nil] Time-to-live in milliseconds
|
|
43
|
+
# @return [self] Returns self for method chaining
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# batch.merge("key", "part1")
|
|
47
|
+
# batch.merge("key", "part2", ttl: 30_000)
|
|
48
|
+
#
|
|
49
|
+
def merge(key, value, ttl: nil)
|
|
50
|
+
if ttl
|
|
51
|
+
_merge_with_options(key, value, { ttl: ttl })
|
|
52
|
+
else
|
|
53
|
+
_merge(key, value)
|
|
54
|
+
end
|
|
55
|
+
self
|
|
56
|
+
end
|
|
37
57
|
end
|
|
38
58
|
end
|
data/lib/slatedb.rb
CHANGED