Skip to content

Commit b40e356

Browse files
committed
Fix a bug where the results of the JSON function in sqlite would be interpreted as a string instead of a json object.
fixes #738 Thank you to @SebastiendOrnano who reported the issue
1 parent c9fdfbb commit b40e356

File tree

5 files changed

+135
-0
lines changed

5 files changed

+135
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- Update ApexCharts to [v4.1.0](https://github.com/apexcharts/apexcharts.js/releases/tag/v4.1.0).
3030
- Temporarily disable automatic tick amount calculation in the chart component. This was causing issues with mislabeled x-axis data, because of a bug in ApexCharts.
3131
- Add a new `max_recursion_depth` configuration option to limit the depth of recursion allowed in the `run_sql` function.
32+
- Fix a bug where the results of the `JSON` function in sqlite would be interpreted as a string instead of a json object.
3233

3334
## 0.31.0 (2024-11-24)
3435

src/webserver/database/execute_queries.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,80 @@ impl<'q> sqlx::Execute<'q, Any> for StatementWithParams<'q> {
442442
true
443443
}
444444
}
445+
446+
#[cfg(test)]
447+
mod tests {
448+
use super::*;
449+
use serde_json::{json, Value};
450+
451+
fn create_row_item(value: Value) -> DbItem {
452+
DbItem::Row(value)
453+
}
454+
455+
fn assert_json_value(item: &DbItem, key: &str, expected: Value) {
456+
let DbItem::Row(Value::Object(row)) = item else {
457+
panic!("Expected DbItem::Row");
458+
};
459+
assert_eq!(row[key], expected);
460+
}
461+
462+
#[test]
463+
fn test_basic_json_string_conversion() {
464+
let mut item = create_row_item(json!({
465+
"json_col": "{\"key\": \"value\"}",
466+
"normal_col": "text"
467+
}));
468+
apply_json_columns(&mut item, &["json_col".to_string()]);
469+
assert_json_value(&item, "json_col", json!({"key": "value"}));
470+
assert_json_value(&item, "normal_col", json!("text"));
471+
}
472+
473+
#[test]
474+
fn test_json_array_conversion() {
475+
let mut item = create_row_item(json!({
476+
"array_col": ["{\"a\": 1}", "{\"b\": 2}"],
477+
"normal_array": ["text"]
478+
}));
479+
apply_json_columns(&mut item, &["array_col".to_string()]);
480+
assert_json_value(&item, "array_col", json!([{"a": 1}, {"b": 2}]));
481+
assert_json_value(&item, "normal_array", json!(["text"]));
482+
}
483+
484+
#[test]
485+
fn test_invalid_json_handling() {
486+
let mut item = create_row_item(json!({
487+
"invalid_json": "{not valid json}",
488+
"normal_col": "text"
489+
}));
490+
apply_json_columns(&mut item, &["invalid_json".to_string()]);
491+
assert_json_value(&item, "invalid_json", json!("{not valid json}"));
492+
assert_json_value(&item, "normal_col", json!("text"));
493+
}
494+
495+
#[test]
496+
fn test_missing_column_handling() {
497+
let mut item = create_row_item(json!({
498+
"existing_col": "text"
499+
}));
500+
apply_json_columns(&mut item, &["missing_col".to_string()]);
501+
assert_json_value(&item, "existing_col", json!("text"));
502+
}
503+
504+
#[test]
505+
fn test_non_row_dbitem_handling() {
506+
let mut item = DbItem::FinishedQuery;
507+
apply_json_columns(&mut item, &["json_col".to_string()]);
508+
assert!(matches!(item, DbItem::FinishedQuery));
509+
}
510+
511+
#[test]
512+
fn test_duplicate_json_column_names() {
513+
let mut item = create_row_item(json!({
514+
"json_col": "{\"key\": \"value\"}",
515+
"normal_col": "text"
516+
}));
517+
apply_json_columns(&mut item, &["json_col".to_string(), "json_col".to_string()]);
518+
assert_json_value(&item, "json_col", json!({"key": "value"}));
519+
assert_json_value(&item, "normal_col", json!("text"));
520+
}
521+
}

src/webserver/database/sql.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,8 @@ fn is_json_function(expr: &Expr) -> bool {
854854
"json_objectagg",
855855
"json_group_array",
856856
"json_group_object",
857+
"json",
858+
"jsonb",
857859
]
858860
.iter()
859861
.any(|&func| value.eq_ignore_ascii_case(func))
@@ -1350,4 +1352,20 @@ mod test {
13501352
assert_eq!(json_columns, Vec::<String>::new());
13511353
}
13521354
}
1355+
1356+
#[test]
1357+
fn test_extract_json_columns_from_literal() {
1358+
let sql = r#"
1359+
SELECT
1360+
'Pro Plan' as title,
1361+
JSON('{"icon":"database","color":"blue","description":"1GB Database"}') as item,
1362+
JSON('{"icon":"headset","color":"green","description":"Priority Support"}') as item
1363+
"#;
1364+
1365+
let stmt = parse_stmt(sql, &SQLiteDialect {});
1366+
let json_columns = extract_json_columns(&stmt, AnyKind::Sqlite);
1367+
1368+
assert!(json_columns.contains(&"item".to_string()));
1369+
assert!(!json_columns.contains(&"title".to_string()));
1370+
}
13531371
}

tests/index.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,31 @@ async fn privileged_paths_are_not_accessible() {
525525
);
526526
}
527527

528+
#[actix_web::test]
529+
/// https://github.com/sqlpage/SQLPage/issues/738
530+
async fn test_json_columns() {
531+
let resp_result = req_path("/tests/json_columns.sql").await;
532+
let resp = resp_result.expect("Failed to request /tests/json_columns.sql");
533+
assert_eq!(resp.status(), http::StatusCode::OK);
534+
let body = test::read_body(resp).await;
535+
let body_str = String::from_utf8(body.to_vec()).unwrap();
536+
let body_html_escaped = body_str.replace("&quot;", "\"");
537+
assert!(
538+
!body_html_escaped.contains("error"),
539+
"the request should not have failed, in: {body_html_escaped}"
540+
);
541+
assert!(body_html_escaped.contains("1GB Database"));
542+
assert!(body_html_escaped.contains("Priority Support"));
543+
assert!(
544+
!body_html_escaped.contains("\"description\""),
545+
"the json should have been parsed, not returned as a string, in: {body_html_escaped}"
546+
);
547+
assert!(
548+
!body_html_escaped.contains("{"),
549+
"the json should have been parsed, not returned as a string, in: {body_html_escaped}"
550+
);
551+
}
552+
528553
#[actix_web::test]
529554
async fn test_static_files() {
530555
let resp = req_path("/tests/it_works.txt").await.unwrap();

tests/json_columns.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
select
2+
'columns' as component;
3+
4+
select
5+
'Pro Plan' as title,
6+
'€40' as value,
7+
'rocket' as icon,
8+
'For growing projects needing enhanced features' as description,
9+
JSON (
10+
'{"icon":"database","color":"blue","description":"1GB Database"}'
11+
) as item,
12+
JSON (
13+
'{"icon":"headset","color":"green","description":"Priority Support"}'
14+
) as item;

0 commit comments

Comments
 (0)