fix(postgres): decode json arrays without binary prefixes

This commit is contained in:
t8y2 committed 2026-07-31 15:46:15 +08:00
1 parent e48902fa06
commit 65bf06f12e
2 files changed
+195

No files matched your search

+113
View File
@@ -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 {