documentrix 0.3.0 → 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/CHANGES.md +11 -0
- data/documentrix.gemspec +2 -2
- data/lib/documentrix/documents/cache/sqlite_cache.rb +43 -12
- data/lib/documentrix/version.rb +1 -1
- data/spec/documentrix/documents/cache/sqlite_cache_spec.rb +41 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3ab97426ab9fbd4ec832a422a18d574f4c03b11f22391cbc6eded16b9ba0609
|
|
4
|
+
data.tar.gz: 991215fb26b4165a4b3562075d9742e3756218e2a24f3f32a550e232f59fb92f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d67e453e27428bcd8364349e988556af844caab5faa8a8edb3dcfba9650554b8dbfc66ad322a6f9f59d5ac64350d7006bf899a16b6c83f92909793301b6954d5
|
|
7
|
+
data.tar.gz: 49706d4984b27ee7b3d0f8ef8a9efec4a8e74d8bef78d78f15ed5c76f5bacc0faf20c36cef739aa78c1f68e5733696d16999bbd14228ae7be8e6cf9c9baf0a75
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-05-18 v0.3.1
|
|
4
|
+
|
|
5
|
+
- Fixed scoping bugs in `clear_by_source` and `source_exist?` by implementing a
|
|
6
|
+
`key LIKE ?` constraint.
|
|
7
|
+
- Added regression tests for prefix isolation in
|
|
8
|
+
`spec/documentrix/documents/cache/sqlite_cache_spec.rb`.
|
|
9
|
+
- Introduced the `start_with_prefix` method in
|
|
10
|
+
`lib/documentrix/documents/cache/sqlite_cache.rb` to unify prefix patterns.
|
|
11
|
+
- Updated `tags`, `size`, `clear_all_with_prefix`, and `each` to utilize the
|
|
12
|
+
new `start_with_prefix` method.
|
|
13
|
+
|
|
3
14
|
## 2026-05-17 v0.3.0
|
|
4
15
|
|
|
5
16
|
### New Features
|
data/documentrix.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: documentrix 0.3.
|
|
2
|
+
# stub: documentrix 0.3.1 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "documentrix".freeze
|
|
6
|
-
s.version = "0.3.
|
|
6
|
+
s.version = "0.3.1".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
@@ -114,7 +114,7 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
114
114
|
result = Documentrix::Utils::Tags.new
|
|
115
115
|
execute(%{
|
|
116
116
|
SELECT DISTINCT(tags) FROM records WHERE key LIKE ?
|
|
117
|
-
}, [
|
|
117
|
+
}, [ start_with_prefix ]
|
|
118
118
|
).flatten.each do
|
|
119
119
|
JSON(_1).each { |t| result.add(t) }
|
|
120
120
|
end
|
|
@@ -126,7 +126,10 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
126
126
|
#
|
|
127
127
|
# @return [ Integer ] the count of records
|
|
128
128
|
def size
|
|
129
|
-
execute(
|
|
129
|
+
execute(
|
|
130
|
+
%{SELECT COUNT(*) FROM records WHERE key LIKE ?},
|
|
131
|
+
[ start_with_prefix ]
|
|
132
|
+
).flatten.first
|
|
130
133
|
end
|
|
131
134
|
|
|
132
135
|
# The clear_for_tags method clears the cache for specific tags by deleting
|
|
@@ -154,7 +157,7 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
154
157
|
#
|
|
155
158
|
# @return [ Documentrix::Documents::RedisBackedMemoryCache ] self
|
|
156
159
|
def clear_all_with_prefix
|
|
157
|
-
execute(%{DELETE FROM records WHERE key LIKE ?}, [
|
|
160
|
+
execute(%{DELETE FROM records WHERE key LIKE ?}, [ start_with_prefix ])
|
|
158
161
|
self
|
|
159
162
|
end
|
|
160
163
|
|
|
@@ -172,9 +175,21 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
172
175
|
def clear_by_source(source, digest: nil, operator: ?=)
|
|
173
176
|
operator = '!=' if operator != ?=
|
|
174
177
|
if digest
|
|
175
|
-
execute(
|
|
178
|
+
execute(
|
|
179
|
+
%{
|
|
180
|
+
DELETE FROM records
|
|
181
|
+
WHERE key LIKE ? AND source = ? AND digest #{operator} ?
|
|
182
|
+
},
|
|
183
|
+
[ start_with_prefix, source, digest ]
|
|
184
|
+
)
|
|
176
185
|
else
|
|
177
|
-
execute(
|
|
186
|
+
execute(
|
|
187
|
+
%{
|
|
188
|
+
DELETE FROM records
|
|
189
|
+
WHERE key LIKE ? AND source = ?
|
|
190
|
+
},
|
|
191
|
+
[ start_with_prefix, source ]
|
|
192
|
+
)
|
|
178
193
|
end
|
|
179
194
|
self
|
|
180
195
|
end
|
|
@@ -194,9 +209,19 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
194
209
|
def source_exist?(source, digest: nil, operator: ?=)
|
|
195
210
|
operator = '!=' if operator != ?=
|
|
196
211
|
if digest
|
|
197
|
-
!!execute(
|
|
212
|
+
!!execute(
|
|
213
|
+
%{
|
|
214
|
+
SELECT 1 FROM records WHERE key LIKE ? AND source = ? AND digest #{operator} ?
|
|
215
|
+
},
|
|
216
|
+
[ start_with_prefix, source, digest ]
|
|
217
|
+
).first
|
|
198
218
|
else
|
|
199
|
-
!!execute(
|
|
219
|
+
!!execute(
|
|
220
|
+
%{
|
|
221
|
+
SELECT 1 FROM records WHERE key LIKE ? AND source = ?
|
|
222
|
+
},
|
|
223
|
+
[ start_with_prefix, source ]
|
|
224
|
+
).first
|
|
200
225
|
end
|
|
201
226
|
end
|
|
202
227
|
|
|
@@ -211,10 +236,9 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
211
236
|
block or return enum_for(__method__)
|
|
212
237
|
|
|
213
238
|
execute(%{
|
|
214
|
-
SELECT DISTINCT source
|
|
215
|
-
FROM records
|
|
239
|
+
SELECT DISTINCT source FROM records
|
|
216
240
|
WHERE key LIKE ? AND source IS NOT NULL
|
|
217
|
-
}, [
|
|
241
|
+
}, [ start_with_prefix ]).each do |source,|
|
|
218
242
|
source = source.full? or next
|
|
219
243
|
|
|
220
244
|
block.(source)
|
|
@@ -257,7 +281,7 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
257
281
|
# cache.each do |key, value|
|
|
258
282
|
# puts "#{key}: #{value}"
|
|
259
283
|
# end
|
|
260
|
-
def each(prefix:
|
|
284
|
+
def each(prefix: start_with_prefix, &block)
|
|
261
285
|
block or return enum_for(__method__, prefix:)
|
|
262
286
|
|
|
263
287
|
execute(%{
|
|
@@ -315,7 +339,7 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
315
339
|
SELECT key, tags, embedding_id
|
|
316
340
|
FROM records
|
|
317
341
|
WHERE key LIKE ?#{tags_where}
|
|
318
|
-
}, [
|
|
342
|
+
}, [ start_with_prefix ])
|
|
319
343
|
if tags_filter
|
|
320
344
|
records = records.select { |key, tags, embedding_id|
|
|
321
345
|
(tags_filter & JSON(tags.to_s).to_a).size >= 1
|
|
@@ -368,6 +392,13 @@ class Documentrix::Documents::Cache::SQLiteCache
|
|
|
368
392
|
|
|
369
393
|
private
|
|
370
394
|
|
|
395
|
+
# Returns the SQL LIKE pattern for records starting with the current prefix.
|
|
396
|
+
#
|
|
397
|
+
# @return [ String ] the prefix pattern used in SQL WHERE clauses
|
|
398
|
+
def start_with_prefix
|
|
399
|
+
"#@prefix%"
|
|
400
|
+
end
|
|
401
|
+
|
|
371
402
|
# The execute method executes an SQL query on the database by calling the
|
|
372
403
|
# \@database.execute method.
|
|
373
404
|
#
|
data/lib/documentrix/version.rb
CHANGED
|
@@ -250,6 +250,47 @@ describe Documentrix::Documents::SQLiteCache do
|
|
|
250
250
|
]
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
+
describe 'Prefix Isolation' do
|
|
254
|
+
let(:cache2) { cache.dup }
|
|
255
|
+
|
|
256
|
+
before do
|
|
257
|
+
cache2.prefix = 'other-'
|
|
258
|
+
|
|
259
|
+
# Setup shared sources and tags across prefixes
|
|
260
|
+
cache['foo'] = test_value.merge(source: 'shared.txt', tags: %w[ a ])
|
|
261
|
+
cache2['bar'] = test_value.merge(source: 'shared.txt', tags: %w[ a ])
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'does not leak clear_by_source' do
|
|
265
|
+
expect {
|
|
266
|
+
cache.clear_by_source('shared.txt')
|
|
267
|
+
}.to change { cache.size }.from(1).to(0)
|
|
268
|
+
|
|
269
|
+
expect(cache2.size).to eq 1
|
|
270
|
+
expect(cache2.key?('bar')).to be true
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it 'does not leak source_exist?' do
|
|
274
|
+
# Ensure we are checking a source that ONLY exists in the other prefix
|
|
275
|
+
cache.clear_all_with_prefix
|
|
276
|
+
cache2['baz'] = test_value.merge(source: 'only-in-2.txt')
|
|
277
|
+
|
|
278
|
+
expect(cache.source_exist?('only-in-2.txt')).to be false
|
|
279
|
+
expect(cache2.source_exist?('only-in-2.txt')).to be true
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it 'does not leak tags' do
|
|
283
|
+
cache.clear_all_with_prefix
|
|
284
|
+
cache2.clear_all_with_prefix
|
|
285
|
+
|
|
286
|
+
cache['foo'] = test_value.merge(tags: %w[ prefix1 ])
|
|
287
|
+
cache2['bar'] = test_value.merge(tags: %w[ prefix2 ])
|
|
288
|
+
|
|
289
|
+
expect(cache.tags.to_a).to match_array(['prefix1'])
|
|
290
|
+
expect(cache2.tags.to_a).to match_array(['prefix2'])
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
253
294
|
describe '#find_records' do
|
|
254
295
|
let(:needle) { [ 0.5 ] * 1_024 }
|
|
255
296
|
|