Skip to content

Commit f643eaf

Browse files
authored
Merge pull request #394 from lovasoa/function_post_result
function post result
2 parents 7f8753e + c629c38 commit f643eaf

File tree

11 files changed

+347
-52
lines changed

11 files changed

+347
-52
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
env:
5353
DATABASE_URL: ${{ matrix.database }}://root:[email protected]/sqlpage
5454
RUST_BACKTRACE: 1
55+
RUST_LOG: sqlpage=debug
5556

5657
windows_test:
5758
runs-on: windows-latest

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
- Added a [new example](https://github.com/lovasoa/SQLpage/tree/main/examples/CRUD%20-%20Authentication) to the documentation
99
- Bug fix: points with a latitude of 0 are now displayed correctly on the map component.
1010
- Bug fix: in sqlite, lower(NULL) now returns NULL instead of an empty string. This is consistent with the standard behavior of lower() in other databases. SQLPage has its own implementation of lower() that supports unicode characters, and our implementation now matches the standard behavior of lower() in mainstream SQLite.
11+
- Allow passing data from the database to sqlpage functions. This fixes most errors like: `Arbitrary SQL expressions as function arguments are not supported.`.
12+
- Better error messages in the dynamic component when properties are missing.
1113

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

src/dynamic_component.rs

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ impl Iterator for DynamicComponentIterator {
2121
fn next(&mut self) -> Option<Self::Item> {
2222
if let Some(db_item) = self.db_item.take() {
2323
if let DbItem::Row(mut row) = db_item {
24-
if let Some(properties) = extract_dynamic_properties(&mut row) {
25-
self.stack = dynamic_properties_to_vec(properties);
26-
} else {
27-
// Most common case: just a regular row. We allocated nothing.
28-
return Some(DbItem::Row(row));
24+
match extract_dynamic_properties(&mut row) {
25+
Ok(None) => {
26+
// Most common case: just a regular row. We allocated nothing.
27+
return Some(DbItem::Row(row));
28+
}
29+
Ok(Some(properties)) => {
30+
self.stack = dynamic_properties_to_vec(properties);
31+
}
32+
Err(err) => {
33+
return Some(DbItem::Error(err));
34+
}
2935
}
3036
} else {
3137
return Some(db_item);
@@ -41,29 +47,47 @@ impl Iterator for DynamicComponentIterator {
4147

4248
fn expand_dynamic_stack(stack: &mut Vec<anyhow::Result<JsonValue>>) {
4349
while let Some(mut next) = stack.pop() {
44-
let dyn_props = next.as_mut().ok().and_then(extract_dynamic_properties);
45-
if let Some(properties) = dyn_props {
46-
// if the properties contain new (nested) dynamic components, push them onto the stack
47-
stack.extend(dynamic_properties_to_vec(properties));
50+
let next_value = next.as_mut().ok();
51+
// .and_then(extract_dynamic_properties);
52+
let dyn_props = if let Some(val) = next_value {
53+
extract_dynamic_properties(val)
4854
} else {
49-
// If the properties are not dynamic, push the row back onto the stack
50-
stack.push(next);
51-
// return at the first non-dynamic row
52-
// we don't support non-dynamic rows after dynamic rows nested in the same array
53-
return;
55+
Ok(None)
56+
};
57+
match dyn_props {
58+
Ok(None) => {
59+
// If the properties are not dynamic, push the row back onto the stack
60+
stack.push(next);
61+
// return at the first non-dynamic row
62+
// we don't support non-dynamic rows after dynamic rows nested in the same array
63+
return;
64+
}
65+
Ok(Some(properties)) => {
66+
// if the properties contain new (nested) dynamic components, push them onto the stack
67+
stack.extend(dynamic_properties_to_vec(properties));
68+
}
69+
Err(err) => {
70+
// if an error occurs, push it onto the stack
71+
stack.push(Err(err));
72+
}
5473
}
5574
}
5675
}
5776

5877
/// if row.component == 'dynamic', return Some(row.properties), otherwise return None
5978
#[inline]
60-
fn extract_dynamic_properties(data: &mut JsonValue) -> Option<JsonValue> {
79+
fn extract_dynamic_properties(data: &mut JsonValue) -> anyhow::Result<Option<JsonValue>> {
6180
let component = data.get("component").and_then(|v| v.as_str());
6281
if component == Some("dynamic") {
63-
let properties = data.get_mut("properties").map(JsonValue::take);
64-
Some(properties.unwrap_or_default())
82+
let Some(properties) = data.get_mut("properties").map(JsonValue::take) else {
83+
anyhow::bail!(
84+
"The dynamic component requires a property named \"properties\". \
85+
Instead, it received the following: {data}"
86+
);
87+
};
88+
Ok(Some(properties))
6589
} else {
66-
None
90+
Ok(None)
6791
}
6892
}
6993

src/webserver/database/execute_queries.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::collections::HashMap;
66
use std::pin::Pin;
77

88
use super::csv_import::run_csv_import;
9-
use super::sql::{ParsedSqlFile, ParsedStatement, SimpleSelectValue, StmtWithParams};
9+
use super::sql::{
10+
DelayedFunctionCall, ParsedSqlFile, ParsedStatement, SimpleSelectValue, StmtWithParams,
11+
};
1012
use crate::dynamic_component::parse_dynamic_rows;
1113
use crate::utils::add_value_to_map;
1214
use crate::webserver::database::sql_to_json::row_to_string;
@@ -55,7 +57,9 @@ pub fn stream_query_results_with_conn<'a>(
5557
let mut stream = connection.fetch_many(query);
5658
while let Some(elem) = stream.next().await {
5759
let is_err = elem.is_err();
58-
for i in parse_dynamic_rows(parse_single_sql_result(&stmt.query, elem)) {
60+
let mut query_result = parse_single_sql_result(&stmt.query, elem);
61+
apply_delayed_functions(request, &stmt.delayed_functions, &mut query_result).await?;
62+
for i in parse_dynamic_rows(query_result) {
5963
yield i;
6064
}
6165
if is_err {
@@ -271,6 +275,56 @@ async fn bind_parameters<'a, 'b>(
271275
Ok(StatementWithParams { sql, arguments })
272276
}
273277

278+
async fn apply_delayed_functions(
279+
request: &RequestInfo,
280+
delayed_functions: &[DelayedFunctionCall],
281+
item: &mut DbItem,
282+
) -> anyhow::Result<()> {
283+
// We need to open new connections for each delayed function call, because we are still fetching the results of the current query in the main connection.
284+
let mut db_conn = None;
285+
if let DbItem::Row(serde_json::Value::Object(ref mut results)) = item {
286+
for f in delayed_functions {
287+
log::trace!("Applying delayed function {} to {:?}", f.function, results);
288+
apply_single_delayed_function(request, &mut db_conn, f, results).await?;
289+
log::trace!(
290+
"Delayed function applied {}. Result: {:?}",
291+
f.function,
292+
results
293+
);
294+
}
295+
}
296+
Ok(())
297+
}
298+
299+
async fn apply_single_delayed_function(
300+
request: &RequestInfo,
301+
db_connection: &mut DbConn,
302+
f: &DelayedFunctionCall,
303+
row: &mut serde_json::Map<String, serde_json::Value>,
304+
) -> anyhow::Result<()> {
305+
let mut params = Vec::new();
306+
for arg in &f.argument_col_names {
307+
let Some(arg_value) = row.remove(arg) else {
308+
anyhow::bail!("The column {arg} is missing in the result set, but it is required by the {} function.", f.function);
309+
};
310+
params.push(json_to_fn_param(arg_value));
311+
}
312+
let result_str = f.function.evaluate(request, db_connection, params).await?;
313+
let result_json = result_str
314+
.map(Cow::into_owned)
315+
.map_or(serde_json::Value::Null, serde_json::Value::String);
316+
row.insert(f.target_col_name.clone(), result_json);
317+
Ok(())
318+
}
319+
320+
fn json_to_fn_param(json: serde_json::Value) -> Option<Cow<'static, str>> {
321+
match json {
322+
serde_json::Value::String(s) => Some(Cow::Owned(s)),
323+
serde_json::Value::Null => None,
324+
_ => Some(Cow::Owned(json.to_string())),
325+
}
326+
}
327+
274328
pub struct StatementWithParams<'a> {
275329
sql: &'a str,
276330
arguments: AnyArguments<'a>,

0 commit comments

Comments
 (0)