Skip to content

Commit be140ec

Browse files
authored
feat(cubesql): Support %s in format (#9129)
Used by Metabase introspection See metabase/metabase#50900 Fixes #9126
1 parent 4583816 commit be140ec

7 files changed

+484
-0
lines changed

rust/cubesql/cubesql/src/compile/engine/udf/common.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3281,6 +3281,42 @@ pub fn create_format_udf() -> ScalarUDF {
32813281
// %% is escaped to single %
32823282
result.push('%');
32833283
}
3284+
Some('s') => {
3285+
// Handle %s - regular string
3286+
if arg_index >= args.len() {
3287+
return Err(DataFusionError::Execution(
3288+
"Not enough arguments for format string".to_string(),
3289+
));
3290+
}
3291+
3292+
let arg = &args[arg_index];
3293+
let value = match arg.data_type() {
3294+
DataType::Utf8 => {
3295+
let str_arr = downcast_string_arg!(arg, "arg", i32);
3296+
if str_arr.is_null(i) {
3297+
// A null value is treated as an empty string
3298+
String::new()
3299+
} else {
3300+
str_arr.value(i).to_string()
3301+
}
3302+
}
3303+
_ => {
3304+
// For other types, try to convert to string
3305+
let str_arr = cast(&arg, &DataType::Utf8)?;
3306+
let str_arr =
3307+
str_arr.as_any().downcast_ref::<StringArray>().unwrap();
3308+
if str_arr.is_null(i) {
3309+
// A null value is treated as an empty string
3310+
String::new()
3311+
} else {
3312+
str_arr.value(i).to_string()
3313+
}
3314+
}
3315+
};
3316+
3317+
result.push_str(&value);
3318+
arg_index += 1;
3319+
}
32843320
Some(c) => {
32853321
return Err(DataFusionError::Execution(format!(
32863322
"Unsupported format specifier %{}",

rust/cubesql/cubesql/src/compile/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16214,6 +16214,30 @@ LIMIT {{ limit }}{% endif %}"#.to_string(),
1621416214

1621516215
#[tokio::test]
1621616216
async fn test_format_function() -> Result<(), CubeError> {
16217+
// Test: Basic usage with a single string
16218+
let result = execute_query(
16219+
"SELECT format('%s', 'foo') AS formatted_string".to_string(),
16220+
DatabaseProtocol::PostgreSQL,
16221+
)
16222+
.await?;
16223+
insta::assert_snapshot!("formatted_string", result);
16224+
16225+
// Test: Basic usage with a single null string
16226+
let result = execute_query(
16227+
"SELECT format('%s', NULL) = '' AS formatted_null_string_is_empty".to_string(),
16228+
DatabaseProtocol::PostgreSQL,
16229+
)
16230+
.await?;
16231+
insta::assert_snapshot!("formatted_null_string_is_empty", result);
16232+
16233+
// Test: Basic usage with a multiple strings
16234+
let result = execute_query(
16235+
"SELECT format('%s.%s', 'foo', 'bar') AS formatted_strings".to_string(),
16236+
DatabaseProtocol::PostgreSQL,
16237+
)
16238+
.await?;
16239+
insta::assert_snapshot!("formatted_strings", result);
16240+
1621716241
// Test: Basic usage with a single identifier
1621816242
let result = execute_query(
1621916243
"SELECT format('%I', 'column_name') AS formatted_identifier".to_string(),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: cubesql/src/compile/mod.rs
3+
expression: result
4+
---
5+
+--------------------------------+
6+
| formatted_null_string_is_empty |
7+
+--------------------------------+
8+
| true |
9+
+--------------------------------+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: cubesql/src/compile/mod.rs
3+
expression: result
4+
---
5+
+------------------+
6+
| formatted_string |
7+
+------------------+
8+
| foo |
9+
+------------------+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: cubesql/src/compile/mod.rs
3+
expression: result
4+
---
5+
+-------------------+
6+
| formatted_strings |
7+
+-------------------+
8+
| foo.bar |
9+
+-------------------+

0 commit comments

Comments
 (0)