test: cover test_backfills_from_existing_rows
This commit is contained in:
1 parent
3c554dff31
commit
6b7a2bd965
1 file changed
+188
@@ -794,3 +794,191 @@ class OrmIndexEqualityRangeStoreTest < Minitest::Test
|
|||||||
wide.insert(:tasks, project: 'a', priority: 'high', position: 1)
|
wide.insert(:tasks, project: 'a', priority: 'high', position: 1)
|
||||||
wide.insert(:tasks, project: 'a', priority: 'high', position: 2)
|
wide.insert(:tasks, project: 'a', priority: 'high', position: 2)
|
||||||
|
|
||||||
|
declared_order = wide.lookup_by_index_equality_range(:tasks, { project: 'a', priority: 'high' }, :position, gte: 0)
|
||||||
|
reversed_order = wide.lookup_by_index_equality_range(:tasks, { priority: 'high', project: 'a' }, :position, gte: 0)
|
||||||
|
assert_equal [1, 2], declared_order.map { |row| row['position'] }.sort
|
||||||
|
assert_equal declared_order.map { |row| row['id'] }.sort, reversed_order.map { |row| row['id'] }.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_backfills_from_existing_rows
|
||||||
|
@store.insert(:posts, featured: true, position: 1)
|
||||||
|
@store.insert(:posts, featured: true, position: 2)
|
||||||
|
@store.add_index(:posts, %i[featured position])
|
||||||
|
|
||||||
|
matches = @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position, gte: 2)
|
||||||
|
assert_equal [2], matches.map { |row| row['position'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tracks_inserts_updates_and_deletes
|
||||||
|
@store.add_index(:posts, %i[featured position])
|
||||||
|
a = @store.insert(:posts, featured: true, position: 1)
|
||||||
|
@store.insert(:posts, featured: true, position: 2)
|
||||||
|
|
||||||
|
assert_equal [1, 2], @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position,
|
||||||
|
gte: 0).map { |r| r['position'] }.sort
|
||||||
|
|
||||||
|
@store.update(:posts, a, position: 9)
|
||||||
|
assert_equal [2, 9], @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position,
|
||||||
|
gte: 0).map { |r| r['position'] }.sort
|
||||||
|
|
||||||
|
@store.delete(:posts, a)
|
||||||
|
assert_equal [2], @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position,
|
||||||
|
gte: 0).map { |r| r['position'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_switching_the_prefix_value_moves_a_row_to_the_other_partition
|
||||||
|
@store.add_index(:posts, %i[featured position])
|
||||||
|
id = @store.insert(:posts, featured: true, position: 1)
|
||||||
|
|
||||||
|
@store.update(:posts, id, featured: false)
|
||||||
|
assert_equal [], @store.lookup_by_index_equality_range(:posts, { 'featured' => true }, :position, gte: 0)
|
||||||
|
assert_equal [1], @store.lookup_by_index_equality_range(:posts, { 'featured' => false }, :position,
|
||||||
|
gte: 0).map { |r| r['position'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_an_unknown_prefix_value_matches_nothing_without_a_scan
|
||||||
|
@store.add_index(:posts, %i[featured position])
|
||||||
|
@store.insert(:posts, featured: true, position: 1)
|
||||||
|
|
||||||
|
assert_equal [], @store.lookup_by_index_equality_range(:posts, { 'featured' => 'nonexistent' }, :position, gte: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_a_three_column_composite_index_still_works_with_a_two_column_prefix
|
||||||
|
@store = Prism::Orm::Store.new(File.join(@dir, 'wide.pstore'))
|
||||||
|
@store.create_table(:tasks, %w[id project priority position])
|
||||||
|
@store.add_index(:tasks, %i[project priority position])
|
||||||
|
@store.insert(:tasks, project: 'a', priority: 'high', position: 1)
|
||||||
|
@store.insert(:tasks, project: 'a', priority: 'high', position: 2)
|
||||||
|
@store.insert(:tasks, project: 'a', priority: 'low', position: 1)
|
||||||
|
|
||||||
|
matches = @store.lookup_by_index_equality_range(:tasks, { project: 'a', priority: 'high' }, :position, gte: 0)
|
||||||
|
assert_equal [1, 2], matches.map { |row| row['position'] }.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lookup_by_index_range_still_refuses_a_composite_index_unchanged_behavior
|
||||||
|
@store.add_index(:posts, %i[featured position])
|
||||||
|
@store.insert(:posts, featured: true, position: 1)
|
||||||
|
assert_nil @store.lookup_by_index_range(:posts, :position, gte: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
# A paranoid model's Model.all already carries `where(deleted_at: nil)`
|
||||||
|
# before any caller-added `.where(featured: ...)` - real regression this
|
||||||
|
# session's blog_demo.rb run caught: the index prefix is only `featured`,
|
||||||
|
# so `equalities` legitimately arrives with an EXTRA column
|
||||||
|
# (`deleted_at`) the index knows nothing about. That must still resolve
|
||||||
|
# through the index (using only the subset it actually covers), not fall
|
||||||
|
# back to a scan just because the caller happened to also constrain an
|
||||||
|
# unrelated column.
|
||||||
|
def test_an_equality_column_outside_the_index_prefix_does_not_block_the_lookup
|
||||||
|
@store.add_index(:posts, %i[featured position])
|
||||||
|
@store.insert(:posts, featured: true, position: 1, deleted_at: nil)
|
||||||
|
@store.insert(:posts, featured: true, position: 5, deleted_at: nil)
|
||||||
|
|
||||||
|
matches = @store.lookup_by_index_equality_range(:posts, { 'deleted_at' => nil, 'featured' => true },
|
||||||
|
:position, lt: 3)
|
||||||
|
assert_equal [1], matches.map { |row| row['position'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_the_most_specific_covering_composite_index_wins_when_several_could_answer
|
||||||
|
wide = Prism::Orm::Store.new(File.join(@dir, 'multi.pstore'))
|
||||||
|
wide.create_table(:tasks, %w[id project priority position])
|
||||||
|
wide.add_index(:tasks, %i[project position])
|
||||||
|
wide.add_index(:tasks, %i[project priority position])
|
||||||
|
wide.insert(:tasks, project: 'a', priority: 'high', position: 1)
|
||||||
|
wide.insert(:tasks, project: 'a', priority: 'low', position: 2)
|
||||||
|
|
||||||
|
matches = wide.lookup_by_index_equality_range(:tasks, { project: 'a', priority: 'high' }, :position, gte: 0)
|
||||||
|
assert_equal [1], matches.map { |row| row['position'] },
|
||||||
|
'the 3-column index covering priority too should be preferred over the 2-column one'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class OrmIndexEqualityRangeRelationTest < Minitest::Test
|
||||||
|
def setup
|
||||||
|
@dir = Dir.mktmpdir('prism-index-eq-range-relation')
|
||||||
|
@store = CountingIndexStore.new(File.join(@dir, 'test.pstore'))
|
||||||
|
@store.create_table(:posts, %w[id featured position])
|
||||||
|
@store.add_index(:posts, %i[featured position])
|
||||||
|
|
||||||
|
post_class = Class.new(Prism::Orm::Model) { table_name :posts }
|
||||||
|
post_class.store = @store
|
||||||
|
Object.const_set(:EqRangeIndexedPost, post_class)
|
||||||
|
|
||||||
|
EqRangeIndexedPost.create!(featured: true, position: 1)
|
||||||
|
EqRangeIndexedPost.create!(featured: true, position: 2)
|
||||||
|
EqRangeIndexedPost.create!(featured: true, position: 3)
|
||||||
|
EqRangeIndexedPost.create!(featured: false, position: 1)
|
||||||
|
EqRangeIndexedPost.create!(featured: false, position: 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
FileUtils.remove_entry(@dir)
|
||||||
|
Object.send(:remove_const, :EqRangeIndexedPost) if Object.const_defined?(:EqRangeIndexedPost)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_two_chained_wheres_scope_equality_then_range_never_scan
|
||||||
|
@store.reset_counts!
|
||||||
|
results = EqRangeIndexedPost.where(featured: true).where(position: { lt: 3 }).order(:position).to_a
|
||||||
|
assert_equal [1, 2], results.map { |p| p['position'] }
|
||||||
|
assert_equal 0, @store.scan_count
|
||||||
|
assert_equal 1, @store.eq_range_hits
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_single_where_with_both_conditions_in_one_hash_never_scans
|
||||||
|
@store.reset_counts!
|
||||||
|
results = EqRangeIndexedPost.where(featured: true, position: { gte: 2 }).to_a
|
||||||
|
assert_equal [2, 3], results.map { |p| p['position'] }.sort
|
||||||
|
assert_equal 0, @store.scan_count
|
||||||
|
assert_equal 1, @store.eq_range_hits
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_a_ruby_range_value_alongside_an_equality_uses_the_composite_index
|
||||||
|
@store.reset_counts!
|
||||||
|
results = EqRangeIndexedPost.where(featured: false).where(position: 1..1).to_a
|
||||||
|
assert_equal [1], results.map { |p| p['position'] }
|
||||||
|
assert_equal 0, @store.scan_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_the_other_scope_partition_is_unaffected_by_the_first
|
||||||
|
@store.reset_counts!
|
||||||
|
results = EqRangeIndexedPost.where(featured: false).where(position: { gte: 0 }).to_a
|
||||||
|
assert_equal [1, 2], results.map { |p| p['position'] }.sort
|
||||||
|
assert_equal 0, @store.scan_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_range_without_any_equality_falls_back_to_a_scan_not_the_composite_index
|
||||||
|
# This fixture only has the composite [featured, position] index, no
|
||||||
|
# standalone index on position - a bare range with no equality column
|
||||||
|
# to pair with can use neither the plain range path (single-column
|
||||||
|
# only) nor the composite one (needs an equality prefix), so a scan is
|
||||||
|
# the correct, honest answer here, not a false "it found an index".
|
||||||
|
@store.reset_counts!
|
||||||
|
results = EqRangeIndexedPost.where(position: { gte: 3 }).to_a
|
||||||
|
assert_equal [3], results.map { |p| p['position'] }
|
||||||
|
assert_equal 1, @store.scan_count
|
||||||
|
assert_equal 0, @store.eq_range_hits, 'no equality column means no composite prefix to look up'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_equality_without_any_range_falls_back_to_a_scan_not_the_composite_index
|
||||||
|
# Same reasoning as above, mirrored: this fixture has no standalone
|
||||||
|
# index on featured either, only the composite - a bare equality with
|
||||||
|
# no range column to pair with can't use a composite index that
|
||||||
|
# requires ALL its columns pinned (see Store#lookup_by_index), so this
|
||||||
|
# is a scan too, not a spurious composite hit.
|
||||||
|
@store.reset_counts!
|
||||||
|
results = EqRangeIndexedPost.where(featured: true).to_a
|
||||||
|
assert_equal 3, results.size
|
||||||
|
assert_equal 1, @store.scan_count
|
||||||
|
assert_equal 0, @store.eq_range_hits
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflects_a_reorder_through_the_underlying_model
|
||||||
|
post = EqRangeIndexedPost.where(featured: true).where(position: 1).first
|
||||||
|
post.class.store.increment_counter(post.class.table_name, post.id, 'position', 10)
|
||||||
|
|
||||||
|
@store.reset_counts!
|
||||||
|
results = EqRangeIndexedPost.where(featured: true).where(position: { lt: 3 }).to_a
|
||||||
|
assert_equal [2], results.map { |p| p['position'] }
|
||||||
|
assert_equal 0, @store.scan_count
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user