Skip to content

Commit 8154e53

Browse files
committed
feat(fuse): add hash-distributed partition writes
1 parent ec61b69 commit 8154e53

15 files changed

Lines changed: 469 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ memchr = { version = "2", default-features = false }
358358
micromarshal = "0.7.0"
359359
mlua = { version = "0.11", features = ["lua54", "vendored", "async", "serialize"] }
360360
mockall = "0.11.2"
361+
murmur3 = "0.5.2"
361362
mysql_async = { version = "0.34", default-features = false, features = ["native-tls-tls"] }
362363
naive-cityhash = "0.2.0"
363364
ndarray = "0.15.6"

src/query/functions/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jsonb = { workspace = true }
4141
libm = { workspace = true }
4242
match-template = { workspace = true }
4343
md-5 = { workspace = true }
44+
murmur3 = { workspace = true }
4445
naive-cityhash = { workspace = true }
4546
num-traits = { workspace = true }
4647
proptest = { workspace = true }

src/query/functions/src/scalars/hash.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ pub fn register(registry: &mut FunctionRegistry) {
102102
});
103103
}
104104

105+
register_bucket(registry);
106+
105107
// Could be used in exchange
106108
registry
107109
.scalar_builder("siphash64")
@@ -222,6 +224,99 @@ pub fn register(registry: &mut FunctionRegistry) {
222224
);
223225
}
224226

227+
fn register_bucket(registry: &mut FunctionRegistry) {
228+
registry
229+
.register_passthrough_nullable_2_arg::<NumberType<u64>, StringType, NumberType<i32>, _, _>(
230+
"bucket",
231+
|_, _, _| FunctionDomain::MayThrow,
232+
vectorize_with_builder_2_arg::<NumberType<u64>, StringType, NumberType<i32>>(
233+
|buckets, value, output, ctx| {
234+
output.push(bucket(buckets, value.as_bytes(), output.len(), ctx));
235+
},
236+
),
237+
);
238+
registry
239+
.register_passthrough_nullable_2_arg::<NumberType<u64>, DateType, NumberType<i32>, _, _>(
240+
"bucket",
241+
|_, _, _| FunctionDomain::MayThrow,
242+
vectorize_with_builder_2_arg::<NumberType<u64>, DateType, NumberType<i32>>(
243+
|buckets, value, output, ctx| {
244+
output.push(bucket(buckets, &value.to_le_bytes(), output.len(), ctx));
245+
},
246+
),
247+
);
248+
registry.register_passthrough_nullable_2_arg::<
249+
NumberType<u64>,
250+
TimestampType,
251+
NumberType<i32>,
252+
_,
253+
_,
254+
>(
255+
"bucket",
256+
|_, _, _| FunctionDomain::MayThrow,
257+
vectorize_with_builder_2_arg::<NumberType<u64>, TimestampType, NumberType<i32>>(
258+
|buckets, value, output, ctx| {
259+
output.push(bucket(
260+
buckets,
261+
&value.to_le_bytes(),
262+
output.len(),
263+
ctx,
264+
));
265+
},
266+
),
267+
);
268+
for num_type in ALL_INTEGER_TYPES {
269+
with_integer_mapped_type!(|NUM_TYPE| match num_type {
270+
NumberDataType::NUM_TYPE => {
271+
registry.register_passthrough_nullable_2_arg::<
272+
NumberType<u64>,
273+
NumberType<NUM_TYPE>,
274+
NumberType<i32>,
275+
_,
276+
_,
277+
>(
278+
"bucket",
279+
|_, _, _| FunctionDomain::MayThrow,
280+
vectorize_with_builder_2_arg::<
281+
NumberType<u64>,
282+
NumberType<NUM_TYPE>,
283+
NumberType<i32>,
284+
>(|buckets, value, output, ctx| {
285+
let value: i64 = value.as_();
286+
output.push(bucket(
287+
buckets,
288+
&value.to_le_bytes(),
289+
output.len(),
290+
ctx,
291+
));
292+
}),
293+
);
294+
}
295+
_ => unreachable!(),
296+
});
297+
}
298+
}
299+
300+
fn bucket(
301+
buckets: u64,
302+
bytes: &[u8],
303+
row: usize,
304+
ctx: &mut databend_common_expression::EvalContext,
305+
) -> i32 {
306+
let Ok(buckets) = i32::try_from(buckets) else {
307+
ctx.set_error(row, "bucket count must be between 1 and 2147483647");
308+
return 0;
309+
};
310+
if buckets == 0 {
311+
ctx.set_error(row, "bucket count must be greater than zero");
312+
return 0;
313+
}
314+
315+
let mut bytes = bytes;
316+
let hash = murmur3::murmur3_32(&mut bytes, 0).unwrap() as i32;
317+
(hash & i32::MAX) % buckets
318+
}
319+
225320
fn register_simple_domain_type_hash<T: ArgType>(registry: &mut FunctionRegistry)
226321
where for<'a> T::ScalarRef<'a>: DFHash {
227322
registry

src/query/functions/tests/it/scalars/hash.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ fn test_hash() {
3333
test_siphash64(file);
3434
test_xxhash64(file);
3535
test_xxhash32(file);
36+
test_bucket(file);
37+
}
38+
39+
fn test_bucket(file: &mut impl Write) {
40+
run_ast(file, "bucket(16, 34)", &[]);
41+
run_ast(file, "bucket(16, 'iceberg')", &[]);
42+
run_ast(file, "bucket(16, to_date(10000))", &[]);
43+
run_ast(file, "bucket(16, to_datetime(100000))", &[]);
44+
run_ast(file, "bucket(0, 1)", &[]);
45+
run_ast(file, "bucket(8, a)", &[(
46+
"a",
47+
Int64Type::from_data(vec![1, 2, 3, 4]),
48+
)]);
3649
}
3750

3851
fn test_md5(file: &mut impl Write) {

src/query/functions/tests/it/scalars/testdata/function_list.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,28 @@ Functions overloads:
934934
1 bitmap_xor(Bitmap NULL, Bitmap NULL) :: Bitmap NULL
935935
0 blake3(String) :: String
936936
1 blake3(String NULL) :: String NULL
937+
0 bucket(UInt64, String) :: Int32
938+
1 bucket(UInt64 NULL, String NULL) :: Int32 NULL
939+
2 bucket(UInt64, Date) :: Int32
940+
3 bucket(UInt64 NULL, Date NULL) :: Int32 NULL
941+
4 bucket(UInt64, Timestamp) :: Int32
942+
5 bucket(UInt64 NULL, Timestamp NULL) :: Int32 NULL
943+
6 bucket(UInt64, UInt8) :: Int32
944+
7 bucket(UInt64 NULL, UInt8 NULL) :: Int32 NULL
945+
8 bucket(UInt64, UInt16) :: Int32
946+
9 bucket(UInt64 NULL, UInt16 NULL) :: Int32 NULL
947+
10 bucket(UInt64, UInt32) :: Int32
948+
11 bucket(UInt64 NULL, UInt32 NULL) :: Int32 NULL
949+
12 bucket(UInt64, UInt64) :: Int32
950+
13 bucket(UInt64 NULL, UInt64 NULL) :: Int32 NULL
951+
14 bucket(UInt64, Int8) :: Int32
952+
15 bucket(UInt64 NULL, Int8 NULL) :: Int32 NULL
953+
16 bucket(UInt64, Int16) :: Int32
954+
17 bucket(UInt64 NULL, Int16 NULL) :: Int32 NULL
955+
18 bucket(UInt64, Int32) :: Int32
956+
19 bucket(UInt64 NULL, Int32 NULL) :: Int32 NULL
957+
20 bucket(UInt64, Int64) :: Int32
958+
21 bucket(UInt64 NULL, Int64 NULL) :: Int32 NULL
937959
0 build_bitmap(Array(UInt8 NULL)) :: Bitmap
938960
1 build_bitmap(Array(UInt8 NULL) NULL) :: Bitmap NULL
939961
2 build_bitmap(Array(UInt16 NULL)) :: Bitmap

src/query/functions/tests/it/scalars/testdata/hash.txt

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,3 +851,71 @@ evaluation (internal):
851851
+--------+----------------------------------+
852852

853853

854+
ast : bucket(16, 34)
855+
raw expr : bucket(16, 34)
856+
checked expr : bucket<UInt64, UInt8>(CAST<UInt8>(16_u8 AS UInt64), 34_u8)
857+
optimized expr : 3_i32
858+
output type : Int32
859+
output domain : {3..=3}
860+
output : 3
861+
862+
863+
ast : bucket(16, 'iceberg')
864+
raw expr : bucket(16, 'iceberg')
865+
checked expr : bucket<UInt64, String>(CAST<UInt8>(16_u8 AS UInt64), "iceberg")
866+
optimized expr : 9_i32
867+
output type : Int32
868+
output domain : {9..=9}
869+
output : 9
870+
871+
872+
ast : bucket(16, to_date(10000))
873+
raw expr : bucket(16, to_date(10000))
874+
checked expr : bucket<UInt64, Date>(CAST<UInt8>(16_u8 AS UInt64), CAST<Int64>(CAST<UInt16>(10000_u16 AS Int64) AS Date))
875+
optimized expr : 12_i32
876+
output type : Int32
877+
output domain : {12..=12}
878+
output : 12
879+
880+
881+
ast : bucket(16, to_datetime(100000))
882+
raw expr : bucket(16, to_datetime(100000))
883+
checked expr : bucket<UInt64, Timestamp>(CAST<UInt8>(16_u8 AS UInt64), CAST<Int64>(CAST<UInt32>(100000_u32 AS Int64) AS Timestamp))
884+
optimized expr : 15_i32
885+
output type : Int32
886+
output domain : {15..=15}
887+
output : 15
888+
889+
890+
error:
891+
--> SQL:1:1
892+
|
893+
1 | bucket(0, 1)
894+
| ^^^^^^^^^^^^ bucket count must be greater than zero while evaluating function `bucket(0, 1)` in expr `bucket(CAST(0 AS UInt64), 1)`
895+
896+
897+
898+
ast : bucket(8, a)
899+
raw expr : bucket(8, a::Int64)
900+
checked expr : bucket<UInt64, Int64>(CAST<UInt8>(8_u8 AS UInt64), a)
901+
optimized expr : bucket<UInt64, Int64>(8_u64, a)
902+
evaluation:
903+
+--------+---------+---------+
904+
| | a | Output |
905+
+--------+---------+---------+
906+
| Type | Int64 | Int32 |
907+
| Domain | {1..=4} | Unknown |
908+
| Row 0 | 1 | 4 |
909+
| Row 1 | 2 | 4 |
910+
| Row 2 | 3 | 3 |
911+
| Row 3 | 4 | 6 |
912+
+--------+---------+---------+
913+
evaluation (internal):
914+
+--------+-----------------------------+
915+
| Column | Data |
916+
+--------+-----------------------------+
917+
| a | Column(Int64([1, 2, 3, 4])) |
918+
| Output | Int32([4, 4, 3, 6]) |
919+
+--------+-----------------------------+
920+
921+

src/query/service/src/interpreters/common/table_option_validation.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ use databend_storages_common_table_meta::table::OPT_KEY_SEGMENT_FORMAT;
7272
use databend_storages_common_table_meta::table::OPT_KEY_STORAGE_FORMAT;
7373
use databend_storages_common_table_meta::table::OPT_KEY_TABLE_COMPRESSION;
7474
use databend_storages_common_table_meta::table::OPT_KEY_TEMP_PREFIX;
75+
use databend_storages_common_table_meta::table::OPT_KEY_WRITE_DISTRIBUTION_MODE;
7576
pub use databend_storages_common_table_meta::table::analyze_count_min_sketch_error_rate_from_options;
7677
pub use databend_storages_common_table_meta::table::analyze_top_n_size_from_options;
7778
use log::error;
@@ -101,6 +102,7 @@ pub static CREATE_FUSE_OPTIONS: LazyLock<HashSet<&'static str>> = LazyLock::new(
101102
r.insert(OPT_KEY_DATABASE_ID);
102103
r.insert(OPT_KEY_COMMENT);
103104
r.insert(OPT_KEY_CHANGE_TRACKING);
105+
r.insert(OPT_KEY_WRITE_DISTRIBUTION_MODE);
104106

105107
r.insert(OPT_KEY_ENGINE);
106108

0 commit comments

Comments
 (0)