slatedb 0.4.2-aarch64-linux-musl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlateDb
4
+ class Admin
5
+ class << self
6
+ # Create an admin handle for a database path/object store.
7
+ #
8
+ # @param path [String] Database path
9
+ # @param url [String, nil] Optional object store URL
10
+ # @return [Admin] The admin handle
11
+ #
12
+ # @example
13
+ # admin = SlateDb::Admin.new("/tmp/mydb")
14
+ # checkpoints = admin.list_checkpoints
15
+ #
16
+ def new(path, url: nil)
17
+ _new(path, url)
18
+ end
19
+ end
20
+
21
+ # Read the latest or a specific manifest as a JSON string.
22
+ #
23
+ # @param id [Integer, nil] Optional manifest id to read. If nil, reads the latest.
24
+ # @return [String, nil] JSON string of the manifest, or nil if no manifests exist
25
+ #
26
+ # @example
27
+ # json = admin.read_manifest
28
+ # json = admin.read_manifest(123)
29
+ #
30
+ def read_manifest(id = nil)
31
+ _read_manifest(id)
32
+ end
33
+
34
+ # List manifests within an optional [start, end) range as JSON.
35
+ #
36
+ # @param start [Integer, nil] Optional inclusive start id
37
+ # @param end_id [Integer, nil] Optional exclusive end id
38
+ # @return [String] JSON string containing a list of manifest metadata
39
+ #
40
+ # @example
41
+ # json = admin.list_manifests
42
+ # json = admin.list_manifests(start: 1, end_id: 10)
43
+ #
44
+ def list_manifests(start: nil, end_id: nil)
45
+ _list_manifests(start, end_id)
46
+ end
47
+
48
+ # Create a detached checkpoint.
49
+ #
50
+ # @param lifetime [Integer, nil] Checkpoint lifetime in milliseconds
51
+ # @param source [String, nil] Source checkpoint UUID string to extend/refresh
52
+ # @param name [String, nil] Checkpoint name
53
+ # @return [Hash] Hash with :id (UUID string) and :manifest_id (Integer)
54
+ #
55
+ # @example
56
+ # result = admin.create_checkpoint(name: "my_checkpoint")
57
+ # puts result[:id] # => "uuid-string"
58
+ # puts result[:manifest_id] # => 7
59
+ #
60
+ def create_checkpoint(lifetime: nil, source: nil, name: nil)
61
+ opts = {}
62
+ opts[:lifetime] = lifetime if lifetime
63
+ opts[:source] = source if source
64
+ opts[:name] = name if name
65
+ _create_checkpoint(opts)
66
+ end
67
+
68
+ # List known checkpoints for the database.
69
+ #
70
+ # @param name [String, nil] Optional checkpoint name filter
71
+ # @return [Array<Hash>] Array of checkpoint hashes
72
+ #
73
+ # @example
74
+ # checkpoints = admin.list_checkpoints
75
+ # checkpoints.each do |cp|
76
+ # puts "#{cp[:id]}: #{cp[:name]}"
77
+ # end
78
+ #
79
+ def list_checkpoints(name: nil)
80
+ _list_checkpoints(name)
81
+ end
82
+
83
+ # Refresh a checkpoint's lifetime.
84
+ #
85
+ # @param id [String] Checkpoint UUID string
86
+ # @param lifetime [Integer, nil] New lifetime in milliseconds
87
+ # @return [void]
88
+ #
89
+ # @example
90
+ # admin.refresh_checkpoint("uuid-here", lifetime: 60_000)
91
+ #
92
+ def refresh_checkpoint(id, lifetime: nil)
93
+ _refresh_checkpoint(id, lifetime)
94
+ end
95
+
96
+ # Delete a checkpoint.
97
+ #
98
+ # @param id [String] Checkpoint UUID string
99
+ # @return [void]
100
+ #
101
+ # @example
102
+ # admin.delete_checkpoint("uuid-here")
103
+ #
104
+ def delete_checkpoint(id)
105
+ _delete_checkpoint(id)
106
+ end
107
+
108
+ # Run garbage collection once.
109
+ #
110
+ # @param min_age [Integer, nil] Minimum age in milliseconds for objects to be collected
111
+ # @return [void]
112
+ #
113
+ # @example
114
+ # admin.run_gc(min_age: 3600_000) # 1 hour
115
+ #
116
+ def run_gc(min_age: nil)
117
+ opts = {}
118
+ opts[:min_age] = min_age if min_age
119
+ _run_gc(opts)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,510 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlateDb
4
+ class Database # rubocop:disable Metrics/ClassLength
5
+ private_class_method :new
6
+
7
+ class << self
8
+ # Open a database at the given path.
9
+ #
10
+ # @param path [String] The path identifier for the database
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.
15
+ # @yield [db] If a block is given, yields the database and ensures it's closed
16
+ # @return [Database] The opened database (or block result if block given)
17
+ #
18
+ # @example Open a database
19
+ # db = SlateDb::Database.open("/tmp/mydb")
20
+ # db.put("key", "value")
21
+ # db.close
22
+ #
23
+ # @example Open with block (auto-close)
24
+ # SlateDb::Database.open("/tmp/mydb") do |db|
25
+ # db.put("key", "value")
26
+ # end # automatically closed
27
+ #
28
+ # @example Open with S3 backend
29
+ # db = SlateDb::Database.open("/tmp/mydb", url: "s3://mybucket/path")
30
+ #
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)
54
+
55
+ if block_given?
56
+ begin
57
+ yield db
58
+ ensure
59
+ begin
60
+ db.close
61
+ rescue StandardError
62
+ nil
63
+ end
64
+ end
65
+ else
66
+ db
67
+ end
68
+ end
69
+ end
70
+
71
+ # Get a value by key.
72
+ #
73
+ # @param key [String] The key to look up
74
+ # @param durability_filter [String, nil] Filter by durability level ("remote" or "memory")
75
+ # @param dirty [Boolean, nil] Whether to include uncommitted data
76
+ # @param cache_blocks [Boolean, nil] Whether to cache blocks
77
+ # @return [String, nil] The value, or nil if not found
78
+ #
79
+ # @example Basic get
80
+ # value = db.get("mykey")
81
+ #
82
+ # @example Get with options
83
+ # value = db.get("mykey", durability_filter: "memory", dirty: true)
84
+ #
85
+ def get(key, durability_filter: nil, dirty: nil, cache_blocks: nil)
86
+ opts = {}
87
+ opts[:durability_filter] = durability_filter.to_s if durability_filter
88
+ opts[:dirty] = dirty unless dirty.nil?
89
+ opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
90
+
91
+ if opts.empty?
92
+ _get(key)
93
+ else
94
+ _get_with_options(key, opts)
95
+ end
96
+ end
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
+
126
+ # Store a key-value pair.
127
+ #
128
+ # @param key [String] The key to store
129
+ # @param value [String] The value to store
130
+ # @param ttl [Integer, nil] Time-to-live in milliseconds
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)
136
+ # @return [void]
137
+ #
138
+ # @example Basic put
139
+ # db.put("mykey", "myvalue")
140
+ #
141
+ # @example Put with TTL
142
+ # db.put("mykey", "myvalue", ttl: 60_000) # expires in 60 seconds
143
+ #
144
+ # @example Put without waiting for durability
145
+ # db.put("mykey", "myvalue", await_durable: false)
146
+ #
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)
151
+ opts = {}
152
+ opts[:ttl] = ttl if ttl
153
+ opts[:await_durable] = await_durable unless await_durable.nil?
154
+ opts[:seqnum] = seqnum if seqnum
155
+
156
+ if opts.empty?
157
+ _put(key, value)
158
+ else
159
+ _put_with_options(key, value, opts)
160
+ end
161
+ end
162
+
163
+ # Delete a key.
164
+ #
165
+ # @param key [String] The key to delete
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)
169
+ # @return [void]
170
+ #
171
+ # @example Basic delete
172
+ # db.delete("mykey")
173
+ #
174
+ # @example Delete without waiting for durability
175
+ # db.delete("mykey", await_durable: false)
176
+ #
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?
183
+ _delete(key)
184
+ else
185
+ _delete_with_options(key, opts)
186
+ end
187
+ end
188
+
189
+ # Scan a range of keys.
190
+ #
191
+ # @param start_key [String] The start key (inclusive)
192
+ # @param end_key [String, nil] The end key (exclusive). If nil, scans to end.
193
+ # @param durability_filter [String, nil] Filter by durability level ("remote" or "memory")
194
+ # @param dirty [Boolean, nil] Whether to include uncommitted data
195
+ # @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
196
+ # @param cache_blocks [Boolean, nil] Whether to cache blocks
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)
199
+ # @return [Iterator] An iterator over key-value pairs
200
+ #
201
+ # @example Basic scan
202
+ # iter = db.scan("a")
203
+ # while entry = iter.next_entry
204
+ # key, value = entry
205
+ # puts "#{key}: #{value}"
206
+ # end
207
+ #
208
+ # @example Scan with range
209
+ # iter = db.scan("a", "z")
210
+ #
211
+ # @example Scan with block
212
+ # db.scan("user:") do |key, value|
213
+ # puts "#{key}: #{value}"
214
+ # end
215
+ #
216
+ def scan(start_key, end_key = nil, durability_filter: nil, dirty: nil,
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
+ )
226
+
227
+ iter = if opts.empty?
228
+ _scan(start_key, end_key)
229
+ else
230
+ _scan_with_options(start_key, end_key, opts)
231
+ end
232
+
233
+ if block_given?
234
+ iter.each(&)
235
+ else
236
+ iter
237
+ end
238
+ end
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
+ # @param from [String, nil] Inclusive lower bound suffix, appended to the
250
+ # prefix, to start scanning from (e.g. prefix "user:" with from "100"
251
+ # starts at "user:100"). Defaults to the start of the prefix.
252
+ # @param to [String, nil] Exclusive upper bound suffix, appended to the
253
+ # prefix, to stop scanning at. Defaults to the end of the prefix.
254
+ # @return [Iterator] An iterator over key-value pairs
255
+ #
256
+ # @example Scan all user keys
257
+ # db.scan_prefix("user:") do |key, value|
258
+ # puts "#{key}: #{value}"
259
+ # end
260
+ #
261
+ # @example Scan a sub-range within a prefix
262
+ # # keys "user:100" (inclusive) up to "user:200" (exclusive)
263
+ # db.scan_prefix("user:", from: "100", to: "200")
264
+ #
265
+ def scan_prefix(prefix, durability_filter: nil, dirty: nil,
266
+ read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, order: nil,
267
+ from: nil, to: nil, &)
268
+ opts = scan_options(
269
+ durability_filter: durability_filter,
270
+ dirty: dirty,
271
+ read_ahead_bytes: read_ahead_bytes,
272
+ cache_blocks: cache_blocks,
273
+ max_fetch_tasks: max_fetch_tasks,
274
+ order: order
275
+ )
276
+ opts[:subrange_from] = from if from
277
+ opts[:subrange_to] = to if to
278
+
279
+ iter = if opts.empty?
280
+ _scan_prefix(prefix)
281
+ else
282
+ _scan_prefix_with_options(prefix, opts)
283
+ end
284
+
285
+ if block_given?
286
+ iter.each(&)
287
+ else
288
+ iter
289
+ end
290
+ end
291
+
292
+ def scan_options(durability_filter:, dirty:, read_ahead_bytes:, cache_blocks:,
293
+ max_fetch_tasks:, order:)
294
+ opts = {}
295
+ opts[:durability_filter] = durability_filter.to_s if durability_filter
296
+ opts[:dirty] = dirty unless dirty.nil?
297
+ opts[:read_ahead_bytes] = read_ahead_bytes if read_ahead_bytes
298
+ opts[:cache_blocks] = cache_blocks unless cache_blocks.nil?
299
+ opts[:max_fetch_tasks] = max_fetch_tasks if max_fetch_tasks
300
+ opts[:order] = order.to_s if order
301
+ opts
302
+ end
303
+
304
+ private :scan_options
305
+
306
+ # Write a batch of operations atomically.
307
+ #
308
+ # @param batch [WriteBatch] The batch to write
309
+ # @param await_durable [Boolean] Whether to wait for durability (default: true)
310
+ # @param seqnum [Integer, nil] User-supplied sequence number applied to the
311
+ # batch. See {#put} for semantics. (Requires SlateDB >= 0.13.0)
312
+ # @return [void]
313
+ #
314
+ # @example Write a batch
315
+ # batch = SlateDb::WriteBatch.new
316
+ # batch.put("key1", "value1")
317
+ # batch.put("key2", "value2")
318
+ # batch.delete("key3")
319
+ # db.write(batch)
320
+ #
321
+ # @example Using batch block helper
322
+ # db.batch do |b|
323
+ # b.put("key1", "value1")
324
+ # b.put("key2", "value2")
325
+ # end
326
+ #
327
+ def write(batch, await_durable: nil, seqnum: nil)
328
+ opts = {}
329
+ opts[:await_durable] = await_durable unless await_durable.nil?
330
+ opts[:seqnum] = seqnum if seqnum
331
+
332
+ if opts.empty?
333
+ _write(batch)
334
+ else
335
+ _write_with_options(batch, opts)
336
+ end
337
+ end
338
+
339
+ # Merge a value into the database.
340
+ #
341
+ # @param key [String] The key to merge into
342
+ # @param value [String] The merge operand to apply
343
+ # @param ttl [Integer, nil] Time-to-live in milliseconds
344
+ # @param await_durable [Boolean] Whether to wait for durability (default: true)
345
+ # @param seqnum [Integer, nil] User-supplied sequence number for this write.
346
+ # See {#put} for semantics. (Requires SlateDB >= 0.13.0)
347
+ # @return [void]
348
+ #
349
+ # @example Merge with string concatenation operator
350
+ # db = SlateDb::Database.open("/tmp/mydb", merge_operator: :string_concat)
351
+ # db.merge("key", "part1")
352
+ # db.merge("key", "part2")
353
+ #
354
+ def merge(key, value, ttl: nil, await_durable: nil, seqnum: nil)
355
+ opts = {}
356
+ opts[:ttl] = ttl if ttl
357
+ opts[:await_durable] = await_durable unless await_durable.nil?
358
+ opts[:seqnum] = seqnum if seqnum
359
+
360
+ if opts.empty?
361
+ _merge(key, value)
362
+ else
363
+ _merge_with_options(key, value, opts)
364
+ end
365
+ end
366
+
367
+ # Create and write a batch using a block.
368
+ #
369
+ # @param await_durable [Boolean] Whether to wait for durability (default: true)
370
+ # @param seqnum [Integer, nil] User-supplied sequence number applied to the
371
+ # batch. See {#put} for semantics. (Requires SlateDB >= 0.13.0)
372
+ # @yield [batch] Yields a WriteBatch to the block
373
+ # @return [void]
374
+ #
375
+ # @example
376
+ # db.batch do |b|
377
+ # b.put("key1", "value1")
378
+ # b.put("key2", "value2")
379
+ # b.delete("old_key")
380
+ # end
381
+ #
382
+ def batch(await_durable: nil, seqnum: nil)
383
+ b = WriteBatch.new
384
+ yield b
385
+ write(b, await_durable: await_durable, seqnum: seqnum)
386
+ end
387
+
388
+ # Begin a new transaction.
389
+ #
390
+ # @param isolation [Symbol, String] Isolation level (:snapshot or :serializable)
391
+ # @yield [txn] If a block is given, yields the transaction and auto-commits/rollbacks
392
+ # @return [Transaction, Object] The transaction (or block result if block given)
393
+ #
394
+ # @example Manual transaction management
395
+ # txn = db.begin_transaction
396
+ # txn.put("key", "value")
397
+ # txn.commit
398
+ #
399
+ # @example Block-based transaction (auto-commit)
400
+ # db.transaction do |txn|
401
+ # txn.put("key", "value")
402
+ # txn.get("other_key")
403
+ # end # automatically committed
404
+ #
405
+ # @example Serializable isolation
406
+ # db.transaction(isolation: :serializable) do |txn|
407
+ # val = txn.get("counter")
408
+ # txn.put("counter", (val.to_i + 1).to_s)
409
+ # end
410
+ #
411
+ def begin_transaction(isolation: nil)
412
+ isolation_str = isolation&.to_s
413
+ _begin_transaction(isolation_str)
414
+ end
415
+
416
+ # Execute a block within a transaction.
417
+ #
418
+ # The transaction is automatically committed if the block succeeds,
419
+ # or rolled back if an exception is raised.
420
+ #
421
+ # @param isolation [Symbol, String] Isolation level (:snapshot or :serializable)
422
+ # @yield [txn] Yields the transaction to the block
423
+ # @return [Object] The result of the block
424
+ #
425
+ # @example
426
+ # result = db.transaction do |txn|
427
+ # old_val = txn.get("counter") || "0"
428
+ # new_val = (old_val.to_i + 1).to_s
429
+ # txn.put("counter", new_val)
430
+ # new_val
431
+ # end
432
+ #
433
+ def transaction(isolation: nil)
434
+ txn = begin_transaction(isolation: isolation)
435
+ begin
436
+ result = yield txn
437
+ txn.commit
438
+ result
439
+ rescue StandardError
440
+ begin
441
+ txn.rollback
442
+ rescue StandardError
443
+ nil
444
+ end
445
+ raise
446
+ end
447
+ end
448
+
449
+ # Create a snapshot for consistent reads.
450
+ #
451
+ # @yield [snapshot] If a block is given, yields the snapshot and auto-closes
452
+ # @return [Snapshot, Object] The snapshot (or block result if block given)
453
+ #
454
+ # @example Manual snapshot management
455
+ # snapshot = db.snapshot
456
+ # snapshot.get("key")
457
+ # snapshot.close
458
+ #
459
+ # @example Block-based snapshot (auto-close)
460
+ # db.snapshot do |snap|
461
+ # snap.get("key1")
462
+ # snap.get("key2")
463
+ # end # automatically closed
464
+ #
465
+ def snapshot
466
+ snap = _snapshot
467
+
468
+ if block_given?
469
+ begin
470
+ yield snap
471
+ ensure
472
+ begin
473
+ snap.close
474
+ rescue StandardError
475
+ nil
476
+ end
477
+ end
478
+ else
479
+ snap
480
+ end
481
+ end
482
+
483
+ # Create a checkpoint of the database.
484
+ #
485
+ # @param lifetime [Integer, nil] Checkpoint lifetime in milliseconds
486
+ # @param name [String, nil] Optional name for the checkpoint
487
+ # @return [Hash] Hash with :id (UUID string) and :manifest_id (integer)
488
+ #
489
+ # @example Create a named checkpoint
490
+ # checkpoint = db.create_checkpoint(name: "before-migration")
491
+ # puts "Checkpoint ID: #{checkpoint[:id]}"
492
+ #
493
+ # @example Create a checkpoint with lifetime
494
+ # checkpoint = db.create_checkpoint(lifetime: 3600_000) # 1 hour
495
+ #
496
+ def create_checkpoint(lifetime: nil, name: nil)
497
+ opts = {}
498
+ opts[:lifetime] = lifetime if lifetime
499
+ opts[:name] = name if name
500
+ _create_checkpoint(opts)
501
+ end
502
+
503
+ # Get database metrics registry.
504
+ #
505
+ # @return [Metrics] Metrics registry
506
+ def metrics
507
+ _metrics
508
+ end
509
+ end
510
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlateDb
4
+ class Iterator
5
+ include Enumerable
6
+
7
+ # Iterate over all entries.
8
+ #
9
+ # @yield [key, value] Yields each key-value pair
10
+ # @return [self, Enumerator] Returns self if block given, otherwise an Enumerator
11
+ #
12
+ # @example
13
+ # iter.each do |key, value|
14
+ # puts "#{key}: #{value}"
15
+ # end
16
+ #
17
+ # @example With Enumerable methods
18
+ # iter.map { |k, v| [k.upcase, v] }
19
+ # iter.select { |k, v| k.start_with?("user:") }
20
+ #
21
+ def each
22
+ return to_enum(:each) unless block_given?
23
+
24
+ while (entry = next_entry)
25
+ yield entry
26
+ end
27
+
28
+ self
29
+ end
30
+ end
31
+ 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