Skip to content

Commit 29fa14c

Browse files
committed
calls to json_object in sqlpage function arguments
1 parent 82a6826 commit 29fa14c

File tree

5 files changed

+56
-8
lines changed

5 files changed

+56
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from products;
3737
```
3838
- Before, you would usually build the link manually with `CONCAT('/product.sql?product=', product_name)`, which would fail if the product name contained special characters like '&'. The new `sqlpage.link` function takes care of encoding the parameters correctly.
39+
- Calls to `json_object` are now accepted as arguments to SQLPage functions. This allows you to pass complex data structures to functions such as `sqlpage.fetch`, `sqlpage.run_sql`, and `sqlpage.link`.
3940

4041
## 0.24.0 (2024-06-23)
4142
- in the form component, searchable `select` fields now support more than 50 options. They used to display only the first 50 options.

src/webserver/database/sql.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -587,14 +587,24 @@ fn expr_to_stmt_param(arg: &mut Expr) -> Option<StmtParam> {
587587
..
588588
}),
589589
..
590-
}) if func_name_parts.len() == 1
591-
&& func_name_parts[0].value.eq_ignore_ascii_case("concat") =>
592-
{
593-
let mut concat_args = Vec::with_capacity(args.len());
594-
for arg in args {
595-
concat_args.push(function_arg_to_stmt_param(arg)?);
590+
}) if func_name_parts.len() == 1 => {
591+
let func_name = func_name_parts[0].value.as_str();
592+
if func_name.eq_ignore_ascii_case("concat") {
593+
let mut concat_args = Vec::with_capacity(args.len());
594+
for arg in args {
595+
concat_args.push(function_arg_to_stmt_param(arg)?);
596+
}
597+
Some(StmtParam::Concat(concat_args))
598+
} else if func_name.eq_ignore_ascii_case("json_object") {
599+
let mut json_obj_args = Vec::with_capacity(args.len());
600+
for arg in args {
601+
json_obj_args.push(function_arg_to_stmt_param(arg)?);
602+
}
603+
Some(StmtParam::JsonObject(json_obj_args))
604+
} else {
605+
log::warn!("SQLPage cannot emulate the following function: {func_name}");
606+
None
596607
}
597-
Some(StmtParam::Concat(concat_args))
598608
}
599609
_ => {
600610
log::warn!("Unsupported function argument: {arg}");

src/webserver/database/syntax_tree.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub(crate) enum StmtParam {
3838
Literal(String),
3939
Null,
4040
Concat(Vec<StmtParam>),
41+
JsonObject(Vec<StmtParam>),
4142
FunctionCall(SqlPageFunctionCall),
4243
}
4344

@@ -56,6 +57,13 @@ impl std::fmt::Display for StmtParam {
5657
}
5758
write!(f, ")")
5859
}
60+
StmtParam::JsonObject(items) => {
61+
write!(f, "JSON_OBJECT(")?;
62+
for item in items {
63+
write!(f, "{item}, ")?;
64+
}
65+
write!(f, ")")
66+
}
5967
StmtParam::FunctionCall(call) => write!(f, "{call}"),
6068
StmtParam::Error(x) => {
6169
if let Some((i, _)) = x.char_indices().nth(21) {
@@ -145,6 +153,7 @@ pub(super) async fn extract_req_param<'a, 'b>(
145153
StmtParam::Literal(x) => Some(Cow::Owned(x.to_string())),
146154
StmtParam::Null => None,
147155
StmtParam::Concat(args) => concat_params(&args[..], request, db_connection).await?,
156+
StmtParam::JsonObject(args) => json_object_params(&args[..], request, db_connection).await?,
148157
StmtParam::FunctionCall(func) => func.evaluate(request, db_connection).await.with_context(|| {
149158
format!(
150159
"Error in function call {func}.\nExpected {:#}",
@@ -168,3 +177,26 @@ async fn concat_params<'a, 'b>(
168177
}
169178
Ok(Some(Cow::Owned(result)))
170179
}
180+
181+
async fn json_object_params<'a, 'b>(
182+
args: &[StmtParam],
183+
request: &'a RequestInfo,
184+
db_connection: &'b mut DbConn,
185+
) -> anyhow::Result<Option<Cow<'a, str>>> {
186+
use serde::{ser::SerializeMap, Serializer};
187+
let mut result = Vec::new();
188+
let mut ser = serde_json::Serializer::new(&mut result);
189+
let mut map_ser = ser.serialize_map(Some(args.len()))?;
190+
let mut it = args.iter();
191+
while let Some(key) = it.next() {
192+
let key = Box::pin(extract_req_param(key, request, db_connection)).await?;
193+
map_ser.serialize_key(&key)?;
194+
let val = it
195+
.next()
196+
.ok_or_else(|| anyhow::anyhow!("Odd number of arguments in JSON_OBJECT"))?;
197+
let val = Box::pin(extract_req_param(val, request, db_connection)).await?;
198+
map_ser.serialize_value(&val)?;
199+
}
200+
map_ser.end()?;
201+
Ok(Some(Cow::Owned(String::from_utf8(result)?)))
202+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
select 'text' as component,
2+
case sqlpage.link('test.sql', json_object('x', 123))
3+
when 'test.sql?x=123' then 'It works !'
4+
else 'error: ' || coalesce(sqlpage.link('test.sql', json_object('x', 123)), 'NULL')
5+
end AS contents;

tests/sql_test_files/it_works_run_sql_from_database.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
select 'dynamic' as component,
2-
sqlpage.run_sql('tests/display_text.sql', CONCAT('{"html": "', html,'"}')) as properties
2+
sqlpage.run_sql('tests/display_text.sql', json_object('html', html)) as properties
33
from (
44
select 'It ' as html
55
union all

0 commit comments

Comments
 (0)