fix(postgres): decode json arrays without binary prefixes
This commit is contained in:
2 files changed
+195
No files matched your search
@@ -1774,6 +1774,9 @@ pub fn format_grid_sql_literal(
|
||||
return number.to_string();
|
||||
}
|
||||
if let Some(arr) = value.as_array() {
|
||||
if let Some(element_type) = postgres_json_array_element_type(database_type, column_info) {
|
||||
return format_postgres_json_array_sql_literal(arr, element_type);
|
||||
}
|
||||
if matches!(database_type, Some(DatabaseType::ClickHouse) | Some(DatabaseType::Databend)) {
|
||||
return format_ch_array_sql_literal(arr);
|
||||
}
|
||||
@@ -1855,6 +1858,44 @@ pub fn format_grid_sql_literal(
|
||||
escaped
|
||||
}
|
||||
|
||||
fn postgres_json_array_element_type(
|
||||
database_type: Option<DatabaseType>,
|
||||
column_info: Option<&DataGridColumnInfo>,
|
||||
) -> Option<&'static str> {
|
||||
if database_type != Some(DatabaseType::Postgres) {
|
||||
return None;
|
||||
}
|
||||
match column_info?.data_type.trim().to_ascii_lowercase().as_str() {
|
||||
"json[]" | "_json" => Some("json"),
|
||||
"jsonb[]" | "_jsonb" => Some("jsonb"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_postgres_json_array_sql_literal(arr: &[Value], element_type: &str) -> String {
|
||||
if arr.is_empty() {
|
||||
return format!("ARRAY[]::{element_type}[]");
|
||||
}
|
||||
let elements = arr
|
||||
.iter()
|
||||
.map(|value| {
|
||||
if value.is_null() {
|
||||
return "NULL".to_string();
|
||||
}
|
||||
let json = match value {
|
||||
Value::String(text) => serde_json::from_str::<Value>(text)
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|_| Value::String(text.clone()).to_string()),
|
||||
_ => value.to_string(),
|
||||
};
|
||||
let escaped = json.replace('\\', "\\\\").replace('\'', "''");
|
||||
format!("E'{escaped}'::{element_type}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("ARRAY[{elements}]")
|
||||
}
|
||||
|
||||
fn format_sqlserver_unicode_literal(text: &str) -> String {
|
||||
let mut parts = Vec::new();
|
||||
let mut segment = String::new();
|
||||
@@ -2959,6 +3000,78 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_keyless_update_preserves_jsonb_array_elements() {
|
||||
let endpoints =
|
||||
json!([r#"{"port":10031,"type":"admin_web"}"#, r#""quoted""#, r#"[1,true,{"nested":null}]"#, null]);
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
schema: Some("public".to_string()),
|
||||
table_name: "services".to_string(),
|
||||
primary_keys: vec![],
|
||||
columns: Some(vec![
|
||||
column("id", "integer", false, None),
|
||||
column("name", "text", false, None),
|
||||
column("endpoints", "jsonb[]", true, None),
|
||||
]),
|
||||
},
|
||||
columns: vec!["id".to_string(), "name".to_string(), "endpoints".to_string()],
|
||||
source_columns: None,
|
||||
rows: vec![vec![json!(1), json!("before"), endpoints]],
|
||||
dirty_rows: vec![(0, vec![(1, json!("after"))])],
|
||||
deleted_rows: vec![],
|
||||
new_rows: vec![],
|
||||
});
|
||||
|
||||
assert_eq!(result.validation_error, None);
|
||||
assert_eq!(result.statements.len(), 1);
|
||||
let statement = &result.statements[0];
|
||||
assert!(statement.starts_with("UPDATE \"public\".\"services\" SET \"name\" = 'after' WHERE "));
|
||||
assert!(statement.contains("\"endpoints\" = ARRAY["));
|
||||
assert!(statement.contains("admin_web"));
|
||||
assert!(statement.contains(r#"E'"quoted"'::jsonb"#), "{statement}");
|
||||
assert!(statement.contains("NULL]"));
|
||||
assert!(!statement.contains('\u{1}'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_json_array_literals_preserve_json_documents_and_nulls() {
|
||||
let value = json!([r#"{"object":true}"#, r#""text""#, "[1,2]", "plain text", "null", null]);
|
||||
let json_column = column("payload", "json[]", true, None);
|
||||
let jsonb_column = column("payload", "jsonb[]", true, None);
|
||||
let text_column = column("payload", "text[]", true, None);
|
||||
let integer_column = column("payload", "integer[]", true, None);
|
||||
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&value, Some(DatabaseType::Postgres), Some(&json_column)),
|
||||
r#"ARRAY[E'{"object":true}'::json, E'"text"'::json, E'[1,2]'::json, E'"plain text"'::json, E'null'::json, NULL]"#
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&value, Some(DatabaseType::Postgres), Some(&jsonb_column)),
|
||||
r#"ARRAY[E'{"object":true}'::jsonb, E'"text"'::jsonb, E'[1,2]'::jsonb, E'"plain text"'::jsonb, E'null'::jsonb, NULL]"#
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!([]), Some(DatabaseType::Postgres), Some(&jsonb_column)),
|
||||
"ARRAY[]::jsonb[]"
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(
|
||||
&json!(["first", null, "second"]),
|
||||
Some(DatabaseType::Postgres),
|
||||
Some(&text_column)
|
||||
),
|
||||
r#"'{"first",NULL,"second"}'"#
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!([1, null, 2]), Some(DatabaseType::Postgres), Some(&integer_column)),
|
||||
"'{1,NULL,2}'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_copy_update_statements() {
|
||||
let statements = build_data_grid_copy_update_statements(DataGridCopyUpdateStatementOptions {
|
||||
|
||||
Reference in new issue
Block a user