Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ members = [
"src/meta/store",
"src/meta/ver",
"src/query/codegen",
"src/query/expression/fuzz",
"src/tests/planner_replay",
"tests/sqllogictests",
]
Expand Down
31 changes: 31 additions & 0 deletions src/common/column/src/binview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,37 @@ impl<T: ViewType + ?Sized> BinaryViewColumnGeneric<T> {
}
}

/// Return the minimum and maximum values without materializing every view.
///
/// Comparisons use the four-byte prefixes stored in [`View`]. The backing
/// buffers are only accessed when two prefixes are equal and when the final
/// extrema are returned.
pub fn min_max(&self) -> Option<(&T, &T)> {
if self.is_empty() {
return None;
}

let mut min_index = 0;
let mut max_index = 0;
for index in 1..self.len() {
if Self::compare(self, index, self, min_index).is_lt() {
min_index = index;
continue;
}
if Self::compare(self, index, self, max_index).is_gt() {
max_index = index;
}
}

unsafe {
Some((
self.value_unchecked(min_index),
self.value_unchecked(max_index),
))
}
}

#[inline]
pub fn compare(col_i: &Self, i: usize, col_j: &Self, j: usize) -> std::cmp::Ordering {
let view_i = unsafe { col_i.views().as_slice().get_unchecked(i) };
let view_j = unsafe { col_j.views().as_slice().get_unchecked(j) };
Expand Down
15 changes: 15 additions & 0 deletions src/common/column/tests/it/binview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,19 @@ fn test_compare() {

assert_eq!(min, min_expect);
assert_eq!(max, max_expect);
assert_eq!(array.min_max(), Some((min, max)));
assert_eq!(Utf8ViewColumn::new_empty().min_max(), None);

// Equal view prefixes must fall back to comparing the complete strings.
let same_prefix: Utf8ViewColumn = [
"same-prefix-middle",
"same-prefix-maximum",
"same-prefix-minimum",
]
.into_iter()
.collect();
assert_eq!(
same_prefix.min_max(),
Some(("same-prefix-maximum", "same-prefix-minimum"))
);
}
6 changes: 6 additions & 0 deletions src/query/expression/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
artifacts
corpus
coverage
target


24 changes: 24 additions & 0 deletions src/query/expression/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "databend-expression-domain-fuzz"
version = "0.0.0"
publish = false
edition = "2024"

[package.metadata]
cargo-fuzz = true

[dependencies]
arbitrary = "1.4"
databend-common-column = { path = "../../../common/column" }
databend-common-expression = { path = ".." }
libfuzzer-sys = "0.4"

[lib]
doctest = false

[[bin]]
name = "column_domain"
path = "fuzz_targets/column_domain.rs"
test = false
doc = false
bench = false
35 changes: 35 additions & 0 deletions src/query/expression/fuzz/fuzz_targets/column_domain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2021 Datafuse Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![no_main]

use databend_expression_domain_fuzz::ColumnCase;
use databend_expression_domain_fuzz::run_column_case;
use libfuzzer_sys::fuzz_target;

// This target validates the fundamental `Column::domain` invariant: every value physically present
// in a generated column must be contained by the domain returned for that column.

// The typed `Arbitrary` generator covers empty and NULL columns, all number and decimal widths,
// Boolean, String, temporal and interval columns, Binary, and recursive Nullable, Array, Map, and
// Tuple shapes. Array and Map offsets may start above zero so sliced-column behavior is exercised as
// well.

// For every row, the oracle recursively checks the actual scalar value against the column domain.
// Nullable checks NULL membership, Array and Map check their real elements, and Tuple checks each
// field. Primitive values use singleton-domain containment. Undefined domains accept unsupported
// scalar types by definition.
//
// cargo fuzz run --dev --sanitizer none --strip-dead-code column_domain -- -runs=10000 -max_len=4096
fuzz_target!(|case: ColumnCase| run_column_case(case));
Loading
Loading