fix: format PostgreSQL array values as {..} instead of JSON [..] in SQL export

PostgreSQL array columns were serialized as JSON arrays (e.g. [1,2,3])
by serde_json::Value::Array Display, producing invalid SQL literals like
'[1,2,3]' and '[]' instead of PostgreSQL's required '{1,2,3}' and '{}'.

Add format_pg_array_sql_literal helper and apply it to all three SQL
literal formatting paths: escape_value_typed (transfer), format_export_sql_literal
(database_export), and format_grid_sql_literal (data_grid_sql).
This commit is contained in:
t8y2 2026-05-25 10:55:16 +08:00
parent 98b34a4db2
commit 9008169dd1
3 changed files with 44 additions and 0 deletions

View File

@ -4,6 +4,7 @@ use serde_json::Value;
use crate::models::connection::DatabaseType;
use crate::sql_dialect::quote_table_identifier;
use crate::transfer::format_pg_array_sql_literal;
const DBX_ROWID_COLUMN: &str = "__DBX_ROWID";
const DBX_NEO4J_ELEMENT_ID_COLUMN: &str = "__DBX_ELEMENT_ID";
@ -936,6 +937,9 @@ pub fn format_grid_sql_literal(
if let Some(number) = value.as_number() {
return number.to_string();
}
if let Some(arr) = value.as_array() {
return format_pg_array_sql_literal(arr);
}
let text = value.as_str().map_or_else(|| value.to_string(), ToString::to_string);
if text.is_empty() {
return if database_type == Some(DatabaseType::SqlServer) { "N''" } else { "''" }.to_string();

View File

@ -6,6 +6,7 @@ use tokio::sync::RwLock;
use crate::models::connection::DatabaseType;
use crate::sql_dialect::{qualified_table_name, quote_table_identifier};
use crate::transfer::format_pg_array_sql_literal;
static EXPORT_CANCELLED: std::sync::LazyLock<RwLock<HashSet<String>>> =
std::sync::LazyLock::new(|| RwLock::new(HashSet::new()));
@ -125,6 +126,9 @@ pub fn format_export_sql_literal(value: &Value) -> String {
if let Some(value) = value.as_bool() {
return if value { "TRUE" } else { "FALSE" }.to_string();
}
if let Some(arr) = value.as_array() {
return format_pg_array_sql_literal(arr);
}
let text = value.as_str().map_or_else(|| value.to_string(), ToString::to_string);
format!("'{}'", text.replace('\\', "\\\\").replace('\'', "''"))
}

View File

@ -108,6 +108,7 @@ pub fn escape_value_typed(val: &serde_json::Value, db_type: &DatabaseType, colum
serde_json::Value::String(s) => {
format!("'{}'", format_literal_string(s, db_type, column_type).replace('\\', "\\\\").replace('\'', "''"))
}
serde_json::Value::Array(arr) => format_pg_array_sql_literal(arr),
_ => {
let s = val.to_string();
format!("'{}'", s.replace('\\', "\\\\").replace('\'', "''"))
@ -115,6 +116,41 @@ pub fn escape_value_typed(val: &serde_json::Value, db_type: &DatabaseType, colum
}
}
pub fn format_pg_array_sql_literal(arr: &[serde_json::Value]) -> String {
if arr.is_empty() {
return "'{}'".to_string();
}
let elements: Vec<String> = arr.iter().map(format_pg_array_element).collect();
let inner = format!("{{{}}}", elements.join(","));
format!("'{}'", inner.replace('\\', "\\\\").replace('\'', "''"))
}
fn format_pg_array_element(val: &serde_json::Value) -> String {
match val {
serde_json::Value::Null => "NULL".to_string(),
serde_json::Value::Array(arr) => {
if arr.is_empty() {
return "{}".to_string();
}
let elements: Vec<String> = arr.iter().map(format_pg_array_element).collect();
format!("{{{}}}", elements.join(","))
}
serde_json::Value::String(s) => {
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{}\"", escaped)
}
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => {
if *b { "true".to_string() } else { "false".to_string() }
}
serde_json::Value::Object(o) => {
let json = serde_json::to_string(o).unwrap_or_default();
let escaped = json.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{}\"", escaped)
}
}
}
fn format_literal_string(value: &str, db_type: &DatabaseType, column_type: Option<&str>) -> String {
if is_mysql_datetime_literal_database(db_type) && column_type.map(is_temporal_column_type).unwrap_or(true) {
normalize_mysql_temporal_literal(value, column_type).unwrap_or_else(|| value.to_string())