Skip to content

Commit 2714c3d

Browse files
committed
tryscanindex: do column matching first, to use possibly rebound matches in both sanity check and index expr rebinding
add test for this scenario
1 parent 36ffa5b commit 2714c3d

2 files changed

Lines changed: 75 additions & 46 deletions

File tree

src/function/table/table_scan.cpp

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -493,16 +493,38 @@ bool TryScanIndex(ART &art, const ColumnList &column_list, TableFunctionInitInpu
493493
index_exprs.push_back(expr->Copy());
494494
}
495495

496-
// If this is a view, the column IDs are relative to the view projection
496+
// If this is a view, the column IDs are (may be?) relative to the view projection
497497
auto &indexed_columns = art.GetColumnIds();
498498

499499
// Allow composite ART scans
500500
if (indexed_columns.size() != index_exprs.size()) {
501501
return false;
502502
}
503503

504-
// ...only if each expression has a single column reference, and
505-
// that single column reference positionally matches that of the index (this is guaranteed? paranoid?)
504+
// Resolve bound column references in the index_expr against the current input projection
505+
bool rewrite_index_exprs = false;
506+
vector<column_t> index_column_to_input_pos;
507+
index_column_to_input_pos.resize(indexed_columns.size(), std::numeric_limits<idx_t>::max());
508+
509+
// Associate indexed columns to input columns
510+
for (idx_t i = 0; i < indexed_columns.size(); ++i) {
511+
for (idx_t j = 0; j < input.column_ids.size(); ++j) {
512+
if (indexed_columns[i] == input.column_ids[j]) {
513+
rewrite_index_exprs = i != j;
514+
index_column_to_input_pos.at(i) = j;
515+
}
516+
}
517+
}
518+
519+
// Make sure that all indexed_columns were bound, or bail out
520+
for (auto col : index_column_to_input_pos) {
521+
if (col == std::numeric_limits<idx_t>::max()) {
522+
return false;
523+
}
524+
}
525+
526+
// Allow scan only if index expressions reference ONE column each, and that column
527+
// is associated with an indexed_column
506528
// NOTE: We do not push down multi-column filters, e.g., 42 = a + b.
507529
for (idx_t i = 0; i < index_exprs.size(); ++i) {
508530
unordered_set<column_t> referenced_columns;
@@ -521,70 +543,57 @@ bool TryScanIndex(ART &art, const ColumnList &column_list, TableFunctionInitInpu
521543
}
522544

523545
auto referenced_column = *referenced_columns.begin();
546+
547+
// The column for this position matches the indexed_column ID for this position directly
524548
auto direct_match = referenced_column == indexed_columns[i];
525-
auto remapped_match =
526-
input.column_ids.size() > referenced_column && input.column_ids[referenced_column] == indexed_columns[i];
527549

528-
if (!(direct_match || remapped_match)) {
550+
// We should know if there is a different mapping for this reference.
551+
// If there is not, it won't match, so it is not worth trying.
552+
if (!direct_match && !rewrite_index_exprs) {
529553
return false;
530554
}
531-
}
532-
533-
// Resolve bound column references in the index_expr against the current input projection
534-
vector<column_t> index_column_to_proj_pos;
535-
index_column_to_proj_pos.resize(indexed_columns.size(), std::numeric_limits<idx_t>::max());
536555

537-
bool found_index_column_in_input = false;
538-
539-
// Associate indexed columns to input columns
540-
for (idx_t i = 0; i < indexed_columns.size(); ++i) {
541-
for (idx_t j = 0; j < input.column_ids.size(); ++j) {
542-
if (indexed_columns[i] == input.column_ids[j]) {
543-
index_column_to_proj_pos.at(i) = j;
544-
found_index_column_in_input = true;
545-
}
546-
}
547-
}
548-
549-
if (!found_index_column_in_input) {
550-
return false;
551-
}
556+
auto remapped_column_id = index_column_to_input_pos[referenced_column];
557+
auto remapped_match =
558+
input.column_ids.size() > remapped_column_id && input.column_ids[remapped_column_id] == indexed_columns[i];
552559

553-
for (auto col : index_column_to_proj_pos) {
554-
if (col == std::numeric_limits<idx_t>::max()) {
560+
if (!(direct_match || remapped_match)) {
555561
return false;
556562
}
557563
}
558564

559-
// Update the bound column refs within all index_exprs
560-
for (auto &index_expr : index_exprs) {
561-
ExpressionIterator::EnumerateExpression(index_expr, [&](Expression &expr) {
562-
if (expr.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF) {
563-
return;
564-
}
565+
// If the position of the indexed_columns differs from the order of the input, remap the index expressions
566+
if (rewrite_index_exprs) {
567+
for (auto &index_expr : index_exprs) {
568+
ExpressionIterator::EnumerateExpression(index_expr, [&](Expression &expr) {
569+
if (expr.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF) {
570+
return;
571+
}
565572

566-
auto &bound_column_ref_expr = expr.Cast<BoundColumnRefExpression>();
573+
auto &bound_column_ref_expr = expr.Cast<BoundColumnRefExpression>();
567574

568-
// If the bound column references an indexed column, update it
569-
for (idx_t i = 0; i < indexed_columns.size(); ++i) {
570-
if (bound_column_ref_expr.binding.column_index == indexed_columns[i]) {
571-
bound_column_ref_expr.binding.column_index = index_column_to_proj_pos[i];
572-
break;
575+
// If the bound column references an indexed column, update it
576+
for (idx_t i = 0; i < indexed_columns.size(); ++i) {
577+
auto remapped_index = index_column_to_input_pos[bound_column_ref_expr.binding.column_index];
578+
if (input.column_ids[remapped_index] == indexed_columns[i]) {
579+
bound_column_ref_expr.binding.column_index = index_column_to_input_pos[i];
580+
break;
581+
}
573582
}
574-
}
575-
});
583+
});
584+
}
576585
}
577586

578587
// The indexes of the filters match input.column_indexes, which are: i -> column_index.
579-
// Reuse the index <-> projection mappings from index expr rebinding
588+
// Reuse the index <-> projection mappings from index expr rebinding (which are canonical even if not rewriting)
580589
vector<vector<unique_ptr<Expression>>> index_filters;
581590

582-
for (idx_t i = 0; i < index_column_to_proj_pos.size(); ++i) {
591+
for (idx_t i = 0; i < index_column_to_input_pos.size(); ++i) {
583592
auto column_def = &column_list.GetColumn(LogicalIndex(indexed_columns[i]));
584-
auto maybe_filter = filter_set.filters.find(index_column_to_proj_pos[i]);
593+
auto maybe_filter = filter_set.filters.find(index_column_to_input_pos[i]);
585594
if (maybe_filter != filter_set.filters.end()) {
586595
auto filter = &maybe_filter->second;
587-
auto filter_expressions = ExtractFilterExpressions(*column_def, *filter, index_column_to_proj_pos[i]);
596+
auto filter_expressions = ExtractFilterExpressions(*column_def, *filter, index_column_to_input_pos[i]);
588597

589598
index_filters.push_back(std::move(filter_expressions));
590599
}

test/sql/index/art/issues/test_art_view_col_binding.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,23 @@ select z, y, x from test_view where upper(x) = '526';
4646

4747
statement ok
4848
drop index test_upper_x;
49+
50+
statement ok
51+
create or replace table other_test_table (
52+
name VARCHAR NOT NULL,
53+
id BIGINT NOT NULL,
54+
age INTEGER,
55+
status VARCHAR NOT NULL
56+
);
57+
58+
# test simple permutation of initial table projection
59+
statement ok
60+
CREATE INDEX index_id_other_test_table ON other_test_table (id);
61+
62+
statement ok
63+
CREATE OR REPLACE VIEW ott_view AS SELECT * FROM other_test_table;
64+
65+
query II
66+
explain analyze select * from ott_view where id = 1;
67+
----
68+
analyzed_plan <REGEX>:.*Index Scan.*

0 commit comments

Comments
 (0)