Skip to content

Commit 211ba53

Browse files
Mytherinsgrebnov
authored andcommitted
Fix index resolution when querying table with index via view (duckdb#18319)
Index scans would fail to match as in duckdb#17290 if the position of the column used in the filter did not match that of the `index_expr`: the ART `filter_match` assertion fails: https://github.com/duckdb/duckdb/blob/0b83e5d2f68bc02dfefde74b846bd039f078affa/src/execution/index/art/art.cpp#L160-L161 It appears to work in the direct table query case (as shown in the issue) because a projection puts the indexed column first (so the binding matches). I am unsure if this is the correct approach/place to make the change, however rebinding the column ref to its correct position relative to the input projection appears to fix the issue. Included is a test that fails on v1.3.2. Fixes duckdb#17290
1 parent c81d583 commit 211ba53

2 files changed

Lines changed: 83 additions & 2 deletions

File tree

src/function/table/table_scan.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
#include "duckdb/catalog/catalog_entry/duck_table_entry.hpp"
44
#include "duckdb/catalog/dependency_list.hpp"
5+
#include "duckdb/common/enums/expression_type.hpp"
56
#include "duckdb/common/mutex.hpp"
67
#include "duckdb/common/serializer/deserializer.hpp"
78
#include "duckdb/common/serializer/serializer.hpp"
9+
#include "duckdb/common/typedefs.hpp"
10+
#include "duckdb/common/unique_ptr.hpp"
811
#include "duckdb/execution/index/art/art.hpp"
912
#include "duckdb/function/function_set.hpp"
1013
#include "duckdb/main/attached_database.hpp"
1114
#include "duckdb/main/client_config.hpp"
12-
#include "duckdb/optimizer/matcher/expression_matcher.hpp"
13-
#include "duckdb/planner/expression/bound_between_expression.hpp"
15+
#include "duckdb/planner/expression.hpp"
16+
#include "duckdb/planner/expression/bound_columnref_expression.hpp"
17+
#include "duckdb/planner/expression/bound_function_expression.hpp"
1418
#include "duckdb/planner/operator/logical_get.hpp"
1519
#include "duckdb/storage/data_table.hpp"
1620
#include "duckdb/storage/table/scan_state.hpp"
@@ -500,6 +504,35 @@ bool TryScanIndex(ART &art, const ColumnList &column_list, TableFunctionInitInpu
500504
return false;
501505
}
502506

507+
// Resolve bound column references in the index_expr against the current input projection
508+
column_t updated_index_column;
509+
bool found_index_column_in_input = false;
510+
511+
// Find the indexed column amongst the input columns
512+
for (idx_t i = 0; i < input.column_ids.size(); ++i) {
513+
if (input.column_ids[i] == indexed_columns[0]) {
514+
updated_index_column = i;
515+
found_index_column_in_input = true;
516+
break;
517+
}
518+
}
519+
520+
// If found, update the bound column ref within index_expr
521+
if (found_index_column_in_input) {
522+
ExpressionIterator::EnumerateExpression(index_expr, [&](Expression &expr) {
523+
if (expr.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF) {
524+
return;
525+
}
526+
527+
auto &bound_column_ref_expr = expr.Cast<BoundColumnRefExpression>();
528+
529+
// If the bound column references the index column, use updated_index_column
530+
if (bound_column_ref_expr.binding.column_index == indexed_columns[0]) {
531+
bound_column_ref_expr.binding.column_index = updated_index_column;
532+
}
533+
});
534+
}
535+
503536
// Get ART column.
504537
auto &col = column_list.GetColumn(LogicalIndex(indexed_columns[0]));
505538

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# name: test/sql/index/art/issues/test_art_view_col_binding.test
2+
# description: Test Index Scan push down against views with reordered projections (issue #17290)
3+
# group: [issues]
4+
5+
statement ok
6+
create or replace table test as (
7+
select
8+
cast(unnest(range(1000)) as varchar) as x,
9+
cast(unnest(range(2000,3000)) as varchar) as y,
10+
cast(unnest(range(3000,4000)) as varchar) as z
11+
);
12+
13+
# test simple permutation of initial table projection
14+
statement ok
15+
create index test_x on test(x);
16+
17+
statement ok
18+
create view test_view as (select z, y, x from test);
19+
20+
query II
21+
explain analyze select * from test_view where x = '525';
22+
----
23+
analyzed_plan <REGEX>:.*Index Scan.*
24+
25+
query III
26+
select z, y, x from test_view where x = '525';
27+
----
28+
3525 2525 525
29+
30+
statement ok
31+
drop index test_x;
32+
33+
# test columnref as a child of function
34+
statement ok
35+
create index test_upper_x on test(upper(x));
36+
37+
query II
38+
explain analyze select * from test_view where upper(x) = '526';
39+
----
40+
analyzed_plan <REGEX>:.*Index Scan.*
41+
42+
query III
43+
select z, y, x from test_view where upper(x) = '526';
44+
----
45+
3526 2526 526
46+
47+
statement ok
48+
drop index test_upper_x;

0 commit comments

Comments
 (0)