Skip to content

Commit 25be845

Browse files
committed
fix(query): avoid rewriting data for decimal precision widening
1 parent 6fefe15 commit 25be845

40 files changed

Lines changed: 1906 additions & 404 deletions

File tree

src/query/catalog/src/runtime_filter_info.rs

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use std::sync::atomic::Ordering;
2828
use databend_common_expression::ColumnId;
2929
use databend_common_expression::Expr;
3030
use databend_common_expression::Scalar;
31+
use databend_common_expression::types::DataType;
3132
use databend_storages_common_table_meta::meta::ColumnStatistics;
3233
use parking_lot::RwLock;
3334
use tokio::sync::watch;
@@ -39,33 +40,37 @@ use crate::sbbf::Sbbf;
3940
pub type RuntimeBloomFilter = Arc<Sbbf>;
4041
pub type RuntimeScanFilterFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
4142

42-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43+
#[derive(Clone, Debug, Eq, PartialEq)]
4344
pub struct RuntimeScanOrder {
4445
pub column_id: ColumnId,
46+
pub data_type: DataType,
4547
pub asc: bool,
4648
pub nulls_first: bool,
4749
}
4850

4951
impl RuntimeScanOrder {
5052
/// Rank column statistics for scheduling: parts more likely to hold top
5153
/// rows under this order rank first.
52-
pub fn rank<'a>(
54+
pub fn rank(
5355
&self,
54-
stats: Option<&'a HashMap<ColumnId, ColumnStatistics>>,
55-
) -> RuntimeTopNRank<&'a Scalar> {
56+
stats: Option<&HashMap<ColumnId, ColumnStatistics>>,
57+
) -> RuntimeTopNRank<Scalar> {
5658
let Some(stat) = stats.and_then(|stats| stats.get(&self.column_id)) else {
5759
return RuntimeTopNRank::Unknown;
5860
};
61+
let Some(view) = stat.try_view(&self.data_type) else {
62+
return RuntimeTopNRank::Unknown;
63+
};
5964
// Under NULLS FIRST null rows sort before every value: parts holding
6065
// nulls are the most promising ones (and are never prunable).
6166
if self.nulls_first && stat.null_count > 0 {
6267
return RuntimeTopNRank::Best;
6368
}
64-
let key = if self.asc { stat.min() } else { stat.max() };
69+
let key = if self.asc { view.min() } else { view.max() };
6570
if matches!(key, Scalar::Null) {
6671
return RuntimeTopNRank::Unknown;
6772
}
68-
RuntimeTopNRank::Value(key)
73+
RuntimeTopNRank::Value(key.clone())
6974
}
7075

7176
/// Compare two scheduling ranks under this order: better-ranked parts
@@ -103,16 +108,6 @@ pub enum RuntimeTopNRank<S> {
103108
Unknown,
104109
}
105110

106-
impl RuntimeTopNRank<&Scalar> {
107-
pub fn cloned(self) -> RuntimeTopNRank<Scalar> {
108-
match self {
109-
RuntimeTopNRank::Best => RuntimeTopNRank::Best,
110-
RuntimeTopNRank::Value(value) => RuntimeTopNRank::Value(value.clone()),
111-
RuntimeTopNRank::Unknown => RuntimeTopNRank::Unknown,
112-
}
113-
}
114-
}
115-
116111
pub trait RuntimeScanFilter: Send + Sync {
117112
fn finished(&self) -> bool {
118113
false
@@ -209,16 +204,18 @@ impl RuntimeFilterNotify {
209204
/// their local boundary tightens, so contention is negligible.
210205
pub struct RuntimeTopNFilter {
211206
column_id: u32,
207+
data_type: DataType,
212208
asc: bool,
213209
nulls_first: bool,
214210
boundary: RwLock<Option<Scalar>>,
215211
recheck: RuntimeFilterNotify,
216212
}
217213

218214
impl RuntimeTopNFilter {
219-
pub fn new(column_id: u32, asc: bool, nulls_first: bool) -> Self {
215+
pub fn new(column_id: u32, data_type: DataType, asc: bool, nulls_first: bool) -> Self {
220216
Self {
221217
column_id,
218+
data_type,
222219
asc,
223220
nulls_first,
224221
boundary: RwLock::new(None),
@@ -305,8 +302,11 @@ impl RuntimeScanFilter for RuntimeTopNFilter {
305302
let Some(stat) = stats.get(&self.column_id) else {
306303
return false;
307304
};
305+
let Some(view) = stat.try_view(&self.data_type) else {
306+
return false;
307+
};
308308

309-
self.boundary_excludes(stat.min(), stat.max(), stat.null_count)
309+
self.boundary_excludes(view.min(), view.max(), view.null_count())
310310
}
311311

312312
fn recheck_notified(&self) -> RuntimeScanFilterFuture {
@@ -316,6 +316,7 @@ impl RuntimeScanFilter for RuntimeTopNFilter {
316316
fn preferred_order(&self) -> Option<RuntimeScanOrder> {
317317
Some(RuntimeScanOrder {
318318
column_id: self.column_id,
319+
data_type: self.data_type.clone(),
319320
asc: self.asc,
320321
nulls_first: self.nulls_first,
321322
})
@@ -529,9 +530,20 @@ mod tests {
529530
use databend_common_expression::ColumnRef;
530531
use databend_common_expression::Expr;
531532
use databend_common_expression::types::DataType;
533+
use databend_common_expression::types::DecimalScalar;
534+
use databend_common_expression::types::DecimalSize;
532535
use databend_common_expression::types::NumberDataType;
533536
use databend_common_expression::types::NumberScalar;
534537
use tokio::time::Duration;
538+
539+
fn int64_filter(column_id: u32, asc: bool, nulls_first: bool) -> RuntimeTopNFilter {
540+
RuntimeTopNFilter::new(
541+
column_id,
542+
DataType::Number(NumberDataType::Int64),
543+
asc,
544+
nulls_first,
545+
)
546+
}
535547
use tokio::time::timeout;
536548

537549
use super::*;
@@ -540,9 +552,43 @@ mod tests {
540552
Scalar::Number(NumberScalar::Int64(value))
541553
}
542554

555+
fn decimal64(value: i64, precision: u8, scale: u8) -> Scalar {
556+
Scalar::Decimal(DecimalScalar::Decimal64(
557+
value,
558+
DecimalSize::new(precision, scale).unwrap(),
559+
))
560+
}
561+
562+
#[test]
563+
fn runtime_top_n_filter_aligns_decimal_stats_and_fails_open() {
564+
let current_size = DecimalSize::new(15, 2).unwrap();
565+
let filter = RuntimeTopNFilter::new(7, DataType::Decimal(current_size), true, false);
566+
filter.update(&decimal64(500, 15, 2));
567+
568+
let widened = HashMap::from([(
569+
7,
570+
ColumnStatistics::new(decimal64(600, 10, 2), decimal64(900, 10, 2), 0, 0, None),
571+
)]);
572+
assert!(filter.should_prune(Some(&widened)));
573+
assert!(matches!(
574+
filter.preferred_order().unwrap().rank(Some(&widened)),
575+
RuntimeTopNRank::Value(value) if value == decimal64(600, 15, 2)
576+
));
577+
578+
let incompatible = HashMap::from([(
579+
7,
580+
ColumnStatistics::new(decimal64(600, 10, 3), decimal64(900, 10, 3), 0, 0, None),
581+
)]);
582+
assert!(!filter.should_prune(Some(&incompatible)));
583+
assert!(matches!(
584+
filter.preferred_order().unwrap().rank(Some(&incompatible)),
585+
RuntimeTopNRank::Unknown
586+
));
587+
}
588+
543589
#[test]
544590
fn runtime_top_n_filter_is_monotonic_and_tie_safe() {
545-
let asc = RuntimeTopNFilter::new(7, true, false);
591+
let asc = int64_filter(7, true, false);
546592
assert!(!asc.boundary_excludes(&int64(11), &int64(20), 0));
547593

548594
asc.update(&int64(10));
@@ -556,7 +602,7 @@ mod tests {
556602
asc.update(&int64(8));
557603
assert_eq!(asc.boundary(), Some(int64(8)));
558604

559-
let desc = RuntimeTopNFilter::new(7, false, false);
605+
let desc = int64_filter(7, false, false);
560606
desc.update(&int64(10));
561607
assert!(desc.boundary_excludes(&int64(1), &int64(9), 0));
562608
assert!(!desc.boundary_excludes(&int64(1), &int64(10), 0));
@@ -571,15 +617,15 @@ mod tests {
571617

572618
#[test]
573619
fn runtime_top_n_filter_ranks_nulls_by_ordering() {
574-
let nulls_last = RuntimeTopNFilter::new(1, true, false);
620+
let nulls_last = int64_filter(1, true, false);
575621
nulls_last.update(&int64(10));
576622
// Nulls sort after the boundary, so null rows are prunable too.
577623
assert!(nulls_last.boundary_excludes(&int64(11), &int64(20), 5));
578624
// All-null blocks sort entirely after the boundary.
579625
assert!(nulls_last.boundary_excludes(&Scalar::Null, &Scalar::Null, 7));
580626
assert!(!nulls_last.boundary_excludes(&int64(9), &int64(20), 5));
581627

582-
let nulls_first = RuntimeTopNFilter::new(1, true, true);
628+
let nulls_first = int64_filter(1, true, true);
583629
nulls_first.update(&int64(10));
584630
// Null rows are always candidates under NULLS FIRST.
585631
assert!(!nulls_first.boundary_excludes(&int64(11), &int64(20), 1));
@@ -589,7 +635,7 @@ mod tests {
589635

590636
#[test]
591637
fn runtime_top_n_filter_concurrent_updates_keep_tightest() {
592-
let filter = Arc::new(RuntimeTopNFilter::new(1, true, false));
638+
let filter = Arc::new(int64_filter(1, true, false));
593639
let threads: Vec<_> = (0..4)
594640
.map(|t| {
595641
let filter = filter.clone();
@@ -615,7 +661,7 @@ mod tests {
615661

616662
#[tokio::test]
617663
async fn runtime_scan_filter_notifications_are_repeatable() {
618-
let filter = RuntimeTopNFilter::new(1, true, false);
664+
let filter = int64_filter(1, true, false);
619665

620666
let first = filter.recheck_notified();
621667
filter.update(&int64(10));
@@ -648,7 +694,7 @@ mod tests {
648694

649695
#[test]
650696
fn runtime_scan_filters_combine_filters() {
651-
let top_n = Arc::new(RuntimeTopNFilter::new(1, true, false));
697+
let top_n = Arc::new(int64_filter(1, true, false));
652698
top_n.update(&int64(10));
653699
let limit = Arc::new(RuntimeLimitFilter::new());
654700

@@ -677,7 +723,7 @@ mod tests {
677723

678724
#[test]
679725
fn runtime_scan_filters_prune_by_column_stats() {
680-
let asc_filter = Arc::new(RuntimeTopNFilter::new(3, true, false));
726+
let asc_filter = Arc::new(int64_filter(3, true, false));
681727
asc_filter.update(&int64(10));
682728
let mut asc = RuntimeScanFilters::default();
683729
asc.push(asc_filter);
@@ -694,13 +740,13 @@ mod tests {
694740
assert!(!asc.should_prune(None));
695741

696742
// Under NULLS FIRST null rows are always candidates.
697-
let nulls_first_filter = Arc::new(RuntimeTopNFilter::new(3, true, true));
743+
let nulls_first_filter = Arc::new(int64_filter(3, true, true));
698744
nulls_first_filter.update(&int64(10));
699745
let mut nulls_first = RuntimeScanFilters::default();
700746
nulls_first.push(nulls_first_filter);
701747
assert!(!nulls_first.should_prune(Some(&columns)));
702748

703-
let desc_filter = Arc::new(RuntimeTopNFilter::new(3, false, false));
749+
let desc_filter = Arc::new(int64_filter(3, false, false));
704750
desc_filter.update(&int64(10));
705751
let mut desc = RuntimeScanFilters::default();
706752
desc.push(desc_filter);

src/query/catalog/src/statistics/basic_statistics.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use databend_common_expression::types::DataType;
1516
use databend_common_statistics::Datum;
1617
use databend_storages_common_table_meta::meta::ColumnStatistics;
1718

@@ -31,19 +32,24 @@ pub struct BasicColumnStatistics {
3132
pub in_memory_size: u64,
3233
}
3334

34-
impl From<ColumnStatistics> for BasicColumnStatistics {
35-
fn from(value: ColumnStatistics) -> Self {
36-
Self {
37-
min: value.min.to_datum(),
38-
max: value.max.to_datum(),
39-
ndv: value.distinct_of_values,
40-
null_count: value.null_count,
41-
in_memory_size: value.in_memory_size,
42-
}
35+
impl BasicColumnStatistics {
36+
pub fn try_from_column_statistics(
37+
value: &ColumnStatistics,
38+
data_type: &DataType,
39+
) -> Option<Self> {
40+
let null_count = value.null_count;
41+
let in_memory_size = value.in_memory_size;
42+
let ndv = value.distinct_of_values;
43+
let (min, max) = value.try_view(data_type)?.datum_bounds();
44+
Some(Self {
45+
min,
46+
max,
47+
ndv,
48+
null_count,
49+
in_memory_size,
50+
})
4351
}
44-
}
4552

46-
impl BasicColumnStatistics {
4753
pub fn new_null() -> Self {
4854
Self {
4955
min: None,

src/query/expression/src/types/decimal.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,12 @@ impl DecimalScalar {
426426
})
427427
}
428428

429+
pub fn data_kind(&self) -> DecimalDataKind {
430+
with_decimal_type!(|DECIMAL| match self {
431+
DecimalScalar::DECIMAL(_, _) => DecimalDataKind::DECIMAL,
432+
})
433+
}
434+
429435
pub fn scale(&self) -> u8 {
430436
with_decimal_type!(|DECIMAL| match self {
431437
DecimalScalar::DECIMAL(_, size) => size.scale,

0 commit comments

Comments
 (0)