Skip to content

Commit 102392d

Browse files
authored
feat(query): add CONV scalar function (#19892)
feat(query): add conv scalar function
1 parent e55530a commit 102392d

9 files changed

Lines changed: 514 additions & 26 deletions

File tree

src/query/functions/src/scalars/binary.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,17 +220,30 @@ pub fn register(registry: &mut FunctionRegistry) {
220220

221221
registry
222222
.scalar_builder("to_hex")
223-
.aliases(&["hex", "hex_encode"])
223+
.aliases(&["hex_encode"])
224224
.function()
225225
.typed_1_arg::<BinaryType, StringType>()
226226
.passthrough_nullable()
227227
.calc_domain(|_, _| FunctionDomain::Full)
228228
.vectorized(vectorize_binary_to_string(
229229
|col| col.total_bytes_len() * 2,
230230
|val, output, _| {
231-
let extra_len = val.len() * 2;
232-
output.row_buffer.resize(extra_len, 0);
233-
hex::encode_to_slice(val, &mut output.row_buffer).unwrap();
231+
write_hex_lower(val, output);
232+
output.commit_row();
233+
},
234+
))
235+
.register();
236+
237+
registry
238+
.scalar_builder("hex")
239+
.function()
240+
.typed_1_arg::<BinaryType, StringType>()
241+
.passthrough_nullable()
242+
.calc_domain(|_, _| FunctionDomain::Full)
243+
.vectorized(vectorize_binary_to_string(
244+
|col| col.total_bytes_len() * 2,
245+
|val, output, _| {
246+
write_hex_lower(val, output);
234247
output.commit_row();
235248
},
236249
))
@@ -349,6 +362,12 @@ fn eval_unhex(val: Value<StringType>, ctx: &mut EvalContext) -> Value<BinaryType
349362
)(val, ctx)
350363
}
351364

365+
pub fn write_hex_lower(bytes: &[u8], output: &mut StringColumnBuilder) {
366+
let old_len = output.row_buffer.len();
367+
output.row_buffer.resize(old_len + bytes.len() * 2, 0);
368+
hex::encode_to_slice(bytes, &mut output.row_buffer[old_len..]).unwrap();
369+
}
370+
352371
fn eval_from_base64(val: Value<StringType>, ctx: &mut EvalContext) -> Value<BinaryType> {
353372
vectorize_string_to_binary(
354373
|col| col.total_bytes_len() * 4 / 3 + col.len() * 4,

src/query/functions/src/scalars/string.rs

Lines changed: 184 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use databend_common_expression::vectorize_with_builder_4_arg;
3232
use databend_functions_scalar_decimal::register_decimal_to_uuid;
3333
use stringslice::StringSlice;
3434

35+
use crate::scalars::binary::write_hex_lower;
3536
use crate::srfs;
3637

3738
pub const ALL_STRING_FUNC_NAMES: &[&str] = &[
@@ -60,6 +61,7 @@ pub const ALL_STRING_FUNC_NAMES: &[&str] = &[
6061
"trim_trailing",
6162
"trim_both",
6263
"to_hex",
64+
"conv",
6365
"bin",
6466
"oct",
6567
"to_hex",
@@ -668,16 +670,66 @@ pub fn register(registry: &mut FunctionRegistry) {
668670
.calc_domain(|_, _| FunctionDomain::Full)
669671
.vectorized(vectorize_with_builder_1_arg::<StringType, StringType>(
670672
|val, output, _| {
671-
let len = val.len() * 2;
672-
output.row_buffer.resize(len, 0);
673-
hex::encode_to_slice(val, &mut output.row_buffer).unwrap();
673+
write_hex_lower(val.as_bytes(), output);
674674
output.commit_row();
675675
},
676676
))
677677
.register();
678678

679-
// TODO: generalize them to be alias of [CONV](https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_conv)
680-
// Tracking issue: https://github.com/datafuselabs/databend/issues/7242
679+
registry
680+
.scalar_builder("hex")
681+
.function()
682+
.typed_1_arg::<StringType, StringType>()
683+
.passthrough_nullable()
684+
.calc_domain(|_, _| FunctionDomain::Full)
685+
.vectorized(vectorize_with_builder_1_arg::<StringType, StringType>(
686+
|val, output, _| {
687+
write_hex_lower(val.as_bytes(), output);
688+
output.commit_row();
689+
},
690+
))
691+
.register();
692+
693+
registry.register_passthrough_nullable_3_arg::<
694+
StringType,
695+
NumberType<i64>,
696+
NumberType<i64>,
697+
StringType,
698+
_,
699+
_,
700+
>(
701+
"conv",
702+
|_, _, _, _| FunctionDomain::MayThrow,
703+
vectorize_with_builder_3_arg::<
704+
StringType,
705+
NumberType<i64>,
706+
NumberType<i64>,
707+
StringType,
708+
>(|num, from_base, to_base, output, ctx| {
709+
conv(num, from_base, to_base, output, ctx);
710+
}),
711+
);
712+
713+
registry.register_passthrough_nullable_3_arg::<
714+
NumberType<i64>,
715+
NumberType<i64>,
716+
NumberType<i64>,
717+
StringType,
718+
_,
719+
_,
720+
>(
721+
"conv",
722+
|_, _, _, _| FunctionDomain::MayThrow,
723+
vectorize_with_builder_3_arg::<
724+
NumberType<i64>,
725+
NumberType<i64>,
726+
NumberType<i64>,
727+
StringType,
728+
>(|num, from_base, to_base, output, ctx| {
729+
conv(&num.to_string(), from_base, to_base, output, ctx);
730+
}),
731+
);
732+
681733
registry
682734
.scalar_builder("bin")
683735
.function()
@@ -686,7 +738,7 @@ pub fn register(registry: &mut FunctionRegistry) {
686738
.calc_domain(|_, _| FunctionDomain::Full)
687739
.vectorized(vectorize_with_builder_1_arg::<NumberType<i64>, StringType>(
688740
|val, output, _| {
689-
write!(output.row_buffer, "{val:b}").unwrap();
741+
write_conv_num(val as u64, 2, output);
690742
output.commit_row();
691743
},
692744
))
@@ -700,7 +752,7 @@ pub fn register(registry: &mut FunctionRegistry) {
700752
.calc_domain(|_, _| FunctionDomain::Full)
701753
.vectorized(vectorize_with_builder_1_arg::<NumberType<i64>, StringType>(
702754
|val, output, _| {
703-
write!(output.row_buffer, "{val:o}").unwrap();
755+
write_conv_num(val as u64, 8, output);
704756
output.commit_row();
705757
},
706758
))
@@ -720,6 +772,20 @@ pub fn register(registry: &mut FunctionRegistry) {
720772
))
721773
.register();
722774

775+
registry
776+
.scalar_builder("hex")
777+
.function()
778+
.typed_1_arg::<NumberType<i64>, StringType>()
779+
.passthrough_nullable()
780+
.calc_domain(|_, _| FunctionDomain::Full)
781+
.vectorized(vectorize_with_builder_1_arg::<NumberType<i64>, StringType>(
782+
|val, output, _| {
783+
write_conv_num(val as u64, 16, output);
784+
output.commit_row();
785+
},
786+
))
787+
.register();
788+
723789
const MAX_REPEAT_TIMES: u64 = 1000000;
724790
registry.register_passthrough_nullable_2_arg::<StringType, NumberType<u64>, StringType, _, _>(
725791
"repeat",
@@ -1079,6 +1145,117 @@ fn substr(builder: &mut StringColumnBuilder, str: &str, pos: i64, len: u64) {
10791145
builder.commit_row();
10801146
}
10811147

1148+
fn conv(
1149+
num: &str,
1150+
from_base: i64,
1151+
to_base: i64,
1152+
output: &mut StringColumnBuilder,
1153+
ctx: &mut databend_common_expression::EvalContext,
1154+
) {
1155+
let Some(from_base) = validate_conv_base(from_base) else {
1156+
ctx.set_error(
1157+
output.len(),
1158+
format!(
1159+
"from_base absolute value must be between 2 and 36, but got {}",
1160+
from_base
1161+
),
1162+
);
1163+
output.commit_row();
1164+
return;
1165+
};
1166+
1167+
let signed_to_base = to_base < 0;
1168+
let Some(to_base) = validate_conv_base(to_base) else {
1169+
ctx.set_error(
1170+
output.len(),
1171+
format!(
1172+
"to_base absolute value must be between 2 and 36, but got {}",
1173+
to_base
1174+
),
1175+
);
1176+
output.commit_row();
1177+
return;
1178+
};
1179+
1180+
let mut num = parse_conv_num(num, from_base);
1181+
if signed_to_base {
1182+
let signed_num = num as i64;
1183+
if signed_num < 0 {
1184+
output.put_char('-');
1185+
num = (signed_num as u64).wrapping_neg();
1186+
}
1187+
}
1188+
write_conv_num(num, to_base, output);
1189+
output.commit_row();
1190+
}
1191+
1192+
fn validate_conv_base(base: i64) -> Option<u32> {
1193+
let base = base.unsigned_abs();
1194+
if (2..=36).contains(&base) {
1195+
Some(base as u32)
1196+
} else {
1197+
None
1198+
}
1199+
}
1200+
1201+
fn parse_conv_num(num: &str, from_base: u32) -> u64 {
1202+
let num = num.trim_start();
1203+
let (negative, digits) = if let Some(rest) = num.strip_prefix('-') {
1204+
(true, rest)
1205+
} else if let Some(rest) = num.strip_prefix('+') {
1206+
(false, rest)
1207+
} else {
1208+
(false, num)
1209+
};
1210+
1211+
let mut value = 0_u64;
1212+
for ch in digits.bytes() {
1213+
let Some(digit) = conv_digit(ch) else {
1214+
break;
1215+
};
1216+
if digit >= from_base {
1217+
break;
1218+
}
1219+
1220+
value = value
1221+
.saturating_mul(from_base as u64)
1222+
.saturating_add(digit as u64);
1223+
}
1224+
1225+
if negative {
1226+
value.wrapping_neg()
1227+
} else {
1228+
value
1229+
}
1230+
}
1231+
1232+
fn conv_digit(ch: u8) -> Option<u32> {
1233+
match ch {
1234+
b'0'..=b'9' => Some((ch - b'0') as u32),
1235+
b'a'..=b'z' => Some((ch - b'a' + 10) as u32),
1236+
b'A'..=b'Z' => Some((ch - b'A' + 10) as u32),
1237+
_ => None,
1238+
}
1239+
}
1240+
1241+
fn write_conv_num(mut num: u64, to_base: u32, output: &mut StringColumnBuilder) {
1242+
const DIGITS: &[u8; 36] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1243+
1244+
if num == 0 {
1245+
output.put_char('0');
1246+
return;
1247+
}
1248+
1249+
let mut buf = [0_u8; 64];
1250+
let mut idx = buf.len();
1251+
while num > 0 {
1252+
idx -= 1;
1253+
buf[idx] = DIGITS[(num % to_base as u64) as usize];
1254+
num /= to_base as u64;
1255+
}
1256+
output.put_slice(&buf[idx..]);
1257+
}
1258+
10821259
#[inline]
10831260
fn substr_ascii(builder: &mut StringColumnBuilder, str: &str, pos: i64, len: u64) {
10841261
let byte_len = str.len();

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ fn test_length(file: &mut impl Write) {
5151

5252
fn test_to_hex(file: &mut impl Write) {
5353
run_ast(file, "to_hex(to_binary('abc'))", &[]);
54+
run_ast(file, "hex(to_binary('abc'))", &[]);
5455
run_ast(file, "to_hex(to_binary(a))", &[(
5556
"a",
5657
StringType::from_data(vec!["abc", "def", "databend"]),

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ fn test_string() {
4949
test_bin(file);
5050
test_oct(file);
5151
test_hex(file);
52+
test_conv(file);
5253
test_pad(file);
5354
test_replace(file);
5455
test_translate(file);
@@ -498,6 +499,34 @@ fn test_hex(file: &mut impl Write) {
498499
run_ast(file, "hex(c)", columns);
499500
run_ast(file, "hex(d)", columns);
500501
run_ast(file, "hex(e)", columns);
502+
run_ast(file, "to_hex(c)", columns);
503+
run_ast(file, "to_hex(e)", columns);
504+
}
505+
506+
fn test_conv(file: &mut impl Write) {
507+
run_ast(file, "conv('a', 16, 10)", &[]);
508+
run_ast(file, "conv('6E', 18, 8)", &[]);
509+
run_ast(file, "conv('zz', 36, 10)", &[]);
510+
run_ast(file, "conv('11', 2, 36)", &[]);
511+
run_ast(file, "conv(10, 10, 2)", &[]);
512+
run_ast(file, "conv(10, 16, 10)", &[]);
513+
run_ast(file, "conv(15, 10, 16)", &[]);
514+
run_ast(file, "conv(-128, 10, 8)", &[]);
515+
run_ast(file, "conv(-17, 10, -18)", &[]);
516+
run_ast(file, "conv('FFFFFFFFFFFFFFFF', 16, -10)", &[]);
517+
run_ast(file, "conv('11', -2, 10)", &[]);
518+
519+
let columns = &[
520+
("a", StringType::from_data(vec!["10", "a", "zz"])),
521+
(
522+
"b",
523+
Int64Type::from_data_with_validity(vec![2i64, 16, 36], vec![true, false, true]),
524+
),
525+
("c", Int64Type::from_data(vec![10i64, 10, 16])),
526+
("d", Int64Type::from_data(vec![10i64, 16, -128])),
527+
];
528+
run_ast(file, "conv(a, b, c)", columns);
529+
run_ast(file, "conv(d, 10, 2)", columns);
501530
}
502531

503532
fn test_pad(file: &mut impl Write) {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ output domain : {"616263"..="616263"}
9595
output : '616263'
9696

9797

98+
ast : hex(to_binary('abc'))
99+
raw expr : hex(to_binary('abc'))
100+
checked expr : hex<Binary>(CAST<String>("abc" AS Binary))
101+
optimized expr : "616263"
102+
output type : String
103+
output domain : {"616263"..="616263"}
104+
output : '616263'
105+
106+
98107
ast : to_hex(to_binary(a))
99108
raw expr : to_hex(to_binary(a::String))
100109
checked expr : to_hex<Binary>(CAST<String>(a AS Binary))

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ diff_dows -> diff_days
3131
diff_doys -> diff_days
3232
diff_epochs -> diff_seconds
3333
diff_isodows -> diff_days
34-
hex -> to_hex
3534
hex_decode_binary -> from_hex
3635
hex_encode -> to_hex
3736
intdiv -> div
@@ -1355,6 +1354,10 @@ Functions overloads:
13551354
28 contains(Array(Boolean), Boolean) :: Boolean
13561355
29 contains(Array(Boolean) NULL, Boolean NULL) :: Boolean NULL
13571356
30 contains(Array(T0) NULL, T0) :: Boolean
1357+
0 conv(String, Int64, Int64) :: String
1358+
1 conv(String NULL, Int64 NULL, Int64 NULL) :: String NULL
1359+
2 conv(Int64, Int64, Int64) :: String
1360+
3 conv(Int64 NULL, Int64 NULL, Int64 NULL) :: String NULL
13581361
0 convert_timezone(String, Timestamp) :: Timestamp
13591362
1 convert_timezone(String NULL, Timestamp NULL) :: Timestamp NULL
13601363
0 cos(Float64) :: Float64
@@ -2259,6 +2262,12 @@ Functions overloads:
22592262
1 h3_unidirectional_edge_is_valid(UInt64 NULL) :: Boolean NULL
22602263
0 haversine(Float64, Float64, Float64, Float64) :: Float64
22612264
1 haversine(Float64 NULL, Float64 NULL, Float64 NULL, Float64 NULL) :: Float64 NULL
2265+
0 hex(String) :: String
2266+
1 hex(String NULL) :: String NULL
2267+
2 hex(Int64) :: String
2268+
3 hex(Int64 NULL) :: String NULL
2269+
4 hex(Binary) :: String
2270+
5 hex(Binary NULL) :: String NULL
22622271
0 hilbert_range_index FACTORY
22632272
0 humanize_number(Float64) :: String
22642273
1 humanize_number(Float64 NULL) :: String NULL

0 commit comments

Comments
 (0)