Skip to content

Commit 1dfd2da

Browse files
authored
feat(query): add decode and to_char(timestamp, format) function (#18008)
* add decode function * add to_char * add to_char * add to_char * update tests * update tests
1 parent 00bd953 commit 1dfd2da

File tree

11 files changed

+202
-32
lines changed

11 files changed

+202
-32
lines changed

.github/actions/build_bindings_python/action.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ runs:
1616
shell: bash
1717
run: |
1818
VERSION=`echo ${{ inputs.version }} | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+'`
19-
echo "building tag and version: $GIT_TAG $VERSION"
19+
echo "building tag and version: git tag: $GIT_TAG version: $VERSION"
2020
sed "s#version = \"0.0.0\"#version = \"$VERSION\"#g" Cargo.toml > Cargo.toml.bak
2121
mv Cargo.toml.bak Cargo.toml
22+
grep 'version' Cargo.toml
2223
2324
- name: Get Toolchain
2425
id: toolchain

.github/workflows/bindings.python.yml

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,11 @@ jobs:
9090
merge-multiple: true
9191
path: src/bendpy/dist
9292

93-
- name: Publish to TestPyPI
94-
uses: pypa/gh-action-pypi-publish@release/v1
95-
if: ${{ startsWith(github.ref, 'refs/tags/') && contains(github.ref, '-') }}
96-
continue-on-error: true
97-
with:
98-
repository-url: https://test.pypi.org/legacy/
99-
skip-existing: true
100-
packages-dir: src/bendpy/dist
101-
10293
- name: Publish to PyPI
10394
timeout-minutes: 10
104-
uses: pypa/gh-action-pypi-publish@release/v1
105-
with:
106-
password: ${{ secrets.PYPI_API_TOKEN }}
107-
skip-existing: true
108-
verify-metadata: false
109-
attestations: false
110-
verbose: true
111-
packages-dir: src/bendpy/dist
95+
run: |
96+
pip install twine
97+
twine upload --skip-existing --verbose src/bendpy/dist/*.whl
98+
env:
99+
TWINE_USERNAME: __token__
100+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

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.

src/query/ast/src/ast/expr.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,20 @@ pub struct FunctionCall {
998998
pub lambda: Option<Lambda>,
999999
}
10001000

1001+
impl Default for FunctionCall {
1002+
fn default() -> Self {
1003+
Self {
1004+
distinct: false,
1005+
name: Identifier::from_name(None, ""),
1006+
args: vec![],
1007+
params: vec![],
1008+
order_by: vec![],
1009+
window: None,
1010+
lambda: None,
1011+
}
1012+
}
1013+
}
1014+
10011015
impl Display for FunctionCall {
10021016
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
10031017
let FunctionCall {

src/query/expression/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ num-traits = { workspace = true }
5050
rand = { workspace = true }
5151
rand_distr = { workspace = true }
5252
recursive = { workspace = true }
53+
regex = { workspace = true }
5354
roaring = { workspace = true, features = ["serde"] }
5455
rust_decimal = { workspace = true }
5556
rustls = { workspace = true }

src/query/expression/src/utils/date_helper.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,3 +1314,44 @@ pub fn previous_or_next_day(dt: &Zoned, target: Weekday, is_previous: bool) -> i
13141314

13151315
datetime_to_date_inner_number(dt) + dir * days_diff
13161316
}
1317+
1318+
pub struct PGDateTimeFormatter;
1319+
1320+
impl PGDateTimeFormatter {
1321+
pub fn format(dt: Zoned, format_string: &str) -> String {
1322+
let mut result = format_string.to_string();
1323+
let mut format_map: Vec<(&str, fn(&Zoned) -> String)> = Vec::new();
1324+
1325+
format_map.push(("YYYY", |dt| dt.strftime("%Y").to_string()));
1326+
format_map.push(("YY", |dt| dt.strftime("%y").to_string()));
1327+
format_map.push(("MM", |dt| dt.strftime("%m").to_string()));
1328+
format_map.push(("MON", |dt| dt.strftime("%b").to_string()));
1329+
format_map.push(("MMMM", |dt| dt.strftime("%B").to_string()));
1330+
format_map.push(("DD", |dt| dt.strftime("%d").to_string()));
1331+
format_map.push(("DY", |dt| dt.strftime("%a").to_string()));
1332+
format_map.push(("HH24", |dt| dt.strftime("%H").to_string()));
1333+
format_map.push(("HH12", |dt| dt.strftime("%I").to_string()));
1334+
format_map.push(("AM", |dt| dt.strftime("%p").to_string()));
1335+
format_map.push(("PM", |dt| dt.strftime("%p").to_string()));
1336+
format_map.push(("MI", |dt| dt.strftime("%M").to_string()));
1337+
format_map.push(("SS", |dt| dt.strftime("%S").to_string()));
1338+
format_map.push(("FF", |dt| dt.strftime("%f").to_string()));
1339+
format_map.push(("TZH", |dt| {
1340+
dt.strftime("%z").to_string().chars().take(3).collect()
1341+
}));
1342+
format_map.push(("TZM", |dt| {
1343+
dt.strftime("%z")
1344+
.to_string()
1345+
.chars()
1346+
.skip(3)
1347+
.take(2)
1348+
.collect()
1349+
}));
1350+
format_map.push(("UUUU", |dt| dt.strftime("%G").to_string()));
1351+
for (key, func) in &format_map {
1352+
let reg = regex::Regex::new(&format!(r"(?i){}", key)).unwrap();
1353+
result = reg.replace_all(&result, func(&dt)).to_string();
1354+
}
1355+
result
1356+
}
1357+
}

src/query/functions/src/scalars/other.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use databend_common_base::base::convert_byte_size;
2121
use databend_common_base::base::convert_number_size;
2222
use databend_common_base::base::uuid::Uuid;
2323
use databend_common_base::base::OrderedFloat;
24+
use databend_common_expression::date_helper::DateConverter;
25+
use databend_common_expression::date_helper::PGDateTimeFormatter;
2426
use databend_common_expression::error_to_null;
2527
use databend_common_expression::types::boolean::BooleanDomain;
2628
use databend_common_expression::types::nullable::NullableColumn;
@@ -483,6 +485,19 @@ fn register_num_to_char(registry: &mut FunctionRegistry) {
483485
},
484486
),
485487
);
488+
489+
registry.register_passthrough_nullable_2_arg::<TimestampType, StringType, StringType, _, _>(
490+
"to_char",
491+
|_, _, _| FunctionDomain::Full,
492+
vectorize_with_builder_2_arg::<TimestampType, StringType, StringType>(
493+
|micros, format, output, ctx| {
494+
let ts = micros.to_timestamp(ctx.func_ctx.tz.clone());
495+
let res = PGDateTimeFormatter::format(ts, format);
496+
output.put_str(&res);
497+
output.commit_row();
498+
},
499+
),
500+
);
486501
}
487502

488503
/// Compute `grouping` by `grouping_id` and `cols`.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3851,6 +3851,8 @@ Functions overloads:
38513851
3 to_char(Float32 NULL, String NULL) :: String NULL
38523852
4 to_char(Float64, String) :: String
38533853
5 to_char(Float64 NULL, String NULL) :: String NULL
3854+
6 to_char(Timestamp, String) :: String
3855+
7 to_char(Timestamp NULL, String NULL) :: String NULL
38543856
0 to_date(Variant) :: Date NULL
38553857
1 to_date(Variant NULL) :: Date NULL
38563858
2 to_date(String, String) :: Date NULL

src/query/sql/src/planner/semantic/type_check.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3478,6 +3478,7 @@ impl<'a> TypeChecker<'a> {
34783478
Ascii::new("is_error"),
34793479
Ascii::new("error_or"),
34803480
Ascii::new("coalesce"),
3481+
Ascii::new("decode"),
34813482
Ascii::new("last_query_id"),
34823483
Ascii::new("array_sort"),
34833484
Ascii::new("array_aggregate"),
@@ -3717,6 +3718,79 @@ impl<'a> TypeChecker<'a> {
37173718
let args_ref: Vec<&Expr> = new_args.iter().collect();
37183719
Some(self.resolve_function(span, "if", vec![], &args_ref))
37193720
}
3721+
("decode", args) => {
3722+
// DECODE( <expr> , <search1> , <result1> [ , <search2> , <result2> ... ] [ , <default> ] )
3723+
// Note that, contrary to CASE, a NULL value in the select expression matches a NULL value in the search expressions.
3724+
if args.len() < 3 {
3725+
return Some(Err(ErrorCode::BadArguments(
3726+
"DECODE requires at least 3 arguments",
3727+
)
3728+
.set_span(span)));
3729+
}
3730+
3731+
let mut new_args = Vec::with_capacity(args.len() * 2 + 1);
3732+
let search_expr = args[0].clone();
3733+
let mut i = 1;
3734+
3735+
while i < args.len() {
3736+
let search = args[i].clone();
3737+
let result = if i + 1 < args.len() {
3738+
args[i + 1].clone()
3739+
} else {
3740+
// If we're at the last argument and it's odd, it's the default value
3741+
break;
3742+
};
3743+
3744+
// (a = b) or (a is null and b is null)
3745+
let is_null_a = Expr::IsNull {
3746+
span,
3747+
expr: Box::new(search_expr.clone()),
3748+
not: false,
3749+
};
3750+
let is_null_b = Expr::IsNull {
3751+
span,
3752+
expr: Box::new(search.clone()),
3753+
not: false,
3754+
};
3755+
let and_expr = Expr::BinaryOp {
3756+
span,
3757+
op: BinaryOperator::And,
3758+
left: Box::new(is_null_a),
3759+
right: Box::new(is_null_b),
3760+
};
3761+
3762+
let eq_expr = Expr::BinaryOp {
3763+
span,
3764+
op: BinaryOperator::Eq,
3765+
left: Box::new(search_expr.clone()),
3766+
right: Box::new(search),
3767+
};
3768+
3769+
let or_expr = Expr::BinaryOp {
3770+
span,
3771+
op: BinaryOperator::Or,
3772+
left: Box::new(eq_expr),
3773+
right: Box::new(and_expr),
3774+
};
3775+
3776+
new_args.push(or_expr);
3777+
new_args.push(result);
3778+
i += 2;
3779+
}
3780+
3781+
// Add default value if it exists
3782+
if i + 1 == args.len() {
3783+
new_args.push(args[i].clone());
3784+
} else {
3785+
new_args.push(Expr::Literal {
3786+
span,
3787+
value: Literal::Null,
3788+
});
3789+
}
3790+
3791+
let args_ref: Vec<&Expr> = new_args.iter().collect();
3792+
Some(self.resolve_function(span, "if", vec![], &args_ref))
3793+
}
37203794
("last_query_id", args) => {
37213795
// last_query_id(index) returns query_id in current session by index
37223796
let res: Result<i64> = try {

tests/sqllogictests/suites/query/functions/02_0010_function_if.test

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ select [{}] from numbers(3)
2222
[{}]
2323
[{}]
2424

25-
query B
25+
query ?
2626
select if(number>1, true, false) from numbers(3) order by number
2727
----
2828
0
@@ -51,21 +51,21 @@ zero
5151
Z+
5252
Z+
5353

54-
query B
54+
query ?
5555
select if(number<1, true, null) from numbers(3) order by number
5656
----
5757
1
5858
NULL
5959
NULL
6060

61-
query F
61+
query ?
6262
select if(number<4, number, number / 0) from numbers(3) order by number
6363
----
6464
0.0
6565
1.0
6666
2.0
6767

68-
query F
68+
query ?
6969
select if(number>4, number / 0, number) from numbers(3) order by number
7070
----
7171
0.0
@@ -75,7 +75,7 @@ select if(number>4, number / 0, number) from numbers(3) order by number
7575
statement error 1006
7676
select if(number<4, number / 0, number) from numbers(3) order by number
7777

78-
query F
78+
query ?
7979
select if (number > 0, 1 / number, null) from numbers(2);
8080
----
8181
NULL
@@ -111,12 +111,12 @@ multi-if
111111
statement error 1065
112112
select if(number = 4, 3) from numbers(1)
113113

114-
query F
114+
query ?
115115
select if(number = 1, number, number = 0, number, number / 0) from numbers(1)
116116
----
117117
0.0
118118

119-
query F
119+
query ?
120120
select if (number > 0, 1 / number, null) from numbers(2);
121121
----
122122
NULL
@@ -160,18 +160,34 @@ select if(true, number, null), if(false, number, null) from numbers(1)
160160
0 NULL
161161

162162
statement ok
163-
drop table if exists t
163+
CREATE or replace TABLE t (a int);
164164

165165
statement ok
166-
create table t(a int null, b int)
167-
168-
statement ok
169-
insert into t values(null, 1)
166+
INSERT INTO t (a) VALUES
167+
(1),
168+
(2),
169+
(NULL),
170+
(4);
170171

171172
query II
172-
select a, b, a*2, if(b=1, a*2, b) from t
173+
select a, a*2, if(a=1, a*2, a*3) from t
173174
----
174-
NULL 1 NULL NULL
175+
1 2 2
176+
2 4 6
177+
NULL NULL NULL
178+
4 8 12
179+
180+
query II
181+
select a, decode(a, 1, 'one',
182+
2, 'two',
183+
NULL, '-NULL-',
184+
'other'
185+
) AS decode_result from t
186+
----
187+
1 one
188+
2 two
189+
NULL -NULL-
190+
4 other
175191

176192
statement ok
177193
drop table t

0 commit comments

Comments
 (0)