Skip to content

Commit fc42ab8

Browse files
committed
x
1 parent d1681a7 commit fc42ab8

34 files changed

Lines changed: 1739 additions & 908 deletions

File tree

src/common/column/src/binview/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,37 @@ impl<T: ViewType + ?Sized> BinaryViewColumnGeneric<T> {
515515
}
516516
}
517517

518+
/// Return the minimum and maximum values without materializing every view.
519+
///
520+
/// Comparisons use the four-byte prefixes stored in [`View`]. The backing
521+
/// buffers are only accessed when two prefixes are equal and when the final
522+
/// extrema are returned.
523+
pub fn min_max(&self) -> Option<(&T, &T)> {
524+
if self.is_empty() {
525+
return None;
526+
}
527+
528+
let mut min_index = 0;
529+
let mut max_index = 0;
530+
for index in 1..self.len() {
531+
if Self::compare(self, index, self, min_index).is_lt() {
532+
min_index = index;
533+
continue;
534+
}
535+
if Self::compare(self, index, self, max_index).is_gt() {
536+
max_index = index;
537+
}
538+
}
539+
540+
unsafe {
541+
Some((
542+
self.value_unchecked(min_index),
543+
self.value_unchecked(max_index),
544+
))
545+
}
546+
}
547+
548+
#[inline]
518549
pub fn compare(col_i: &Self, i: usize, col_j: &Self, j: usize) -> std::cmp::Ordering {
519550
let view_i = unsafe { col_i.views().as_slice().get_unchecked(i) };
520551
let view_j = unsafe { col_j.views().as_slice().get_unchecked(j) };

src/common/column/tests/it/binview/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,19 @@ fn test_compare() {
190190

191191
assert_eq!(min, min_expect);
192192
assert_eq!(max, max_expect);
193+
assert_eq!(array.min_max(), Some((min, max)));
194+
assert_eq!(Utf8ViewColumn::new_empty().min_max(), None);
195+
196+
// Equal view prefixes must fall back to comparing the complete strings.
197+
let same_prefix: Utf8ViewColumn = [
198+
"same-prefix-middle",
199+
"same-prefix-maximum",
200+
"same-prefix-minimum",
201+
]
202+
.into_iter()
203+
.collect();
204+
assert_eq!(
205+
same_prefix.min_max(),
206+
Some(("same-prefix-maximum", "same-prefix-minimum"))
207+
);
193208
}

src/query/expression/src/constant_folder.rs

Lines changed: 10 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ use std::collections::HashMap;
1717
use databend_common_ast::Span;
1818
use log::error;
1919

20-
use crate::ColumnBuilder;
2120
use crate::ColumnIndex;
22-
use crate::EvalContext;
2321
use crate::FunctionContext;
2422
use crate::FunctionDomain;
2523
use crate::FunctionEval;
@@ -44,6 +42,8 @@ use crate::types::number::NumberScalar;
4442

4543
const MAX_FUNCTION_ARGS_TO_FOLD: usize = 4096;
4644

45+
mod monotonicity;
46+
4747
pub struct ConstantFolder<'a, Index: ColumnIndex> {
4848
input_domains: &'a HashMap<Index, Domain>,
4949
func_ctx: &'a FunctionContext,
@@ -440,16 +440,7 @@ impl<'a, Index: ColumnIndex> ConstantFolder<'a, Index> {
440440
}
441441

442442
let all_args_is_scalar = args_expr.iter().all(|arg| arg.as_constant().is_some());
443-
let is_monotonicity = self
444-
.fn_registry
445-
.properties
446-
.get(&function.signature.name)
447-
.map(|p| {
448-
args_expr.len() == 1
449-
&& (p.monotonicity
450-
|| p.monotonicity_by_type.contains(args_expr[0].data_type()))
451-
})
452-
.unwrap_or_default();
443+
let is_monotonicity = self.is_monotonic(&function.signature.name, &args_expr);
453444

454445
// Check for mutually exclusive ranges in AND function
455446
if function.signature.name == "and"
@@ -495,69 +486,13 @@ impl<'a, Index: ColumnIndex> ConstantFolder<'a, Index> {
495486
let func_domain = args_domain.and_then(|domains: Vec<Domain>| {
496487
let res = calc_domain.domain_eval(self.func_ctx, &domains);
497488
match (res, is_monotonicity) {
498-
(FunctionDomain::MayThrow | FunctionDomain::Full, true) => {
499-
let domain = domains.first().unwrap();
500-
if args[0].data_type().is_nullable_or_null() {
501-
return None;
502-
}
503-
504-
let (min, max) = domain.to_minmax();
505-
if min.is_null() || max.is_null() {
506-
return None;
507-
}
508-
509-
{
510-
let mut ctx = EvalContext {
511-
generics,
512-
num_rows: 2,
513-
validity: None,
514-
errors: None,
515-
func_ctx: self.func_ctx,
516-
suppress_error: false,
517-
strict_eval: true,
518-
};
519-
let mut builder =
520-
ColumnBuilder::with_capacity(args[0].data_type(), 2);
521-
builder.push(min.as_ref());
522-
builder.push(max.as_ref());
523-
524-
let input = Value::Column(builder.build());
525-
let result = eval.eval(&[input], &mut ctx);
526-
527-
if result.is_scalar() {
528-
None
529-
} else {
530-
// if error happens, domain maybe incorrect
531-
// min, max: String("2024-09-02 00:00") String("2024-09-02 00:0�")
532-
// to_date(s) > to_date('2024-01-1')
533-
let col = result.as_column().unwrap();
534-
let d = if ctx.has_error(0) || ctx.has_error(1) {
535-
let (full_min, full_max) =
536-
Domain::full(return_type).to_minmax();
537-
if full_min.is_null() || full_max.is_null() {
538-
return None;
539-
}
540-
541-
let mut builder =
542-
ColumnBuilder::with_capacity(return_type, 2);
543-
544-
for (i, (v, f)) in
545-
col.iter().zip([full_min, full_max].iter()).enumerate()
546-
{
547-
if ctx.has_error(i) {
548-
builder.push(f.as_ref());
549-
} else {
550-
builder.push(v);
551-
}
552-
}
553-
builder.build().domain()
554-
} else {
555-
result.as_column().unwrap().domain()
556-
};
557-
Some(d)
558-
}
559-
}
560-
}
489+
(FunctionDomain::MayThrow | FunctionDomain::Full, true) => self
490+
.calculate_monotonicity_domain(
491+
return_type,
492+
domains.first().unwrap(),
493+
generics,
494+
eval.as_ref(),
495+
),
561496
(FunctionDomain::MayThrow, _) => None,
562497
(FunctionDomain::Full, _) => Some(Domain::full(return_type)),
563498
(FunctionDomain::Domain(domain), _) => Some(domain),
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2021 Datafuse Labs
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use databend_common_column::bitmap::Bitmap;
16+
17+
use super::ConstantFolder;
18+
use crate::Column;
19+
use crate::ColumnBuilder;
20+
use crate::ColumnIndex;
21+
use crate::EvalContext;
22+
use crate::Expr;
23+
use crate::Value;
24+
use crate::function::ScalarFunction;
25+
use crate::property::Domain;
26+
use crate::types::boolean::BooleanDomain;
27+
use crate::types::nullable::NullableDomain;
28+
use crate::types::string::StringDomain;
29+
use crate::types::*;
30+
31+
impl<'a, Index: ColumnIndex> ConstantFolder<'a, Index> {
32+
pub(super) fn is_monotonic(&self, function_name: &str, args: &[Expr<Index>]) -> bool {
33+
self.fn_registry
34+
.properties
35+
.get(function_name)
36+
.is_some_and(|property| {
37+
if let [arg] = args {
38+
property.monotonicity
39+
|| property.monotonicity_by_type.contains(&arg.data_type())
40+
} else {
41+
false
42+
}
43+
})
44+
}
45+
46+
pub(super) fn calculate_monotonicity_domain(
47+
&self,
48+
return_type: &DataType,
49+
input_domain: &Domain,
50+
generics: &[DataType],
51+
eval: &dyn ScalarFunction,
52+
) -> Option<Domain> {
53+
let input = input_domain.boundary_column()?;
54+
let mut ctx = EvalContext {
55+
generics,
56+
num_rows: 2,
57+
validity: None,
58+
errors: None,
59+
func_ctx: self.func_ctx,
60+
suppress_error: false,
61+
strict_eval: true,
62+
};
63+
let Value::Column(col) = eval.eval(&[Value::Column(input)], &mut ctx) else {
64+
return None;
65+
};
66+
67+
// if error happens, domain maybe incorrect
68+
// min, max: String("2024-09-02 00:00") String("2024-09-02 00:0�")
69+
// to_date(s) > to_date('2024-01-1')
70+
let domain = if ctx.has_error(0) || ctx.has_error(1) {
71+
// Preserve the successful boundary and widen only the failed side.
72+
// For example, a malformed minimum string must not discard a valid
73+
// maximum timestamp that can still prove an upper bound.
74+
// This assumes all currently registered monotonic functions are
75+
// non-decreasing. Supporting a decreasing function here requires
76+
// recording its direction in `FunctionProperty` and reversing the
77+
// fallback boundary.
78+
let full_domain = Domain::full(return_type);
79+
let Some(fallback) = full_domain.boundary_column() else {
80+
return Some(full_domain);
81+
};
82+
let mut builder = ColumnBuilder::with_capacity(return_type, 2);
83+
for (index, (value, fallback)) in col.iter().zip(fallback.iter()).enumerate() {
84+
if ctx.has_error(index) {
85+
builder.push(fallback);
86+
} else {
87+
builder.push(value);
88+
}
89+
}
90+
builder.build().domain()
91+
} else {
92+
col.domain()
93+
};
94+
95+
if !return_type.is_nullable_or_null() {
96+
return Some(domain);
97+
}
98+
99+
Some(match domain {
100+
Domain::Nullable(mut domain) => {
101+
domain.has_null = true;
102+
Domain::Nullable(domain)
103+
}
104+
domain => Domain::Nullable(NullableDomain {
105+
has_null: true,
106+
value: Some(Box::new(domain)),
107+
}),
108+
})
109+
}
110+
}
111+
112+
impl Domain {
113+
/// Materialize the finite, non-NULL endpoints used for monotonicity probing.
114+
fn boundary_column(&self) -> Option<Column> {
115+
Some(match self {
116+
Domain::Number(domain) => crate::with_number_type!(|NUM| match domain {
117+
NumberDomain::NUM(SimpleDomain { min, max }) =>
118+
Column::Number(NumberColumn::NUM(Buffer::from(vec![*min, *max])),),
119+
}),
120+
Domain::Decimal(domain) => crate::with_decimal_type!(|DECIMAL| match domain {
121+
DecimalDomain::DECIMAL(SimpleDomain { min, max }, size) => Column::Decimal(
122+
DecimalColumn::DECIMAL(Buffer::from(vec![*min, *max]), *size),
123+
),
124+
}),
125+
Domain::Boolean(BooleanDomain {
126+
has_false,
127+
has_true,
128+
}) => match (*has_false, *has_true) {
129+
(true, true) => Column::Boolean(Bitmap::from([false, true])),
130+
(true, false) => Column::Boolean(Bitmap::from([false, false])),
131+
(false, true) => Column::Boolean(Bitmap::from([true, true])),
132+
(false, false) => return None,
133+
},
134+
Domain::String(StringDomain {
135+
min,
136+
max: Some(max),
137+
}) => Column::String(StringColumn::from_slice([min.as_str(), max.as_str()])),
138+
Domain::Timestamp(SimpleDomain { min, max }) => {
139+
Column::Timestamp(Buffer::from(vec![*min, *max]))
140+
}
141+
Domain::TimestampTz(SimpleDomain { min, max }) => {
142+
Column::TimestampTz(Buffer::from(vec![*min, *max]))
143+
}
144+
Domain::Date(SimpleDomain { min, max }) => Column::Date(Buffer::from(vec![*min, *max])),
145+
Domain::Interval(SimpleDomain { min, max }) => {
146+
Column::Interval(Buffer::from(vec![*min, *max]))
147+
}
148+
Domain::Nullable(NullableDomain {
149+
value: Some(domain),
150+
..
151+
}) => NullableColumn::new_column(domain.boundary_column()?, Bitmap::new_trued(2)),
152+
_ => return None,
153+
})
154+
}
155+
}

0 commit comments

Comments
 (0)