Skip to content

Commit 3f741ad

Browse files
committed
fix pruner
1 parent e3ebc93 commit 3f741ad

4 files changed

Lines changed: 105 additions & 49 deletions

File tree

src/query/storages/common/index/src/eliminate_cast.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,31 @@ use databend_common_functions::BUILTIN_FUNCTIONS;
3232

3333
pub(super) struct RewriteVisitor<'a> {
3434
pub input_domains: HashMap<String, Domain>,
35+
/// Optional block-local physical types for virtual column references.
36+
pub column_types: Option<&'a HashMap<String, DataType>>,
3537
pub func_ctx: &'a FunctionContext,
3638
pub fn_registry: &'a FunctionRegistry,
3739
}
3840

3941
type RewriteResult = std::result::Result<Option<Expr<String>>, !>;
4042

4143
impl ExprVisitor<String> for RewriteVisitor<'_> {
44+
fn enter_column_ref(&mut self, column: &ColumnRef<String>) -> RewriteResult {
45+
let Some(data_type) = self
46+
.column_types
47+
.and_then(|column_types| column_types.get(&column.id))
48+
else {
49+
return Ok(None);
50+
};
51+
if data_type == &column.data_type {
52+
return Ok(None);
53+
}
54+
55+
let mut column = column.clone();
56+
column.data_type = data_type.clone();
57+
Ok(Some(column.into()))
58+
}
59+
4260
fn enter_function_call(&mut self, call: &FunctionCall<String>) -> RewriteResult {
4361
if call.id.name() == "eq" {
4462
let result = match call.args.as_slice() {
@@ -268,6 +286,7 @@ pub fn eliminate_cast(
268286
) -> Option<Expr<String>> {
269287
let mut visitor = RewriteVisitor {
270288
input_domains,
289+
column_types: None,
271290
func_ctx: &FunctionContext::default(),
272291
fn_registry: &BUILTIN_FUNCTIONS,
273292
};

src/query/storages/common/index/src/range_index.rs

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -114,59 +114,58 @@ impl RangeIndex {
114114
where
115115
F: Fn(&ColumnId) -> bool,
116116
{
117-
let mut input_domains: HashMap<String, Domain> = self
118-
.expr
119-
.column_refs()
120-
.into_iter()
121-
.map(|(name, ty)| {
122-
// internal column and stream column are not actual stored columns
123-
if is_internal_column(&name) || is_stream_column(&name) {
124-
return Ok((name, Domain::full(&ty)));
125-
}
117+
let mut input_domains = HashMap::new();
118+
let mut virtual_column_types = HashMap::new();
119+
for (name, ty) in self.expr.column_refs() {
120+
// internal column and stream column are not actual stored columns
121+
if is_internal_column(&name) || is_stream_column(&name) {
122+
input_domains.insert(name, Domain::full(&ty));
123+
continue;
124+
}
126125

127-
let column_ids = self.schema.leaf_columns_of(&name);
128-
if column_ids.is_empty() {
129-
// The name may refer to a virtual column (e.g. `v['a']`). Use the
130-
// block-local virtual column statistics to build the domain.
131-
// Only typed statistics are injected; everything else falls back
132-
// to a full domain to avoid wrong pruning.
133-
let virtual_domain = virtual_col_stats.and_then(|virtual_col_stats| {
134-
virtual_col_stats.get(&name).map(|stat| {
135-
let column_stat = stat.to_column_statistics();
136-
let data_type = DataType::from(&stat.data_type);
137-
statistics_to_domain(vec![&column_stat], &data_type)
138-
})
139-
});
140-
return Ok((name, virtual_domain.unwrap_or_else(|| Domain::full(&ty))));
126+
let column_ids = self.schema.leaf_columns_of(&name);
127+
if column_ids.is_empty() {
128+
// The name may refer to a virtual column (e.g. `v['a']`). Use the
129+
// block-local virtual column statistics to build the domain.
130+
// Only typed statistics are injected; everything else falls back
131+
// to a full domain to avoid wrong pruning.
132+
if let Some(stat) = virtual_col_stats.and_then(|stats| stats.get(&name)) {
133+
let column_stat = stat.to_column_statistics();
134+
let data_type = DataType::from(&stat.data_type);
135+
let domain = statistics_to_domain(vec![&column_stat], &data_type);
136+
virtual_column_types.insert(name.clone(), data_type);
137+
input_domains.insert(name, domain);
138+
} else {
139+
input_domains.insert(name, Domain::full(&ty));
141140
}
141+
continue;
142+
}
142143

143-
let stats = column_ids
144-
.iter()
145-
.filter_map(|column_id| match stats.get(column_id) {
146-
None => {
147-
if column_is_default(column_id)
148-
&& self.default_stats.contains_key(column_id)
149-
{
150-
Some(&self.default_stats[column_id])
151-
} else {
152-
None
153-
}
144+
let stats = column_ids
145+
.iter()
146+
.filter_map(|column_id| match stats.get(column_id) {
147+
None => {
148+
if column_is_default(column_id)
149+
&& self.default_stats.contains_key(column_id)
150+
{
151+
Some(&self.default_stats[column_id])
152+
} else {
153+
None
154154
}
155-
other => other,
156-
})
157-
.collect();
158-
159-
let domain = statistics_to_domain(stats, &ty);
160-
Ok((name, domain))
161-
})
162-
.collect::<Result<_>>()?;
155+
}
156+
other => other,
157+
})
158+
.collect();
159+
input_domains.insert(name, statistics_to_domain(stats, &ty));
160+
}
163161

164162
for (name, domain) in self.spatial_predicate_domains(spatial_stats) {
165163
input_domains.insert(name, domain);
166164
}
167165

168166
let mut visitor = RewriteVisitor {
169167
input_domains,
168+
column_types: (!virtual_column_types.is_empty()).then_some(&virtual_column_types),
170169
func_ctx: &self.func_ctx,
171170
fn_registry: &BUILTIN_FUNCTIONS,
172171
};

src/query/storages/common/index/tests/it/range_pruner.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,42 @@ fn test_range_index_prunes_by_virtual_column_statistics() {
173173
);
174174
}
175175

176+
#[test]
177+
fn test_range_index_prunes_try_cast_virtual_column_by_physical_type() {
178+
fn n(value: u64) -> Scalar {
179+
Scalar::Number(value.into())
180+
}
181+
182+
let schema = Arc::new(TableSchema::new(vec![TableField::new(
183+
"v",
184+
TableDataType::Variant,
185+
)]));
186+
let expr = parse_expr("is_true(try_cast(v.a as uint8) = 100)", &[(
187+
"v.a",
188+
DataType::Variant.wrap_nullable(),
189+
)]);
190+
let index = RangeIndex::try_create(
191+
FunctionContext::default(),
192+
&expr,
193+
schema,
194+
Default::default(),
195+
)
196+
.unwrap();
197+
let stats = VirtualColumnStatsOfNames::from([("v.a".to_string(), VirtualColumnStat {
198+
query_column_id: 3_000_000_000,
199+
min: n(1),
200+
max: n(8),
201+
null_count: 0,
202+
data_type: TableDataType::Number(NumberDataType::UInt64),
203+
})]);
204+
205+
assert!(
206+
!index
207+
.apply(&Default::default(), None, Some(&stats), |_| false)
208+
.unwrap()
209+
);
210+
}
211+
176212
#[test]
177213
fn test_range_index_keeps_without_virtual_column_statistics() {
178214
let schema = Arc::new(TableSchema::new(vec![TableField::new(

src/query/storages/common/pruner/src/range_pruner.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use databend_common_expression::Expr;
2121
use databend_common_expression::FunctionContext;
2222
use databend_common_expression::Scalar;
2323
use databend_common_expression::TableSchemaRef;
24-
use databend_common_expression::infer_schema_type;
2524
use databend_common_expression::types::DataType;
2625
use databend_storages_common_index::RangeIndex;
2726
use databend_storages_common_index::VirtualColumnStat;
@@ -115,25 +114,28 @@ fn build_virtual_col_stats(
115114
let Some(column_stat) = column.column_stat.as_ref() else {
116115
continue;
117116
};
118-
let Some(data_type) = column_stat
117+
let Some(scalar_data_type) = column_stat
119118
.min
120119
.as_ref()
121120
.infer_common_type(&column_stat.max.as_ref())
122121
else {
123122
continue;
124123
};
125-
if matches!(data_type.remove_nullable(), DataType::Variant) {
124+
let physical_data_type = column.data_type();
125+
let physical_expression_type = DataType::from(&physical_data_type);
126+
if matches!(
127+
physical_expression_type.remove_nullable(),
128+
DataType::Variant
129+
) || physical_expression_type.remove_nullable() != scalar_data_type.remove_nullable()
130+
{
126131
continue;
127132
}
128-
let Ok(data_type) = infer_schema_type(&data_type) else {
129-
continue;
130-
};
131133
stats.insert(virtual_ref.name.clone(), VirtualColumnStat {
132134
query_column_id: virtual_ref.query_column_id,
133135
min: column_stat.min.clone(),
134136
max: column_stat.max.clone(),
135137
null_count: column_stat.null_count,
136-
data_type,
138+
data_type: physical_data_type,
137139
});
138140
}
139141
(!stats.is_empty()).then_some(stats)

0 commit comments

Comments
 (0)