fix(core): use ClickHouse array syntax `[...]` instead of PostgreSQL `{...}` for array literals
ClickHouse and Databend expect square-bracket array literals (unquoted),
while PostgreSQL/DuckDB/etc. use curly-brace quoted format. Previously
all array values were unconditionally rendered with `'{...}'` syntax,
which produced wrong INSERT/UPDATE statements for ClickHouse columns
of nested array types like `Array(Array(Point))`.
This commit is contained in:
parent
4a64240e8a
commit
3b457f7803
|
|
@ -12,7 +12,7 @@ use data_grid_tdengine_sql::build_tdengine_data_grid_save_statements;
|
|||
|
||||
use crate::models::connection::DatabaseType;
|
||||
use crate::sql_dialect::quote_table_identifier;
|
||||
use crate::transfer::format_pg_array_sql_literal;
|
||||
use crate::transfer::{format_ch_array_sql_literal, format_pg_array_sql_literal};
|
||||
|
||||
const DBX_ROWID_COLUMN: &str = "__DBX_ROWID";
|
||||
pub(crate) const DBX_NEO4J_ELEMENT_ID_COLUMN: &str = "__DBX_ELEMENT_ID";
|
||||
|
|
@ -743,6 +743,9 @@ pub fn format_grid_sql_literal(
|
|||
return number.to_string();
|
||||
}
|
||||
if let Some(arr) = value.as_array() {
|
||||
if matches!(database_type, Some(DatabaseType::ClickHouse) | Some(DatabaseType::Databend)) {
|
||||
return format_ch_array_sql_literal(arr);
|
||||
}
|
||||
return format_pg_array_sql_literal(arr);
|
||||
}
|
||||
let text = value.as_str().map_or_else(|| value.to_string(), ToString::to_string);
|
||||
|
|
|
|||
|
|
@ -6,7 +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;
|
||||
use crate::transfer::{format_ch_array_sql_literal, format_pg_array_sql_literal};
|
||||
|
||||
static EXPORT_CANCELLED: std::sync::LazyLock<RwLock<HashSet<String>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashSet::new()));
|
||||
|
|
@ -146,6 +146,11 @@ fn format_export_sql_literal_typed(
|
|||
if matches!(database_type, Some(DatabaseType::Mysql)) && column_type.is_some_and(is_mysql_bit_type) {
|
||||
return format_mysql_bit_literal(value);
|
||||
}
|
||||
if let Some(arr) = value.as_array() {
|
||||
if matches!(database_type, Some(DatabaseType::ClickHouse) | Some(DatabaseType::Databend)) {
|
||||
return format_ch_array_sql_literal(arr);
|
||||
}
|
||||
}
|
||||
format_export_sql_literal(value)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -485,7 +485,10 @@ 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),
|
||||
serde_json::Value::Array(arr) => match db_type {
|
||||
DatabaseType::ClickHouse | DatabaseType::Databend => format_ch_array_sql_literal(arr),
|
||||
_ => format_pg_array_sql_literal(arr),
|
||||
},
|
||||
_ => {
|
||||
let s = val.to_string();
|
||||
format!("'{}'", s.replace('\\', "\\\\").replace('\'', "''"))
|
||||
|
|
@ -532,6 +535,43 @@ fn format_pg_array_element(val: &serde_json::Value) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn format_ch_array_sql_literal(arr: &[serde_json::Value]) -> String {
|
||||
if arr.is_empty() {
|
||||
return "[]".to_string();
|
||||
}
|
||||
let elements: Vec<String> = arr.iter().map(format_ch_array_element).collect();
|
||||
format!("[{}]", elements.join(","))
|
||||
}
|
||||
|
||||
fn format_ch_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_ch_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();
|
||||
format!("'{}'", json.replace('\\', "\\\\").replace('\'', "''"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
|
|
|
|||
Loading…
Reference in New Issue