From 4ca517b1a7b241e6ef2ba9ada0ac3d1f34f9085f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 16:16:07 +0200 Subject: [PATCH] test: cover teardown and test_add_column_backfill_rebuilds_an_index_b... --- test/test_orm_index.rb | 162 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/test/test_orm_index.rb b/test/test_orm_index.rb index dfebb66..1c23195 100644 --- a/test/test_orm_index.rb +++ b/test/test_orm_index.rb @@ -158,3 +158,165 @@ class OrmIndexColumnMigrationTest < Minitest::Test @store.create_table(:posts, %w[id title]) end + def teardown + FileUtils.remove_entry(@dir) + end + + def test_add_column_backfill_rebuilds_an_index_built_before_the_column_existed + id1 = @store.insert(:posts, title: 'first') + id2 = @store.insert(:posts, title: 'second') + @store.add_index(:posts, :status) + + @store.add_column(:posts, :status, default: 'draft') + + matches = @store.lookup_by_index(:posts, { 'status' => 'draft' }) + refute_nil matches, 'the index should still cover the column after the backfill' + assert_equal [id1, id2].sort, matches.map { |row| row['id'] }.sort + end + + def test_add_column_backfill_drops_the_stale_nil_key_from_the_index + @store.insert(:posts, title: 'first') + @store.add_index(:posts, :status) + + @store.add_column(:posts, :status, default: 'draft') + + assert_equal [], @store.lookup_by_index(:posts, { 'status' => nil }), + 'no row should still be indexed under the pre-backfill nil value' + end + + def test_add_column_backfill_keeps_a_range_capable_index_usable + id1 = @store.insert(:posts, title: 'first') + id2 = @store.insert(:posts, title: 'second') + @store.add_index(:posts, :priority) + + @store.add_column(:posts, :priority, default: 5) + + matches = @store.lookup_by_index_range(:posts, :priority, gte: 1) + refute_nil matches + assert_equal [id1, id2].sort, matches.map { |row| row['id'] }.sort + end + + def test_remove_column_drops_a_single_column_index_covering_it + @store.add_index(:posts, :status, name: 'idx_status') + @store.remove_column(:posts, :status) + + assert_equal [], @store.indexes(:posts) + assert_nil @store.lookup_by_index(:posts, { 'status' => 'draft' }), + 'a dropped column must never be reported as indexed again' + end + + def test_remove_column_drops_a_composite_index_covering_it + @store.add_column(:posts, :status, default: 'draft') + @store.add_index(:posts, %i[title status]) + + @store.remove_column(:posts, :title) + + assert_equal [], @store.indexes(:posts), + 'a composite index cannot survive with one of its columns gone' + end + + def test_remove_column_leaves_indexes_on_other_columns_untouched + @store.add_column(:posts, :status, default: 'draft') + @store.add_index(:posts, :title) + @store.add_index(:posts, :status) + + @store.remove_column(:posts, :status) + + assert_equal ['index_posts_on_title'], @store.indexes(:posts).map { |index| index[:name] } + end + + def test_remove_column_then_readding_it_starts_with_a_clean_index_free_slate + id = @store.insert(:posts, title: 'first') + @store.add_index(:posts, :status) + @store.add_column(:posts, :status, default: 'draft') + @store.remove_column(:posts, :status) + + @store.add_column(:posts, :status, default: 'archived') + + assert_equal [], @store.indexes(:posts), + 'removing the column must have dropped the earlier index for good' + assert_equal 'archived', @store.find(:posts, id)['status'] + end +end + +class OrmIndexRangeStoreTest < Minitest::Test + def setup + @dir = Dir.mktmpdir('prism-index-range-store') + @store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore')) + @store.create_table(:users, %w[id email age]) + end + + def teardown + FileUtils.remove_entry(@dir) + end + + def test_single_column_index_is_range_capable_and_sorted + @store.insert(:users, email: 'c@x.com', age: 30) + @store.insert(:users, email: 'a@x.com', age: 10) + @store.insert(:users, email: 'b@x.com', age: 20) + @store.add_index(:users, :age) + + matches = @store.lookup_by_index_range(:users, :age, gte: 20) + assert_equal [20, 30], matches.map { |row| row['age'] }.sort + end + + def test_composite_index_is_never_range_capable + @store.add_index(:users, [:email, :age]) + @store.insert(:users, email: 'a@x.com', age: 10) + assert_nil @store.lookup_by_index_range(:users, :age, gte: 0) + end + + def test_lookup_by_index_range_returns_nil_without_a_covering_index + @store.insert(:users, email: 'a@x.com', age: 10) + assert_nil @store.lookup_by_index_range(:users, :age, gte: 0) + end + + def test_gte_is_inclusive + @store.add_index(:users, :age) + @store.insert(:users, email: 'a@x.com', age: 20) + matches = @store.lookup_by_index_range(:users, :age, gte: 20) + assert_equal 1, matches.size + end + + def test_gt_is_exclusive + @store.add_index(:users, :age) + @store.insert(:users, email: 'a@x.com', age: 20) + assert_equal [], @store.lookup_by_index_range(:users, :age, gt: 20) + end + + def test_lte_is_inclusive + @store.add_index(:users, :age) + @store.insert(:users, email: 'a@x.com', age: 20) + matches = @store.lookup_by_index_range(:users, :age, lte: 20) + assert_equal 1, matches.size + end + + def test_lt_is_exclusive + @store.add_index(:users, :age) + @store.insert(:users, email: 'a@x.com', age: 20) + assert_equal [], @store.lookup_by_index_range(:users, :age, lt: 20) + end + + def test_combined_lower_and_upper_bound + @store.add_index(:users, :age) + [5, 15, 25, 35, 45].each { |age| @store.insert(:users, email: "#{age}@x.com", age: age) } + matches = @store.lookup_by_index_range(:users, :age, gte: 15, lte: 35) + assert_equal [15, 25, 35], matches.map { |row| row['age'] }.sort + end + + def test_nil_valued_rows_are_never_returned_by_a_range_lookup + @store.add_index(:users, :age) + @store.insert(:users, email: 'a@x.com', age: nil) + @store.insert(:users, email: 'b@x.com', age: 10) + matches = @store.lookup_by_index_range(:users, :age, gte: 0) + assert_equal ['b@x.com'], matches.map { |row| row['email'] } + end + + def test_index_backfilled_from_existing_rows_is_range_capable + @store.insert(:users, email: 'a@x.com', age: 10) + @store.insert(:users, email: 'b@x.com', age: 20) + @store.add_index(:users, :age) + matches = @store.lookup_by_index_range(:users, :age, gte: 15) + assert_equal ['b@x.com'], matches.map { |row| row['email'] } + end +