lutaml-store 0.2.2 → 0.2.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/.rubocop_todo.yml +22 -13
- data/lib/lutaml/store/adapter/base.rb +28 -0
- data/lib/lutaml/store/adapter/memory.rb +213 -121
- data/lib/lutaml/store/adapter/sqlite.rb +181 -7
- data/lib/lutaml/store/database_store.rb +302 -49
- data/lib/lutaml/store/predicate.rb +248 -0
- data/lib/lutaml/store/query.rb +259 -0
- data/lib/lutaml/store/version.rb +1 -1
- data/lib/lutaml/store.rb +2 -0
- metadata +12 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6093f17ea0bca9240b608835a29b8f060a0c8c41be950e93fc0713e8c1b9444
|
|
4
|
+
data.tar.gz: f0f9ee8ace22fa22ec62408425ba7fa479648c57158c92967bc8e235fd037285
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97a8aa5dc0d52617b1f4d97bd8378664eead01e299410d8dd506b775be23d69616008cde2e053b6a649819726048afdd2b227a455979630b2f07d04e76deed6d
|
|
7
|
+
data.tar.gz: b80834cd232f87d378c0a3a5f7ea17ef2c7ea2d68103d890756eabb2f2c85e20e1b1f193ec64cd0198fdb4235c0ff68ca6de87e788b7aabaa26b49a081e2023e
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2026-06-
|
|
3
|
+
# on 2026-06-10 09:02:53 UTC using RuboCop version 1.87.0.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -11,54 +11,63 @@ Gemspec/RequiredRubyVersion:
|
|
|
11
11
|
Exclude:
|
|
12
12
|
- 'lutaml-store.gemspec'
|
|
13
13
|
|
|
14
|
-
# Offense count:
|
|
14
|
+
# Offense count: 38
|
|
15
15
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
|
16
16
|
Metrics/AbcSize:
|
|
17
17
|
Max: 112
|
|
18
18
|
|
|
19
|
-
# Offense count:
|
|
19
|
+
# Offense count: 63
|
|
20
20
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
21
21
|
# AllowedMethods: refine
|
|
22
22
|
Metrics/BlockLength:
|
|
23
23
|
Max: 387
|
|
24
24
|
|
|
25
|
-
# Offense count:
|
|
25
|
+
# Offense count: 17
|
|
26
26
|
# Configuration parameters: CountComments, CountAsOne.
|
|
27
27
|
Metrics/ClassLength:
|
|
28
|
-
Max:
|
|
28
|
+
Max: 516
|
|
29
29
|
|
|
30
|
-
# Offense count:
|
|
30
|
+
# Offense count: 15
|
|
31
31
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
32
32
|
Metrics/CyclomaticComplexity:
|
|
33
|
-
Max:
|
|
33
|
+
Max: 13
|
|
34
34
|
|
|
35
|
-
# Offense count:
|
|
35
|
+
# Offense count: 89
|
|
36
36
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
37
37
|
Metrics/MethodLength:
|
|
38
38
|
Max: 84
|
|
39
39
|
|
|
40
|
-
# Offense count:
|
|
40
|
+
# Offense count: 6
|
|
41
41
|
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
|
42
42
|
Metrics/ParameterLists:
|
|
43
43
|
Max: 8
|
|
44
44
|
|
|
45
|
-
# Offense count:
|
|
45
|
+
# Offense count: 8
|
|
46
46
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
47
47
|
Metrics/PerceivedComplexity:
|
|
48
|
-
Max:
|
|
48
|
+
Max: 14
|
|
49
49
|
|
|
50
50
|
# Offense count: 1
|
|
51
51
|
Naming/AccessorMethodName:
|
|
52
52
|
Exclude:
|
|
53
53
|
- 'lib/lutaml/store/adapter/sqlite.rb'
|
|
54
54
|
|
|
55
|
-
# Offense count:
|
|
55
|
+
# Offense count: 1
|
|
56
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
57
|
+
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
|
|
58
|
+
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
|
|
59
|
+
Naming/MemoizedInstanceVariableName:
|
|
60
|
+
Exclude:
|
|
61
|
+
- 'lib/lutaml/store/query.rb'
|
|
62
|
+
|
|
63
|
+
# Offense count: 4
|
|
56
64
|
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
|
57
65
|
# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
|
|
58
66
|
Naming/MethodParameterName:
|
|
59
67
|
Exclude:
|
|
60
68
|
- 'demo/vcard_models.rb'
|
|
61
69
|
- 'demo/vcard_store_demo.rb'
|
|
70
|
+
- 'lib/lutaml/store/adapter/sqlite.rb'
|
|
62
71
|
|
|
63
72
|
# Offense count: 1
|
|
64
73
|
# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
|
|
@@ -84,7 +93,7 @@ Security/MarshalLoad:
|
|
|
84
93
|
Exclude:
|
|
85
94
|
- 'lib/lutaml/store/format/marshal_format.rb'
|
|
86
95
|
|
|
87
|
-
# Offense count:
|
|
96
|
+
# Offense count: 51
|
|
88
97
|
# Configuration parameters: AllowedConstants.
|
|
89
98
|
Style/Documentation:
|
|
90
99
|
Enabled: false
|
|
@@ -8,6 +8,8 @@ module Lutaml
|
|
|
8
8
|
@config = config
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
# ── Key-value operations ──
|
|
12
|
+
|
|
11
13
|
def get(key)
|
|
12
14
|
raise NotImplementedError
|
|
13
15
|
end
|
|
@@ -60,6 +62,32 @@ module Lutaml
|
|
|
60
62
|
keys.each_with_object({}) { |k, h| h[k] = delete(k) }
|
|
61
63
|
end
|
|
62
64
|
|
|
65
|
+
# ── Query operations ──
|
|
66
|
+
|
|
67
|
+
# Execute a query specification.
|
|
68
|
+
# Returns array of [storage_key, hash_data] pairs, sorted and paginated.
|
|
69
|
+
# Returns nil to indicate "not supported — fall back to in-memory scan".
|
|
70
|
+
def execute_query(_query)
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Count matching records without returning data.
|
|
75
|
+
# Returns nil to indicate "not supported — count execute_query results".
|
|
76
|
+
def count_query(_query)
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Fetch a batch of [storage_key, hash_data] pairs for keyset pagination.
|
|
81
|
+
# Returns nil to indicate "not supported — fall back to in-memory".
|
|
82
|
+
def batch_query(_query, _after: nil, _limit: 1000)
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Execute a block atomically. No-op for adapters without transaction support.
|
|
87
|
+
def transaction
|
|
88
|
+
yield
|
|
89
|
+
end
|
|
90
|
+
|
|
63
91
|
def stats
|
|
64
92
|
{ adapter: self.class.name }
|
|
65
93
|
end
|
|
@@ -3,136 +3,145 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Store
|
|
5
5
|
module Adapter
|
|
6
|
+
# In-memory key-value adapter optimized for read-heavy workloads.
|
|
7
|
+
# Writes are synchronized; reads are lock-free using snapshot copies.
|
|
6
8
|
class Memory < Base
|
|
7
9
|
def initialize(config = {})
|
|
8
10
|
super
|
|
9
11
|
@data = {}
|
|
10
|
-
@
|
|
12
|
+
@write_mutex = Mutex.new
|
|
13
|
+
@read_snapshot = {}.freeze
|
|
14
|
+
@snapshot_stale = true
|
|
11
15
|
@ttl_enabled = @config.fetch(:ttl_enabled, false)
|
|
12
|
-
@ttl_data = {}
|
|
16
|
+
@ttl_data = @ttl_enabled ? {} : nil
|
|
13
17
|
@default_ttl = @config[:default_ttl] || 3600
|
|
14
18
|
end
|
|
15
19
|
|
|
20
|
+
# ── Read operations (lock-free via snapshot) ──
|
|
21
|
+
|
|
16
22
|
def get(key)
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
cleanup_expired_if_needed
|
|
24
|
+
snapshot[key]
|
|
25
|
+
end
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
def exists?(key)
|
|
28
|
+
cleanup_expired_if_needed
|
|
29
|
+
snapshot.key?(key)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def all
|
|
33
|
+
cleanup_expired_if_needed
|
|
34
|
+
snapshot.dup
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def size
|
|
38
|
+
cleanup_expired_if_needed
|
|
39
|
+
snapshot.size
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def keys
|
|
43
|
+
cleanup_expired_if_needed
|
|
44
|
+
snapshot.keys
|
|
45
|
+
end
|
|
25
46
|
|
|
26
|
-
|
|
47
|
+
def each_key(&block)
|
|
48
|
+
cleanup_expired_if_needed
|
|
49
|
+
snapshot.each_key(&block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def bulk_get(keys)
|
|
53
|
+
cleanup_expired_if_needed
|
|
54
|
+
snap = snapshot
|
|
55
|
+
keys.each_with_object({}) do |k, h|
|
|
56
|
+
h[k] = snap[k]
|
|
27
57
|
end
|
|
28
58
|
end
|
|
29
59
|
|
|
60
|
+
# ── Write operations (synchronized) ──
|
|
61
|
+
|
|
30
62
|
def set(key, value, metadata = {})
|
|
31
|
-
@
|
|
63
|
+
@write_mutex.synchronize do
|
|
32
64
|
@data[key] = value
|
|
65
|
+
invalidate_snapshot
|
|
33
66
|
|
|
34
67
|
if @ttl_enabled
|
|
35
68
|
ttl_value = metadata[:ttl] || @default_ttl
|
|
36
69
|
@ttl_data[key] = Time.now + ttl_value if ttl_value
|
|
37
70
|
end
|
|
38
|
-
|
|
39
|
-
value
|
|
40
71
|
end
|
|
72
|
+
value
|
|
41
73
|
end
|
|
42
74
|
|
|
43
75
|
def delete(key)
|
|
44
|
-
@
|
|
76
|
+
@write_mutex.synchronize do
|
|
45
77
|
existed = @data.key?(key)
|
|
46
78
|
@data.delete(key)
|
|
47
|
-
@ttl_data
|
|
79
|
+
@ttl_data&.delete(key)
|
|
80
|
+
invalidate_snapshot
|
|
48
81
|
existed
|
|
49
82
|
end
|
|
50
83
|
end
|
|
51
84
|
|
|
52
|
-
def exists?(key)
|
|
53
|
-
@mutex.synchronize do
|
|
54
|
-
return false unless @data.key?(key)
|
|
55
|
-
|
|
56
|
-
if @ttl_enabled && expired?(key)
|
|
57
|
-
@data.delete(key)
|
|
58
|
-
@ttl_data.delete(key)
|
|
59
|
-
return false
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
true
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def all
|
|
67
|
-
@mutex.synchronize do
|
|
68
|
-
cleanup_expired if @ttl_enabled
|
|
69
|
-
@data.dup
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
85
|
def clear
|
|
74
|
-
@
|
|
86
|
+
@write_mutex.synchronize do
|
|
75
87
|
count = @data.size
|
|
76
88
|
@data.clear
|
|
77
|
-
@ttl_data
|
|
89
|
+
@ttl_data&.clear
|
|
90
|
+
invalidate_snapshot
|
|
78
91
|
count
|
|
79
92
|
end
|
|
80
93
|
end
|
|
81
94
|
|
|
82
|
-
def
|
|
83
|
-
@
|
|
84
|
-
|
|
85
|
-
@
|
|
95
|
+
def close
|
|
96
|
+
@write_mutex.synchronize do
|
|
97
|
+
@data.clear
|
|
98
|
+
@ttl_data&.clear
|
|
99
|
+
invalidate_snapshot
|
|
86
100
|
end
|
|
87
101
|
end
|
|
88
102
|
|
|
89
|
-
def
|
|
90
|
-
@
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
end
|
|
94
|
-
end
|
|
103
|
+
def bulk_set(key_value_pairs, ttl: nil)
|
|
104
|
+
@write_mutex.synchronize do
|
|
105
|
+
key_value_pairs.each do |key, value|
|
|
106
|
+
@data[key] = value
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
if @ttl_enabled
|
|
109
|
+
ttl_value = ttl || @default_ttl
|
|
110
|
+
@ttl_data[key] = Time.now + ttl_value if ttl_value
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
invalidate_snapshot
|
|
100
114
|
end
|
|
101
|
-
current_keys.each(&block)
|
|
102
115
|
end
|
|
103
116
|
|
|
104
|
-
def
|
|
105
|
-
@
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
def bulk_delete(keys)
|
|
118
|
+
@write_mutex.synchronize do
|
|
119
|
+
result = {}
|
|
120
|
+
keys.each do |key|
|
|
121
|
+
result[key] = @data.delete(key)
|
|
122
|
+
@ttl_data&.delete(key)
|
|
123
|
+
end
|
|
124
|
+
invalidate_snapshot
|
|
125
|
+
result
|
|
108
126
|
end
|
|
109
127
|
end
|
|
110
128
|
|
|
111
|
-
|
|
112
|
-
@mutex.synchronize do
|
|
113
|
-
cleanup_expired if @ttl_enabled
|
|
114
|
-
super.merge(
|
|
115
|
-
size: @data.size,
|
|
116
|
-
ttl_enabled: @ttl_enabled,
|
|
117
|
-
expired_keys: @ttl_enabled ? count_expired_keys : 0
|
|
118
|
-
)
|
|
119
|
-
end
|
|
120
|
-
end
|
|
129
|
+
# ── TTL operations ──
|
|
121
130
|
|
|
122
131
|
def cleanup_expired
|
|
123
132
|
return unless @ttl_enabled
|
|
124
133
|
|
|
125
|
-
@
|
|
126
|
-
|
|
127
|
-
@ttl_data.
|
|
128
|
-
|
|
134
|
+
@write_mutex.synchronize do
|
|
135
|
+
now = Time.now
|
|
136
|
+
expired_keys = @ttl_data.each_with_object([]) do |(key, expiry_time), arr|
|
|
137
|
+
arr << key if now > expiry_time
|
|
129
138
|
end
|
|
130
139
|
|
|
131
140
|
expired_keys.each do |key|
|
|
132
141
|
@data.delete(key)
|
|
133
142
|
@ttl_data.delete(key)
|
|
134
143
|
end
|
|
135
|
-
|
|
144
|
+
invalidate_snapshot unless expired_keys.empty?
|
|
136
145
|
expired_keys.size
|
|
137
146
|
end
|
|
138
147
|
end
|
|
@@ -140,7 +149,7 @@ module Lutaml
|
|
|
140
149
|
def set_ttl(key, ttl)
|
|
141
150
|
return false unless @ttl_enabled
|
|
142
151
|
|
|
143
|
-
@
|
|
152
|
+
@write_mutex.synchronize do
|
|
144
153
|
return false unless @data.key?(key)
|
|
145
154
|
|
|
146
155
|
if ttl
|
|
@@ -148,7 +157,7 @@ module Lutaml
|
|
|
148
157
|
else
|
|
149
158
|
@ttl_data.delete(key)
|
|
150
159
|
end
|
|
151
|
-
|
|
160
|
+
invalidate_snapshot
|
|
152
161
|
true
|
|
153
162
|
end
|
|
154
163
|
end
|
|
@@ -156,76 +165,159 @@ module Lutaml
|
|
|
156
165
|
def get_ttl(key)
|
|
157
166
|
return nil unless @ttl_enabled
|
|
158
167
|
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
snap = snapshot
|
|
169
|
+
return nil unless snap.key?(key)
|
|
170
|
+
return nil unless @ttl_data.key?(key)
|
|
171
|
+
|
|
172
|
+
remaining = @ttl_data[key] - Time.now
|
|
173
|
+
remaining.positive? ? remaining : nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# ── Query operations (lock-free via snapshot) ──
|
|
177
|
+
|
|
178
|
+
def execute_query(query)
|
|
179
|
+
cleanup_expired_if_needed
|
|
180
|
+
snap = snapshot
|
|
181
|
+
model_name = query.model_class.name
|
|
182
|
+
preds = query.predicates
|
|
183
|
+
|
|
184
|
+
results = []
|
|
185
|
+
snap.each do |key, value|
|
|
186
|
+
parsed = StorageKey.parse(key.to_s)
|
|
187
|
+
next unless parsed.class_name == model_name
|
|
188
|
+
next unless preds_all_match?(preds, value)
|
|
161
189
|
|
|
162
|
-
|
|
163
|
-
remaining = expiry_time - Time.now
|
|
164
|
-
remaining.positive? ? remaining : nil
|
|
190
|
+
results << [key, value]
|
|
165
191
|
end
|
|
192
|
+
|
|
193
|
+
results = apply_sort(results, query.orders)
|
|
194
|
+
results = apply_pagination(results, query.limit_value, query.offset_value)
|
|
166
195
|
end
|
|
167
196
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
197
|
+
def count_query(query)
|
|
198
|
+
cleanup_expired_if_needed
|
|
199
|
+
snap = snapshot
|
|
200
|
+
model_name = query.model_class.name
|
|
201
|
+
preds = query.predicates
|
|
172
202
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
203
|
+
count = 0
|
|
204
|
+
snap.each do |key, value|
|
|
205
|
+
parsed = StorageKey.parse(key.to_s)
|
|
206
|
+
next unless parsed.class_name == model_name
|
|
207
|
+
next unless preds_all_match?(preds, value)
|
|
208
|
+
|
|
209
|
+
count += 1
|
|
178
210
|
end
|
|
211
|
+
count
|
|
179
212
|
end
|
|
180
213
|
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
214
|
+
def batch_query(query, after: nil, limit: 1000)
|
|
215
|
+
cleanup_expired_if_needed
|
|
216
|
+
snap = snapshot
|
|
217
|
+
model_name = query.model_class.name
|
|
218
|
+
preds = query.predicates
|
|
219
|
+
|
|
220
|
+
sorted_keys = snap.keys.sort_by(&:to_s)
|
|
221
|
+
sorted_keys = sorted_keys.select { |k| k.to_s > after } if after
|
|
222
|
+
|
|
223
|
+
results = []
|
|
224
|
+
sorted_keys.each do |key|
|
|
225
|
+
parsed = StorageKey.parse(key.to_s)
|
|
226
|
+
next unless parsed.class_name == model_name
|
|
227
|
+
|
|
228
|
+
value = snap[key]
|
|
229
|
+
next unless value
|
|
230
|
+
next unless preds_all_match?(preds, value)
|
|
231
|
+
|
|
232
|
+
results << [key.to_s, value]
|
|
233
|
+
break if results.size >= limit
|
|
198
234
|
end
|
|
235
|
+
results
|
|
199
236
|
end
|
|
200
237
|
|
|
201
|
-
def
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
238
|
+
def stats
|
|
239
|
+
cleanup_expired_if_needed
|
|
240
|
+
snap = snapshot
|
|
241
|
+
super.merge(
|
|
242
|
+
size: snap.size,
|
|
243
|
+
ttl_enabled: @ttl_enabled,
|
|
244
|
+
expired_keys: @ttl_enabled ? count_expired_keys : 0
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
private
|
|
249
|
+
|
|
250
|
+
# Frozen snapshot — lock-free reads. Rebuilt lazily after writes.
|
|
251
|
+
def snapshot
|
|
252
|
+
if @snapshot_stale
|
|
253
|
+
@write_mutex.synchronize do
|
|
254
|
+
if @snapshot_stale
|
|
255
|
+
@read_snapshot = @data.dup.freeze
|
|
256
|
+
@snapshot_stale = false
|
|
257
|
+
end
|
|
207
258
|
end
|
|
208
|
-
result
|
|
209
259
|
end
|
|
260
|
+
@read_snapshot
|
|
210
261
|
end
|
|
211
262
|
|
|
212
|
-
|
|
263
|
+
def invalidate_snapshot
|
|
264
|
+
@snapshot_stale = true
|
|
265
|
+
end
|
|
213
266
|
|
|
214
|
-
def
|
|
215
|
-
return
|
|
216
|
-
|
|
267
|
+
def cleanup_expired_if_needed
|
|
268
|
+
return unless @ttl_enabled
|
|
269
|
+
|
|
270
|
+
now = Time.now
|
|
271
|
+
return unless @ttl_data.any? { |_k, expiry| now > expiry }
|
|
217
272
|
|
|
218
|
-
|
|
273
|
+
cleanup_expired
|
|
219
274
|
end
|
|
220
275
|
|
|
221
276
|
def count_expired_keys
|
|
222
277
|
return 0 unless @ttl_enabled
|
|
223
278
|
|
|
224
|
-
|
|
225
|
-
@ttl_data.
|
|
226
|
-
|
|
279
|
+
now = Time.now
|
|
280
|
+
@ttl_data.count { |_k, expiry| now > expiry }
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Inline predicate evaluation — avoids method dispatch overhead in tight loops
|
|
284
|
+
def preds_all_match?(predicates, value)
|
|
285
|
+
predicates.all? { |p| p.match?(value) }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def apply_sort(results, orders)
|
|
289
|
+
return results if orders.empty?
|
|
290
|
+
|
|
291
|
+
results.sort do |a, b|
|
|
292
|
+
orders.reduce(0) do |cmp, o|
|
|
293
|
+
break cmp unless cmp.zero?
|
|
294
|
+
|
|
295
|
+
va = a.last.is_a?(Hash) ? a.last[o.field.to_s] : nil
|
|
296
|
+
vb = b.last.is_a?(Hash) ? b.last[o.field.to_s] : nil
|
|
297
|
+
|
|
298
|
+
cmp_val = compare_values(va, vb)
|
|
299
|
+
o.direction == :desc ? -cmp_val : cmp_val
|
|
300
|
+
end
|
|
227
301
|
end
|
|
228
|
-
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def compare_values(val_a, val_b)
|
|
305
|
+
if val_a.nil? && val_b.nil?
|
|
306
|
+
0
|
|
307
|
+
elsif val_a.nil?
|
|
308
|
+
1
|
|
309
|
+
elsif val_b.nil?
|
|
310
|
+
-1
|
|
311
|
+
else
|
|
312
|
+
val_a <=> val_b || 0
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def apply_pagination(results, limit, offset)
|
|
317
|
+
start = offset || 0
|
|
318
|
+
results = results[start..] || []
|
|
319
|
+
results = results.first(limit) if limit
|
|
320
|
+
results
|
|
229
321
|
end
|
|
230
322
|
end
|
|
231
323
|
end
|