Skip to content

Commit 4da279b

Browse files
committed
Allow string concatenation in inside sqlpage function parameters
1 parent e338a99 commit 4da279b

File tree

5 files changed

+80
-14
lines changed

5 files changed

+80
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- the default component is now [table](https://sql.ophir.dev/documentation.sql?component=table#component), which makes it extremely easy to display the results of any SQL query. Just write any query in a `.sql` file open it in your browser, and you will see the results displayed in a table, without having to use any SQLPage-specific column names or attributes.
66
- Better error messages when a [custom component](https://sql.ophir.dev/custom_components.sql) contains a syntax error. [Fix contributed upstream](https://github.com/sunng87/handlebars-rust/pull/638)
77
- Lift a limitation on sqlpage function nesting. In previous versions, some sqlpage functions could not be used inside other sqlpage functions. For instance, `sqlpage.url_encode(sqlpage.exec('my_program'))` used to throw an error saying `Nested exec() function not allowed`. This limitation is now lifted, and you can nest any sqlpage function inside any other sqlpage function.
8+
- Allow string concatenation in inside sqlpage function parameters. For instance, `sqlpage.exec('echo', 'Hello ' || 'world')` is now supported, whereas it used to throw an error saying `exec('echo', 'Hello ' || 'world') is not a valid call. Only variables (such as $my_variable) and sqlpage function calls (such as sqlpage.header('my_header')) are supported as arguments to sqlpage functions.`.
89

910
## 0.20.1 (2024-03-23)
1011

Cargo.lock

Lines changed: 4 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/webserver/database/sql.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -371,23 +371,54 @@ pub(super) fn extract_integer(
371371
}
372372

373373
pub(super) fn function_arg_to_stmt_param(arg: &mut FunctionArg) -> Option<StmtParam> {
374-
match function_arg_expr(arg) {
375-
Some(Expr::Value(Value::Placeholder(placeholder))) => {
374+
function_arg_expr(arg).and_then(expr_to_stmt_param)
375+
}
376+
377+
fn expr_to_stmt_param(arg: &mut Expr) -> Option<StmtParam> {
378+
match arg {
379+
Expr::Value(Value::Placeholder(placeholder)) => {
376380
Some(map_param(std::mem::take(placeholder)))
377381
}
378-
Some(Expr::Identifier(ident)) => extract_ident_param(ident),
379-
Some(Expr::Function(Function {
382+
Expr::Identifier(ident) => extract_ident_param(ident),
383+
Expr::Function(Function {
380384
name: ObjectName(func_name_parts),
381385
args,
382386
..
383-
})) if is_sqlpage_func(func_name_parts) => Some(func_call_to_param(
387+
}) if is_sqlpage_func(func_name_parts) => Some(func_call_to_param(
384388
sqlpage_func_name(func_name_parts),
385389
args.as_mut_slice(),
386390
)),
387-
Some(Expr::Value(Value::SingleQuotedString(param_value))) => {
391+
Expr::Value(Value::SingleQuotedString(param_value)) => {
388392
Some(StmtParam::Literal(std::mem::take(param_value)))
389393
}
390-
_ => None,
394+
Expr::BinaryOp {
395+
// 'str1' || 'str2'
396+
left,
397+
op: BinaryOperator::StringConcat,
398+
right,
399+
} => {
400+
let left = expr_to_stmt_param(left)?;
401+
let right = expr_to_stmt_param(right)?;
402+
Some(StmtParam::Concat(vec![left, right]))
403+
}
404+
// CONCAT('str1', 'str2', ...)
405+
Expr::Function(Function {
406+
name: ObjectName(func_name_parts),
407+
args,
408+
..
409+
}) if func_name_parts.len() == 1
410+
&& func_name_parts[0].value.eq_ignore_ascii_case("concat") =>
411+
{
412+
let mut concat_args = Vec::with_capacity(args.len());
413+
for arg in args {
414+
concat_args.push(function_arg_to_stmt_param(arg)?);
415+
}
416+
Some(StmtParam::Concat(concat_args))
417+
}
418+
_ => {
419+
log::warn!("Unsupported function argument: {arg}");
420+
None
421+
}
391422
}
392423
}
393424

@@ -397,8 +428,10 @@ pub(super) fn stmt_param_error_invalid_arguments(
397428
) -> StmtParam {
398429
StmtParam::Error(format!(
399430
"{func_name}({}) is not a valid call. \
400-
Only variables (such as $my_variable) \
401-
and sqlpage function calls (such as sqlpage.header('my_header')) \
431+
Only variables (such as $my_variable), \
432+
sqlpage function calls (such as sqlpage.header('my_header')), \
433+
constants (such as 'my_string'), \
434+
and concatenations (such as CONCAT(x, y)) \
402435
are supported as arguments to sqlpage functions.",
403436
FormatArguments(arguments)
404437
))

src/webserver/database/sql_pseudofunctions.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub(super) enum StmtParam {
4040
EnvironmentVariable(String),
4141
SqlPageVersion,
4242
Literal(String),
43+
Concat(Vec<StmtParam>),
4344
UploadedFilePath(String),
4445
UploadedFileMimeType(String),
4546
PersistUploadedFile {
@@ -453,6 +454,7 @@ pub(super) async fn extract_req_param<'a>(
453454
.with_context(|| format!("Unable to read environment variable {var}"))?,
454455
StmtParam::SqlPageVersion => Some(Cow::Borrowed(env!("CARGO_PKG_VERSION"))),
455456
StmtParam::Literal(x) => Some(Cow::Owned(x.to_string())),
457+
StmtParam::Concat(args) => concat_params(&args[..], request).await?,
456458
StmtParam::AllVariables(get_or_post) => extract_get_or_post(*get_or_post, request),
457459
StmtParam::Path => Some(Cow::Borrowed(&request.path)),
458460
StmtParam::Protocol => Some(Cow::Borrowed(&request.protocol)),
@@ -469,6 +471,20 @@ pub(super) async fn extract_req_param<'a>(
469471
})
470472
}
471473

474+
async fn concat_params<'a>(
475+
args: &[StmtParam],
476+
request: &'a RequestInfo,
477+
) -> anyhow::Result<Option<Cow<'a, str>>> {
478+
let mut result = String::new();
479+
for arg in args {
480+
let Some(arg) = Box::pin(extract_req_param(arg, request)).await? else {
481+
return Ok(None);
482+
};
483+
result.push_str(&arg);
484+
}
485+
Ok(Some(Cow::Owned(result)))
486+
}
487+
472488
fn extract_get_or_post(
473489
get_or_post: Option<GetOrPost>,
474490
request: &RequestInfo,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
select 'text' as component,
2+
'With "||": ' ||
3+
CASE sqlpage.url_encode('/' || $x)
4+
WHEN '%2F1' THEN 'It works !'
5+
ELSE 'Error: "/1" should be urlencoded to "%2F1"'
6+
END
7+
|| ' | With CONCAT: ' ||
8+
CASE sqlpage.url_encode(CONCAT('/', $x)) -- $x is set to '1' in the test
9+
WHEN '%2F1' THEN 'With CONCAT: It works !'
10+
ELSE 'Error: "/1" should be urlencoded to "%2F1"'
11+
END
12+
|| ' | With a null value: ' ||
13+
CASE sqlpage.url_encode(CONCAT('/', $thisisnull)) IS NULL
14+
WHEN true THEN 'With a null value: It works !'
15+
ELSE 'Error: a null value concatenated with "/" should be null, and urlencoded to NULL'
16+
END
17+
AS contents;

0 commit comments

Comments
 (0)