diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 81d7e78139..c9cf2e8c7b 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -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(); diff --git a/crates/dbx-core/src/database_export.rs b/crates/dbx-core/src/database_export.rs index 50ac862811..608e963308 100644 --- a/crates/dbx-core/src/database_export.rs +++ b/crates/dbx-core/src/database_export.rs @@ -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>> = 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('\'', "''")) } diff --git a/crates/dbx-core/src/transfer.rs b/crates/dbx-core/src/transfer.rs index 56b2c17aa3..15439f1ca7 100644 --- a/crates/dbx-core/src/transfer.rs +++ b/crates/dbx-core/src/transfer.rs @@ -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 = 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 = 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())