Skip to content

Commit 752a2c1

Browse files
committed
feat: add paradedb-named aliases for bm25-named API (paradedb/paradedb#5706)
1 parent afe980b commit 752a2c1

9 files changed

Lines changed: 160 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file. The format
66

77
### Added
88

9-
- Support the `paradedb` index access method (requires pg_search 0.25.0+, unreleased). `ParadeDB::Index` and `add_bm25_index` accept an `am:` option (`"paradedb"` or `"bm25"`), and schema dumps record `am:` when an index does not use the default.
9+
- Support the `paradedb` index access method (requires pg_search 0.25.0+, unreleased). `ParadeDB::Index` and `add_paradedb_index` accept an `am:` option (`"paradedb"` or `"bm25"`), and schema dumps record `am:` when an index does not use the default.
10+
- `add_paradedb_index`, `remove_paradedb_index`, and `reindex_paradedb_index` are the primary names for the migration helpers (connection and migration DSL). The `bm25`-named helpers (`add_bm25_index`, `remove_bm25_index`, `reindex_bm25`) remain fully supported aliases. Schema dumps and generated migrations now emit the `paradedb`-named helpers; `schema.rb` files using either name continue to load.
1011

1112
### Changed
1213

examples/autocomplete/setup.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def setup_autocomplete_table!
106106
puts " + Copied #{count} products from #{MockItem.table_name}"
107107

108108
puts "\nCreating autocomplete-optimized BM25 index..."
109-
conn.remove_bm25_index(:autocomplete_items, name: :autocomplete_items_idx, if_exists: true)
109+
conn.remove_paradedb_index(:autocomplete_items, name: :autocomplete_items_idx, if_exists: true)
110110
ensure_supported_am!(conn, AutocompleteItemIndex)
111111
conn.create_paradedb_index(AutocompleteItemIndex, if_not_exists: true)
112112

examples/faceted_search/setup.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def setup_mock_items!
5656
)
5757
conn.execute("DROP TABLE IF EXISTS mock_items_faceted_search CASCADE;")
5858
conn.execute("CREATE TABLE mock_items_faceted_search AS TABLE mock_items;")
59-
conn.remove_bm25_index(:mock_items_faceted_search, name: :mock_items_faceted_search_bm25_idx, if_exists: true)
59+
conn.remove_paradedb_index(:mock_items_faceted_search, name: :mock_items_faceted_search_bm25_idx, if_exists: true)
6060
ensure_supported_am!(conn, MockItemIndex)
6161
conn.create_paradedb_index(MockItemIndex, if_not_exists: true)
6262

examples/hybrid_rrf/setup.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def setup_mock_items!
6464
)
6565
conn.execute("DROP TABLE IF EXISTS mock_items_hybrid_rrf CASCADE;")
6666
conn.execute("CREATE TABLE mock_items_hybrid_rrf AS TABLE mock_items;")
67-
conn.remove_bm25_index(:mock_items_hybrid_rrf, name: :mock_items_hybrid_rrf_bm25_idx, if_exists: true)
67+
conn.remove_paradedb_index(:mock_items_hybrid_rrf, name: :mock_items_hybrid_rrf_bm25_idx, if_exists: true)
6868
ensure_supported_am!(conn, MockItemIndex)
6969
conn.create_paradedb_index(MockItemIndex, if_not_exists: true)
7070

lib/generators/parade_db/index/templates/migration.rb.tt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
1010
end
1111

1212
def down
13-
remove_bm25_index :<%= table_name %>, name: :<%= index_name %>, if_exists: true
13+
remove_paradedb_index :<%= table_name %>, name: :<%= index_name %>, if_exists: true
1414
end
1515
end

lib/parade_db/migration_helpers.rb

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ def add_bm25_index(table, fields:, key_field:, name: nil, index_options: nil, wh
3838

3939
create_paradedb_index(anonymous, if_not_exists: if_not_exists, concurrently: concurrently)
4040
end
41+
alias add_paradedb_index add_bm25_index
4142

4243
def remove_bm25_index(table, name: nil, if_exists: false)
4344
ensure_postgresql_adapter!
4445
index_name = (name || "#{table}_bm25_idx").to_s
4546
prefix = if_exists ? "IF EXISTS " : ""
4647
execute("DROP INDEX #{prefix}#{quote_table_name(index_name)}")
4748
end
49+
alias remove_paradedb_index remove_bm25_index
4850

4951
def reindex_bm25(table, name: nil, concurrently: false)
5052
ensure_postgresql_adapter!
@@ -56,6 +58,7 @@ def reindex_bm25(table, name: nil, concurrently: false)
5658
modifier = concurrently ? " CONCURRENTLY" : ""
5759
execute("REINDEX INDEX#{modifier} #{quote_table_name(index_name)}")
5860
end
61+
alias reindex_paradedb_index reindex_bm25
5962

6063
def dump_paradedb_indexes(stream)
6164
rows = paradedb_bm25_index_rows
@@ -343,7 +346,7 @@ def bm25_index_to_ruby(row)
343346
end
344347
end
345348

346-
statement = "add_bm25_index #{table.to_sym.inspect}, " \
349+
statement = "add_paradedb_index #{table.to_sym.inspect}, " \
347350
"fields: { #{fields_pairs.join(', ')} }, " \
348351
"key_field: #{key_field.to_sym.inspect}, " \
349352
"name: #{name.inspect}"
@@ -893,6 +896,28 @@ def remove_bm25_index(table, name: nil, if_exists: false)
893896
def reindex_bm25(table, name: nil, concurrently: false)
894897
connection.reindex_bm25(table, name: name, concurrently: concurrently)
895898
end
899+
900+
def add_paradedb_index(table, fields:, key_field:, name: nil, index_options: nil, where: nil, am: nil, if_not_exists: false, concurrently: false)
901+
connection.add_paradedb_index(
902+
table,
903+
fields: fields,
904+
key_field: key_field,
905+
name: name,
906+
index_options: index_options,
907+
where: where,
908+
am: am,
909+
if_not_exists: if_not_exists,
910+
concurrently: concurrently
911+
)
912+
end
913+
914+
def remove_paradedb_index(table, name: nil, if_exists: false)
915+
connection.remove_paradedb_index(table, name: name, if_exists: if_exists)
916+
end
917+
918+
def reindex_paradedb_index(table, name: nil, concurrently: false)
919+
connection.reindex_paradedb_index(table, name: name, concurrently: concurrently)
920+
end
896921
end
897922
end
898923

@@ -948,9 +973,12 @@ module CommandRecorderPatch
948973
%i[
949974
create_paradedb_index
950975
add_bm25_index
976+
add_paradedb_index
951977
remove_bm25_index
978+
remove_paradedb_index
952979
replace_paradedb_index
953980
reindex_bm25
981+
reindex_paradedb_index
954982
].each do |method_name|
955983
define_method(method_name) do |*args, &block|
956984
record(method_name, args, &block)
@@ -965,7 +993,7 @@ def invert_create_paradedb_index(args)
965993
compiled = resolve_paradedb_index_klass(index_klass).compiled_definition
966994
remove_options = Hash.ruby2_keywords_hash(name: compiled.index_name, if_exists: true)
967995

968-
[:remove_bm25_index, [compiled.table_name, remove_options]]
996+
[:remove_paradedb_index, [compiled.table_name, remove_options]]
969997
end
970998

971999
def invert_add_bm25_index(args)
@@ -976,14 +1004,20 @@ def invert_add_bm25_index(args)
9761004
remove_options[:name] = options[:name] if options[:name]
9771005
remove_options = Hash.ruby2_keywords_hash(remove_options)
9781006

979-
[:remove_bm25_index, [table, remove_options]]
1007+
[:remove_paradedb_index, [table, remove_options]]
9801008
end
1009+
alias_method :invert_add_paradedb_index, :invert_add_bm25_index
9811010

9821011
def invert_remove_bm25_index(_args)
9831012
raise ActiveRecord::IrreversibleMigration,
9841013
"remove_bm25_index is not automatically reversible. Use #up/#down or #reversible."
9851014
end
9861015

1016+
def invert_remove_paradedb_index(_args)
1017+
raise ActiveRecord::IrreversibleMigration,
1018+
"remove_paradedb_index is not automatically reversible. Use #up/#down or #reversible."
1019+
end
1020+
9871021
def invert_replace_paradedb_index(_args)
9881022
raise ActiveRecord::IrreversibleMigration,
9891023
"replace_paradedb_index is not automatically reversible. Use #up/#down or #reversible."
@@ -994,6 +1028,11 @@ def invert_reindex_bm25(_args)
9941028
"reindex_bm25 is not automatically reversible. Use #up/#down or #reversible."
9951029
end
9961030

1031+
def invert_reindex_paradedb_index(_args)
1032+
raise ActiveRecord::IrreversibleMigration,
1033+
"reindex_paradedb_index is not automatically reversible. Use #up/#down or #reversible."
1034+
end
1035+
9971036
def resolve_paradedb_index_klass(index_klass)
9981037
case index_klass
9991038
when String

spec/index_dsl_unit_spec.rb

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@
299299
recorder = Class.new do
300300
attr_reader :captured
301301

302-
def add_bm25_index(table, fields:, key_field:, name:, index_options: nil, if_not_exists: false)
302+
def add_paradedb_index(table, fields:, key_field:, name:, index_options: nil, if_not_exists: false)
303303
@captured = {
304304
table: table,
305305
fields: fields,
@@ -346,7 +346,7 @@ def add_bm25_index(table, fields:, key_field:, name:, index_options: nil, if_not
346346
)
347347

348348
assert_equal(
349-
%(add_bm25_index :products, fields: { id: {}, description: {} }, key_field: :id, name: "products_bm25_idx", am: "bm25"),
349+
%(add_paradedb_index :products, fields: { id: {}, description: {} }, key_field: :id, name: "products_bm25_idx", am: "bm25"),
350350
ruby_stmt
351351
)
352352
end
@@ -370,7 +370,7 @@ def add_bm25_index(table, fields:, key_field:, name:, index_options: nil, if_not
370370
)
371371

372372
assert_equal(
373-
%(add_bm25_index :products, fields: { id: {}, description: {} }, key_field: :id, name: "products_bm25_idx"),
373+
%(add_paradedb_index :products, fields: { id: {}, description: {} }, key_field: :id, name: "products_bm25_idx"),
374374
ruby_stmt
375375
)
376376
end
@@ -389,4 +389,37 @@ def add_bm25_index(table, fields:, key_field:, name:, index_options: nil, if_not
389389
assert_equal "key_field=id, target_segment_count=((17))", with_sql
390390
assert_equal "WHERE ((archived_at IS NULL))", trailing_sql
391391
end
392+
393+
it "exposes paradedb-named aliases for the bm25 migration helpers" do
394+
conn = ActiveRecord::Base.connection
395+
396+
{
397+
add_paradedb_index: :add_bm25_index,
398+
remove_paradedb_index: :remove_bm25_index,
399+
reindex_paradedb_index: :reindex_bm25
400+
}.each do |alias_name, original|
401+
assert conn.respond_to?(alias_name)
402+
assert conn.respond_to?(original)
403+
assert_equal conn.method(original).parameters, conn.method(alias_name).parameters
404+
assert ParadeDB::MigrationDSL.method_defined?(alias_name)
405+
assert ParadeDB::MigrationDSL.method_defined?(original)
406+
end
407+
end
408+
409+
it "inverts index helpers to paradedb-named commands in the command recorder" do
410+
recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection)
411+
412+
%i[add_paradedb_index add_bm25_index].each do |command|
413+
method, args = recorder.inverse_of(command, [:products, { key_field: :id, name: :products_alias_idx }])
414+
assert_equal :remove_paradedb_index, method
415+
assert_equal :products, args.first
416+
assert_equal({ if_exists: true, name: :products_alias_idx }, args.last)
417+
end
418+
419+
%i[remove_paradedb_index reindex_paradedb_index remove_bm25_index reindex_bm25].each do |command|
420+
assert_raises(ActiveRecord::IrreversibleMigration) do
421+
recorder.inverse_of(command, [:products])
422+
end
423+
end
424+
end
392425
end

spec/index_generator_unit_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ def generated_migration_path(model)
9090
expect(content).to include("create_paradedb_index(ProductIndex, if_not_exists: true)")
9191
end
9292

93-
it "calls remove_bm25_index in down" do
93+
it "calls remove_paradedb_index in down" do
9494
run_generator(["Product"])
9595
content = File.read(generated_migration_path("Product"))
96-
expect(content).to include("remove_bm25_index :products, name: :products_bm25_idx, if_exists: true")
96+
expect(content).to include("remove_paradedb_index :products, name: :products_bm25_idx, if_exists: true")
9797
end
9898

9999
it "does not include disable_ddl_transaction! by default" do
@@ -126,7 +126,7 @@ def generated_migration_path(model)
126126
run_generator(["LineItem"])
127127
content = File.read(generated_migration_path("LineItem"))
128128
expect(content).to include("class CreateLineItemBm25Index")
129-
expect(content).to include("remove_bm25_index :line_items")
129+
expect(content).to include("remove_paradedb_index :line_items")
130130
end
131131
end
132132
end

spec/index_migration_integration_spec.rb

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,71 @@ class IndexMigrationBookByNameIndex < ParadeDB::Index
274274
assert_not index_exists?("books_custom_bm25_idx")
275275
end
276276

277+
it "supports the paradedb-named alias helpers end-to-end" do
278+
conn = ActiveRecord::Base.connection
279+
conn.remove_paradedb_index(:books, if_exists: true)
280+
conn.remove_paradedb_index(:books, name: :books_alias_idx, if_exists: true)
281+
282+
conn.add_paradedb_index(
283+
:books,
284+
fields: {
285+
id: {},
286+
title: { tokenizer: ParadeDB::Tokenizer.simple() }
287+
},
288+
key_field: :id,
289+
name: :books_alias_idx,
290+
am: paradedb_test_am
291+
)
292+
assert index_exists?("books_alias_idx")
293+
294+
conn.reindex_paradedb_index(:books, name: :books_alias_idx)
295+
296+
conn.remove_paradedb_index(:books, name: :books_alias_idx)
297+
assert_not index_exists?("books_alias_idx")
298+
end
299+
300+
it "rolls back add_paradedb_index in change migrations" do
301+
conn = ActiveRecord::Base.connection
302+
conn.remove_paradedb_index(:books, if_exists: true)
303+
conn.remove_paradedb_index(:books, name: :books_alias_idx, if_exists: true)
304+
305+
migration = build_change_migration do
306+
add_paradedb_index(
307+
:books,
308+
fields: {
309+
id: {},
310+
title: { tokenizer: ParadeDB::Tokenizer.simple() }
311+
},
312+
key_field: :id,
313+
name: :books_alias_idx,
314+
am: paradedb_test_am,
315+
if_not_exists: true
316+
)
317+
end
318+
319+
run_migration(migration, :up, connection: conn)
320+
assert index_exists?("books_alias_idx")
321+
322+
run_migration(migration, :down, connection: conn)
323+
assert_not index_exists?("books_alias_idx")
324+
end
325+
326+
it "raises for remove_paradedb_index in change migrations" do
327+
conn = ActiveRecord::Base.connection
328+
329+
migration = build_change_migration do
330+
remove_paradedb_index(:books, if_exists: true)
331+
end
332+
333+
run_migration(migration, :up, connection: conn)
334+
assert_not index_exists?("books_bm25_idx")
335+
336+
error = assert_raises(ActiveRecord::IrreversibleMigration) do
337+
run_migration(migration, :down, connection: conn)
338+
end
339+
assert_includes error.message, "remove_paradedb_index"
340+
end
341+
277342
it "raises for remove_bm25_index in change migrations" do
278343
conn = ActiveRecord::Base.connection
279344

@@ -399,13 +464,13 @@ class IndexMigrationBookByNameIndex < ParadeDB::Index
399464
schema = stream.string
400465

401466
add_stmt = schema.each_line.find do |line|
402-
line.include?("add_bm25_index :books") &&
467+
line.include?("add_paradedb_index :books") &&
403468
line.include?("title_simple") &&
404469
line.include?("target_segment_count")
405470
end
406471

407472
assert_equal <<~RUBY.strip, add_stmt.to_s.strip
408-
add_bm25_index :books, fields: { id: {}, title: { tokenizer: ParadeDB::Tokenizer.simple(options: { :alias => "title_simple" }) } }, key_field: :id, name: "books_bm25_idx", index_options: { :target_segment_count => 17 }#{paradedb_test_am_dump_suffix}
473+
add_paradedb_index :books, fields: { id: {}, title: { tokenizer: ParadeDB::Tokenizer.simple(options: { :alias => "title_simple" }) } }, key_field: :id, name: "books_bm25_idx", index_options: { :target_segment_count => 17 }#{paradedb_test_am_dump_suffix}
409474
RUBY
410475
expect(schema).not_to match(/add_index.*books_bm25_idx/)
411476
expect(schema).not_to match(/t\.index.*books_bm25_idx/)
@@ -497,13 +562,13 @@ class IndexMigrationBookByNameIndex < ParadeDB::Index
497562
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
498563
schema = stream.string
499564
add_stmt = schema.each_line.find do |line|
500-
line.include?("add_bm25_index :books") &&
565+
line.include?("add_paradedb_index :books") &&
501566
line.include?("title_simple") &&
502567
line.include?("target_segment_count")
503568
end
504569

505570
assert_equal <<~RUBY.strip, add_stmt.to_s.strip
506-
add_bm25_index :books, fields: { id: {}, title: { tokenizers: [ParadeDB::Tokenizer.literal(), ParadeDB::Tokenizer.simple(options: { :alias => "title_simple" })] } }, key_field: :id, name: "books_bm25_idx", index_options: { :target_segment_count => 17 }#{paradedb_test_am_dump_suffix}
571+
add_paradedb_index :books, fields: { id: {}, title: { tokenizers: [ParadeDB::Tokenizer.literal(), ParadeDB::Tokenizer.simple(options: { :alias => "title_simple" })] } }, key_field: :id, name: "books_bm25_idx", index_options: { :target_segment_count => 17 }#{paradedb_test_am_dump_suffix}
507572
RUBY
508573

509574
conn.remove_bm25_index(:books, if_exists: true)
@@ -532,11 +597,11 @@ class IndexMigrationBookByNameIndex < ParadeDB::Index
532597
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
533598
schema = stream.string
534599
add_stmt = schema.each_line.find do |line|
535-
line.include?("add_bm25_index :books") && line.include?("metadata_title_text")
600+
line.include?("add_paradedb_index :books") && line.include?("metadata_title_text")
536601
end
537602

538603
assert_equal <<~RUBY.strip, add_stmt.to_s.strip
539-
add_bm25_index :books, fields: { id: {}, "metadata ->> 'title'::text" => { tokenizer: ParadeDB::Tokenizer.simple(options: { :lowercase => true, :alias => "metadata_title_text" }) } }, key_field: :id, name: "books_bm25_idx"#{paradedb_test_am_dump_suffix}
604+
add_paradedb_index :books, fields: { id: {}, "metadata ->> 'title'::text" => { tokenizer: ParadeDB::Tokenizer.simple(options: { :lowercase => true, :alias => "metadata_title_text" }) } }, key_field: :id, name: "books_bm25_idx"#{paradedb_test_am_dump_suffix}
540605
RUBY
541606

542607
conn.remove_bm25_index(:books, if_exists: true)
@@ -564,13 +629,13 @@ class IndexMigrationBookByNameIndex < ParadeDB::Index
564629
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
565630
schema = stream.string
566631
add_stmt = schema.each_line.find do |line|
567-
line.include?("add_bm25_index :books") &&
632+
line.include?("add_paradedb_index :books") &&
568633
line.include?("title_simple") &&
569634
line.include?("where:")
570635
end
571636

572637
assert_equal <<~RUBY.strip, add_stmt.to_s.strip
573-
add_bm25_index :books, fields: { id: {}, title: { tokenizer: ParadeDB::Tokenizer.simple(options: { :alias => "title_simple" }) } }, key_field: :id, name: "books_bm25_idx", where: "author IS NOT NULL"#{paradedb_test_am_dump_suffix}
638+
add_paradedb_index :books, fields: { id: {}, title: { tokenizer: ParadeDB::Tokenizer.simple(options: { :alias => "title_simple" }) } }, key_field: :id, name: "books_bm25_idx", where: "author IS NOT NULL"#{paradedb_test_am_dump_suffix}
574639
RUBY
575640

576641
conn.remove_bm25_index(:books, if_exists: true)

0 commit comments

Comments
 (0)