test: cover test_combined_gte_and_lte_in_one_hash_uses_the_range_index

This commit is contained in:
dualfroz committed 2026-09-12 16:58:08 +02:00
1 parent a1118e05ac
commit 3c554dff31
1 file changed
+157
+157
View File
@@ -637,3 +637,160 @@ class OrmIndexRangeRelationTest < Minitest::Test
assert_equal 1, @store.range_hits assert_equal 1, @store.range_hits
end end
def test_combined_gte_and_lte_in_one_hash_uses_the_range_index
@store.reset_counts!
results = RangeIndexedProduct.where(price: { gte: 10, lte: 30 }).order(:price).to_a
assert_equal %w[mid pricey], results.map { |p| p['name'] }
assert_equal 0, @store.scan_count
assert_equal 1, @store.range_hits
end
def test_a_ruby_range_value_uses_the_range_index
@store.reset_counts!
results = RangeIndexedProduct.where(price: 10..30).order(:price).to_a
assert_equal %w[mid pricey], results.map { |p| p['name'] }
assert_equal 0, @store.scan_count
assert_equal 1, @store.range_hits
end
def test_an_exclusive_end_ruby_range_excludes_the_boundary
@store.reset_counts!
results = RangeIndexedProduct.where(price: 15...25).to_a
assert_equal ['mid'], results.map { |p| p['name'] }
assert_equal 0, @store.scan_count
end
def test_two_chained_wheres_narrow_the_same_column_via_and
@store.reset_counts!
results = RangeIndexedProduct.where(price: { gte: 10 }).where(price: { lte: 30 }).order(:price).to_a
assert_equal %w[mid pricey], results.map { |p| p['name'] }
assert_equal 0, @store.scan_count, 'the AND of two range clauses on the same column must still avoid a scan'
end
def test_a_hash_mixing_a_range_operator_with_a_non_range_operator_falls_back_to_a_scan
@store.reset_counts!
results = RangeIndexedProduct.where(price: { gte: 10, ne: 25 }).order(:price).to_a
assert_equal %w[mid luxury], results.map { |p| p['name'] }
assert_equal 1, @store.scan_count, ':ne cannot be expressed as a contiguous bound, so this must be a full scan'
assert_equal 0, @store.range_hits
end
def test_or_across_columns_still_falls_back_to_a_scan_when_only_one_side_is_indexed
@store.reset_counts!
results = RangeIndexedProduct.where(price: { gte: 30 }).or(RangeIndexedProduct.where(name: 'cheap')).to_a
assert_equal %w[cheap luxury], results.map { |p| p['name'] }.sort
assert_equal 1, @store.scan_count, 'name has no index, so OrNode cannot union candidates from both sides'
# The price side IS individually range-indexed, and OrNode#indexed_candidates
# tries it before discovering the name side has no covering index at all -
# that one probe is cheap (an O(log n) index lookup, not a scan) and its
# result is simply discarded once the other side comes back nil.
assert_equal 1, @store.range_hits
end
end
class OrmIndexInListRelationTest < Minitest::Test
def setup
@dir = Dir.mktmpdir('prism-index-in-relation')
@store = CountingIndexStore.new(File.join(@dir, 'test.pstore'))
@store.create_table(:widgets, %w[id sku status])
@store.add_index(:widgets, :sku)
widget_class = Class.new(Prism::Orm::Model) { table_name :widgets }
widget_class.store = @store
Object.const_set(:InListWidget, widget_class)
@a = InListWidget.create!(sku: 'AAA', status: 'active')
@b = InListWidget.create!(sku: 'BBB', status: 'active')
@c = InListWidget.create!(sku: 'CCC', status: 'retired')
end
def teardown
FileUtils.remove_entry(@dir)
Object.send(:remove_const, :InListWidget) if Object.const_defined?(:InListWidget)
end
def test_where_id_in_never_scans_even_with_no_add_index_call_for_id
@store.reset_counts!
results = InListWidget.where(id: [@a.id, @c.id]).to_a
assert_equal %w[AAA CCC], results.map { |w| w['sku'] }.sort
assert_equal 0, @store.scan_count
assert_equal 1, @store.in_hits
end
def test_where_on_an_in_indexed_column_never_scans
@store.reset_counts!
results = InListWidget.where(sku: %w[AAA CCC]).to_a
assert_equal %w[AAA CCC], results.map { |w| w['sku'] }.sort
assert_equal 0, @store.scan_count
assert_equal 1, @store.in_hits
end
def test_where_on_an_unindexed_column_in_list_falls_back_to_a_scan
@store.reset_counts!
results = InListWidget.where(status: ['retired']).to_a
assert_equal ['CCC'], results.map { |w| w['sku'] }
assert_equal 1, @store.scan_count
assert_equal 0, @store.in_hits
end
def test_unknown_ids_are_silently_dropped_not_nil_rows
@store.reset_counts!
results = InListWidget.where(id: [@a.id, @a.id + 999]).to_a
assert_equal [@a.id], results.map(&:id)
end
def test_two_chained_id_in_wheres_intersect_instead_of_scanning
@store.reset_counts!
results = InListWidget.where(id: [@a.id, @b.id, @c.id]).where(id: [@b.id, @c.id]).to_a
assert_equal [@b.id, @c.id].sort, results.map(&:id).sort
assert_equal 0, @store.scan_count
end
def test_an_equality_condition_alongside_an_id_in_list_still_uses_an_index_and_stays_correct
@store.reset_counts!
results = InListWidget.where(id: [@a.id, @b.id, @c.id]).where(sku: 'BBB').to_a
assert_equal ['BBB'], results.map { |w| w['sku'] }
assert_equal 0, @store.scan_count
end
end
class OrmIndexEqualityRangeStoreTest < Minitest::Test
def setup
@dir = Dir.mktmpdir('prism-index-eq-range-store')
@store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore'))
@store.create_table(:posts, %w[id featured position])
end
def teardown
FileUtils.remove_entry(@dir)
end
def test_returns_nil_without_a_covering_composite_index
@store.insert(:posts, featured: true, position: 1)
assert_nil @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position, gte: 0)
end
def test_a_single_column_index_on_the_range_column_alone_does_not_cover_it
@store.add_index(:posts, :position)
@store.insert(:posts, featured: true, position: 1)
assert_nil @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position, gte: 0)
end
def test_finds_rows_within_the_prefix_and_bounds
@store.add_index(:posts, %i[featured position])
@store.insert(:posts, featured: true, position: 1)
@store.insert(:posts, featured: true, position: 2)
@store.insert(:posts, featured: true, position: 3)
@store.insert(:posts, featured: false, position: 1)
matches = @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position, lt: 3)
assert_equal [1, 2], matches.map { |row| row['position'] }.sort
end
def test_equality_key_order_from_the_caller_does_not_matter
wide = Prism::Orm::Store.new(File.join(@dir, 'order.pstore'))
wide.create_table(:tasks, %w[id project priority position])
wide.add_index(:tasks, %i[project priority position])
wide.insert(:tasks, project: 'a', priority: 'high', position: 1)
wide.insert(:tasks, project: 'a', priority: 'high', position: 2)