fix(kingbase): format bit column values as bit-string literals

This commit is contained in:
zipg authored and GitHub committed 2026-09-04 00:29:17 +08:00
1 parent 730a4eca5f
commit de73c2bd73
4 files changed
+274 -40

No files matched your search

@@ -5,9 +5,9 @@ use super::{
}; };
use crate::data_grid_sql::{ use crate::data_grid_sql::{
build_column_predicate, build_data_grid_copy_insert_statement, build_data_grid_copy_update_statements, build_column_predicate, build_data_grid_copy_insert_statement, build_data_grid_copy_update_statements,
data_grid_qualified_table_name, format_grid_sql_literal, is_auto_generated_column, is_grid_insert_omitted_column, data_grid_qualified_table_name, format_grid_sql_literal_with_identifier_quote, is_auto_generated_column,
is_non_identity_generated_column, supports_relational_copy_predicates, DataGridCopyInsertStatementOptions, is_grid_insert_omitted_column, is_non_identity_generated_column, supports_relational_copy_predicates,
DataGridCopyUpdateStatementOptions, DataGridTableMeta, DataGridCopyInsertStatementOptions, DataGridCopyUpdateStatementOptions, DataGridTableMeta,
}; };
use serde_json::Value; use serde_json::Value;
use std::collections::{hash_map::Entry, HashMap, HashSet}; use std::collections::{hash_map::Entry, HashMap, HashSet};
@@ -31,7 +31,15 @@ pub(super) fn write_sql_in_list(
} }
let value = &row[*source_index]; let value = &row[*source_index];
let info = context.selected_column_info[column_index]; let info = context.selected_column_info[column_index];
tuple.extend_from_slice(format_grid_sql_literal(value, context.request.database_type, info).as_bytes()); tuple.extend_from_slice(
format_grid_sql_literal_with_identifier_quote(
value,
context.request.database_type,
info,
context.request.identifier_quote.as_deref(),
)
.as_bytes(),
);
} }
if context.selected_columns.len() > 1 { if context.selected_columns.len() > 1 {
tuple.push(b')'); tuple.push(b')');
@@ -248,6 +248,34 @@ fn sql_in_list_deduplicates_only_identical_rendered_literals() {
assert_eq!(result.text, "(1, '1', NULL, 'O''Reilly')"); assert_eq!(result.text, "(1, '1', NULL, 'O''Reilly')");
} }
#[test]
fn sql_in_list_honors_kingbase_bit_literals() {
let mut request = request(DataGridExtractorId::SqlInList);
request.database_type = Some(DatabaseType::Kingbase);
request.identifier_quote = Some("`".to_string());
request.columns = vec![column("flag", 0)];
request.selected_column_indexes = vec![0];
request.table_meta = Some(DataGridTableMeta {
catalog: None,
database: None,
schema: None,
table_name: "flags".to_string(),
primary_keys: vec![],
columns: Some(vec![DataGridColumnInfo {
name: "flag".to_string(),
data_type: "pg_catalog.bit(1)".to_string(),
is_nullable: true,
is_primary_key: false,
column_default: None,
extra: None,
}]),
});
request.rows = vec![vec![json!("0")], vec![json!("1")]];
let result = extract_data_grid_selection(request).expect("Kingbase SQL IN extraction");
assert_eq!(result.text, "(b'0', b'1')");
}
#[test] #[test]
fn preserves_duplicate_json_columns_without_overwriting_values() { fn preserves_duplicate_json_columns_without_overwriting_values() {
let mut request = request(DataGridExtractorId::Json); let mut request = request(DataGridExtractorId::Json);
@@ -176,6 +176,7 @@ fn build_iotdb_insert_statement(
value, value,
Some(DatabaseType::Iotdb), Some(DatabaseType::Iotdb),
column_info_for(iotdb_column_info(options), column), column_info_for(iotdb_column_info(options), column),
options.identifier_quote.as_deref(),
) )
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
+233 -36
View File
@@ -411,6 +411,7 @@ pub fn build_data_grid_copy_update_statements(options: DataGridCopyUpdateStateme
row.get(*index).unwrap_or(&Value::Null), row.get(*index).unwrap_or(&Value::Null),
options.database_type, options.database_type,
*info, *info,
options.identifier_quote.as_deref(),
) )
) )
}) })
@@ -511,6 +512,7 @@ pub fn build_data_grid_copy_insert_statement(options: DataGridCopyInsertStatemen
row.get(*index).unwrap_or(&Value::Null), row.get(*index).unwrap_or(&Value::Null),
options.database_type, options.database_type,
info.as_ref(), info.as_ref(),
options.identifier_quote.as_deref(),
) )
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -604,7 +606,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
value, value,
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref() options.column_info.as_ref(),
options.identifier_quote.as_deref(),
) )
)), )),
DataGridContextFilterMode::LessThanOrEqual => Some(format!( DataGridContextFilterMode::LessThanOrEqual => Some(format!(
@@ -613,7 +616,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
value, value,
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref() options.column_info.as_ref(),
options.identifier_quote.as_deref(),
) )
)), )),
DataGridContextFilterMode::GreaterThan => Some(format!( DataGridContextFilterMode::GreaterThan => Some(format!(
@@ -622,7 +626,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
value, value,
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref() options.column_info.as_ref(),
options.identifier_quote.as_deref(),
) )
)), )),
DataGridContextFilterMode::GreaterThanOrEqual => Some(format!( DataGridContextFilterMode::GreaterThanOrEqual => Some(format!(
@@ -631,7 +636,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
value, value,
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref() options.column_info.as_ref(),
options.identifier_quote.as_deref(),
) )
)), )),
DataGridContextFilterMode::In => build_data_grid_context_membership_filter_condition( DataGridContextFilterMode::In => build_data_grid_context_membership_filter_condition(
@@ -640,6 +646,7 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref(), options.column_info.as_ref(),
options.identifier_quote.as_deref(),
false, false,
), ),
DataGridContextFilterMode::NotIn => build_data_grid_context_membership_filter_condition( DataGridContextFilterMode::NotIn => build_data_grid_context_membership_filter_condition(
@@ -648,6 +655,7 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref(), options.column_info.as_ref(),
options.identifier_quote.as_deref(),
true, true,
), ),
DataGridContextFilterMode::Between => build_data_grid_context_range_filter_condition( DataGridContextFilterMode::Between => build_data_grid_context_range_filter_condition(
@@ -657,6 +665,7 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref(), options.column_info.as_ref(),
options.identifier_quote.as_deref(),
false, false,
), ),
DataGridContextFilterMode::NotBetween => build_data_grid_context_range_filter_condition( DataGridContextFilterMode::NotBetween => build_data_grid_context_range_filter_condition(
@@ -666,6 +675,7 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref(), options.column_info.as_ref(),
options.identifier_quote.as_deref(),
true, true,
), ),
DataGridContextFilterMode::Equals => Some(format!( DataGridContextFilterMode::Equals => Some(format!(
@@ -674,7 +684,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
value, value,
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref() options.column_info.as_ref(),
options.identifier_quote.as_deref(),
) )
)), )),
DataGridContextFilterMode::NotEquals => Some(format!( DataGridContextFilterMode::NotEquals => Some(format!(
@@ -683,7 +694,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
value, value,
options.database_type, options.database_type,
&options.column_name, &options.column_name,
options.column_info.as_ref() options.column_info.as_ref(),
options.identifier_quote.as_deref(),
) )
)), )),
} }
@@ -694,13 +706,14 @@ fn format_data_grid_context_filter_literal(
database_type: Option<DatabaseType>, database_type: Option<DatabaseType>,
column_name: &str, column_name: &str,
column_info: Option<&DataGridColumnInfo>, column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
) -> String { ) -> String {
if database_type == Some(DatabaseType::Iotdb) && column_info.is_none() && column_name.eq_ignore_ascii_case("time") { if database_type == Some(DatabaseType::Iotdb) && column_info.is_none() && column_name.eq_ignore_ascii_case("time") {
if let Some(value) = value.as_str().filter(|value| is_decimal_i64(value)) { if let Some(value) = value.as_str().filter(|value| is_decimal_i64(value)) {
return value.to_string(); return value.to_string();
} }
} }
format_grid_sql_literal(value, database_type, column_info) format_grid_sql_literal_with_identifier_quote(value, database_type, column_info, identifier_quote)
} }
fn is_decimal_i64(value: &str) -> bool { fn is_decimal_i64(value: &str) -> bool {
@@ -714,6 +727,7 @@ fn build_data_grid_context_membership_filter_condition(
database_type: Option<DatabaseType>, database_type: Option<DatabaseType>,
column_name: &str, column_name: &str,
column_info: Option<&DataGridColumnInfo>, column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
negated: bool, negated: bool,
) -> Option<String> { ) -> Option<String> {
if values.is_empty() { if values.is_empty() {
@@ -728,7 +742,8 @@ fn build_data_grid_context_membership_filter_condition(
has_null = true; has_null = true;
continue; continue;
} }
let literal = format_data_grid_context_filter_literal(value, database_type, column_name, column_info); let literal =
format_data_grid_context_filter_literal(value, database_type, column_name, column_info, identifier_quote);
if seen_literals.insert(literal.clone()) { if seen_literals.insert(literal.clone()) {
literals.push(literal); literals.push(literal);
} }
@@ -786,14 +801,17 @@ fn build_data_grid_context_range_filter_condition(
database_type: Option<DatabaseType>, database_type: Option<DatabaseType>,
column_name: &str, column_name: &str,
column_info: Option<&DataGridColumnInfo>, column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
negated: bool, negated: bool,
) -> Option<String> { ) -> Option<String> {
let end_value = end_value?; let end_value = end_value?;
if start_value.is_null() || end_value.is_null() { if start_value.is_null() || end_value.is_null() {
return None; return None;
} }
let start = format_data_grid_context_filter_literal(start_value, database_type, column_name, column_info); let start =
let end = format_data_grid_context_filter_literal(end_value, database_type, column_name, column_info); format_data_grid_context_filter_literal(start_value, database_type, column_name, column_info, identifier_quote);
let end =
format_data_grid_context_filter_literal(end_value, database_type, column_name, column_info, identifier_quote);
if database_type == Some(DatabaseType::Neo4j) { if database_type == Some(DatabaseType::Neo4j) {
return if negated { return if negated {
Some(format!("({column} < {start} OR {column} > {end})")) Some(format!("({column} < {start} OR {column} > {end})"))
@@ -987,7 +1005,12 @@ pub fn build_data_grid_conditional_update_sql(options: DataGridConditionalUpdate
let sets = format!( let sets = format!(
"{} = {}", "{} = {}",
data_grid_identifier(options.database_type, &column_info.name, options.identifier_quote.as_deref()), data_grid_identifier(options.database_type, &column_info.name, options.identifier_quote.as_deref()),
format_grid_save_sql_literal(&options.value, options.database_type, Some(column_info)) format_grid_save_sql_literal(
&options.value,
options.database_type,
Some(column_info),
options.identifier_quote.as_deref(),
)
); );
Some(data_grid_statement( Some(data_grid_statement(
options.database_type, options.database_type,
@@ -1328,7 +1351,12 @@ fn build_data_grid_save_statements(
Some(format!( Some(format!(
"{} = {}", "{} = {}",
data_grid_identifier(options.database_type, column, options.identifier_quote.as_deref()), data_grid_identifier(options.database_type, column, options.identifier_quote.as_deref()),
format_grid_save_sql_literal(value, options.database_type, column_info_for(column_info, column)) format_grid_save_sql_literal(
value,
options.database_type,
column_info_for(column_info, column),
options.identifier_quote.as_deref(),
)
)) ))
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -1433,7 +1461,12 @@ fn build_data_grid_save_statements(
let values = insert_pairs let values = insert_pairs
.iter() .iter()
.map(|(column, value)| { .map(|(column, value)| {
format_grid_save_sql_literal(value, options.database_type, column_info_for(column_info, column)) format_grid_save_sql_literal(
value,
options.database_type,
column_info_for(column_info, column),
options.identifier_quote.as_deref(),
)
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
@@ -1533,7 +1566,12 @@ fn build_data_grid_rollback_statements(
let values = insert_pairs let values = insert_pairs
.iter() .iter()
.map(|(column, value)| { .map(|(column, value)| {
format_grid_assignment_sql_literal(value, options.database_type, column_info_for(column_info, column)) format_grid_assignment_sql_literal(
value,
options.database_type,
column_info_for(column_info, column),
options.identifier_quote.as_deref(),
)
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
@@ -1594,7 +1632,8 @@ fn build_data_grid_rollback_statements(
format_grid_assignment_sql_literal( format_grid_assignment_sql_literal(
row.get(*column_index).unwrap_or(&Value::Null), row.get(*column_index).unwrap_or(&Value::Null),
options.database_type, options.database_type,
column_info_for(column_info, column) column_info_for(column_info, column),
options.identifier_quote.as_deref(),
) )
) )
}) })
@@ -1882,9 +1921,14 @@ fn build_hive_values_insert(
.map(|(column, value)| { .map(|(column, value)| {
let info = column_info_for(metadata_columns, column); let info = column_info_for(metadata_columns, column);
if save_literals { if save_literals {
format_grid_save_sql_literal(value, options.database_type, info) format_grid_save_sql_literal(value, options.database_type, info, options.identifier_quote.as_deref())
} else { } else {
format_grid_assignment_sql_literal(value, options.database_type, info) format_grid_assignment_sql_literal(
value,
options.database_type,
info,
options.identifier_quote.as_deref(),
)
} }
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -1942,6 +1986,7 @@ fn format_grid_copy_insert_sql_literal(
value: &Value, value: &Value,
database_type: Option<DatabaseType>, database_type: Option<DatabaseType>,
column_info: Option<&DataGridColumnInfo>, column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
) -> String { ) -> String {
if is_oracle_temporal_literal_database(database_type) { if is_oracle_temporal_literal_database(database_type) {
if let Some(text) = value.as_str() { if let Some(text) = value.as_str() {
@@ -1952,13 +1997,14 @@ fn format_grid_copy_insert_sql_literal(
} }
} }
} }
format_grid_assignment_sql_literal(value, database_type, column_info) format_grid_assignment_sql_literal(value, database_type, column_info, identifier_quote)
} }
fn format_grid_assignment_sql_literal( fn format_grid_assignment_sql_literal(
value: &Value, value: &Value,
database_type: Option<DatabaseType>, database_type: Option<DatabaseType>,
column_info: Option<&DataGridColumnInfo>, column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
) -> String { ) -> String {
if matches!(database_type, Some(DatabaseType::Oracle | DatabaseType::OceanbaseOracle)) { if matches!(database_type, Some(DatabaseType::Oracle | DatabaseType::OceanbaseOracle)) {
if let Some(constructor) = column_info.and_then(|column| oracle_character_lob_constructor(&column.data_type)) { if let Some(constructor) = column_info.and_then(|column| oracle_character_lob_constructor(&column.data_type)) {
@@ -1968,9 +2014,14 @@ fn format_grid_assignment_sql_literal(
} }
} }
if (value.is_array() || value.is_object()) && is_json_document_column(column_info) { if (value.is_array() || value.is_object()) && is_json_document_column(column_info) {
return format_grid_sql_literal(&Value::String(value.to_string()), database_type, column_info); return format_grid_sql_literal_with_identifier_quote(
&Value::String(value.to_string()),
database_type,
column_info,
identifier_quote,
);
} }
format_grid_sql_literal(value, database_type, column_info) format_grid_sql_literal_with_identifier_quote(value, database_type, column_info, identifier_quote)
} }
fn format_oracle_lob_assignment_literal(text: &str, constructor: &str) -> String { fn format_oracle_lob_assignment_literal(text: &str, constructor: &str) -> String {
@@ -2033,15 +2084,27 @@ pub fn format_grid_sql_literal(
value: &Value, value: &Value,
database_type: Option<DatabaseType>, database_type: Option<DatabaseType>,
column_info: Option<&DataGridColumnInfo>, column_info: Option<&DataGridColumnInfo>,
) -> String {
format_grid_sql_literal_with_identifier_quote(value, database_type, column_info, None)
}
pub(crate) fn format_grid_sql_literal_with_identifier_quote(
value: &Value,
database_type: Option<DatabaseType>,
column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
) -> String { ) -> String {
if value.is_null() { if value.is_null() {
return "NULL".to_string(); return "NULL".to_string();
} }
// Boolean values on BIT columns always use numeric 0/1. // Boolean values on BIT columns use database-native boolean/bit literals.
// This covers MySQL, SQL Server, and any other database where BIT // This covers MySQL, SQL Server, and any other database where BIT
// is a numeric/boolean type rather than a bit-string type like // is a numeric/boolean type rather than a bit-string type like
// PostgreSQL's bit(n). // PostgreSQL's bit(n).
if let Some(value) = value.as_bool() { if let Some(value) = value.as_bool() {
if is_kingbase_bit_literal_column(database_type, column_info) {
return format!("b'{}'", if value { '1' } else { '0' });
}
// SQL Server has no TRUE/FALSE literals (its boolean type is BIT, which // SQL Server has no TRUE/FALSE literals (its boolean type is BIT, which
// is_bit_literal_column already covers); any other column there still // is_bit_literal_column already covers); any other column there still
// needs numeric 1/0 instead of a literal. // needs numeric 1/0 instead of a literal.
@@ -2050,7 +2113,19 @@ pub fn format_grid_sql_literal(
} }
return if value { "TRUE" } else { "FALSE" }.to_string(); return if value { "TRUE" } else { "FALSE" }.to_string();
} }
if is_mysql_bit_literal_column(database_type, column_info) { if is_kingbase_bit_literal_column(database_type, column_info) {
if let Some(number) = value.as_number() {
if let Some(literal) = format_kingbase_bit_literal_text(&number.to_string()) {
return literal;
}
}
if let Some(text) = value.as_str() {
if let Some(literal) = format_kingbase_bit_literal_text(text) {
return literal;
}
}
}
if is_mysql_bit_literal_column(database_type, column_info, identifier_quote) {
if let Some(number) = value.as_number() { if let Some(number) = value.as_number() {
return number.to_string(); return number.to_string();
} }
@@ -2106,7 +2181,12 @@ pub fn format_grid_sql_literal(
} }
if database_type == Some(DatabaseType::ManticoreSearch) { if database_type == Some(DatabaseType::ManticoreSearch) {
if let Some(typed_value) = manticore_typed_attribute_value(&text, column_info) { if let Some(typed_value) = manticore_typed_attribute_value(&text, column_info) {
return format_grid_sql_literal(&typed_value, database_type, column_info); return format_grid_sql_literal_with_identifier_quote(
&typed_value,
database_type,
column_info,
identifier_quote,
);
} }
} }
if text.is_empty() { if text.is_empty() {
@@ -2245,11 +2325,12 @@ fn format_grid_save_sql_literal(
value: &Value, value: &Value,
database_type: Option<DatabaseType>, database_type: Option<DatabaseType>,
column_info: Option<&DataGridColumnInfo>, column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
) -> String { ) -> String {
if empty_string_saves_as_null(value, column_info) { if empty_string_saves_as_null(value, column_info) {
"NULL".to_string() "NULL".to_string()
} else { } else {
format_grid_assignment_sql_literal(value, database_type, column_info) format_grid_assignment_sql_literal(value, database_type, column_info, identifier_quote)
} }
} }
@@ -2377,8 +2458,21 @@ fn regex_like_local_datetime(text: &str) -> Option<Rfc3339Parts> {
Some(Rfc3339Parts { date: date.to_string(), time: time.to_string(), fraction, zone: String::new() }) Some(Rfc3339Parts { date: date.to_string(), time: time.to_string(), fraction, zone: String::new() })
} }
fn is_mysql_bit_literal_column(database_type: Option<DatabaseType>, column_info: Option<&DataGridColumnInfo>) -> bool { fn is_mysql_bit_literal_column(
is_mysql_datetime_literal_database(database_type) database_type: Option<DatabaseType>,
column_info: Option<&DataGridColumnInfo>,
identifier_quote: Option<&str>,
) -> bool {
(is_mysql_datetime_literal_database(database_type)
|| (database_type == Some(DatabaseType::Kingbase) && identifier_quote == Some("`")))
&& column_info.map(|column| is_bit_column_type(&column.data_type)).unwrap_or(false)
}
fn is_kingbase_bit_literal_column(
database_type: Option<DatabaseType>,
column_info: Option<&DataGridColumnInfo>,
) -> bool {
database_type == Some(DatabaseType::Kingbase)
&& column_info.map(|column| is_bit_column_type(&column.data_type)).unwrap_or(false) && column_info.map(|column| is_bit_column_type(&column.data_type)).unwrap_or(false)
} }
@@ -2526,6 +2620,27 @@ fn format_mysql_bit_literal_text(text: &str) -> Option<String> {
None None
} }
fn format_kingbase_bit_literal_text(text: &str) -> Option<String> {
let trimmed = text.trim();
if trimmed.eq_ignore_ascii_case("true") {
return Some("b'1'".to_string());
}
if trimmed.eq_ignore_ascii_case("false") {
return Some("b'0'".to_string());
}
if !trimmed.is_empty() && trimmed.chars().all(|ch| matches!(ch, '0' | '1')) {
return Some(format!("b'{trimmed}'"));
}
let lower = trimmed.to_ascii_lowercase();
if lower.starts_with("b'") && trimmed.ends_with('\'') {
let bits = &trimmed[2..trimmed.len() - 1];
if !bits.is_empty() && bits.chars().all(|ch| matches!(ch, '0' | '1')) {
return Some(format!("b'{bits}'"));
}
}
None
}
fn is_mysql_datetime_literal_database(database_type: Option<DatabaseType>) -> bool { fn is_mysql_datetime_literal_database(database_type: Option<DatabaseType>) -> bool {
matches!( matches!(
database_type, database_type,
@@ -2783,9 +2898,12 @@ pub(crate) fn build_column_predicate(
if value.is_null() { if value.is_null() {
format!("{ident} IS NULL") format!("{ident} IS NULL")
} else if use_binary_text_comparison && uses_mysql_binary_text_predicate(database_type, value, column_info) { } else if use_binary_text_comparison && uses_mysql_binary_text_predicate(database_type, value, column_info) {
format!("BINARY {ident} = {}", format_grid_assignment_sql_literal(value, database_type, column_info)) format!(
"BINARY {ident} = {}",
format_grid_assignment_sql_literal(value, database_type, column_info, identifier_quote)
)
} else { } else {
let literal = format_grid_assignment_sql_literal(value, database_type, column_info); let literal = format_grid_assignment_sql_literal(value, database_type, column_info, identifier_quote);
if use_binary_text_comparison { if use_binary_text_comparison {
if let Some(predicate) = postgres_keyless_json_predicate(database_type, &ident, &literal, column_info) { if let Some(predicate) = postgres_keyless_json_predicate(database_type, &ident, &literal, column_info) {
return predicate; return predicate;
@@ -2807,9 +2925,12 @@ fn build_save_column_predicate(
if value.is_null() || empty_string_saves_as_null(value, column_info) { if value.is_null() || empty_string_saves_as_null(value, column_info) {
format!("{ident} IS NULL") format!("{ident} IS NULL")
} else if use_binary_text_comparison && uses_mysql_binary_text_predicate(database_type, value, column_info) { } else if use_binary_text_comparison && uses_mysql_binary_text_predicate(database_type, value, column_info) {
format!("BINARY {ident} = {}", format_grid_save_sql_literal(value, database_type, column_info)) format!(
"BINARY {ident} = {}",
format_grid_save_sql_literal(value, database_type, column_info, identifier_quote)
)
} else { } else {
let literal = format_grid_save_sql_literal(value, database_type, column_info); let literal = format_grid_save_sql_literal(value, database_type, column_info, identifier_quote);
if use_binary_text_comparison { if use_binary_text_comparison {
if let Some(predicate) = postgres_keyless_json_predicate(database_type, &ident, &literal, column_info) { if let Some(predicate) = postgres_keyless_json_predicate(database_type, &ident, &literal, column_info) {
return predicate; return predicate;
@@ -5316,7 +5437,8 @@ mod tests {
fn prepares_oracle_clob_update_without_oversized_string_literals() { fn prepares_oracle_clob_update_without_oversized_string_literals() {
let clob = column("body", "CLOB", true, None); let clob = column("body", "CLOB", true, None);
let large_value = "x".repeat(4205); let large_value = "x".repeat(4205);
let literal = format_grid_assignment_sql_literal(&json!(large_value), Some(DatabaseType::Oracle), Some(&clob)); let literal =
format_grid_assignment_sql_literal(&json!(large_value), Some(DatabaseType::Oracle), Some(&clob), None);
let chunks = literal let chunks = literal
.split("TO_CLOB('") .split("TO_CLOB('")
.skip(1) .skip(1)
@@ -5328,21 +5450,26 @@ mod tests {
assert_eq!(chunks[0].len(), ORACLE_LOB_LITERAL_CHUNK_BYTES); assert_eq!(chunks[0].len(), ORACLE_LOB_LITERAL_CHUNK_BYTES);
assert_eq!(chunks[1].len(), 4205 - ORACLE_LOB_LITERAL_CHUNK_BYTES); assert_eq!(chunks[1].len(), 4205 - ORACLE_LOB_LITERAL_CHUNK_BYTES);
assert_eq!( assert_eq!(
format_grid_assignment_sql_literal(&json!("short"), Some(DatabaseType::Oracle), Some(&clob)), format_grid_assignment_sql_literal(&json!("short"), Some(DatabaseType::Oracle), Some(&clob), None),
"'short'" "'short'"
); );
let nclob = column("body", "NCLOB", true, None); let nclob = column("body", "NCLOB", true, None);
assert!(format_grid_assignment_sql_literal(&json!(large_value), Some(DatabaseType::Oracle), Some(&nclob)) assert!(format_grid_assignment_sql_literal(
.starts_with("TO_NCLOB('")); &json!(large_value),
Some(DatabaseType::Oracle),
Some(&nclob),
None
)
.starts_with("TO_NCLOB('"));
let special_value = format!("{}'\\", "x".repeat(3998)); let special_value = format!("{}'\\", "x".repeat(3998));
let special_literal = let special_literal =
format_grid_assignment_sql_literal(&json!(special_value), Some(DatabaseType::Oracle), Some(&clob)); format_grid_assignment_sql_literal(&json!(special_value), Some(DatabaseType::Oracle), Some(&clob), None);
assert!(special_literal.starts_with("TO_CLOB('")); assert!(special_literal.starts_with("TO_CLOB('"));
assert!(special_literal.contains("''")); assert!(special_literal.contains("''"));
assert!(special_literal.contains("\\\\")); assert!(special_literal.contains("\\\\"));
let varchar = column("body", "VARCHAR2(5000)", true, None); let varchar = column("body", "VARCHAR2(5000)", true, None);
let varchar_literal = let varchar_literal =
format_grid_assignment_sql_literal(&json!(large_value), Some(DatabaseType::Oracle), Some(&varchar)); format_grid_assignment_sql_literal(&json!(large_value), Some(DatabaseType::Oracle), Some(&varchar), None);
assert!(varchar_literal.starts_with("'")); assert!(varchar_literal.starts_with("'"));
assert!(!varchar_literal.contains("TO_CLOB(")); assert!(!varchar_literal.contains("TO_CLOB("));
@@ -6649,6 +6776,76 @@ mod tests {
assert_eq!(format_grid_sql_literal(&json!("0"), Some(DatabaseType::Postgres), Some(&bit)), "'0'"); assert_eq!(format_grid_sql_literal(&json!("0"), Some(DatabaseType::Postgres), Some(&bit)), "'0'");
} }
#[test]
fn formats_kingbase_bit_literals_for_both_compatibility_modes() {
let bit = column("flag", "pg_catalog.bit(1)", true, None);
assert_eq!(
format_grid_sql_literal_with_identifier_quote(
&json!("0"),
Some(DatabaseType::Kingbase),
Some(&bit),
Some("`"),
),
"b'0'"
);
assert_eq!(
format_grid_sql_literal_with_identifier_quote(
&json!("10101010"),
Some(DatabaseType::Kingbase),
Some(&column("flags", "pg_catalog.bit(8)", true, None)),
Some("`"),
),
"b'10101010'"
);
assert_eq!(
format_grid_sql_literal_with_identifier_quote(
&json!("0"),
Some(DatabaseType::Kingbase),
Some(&bit),
Some("\""),
),
"b'0'"
);
assert_eq!(
format_grid_sql_literal_with_identifier_quote(
&json!(1),
Some(DatabaseType::Kingbase),
Some(&bit),
Some("\""),
),
"b'1'"
);
}
#[test]
fn kingbase_bit_save_uses_bit_string_literals() {
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
database_type: Some(DatabaseType::Kingbase),
identifier_quote: Some("`".to_string()),
table_meta: DataGridTableMeta {
catalog: None,
database: None,
schema: Some("public".to_string()),
table_name: "flags".to_string(),
primary_keys: vec!["id".to_string()],
columns: Some(vec![column("id", "int", false, None), column("flag", "pg_catalog.bit(1)", true, None)]),
},
columns: vec!["id".to_string(), "flag".to_string()],
source_columns: None,
rows: vec![vec![json!(1), json!("1")]],
dirty_rows: vec![(0, vec![(1, json!("0"))])],
deleted_rows: vec![],
new_rows: vec![],
});
assert_eq!(result.statements, vec!["UPDATE `public`.`flags` SET `flag` = b'0' WHERE `id` = 1;"],);
assert_eq!(
result.rollback_statements,
vec!["UPDATE `public`.`flags` SET `flag` = b'1' WHERE `id` = 1 AND `flag` = b'0';"],
);
}
#[test] #[test]
fn postgres_literals_use_escape_strings_for_stable_backslash_semantics() { fn postgres_literals_use_escape_strings_for_stable_backslash_semantics() {
let value = json!(r#"{"json_raw":"{\"foo\":1,\"bar\":\"sometext\"}"}"#); let value = json!(r#"{"json_raw":"{\"foo\":1,\"bar\":\"sometext\"}"}"#);