rails-paradedb 0.4.0 → 0.5.0
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/CHANGELOG.md +12 -1
- data/lib/parade_db/index.rb +20 -9
- data/lib/parade_db/migration_helpers.rb +67 -10
- data/lib/parade_db/version.rb +1 -1
- 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: 04a77503859e70b3362f1a5679a2d1b229c62c5c49a0383fbe1164b317d902f2
|
|
4
|
+
data.tar.gz: 1ec3bc0ae18c1232ddfc1b67aec6eb3a92dde0285297fa4a45afa7b789ae9d7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e81872356be536dd22423f39c7c8a899630dd5a601d5c54c6a8cbfc2c165ba3aa1c8edbe016a88ff9bc3492fcb0925c35cf31055dbca39de59f05f1b49ce2cec
|
|
7
|
+
data.tar.gz: 0c3781ba0d93f5aa4067545354f87e186da71c43fab1a257c9715ce821dfde474549c200d22f9814f75747a687a7e21626148de2b474759bc9317395f413902f
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.0] - 2026-04-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Support partial indexes via `where:` in `add_bm25_index` and `ParadeDB::Index`
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Allow aliased indexed expressions like `"(rating + 1)" => { alias: "rating" }`
|
|
16
|
+
|
|
7
17
|
## [0.4.0] - 2026-04-09
|
|
8
18
|
|
|
9
19
|
### Changed
|
|
@@ -110,7 +120,8 @@ All notable changes to this project will be documented in this file. The format
|
|
|
110
120
|
- Schema dump/load round-trip for tokenizer configuration and index options
|
|
111
121
|
(including `target_segment_count`)
|
|
112
122
|
|
|
113
|
-
[Unreleased]: https://github.com/paradedb/rails-paradedb/compare/v0.
|
|
123
|
+
[Unreleased]: https://github.com/paradedb/rails-paradedb/compare/v0.5.0...HEAD
|
|
124
|
+
[0.5.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.5.0
|
|
114
125
|
[0.4.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.4.0
|
|
115
126
|
[0.3.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.3.0
|
|
116
127
|
[0.2.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.2.0
|
data/lib/parade_db/index.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module ParadeDB
|
|
4
4
|
class Index
|
|
5
5
|
class << self
|
|
6
|
-
attr_writer :table_name, :key_field, :index_name, :fields, :index_options
|
|
6
|
+
attr_writer :table_name, :key_field, :index_name, :fields, :index_options, :where
|
|
7
7
|
|
|
8
8
|
def table_name
|
|
9
9
|
@table_name
|
|
@@ -25,6 +25,10 @@ module ParadeDB
|
|
|
25
25
|
@index_options || {}
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def where
|
|
29
|
+
@where
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def default_index_name
|
|
29
33
|
return nil if table_name.nil?
|
|
30
34
|
|
|
@@ -166,15 +170,16 @@ module ParadeDB
|
|
|
166
170
|
FIELD_OPTION_KEYS = %i[fast record normalizer expand_dots].freeze
|
|
167
171
|
|
|
168
172
|
class Compiled
|
|
169
|
-
attr_reader :table_name, :key_field, :index_name, :entries, :index_options, :field_options
|
|
173
|
+
attr_reader :table_name, :key_field, :index_name, :entries, :index_options, :field_options, :where
|
|
170
174
|
|
|
171
|
-
def initialize(table_name:, key_field:, index_name:, entries:, index_options:, field_options:)
|
|
175
|
+
def initialize(table_name:, key_field:, index_name:, entries:, index_options:, field_options:, where:)
|
|
172
176
|
@table_name = table_name
|
|
173
177
|
@key_field = key_field
|
|
174
178
|
@index_name = index_name
|
|
175
179
|
@entries = entries
|
|
176
180
|
@index_options = index_options
|
|
177
181
|
@field_options = field_options
|
|
182
|
+
@where = where
|
|
178
183
|
end
|
|
179
184
|
end
|
|
180
185
|
Entry = Struct.new(:source, :expression, :tokenizer, :options, :query_key, keyword_init: true)
|
|
@@ -203,7 +208,8 @@ module ParadeDB
|
|
|
203
208
|
index_name: index_name,
|
|
204
209
|
entries: entries,
|
|
205
210
|
index_options: index_options,
|
|
206
|
-
field_options: field_options
|
|
211
|
+
field_options: field_options,
|
|
212
|
+
where: klass.where
|
|
207
213
|
)
|
|
208
214
|
end
|
|
209
215
|
|
|
@@ -223,10 +229,6 @@ module ParadeDB
|
|
|
223
229
|
raise InvalidIndexDefinition, "fields must be a Hash"
|
|
224
230
|
end
|
|
225
231
|
|
|
226
|
-
build_entries_from_structured_fields(raw_fields)
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def build_entries_from_structured_fields(raw_fields)
|
|
230
232
|
entries = []
|
|
231
233
|
field_options = {}
|
|
232
234
|
|
|
@@ -246,7 +248,16 @@ module ParadeDB
|
|
|
246
248
|
tokenizers = normalized[:tokenizers]
|
|
247
249
|
single_tokenizer_keys_present = TokenizerParser::TOKENIZER_SINGLE_KEYS.any? { |key| normalized.key?(key) }
|
|
248
250
|
|
|
249
|
-
|
|
251
|
+
is_alias = normalized[:alias] && normalized.length == 1
|
|
252
|
+
if is_alias
|
|
253
|
+
entries << Entry.new(
|
|
254
|
+
source: source_name,
|
|
255
|
+
expression: expression?(source_name),
|
|
256
|
+
tokenizer: nil,
|
|
257
|
+
options: {},
|
|
258
|
+
query_key: normalized[:alias]
|
|
259
|
+
)
|
|
260
|
+
elsif tokenizers
|
|
250
261
|
if single_tokenizer_keys_present
|
|
251
262
|
raise InvalidIndexDefinition,
|
|
252
263
|
"field #{source_name.inspect} cannot mix :tokenizers with :tokenizer/:args/:named_args/:filters/:stemmer/:alias"
|
|
@@ -21,7 +21,7 @@ module ParadeDB
|
|
|
21
21
|
remember_schema_index_reference(resolved)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def add_bm25_index(table, fields:, key_field:, name: nil, index_options: nil, if_not_exists: false)
|
|
24
|
+
def add_bm25_index(table, fields:, key_field:, name: nil, index_options: nil, where: nil, if_not_exists: false)
|
|
25
25
|
ensure_postgresql_adapter!
|
|
26
26
|
anonymous = Class.new(ParadeDB::Index)
|
|
27
27
|
anonymous.table_name = table
|
|
@@ -29,6 +29,7 @@ module ParadeDB
|
|
|
29
29
|
anonymous.index_name = name unless name.nil?
|
|
30
30
|
anonymous.fields = fields
|
|
31
31
|
anonymous.index_options = index_options unless index_options.nil?
|
|
32
|
+
anonymous.where = where unless where.nil?
|
|
32
33
|
|
|
33
34
|
create_paradedb_index(anonymous, if_not_exists: if_not_exists)
|
|
34
35
|
end
|
|
@@ -80,11 +81,12 @@ module ParadeDB
|
|
|
80
81
|
prefix = if_not_exists ? "IF NOT EXISTS " : ""
|
|
81
82
|
fields_sql = compiled.entries.map { |entry| bm25_entry_sql(entry) }.join(", ")
|
|
82
83
|
with_options_sql = bm25_with_options_sql(compiled)
|
|
84
|
+
where_sql = compiled.where ? "\nWHERE #{compiled.where}" : ""
|
|
83
85
|
|
|
84
86
|
<<~SQL.strip.gsub(/\s+/, " ")
|
|
85
87
|
CREATE INDEX #{prefix}#{quote_table_name(compiled.index_name)} ON #{quote_table_name(compiled.table_name)}
|
|
86
88
|
USING bm25 (#{fields_sql})
|
|
87
|
-
WITH (#{with_options_sql})
|
|
89
|
+
WITH (#{with_options_sql})#{where_sql}
|
|
88
90
|
SQL
|
|
89
91
|
end
|
|
90
92
|
|
|
@@ -177,6 +179,11 @@ module ParadeDB
|
|
|
177
179
|
|
|
178
180
|
def bm25_entry_sql(entry)
|
|
179
181
|
source_sql = bm25_source_sql(entry)
|
|
182
|
+
|
|
183
|
+
if entry.tokenizer.nil? && entry.query_key != entry.source
|
|
184
|
+
return "(#{source_sql}::pdb.alias(#{quote(entry.query_key)}))"
|
|
185
|
+
end
|
|
186
|
+
|
|
180
187
|
return source_sql if entry.tokenizer.nil?
|
|
181
188
|
|
|
182
189
|
"(#{source_sql}::#{tokenizer_sql(entry.tokenizer, entry.options)})"
|
|
@@ -282,7 +289,8 @@ module ParadeDB
|
|
|
282
289
|
SELECT
|
|
283
290
|
c.relname AS index_name,
|
|
284
291
|
t.relname AS table_name,
|
|
285
|
-
pg_get_indexdef(c.oid) AS indexdef
|
|
292
|
+
pg_get_indexdef(c.oid) AS indexdef,
|
|
293
|
+
pg_get_expr(i.indpred, i.indrelid) AS where_clause
|
|
286
294
|
FROM pg_class c
|
|
287
295
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
288
296
|
JOIN pg_index i ON i.indexrelid = c.oid
|
|
@@ -306,6 +314,7 @@ module ParadeDB
|
|
|
306
314
|
key_field = extract_bm25_key_field(indexdef)
|
|
307
315
|
index_options = extract_bm25_index_options(indexdef)
|
|
308
316
|
fields_sql = extract_bm25_fields_sql(indexdef)
|
|
317
|
+
where = normalize_bm25_where_clause(row["where_clause"])
|
|
309
318
|
|
|
310
319
|
if key_field && fields_sql
|
|
311
320
|
field_sqls = split_bm25_top_level(fields_sql).map(&:strip)
|
|
@@ -334,6 +343,7 @@ module ParadeDB
|
|
|
334
343
|
unless index_options.empty?
|
|
335
344
|
statement += ", index_options: #{ruby_hash_literal(index_options)}"
|
|
336
345
|
end
|
|
346
|
+
statement += ", where: #{where.inspect}" if where
|
|
337
347
|
statement
|
|
338
348
|
else
|
|
339
349
|
"execute #{indexdef.inspect}"
|
|
@@ -351,10 +361,7 @@ module ParadeDB
|
|
|
351
361
|
end
|
|
352
362
|
|
|
353
363
|
def extract_bm25_index_options(indexdef)
|
|
354
|
-
|
|
355
|
-
return {} unless with_match
|
|
356
|
-
|
|
357
|
-
with_sql = with_match[1]
|
|
364
|
+
with_sql, = extract_bm25_with_components(indexdef)
|
|
358
365
|
options = {}
|
|
359
366
|
split_sql_arguments(with_sql).each do |argument|
|
|
360
367
|
key, value_sql = split_assignment(argument)
|
|
@@ -378,7 +385,6 @@ module ParadeDB
|
|
|
378
385
|
|
|
379
386
|
def extract_bm25_fields_sql(indexdef)
|
|
380
387
|
match = indexdef.match(/USING\s+bm25\s*\(/im)
|
|
381
|
-
return nil unless match
|
|
382
388
|
|
|
383
389
|
start = match.end(0)
|
|
384
390
|
depth = 1
|
|
@@ -390,11 +396,61 @@ module ParadeDB
|
|
|
390
396
|
end
|
|
391
397
|
pos += 1
|
|
392
398
|
end
|
|
393
|
-
|
|
399
|
+
raise "Found invalid index definition `#{indexdef}`" if depth != 0
|
|
394
400
|
|
|
395
401
|
indexdef[start..pos - 2]
|
|
396
402
|
end
|
|
397
403
|
|
|
404
|
+
def extract_bm25_with_components(indexdef)
|
|
405
|
+
match = indexdef.match(/WITH\s*\(/im)
|
|
406
|
+
start = match.end(0)
|
|
407
|
+
depth = 1
|
|
408
|
+
pos = start
|
|
409
|
+
while pos < indexdef.length && depth > 0
|
|
410
|
+
case indexdef[pos]
|
|
411
|
+
when "(" then depth += 1
|
|
412
|
+
when ")" then depth -= 1
|
|
413
|
+
end
|
|
414
|
+
pos += 1
|
|
415
|
+
end
|
|
416
|
+
raise "Found invalid index definition `#{indexdef}`" if depth != 0
|
|
417
|
+
|
|
418
|
+
with_sql = indexdef[start..pos - 2]
|
|
419
|
+
trailing_sql = indexdef[pos..]&.strip
|
|
420
|
+
trailing_sql = nil if trailing_sql&.empty?
|
|
421
|
+
|
|
422
|
+
[with_sql, trailing_sql]
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def normalize_bm25_where_clause(where)
|
|
426
|
+
return nil if where.nil?
|
|
427
|
+
|
|
428
|
+
normalized = where.to_s.strip
|
|
429
|
+
return nil if normalized.empty?
|
|
430
|
+
|
|
431
|
+
while bm25_wrapped_in_parentheses?(normalized)
|
|
432
|
+
normalized = normalized[1...-1].strip
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
normalized.empty? ? nil : normalized
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def bm25_wrapped_in_parentheses?(sql)
|
|
439
|
+
return false unless sql.start_with?("(") && sql.end_with?(")")
|
|
440
|
+
|
|
441
|
+
depth = 0
|
|
442
|
+
sql.each_char.with_index do |char, idx|
|
|
443
|
+
case char
|
|
444
|
+
when "(" then depth += 1
|
|
445
|
+
when ")"
|
|
446
|
+
depth -= 1
|
|
447
|
+
return false if depth.zero? && idx < sql.length - 1
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
depth.zero?
|
|
452
|
+
end
|
|
453
|
+
|
|
398
454
|
def split_bm25_top_level(str)
|
|
399
455
|
parts = []
|
|
400
456
|
current = +""
|
|
@@ -798,13 +854,14 @@ if defined?(ActiveRecord::Migration)
|
|
|
798
854
|
connection.replace_paradedb_index(index_klass)
|
|
799
855
|
end
|
|
800
856
|
|
|
801
|
-
def add_bm25_index(table, fields:, key_field:, name: nil, index_options: nil, if_not_exists: false)
|
|
857
|
+
def add_bm25_index(table, fields:, key_field:, name: nil, index_options: nil, where: nil, if_not_exists: false)
|
|
802
858
|
connection.add_bm25_index(
|
|
803
859
|
table,
|
|
804
860
|
fields: fields,
|
|
805
861
|
key_field: key_field,
|
|
806
862
|
name: name,
|
|
807
863
|
index_options: index_options,
|
|
864
|
+
where: where,
|
|
808
865
|
if_not_exists: if_not_exists
|
|
809
866
|
)
|
|
810
867
|
end
|
data/lib/parade_db/version.rb
CHANGED