fix(kingbase): format bit column values as bit-string literals
This commit is contained in:
4 files changed
+274
-40
No files matched your search
@@ -5,9 +5,9 @@ use super::{
|
||||
};
|
||||
use crate::data_grid_sql::{
|
||||
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,
|
||||
is_non_identity_generated_column, supports_relational_copy_predicates, DataGridCopyInsertStatementOptions,
|
||||
DataGridCopyUpdateStatementOptions, DataGridTableMeta,
|
||||
data_grid_qualified_table_name, format_grid_sql_literal_with_identifier_quote, is_auto_generated_column,
|
||||
is_grid_insert_omitted_column, is_non_identity_generated_column, supports_relational_copy_predicates,
|
||||
DataGridCopyInsertStatementOptions, DataGridCopyUpdateStatementOptions, DataGridTableMeta,
|
||||
};
|
||||
use serde_json::Value;
|
||||
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 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 {
|
||||
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')");
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn preserves_duplicate_json_columns_without_overwriting_values() {
|
||||
let mut request = request(DataGridExtractorId::Json);
|
||||
|
||||
@@ -176,6 +176,7 @@ fn build_iotdb_insert_statement(
|
||||
value,
|
||||
Some(DatabaseType::Iotdb),
|
||||
column_info_for(iotdb_column_info(options), column),
|
||||
options.identifier_quote.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@@ -411,6 +411,7 @@ pub fn build_data_grid_copy_update_statements(options: DataGridCopyUpdateStateme
|
||||
row.get(*index).unwrap_or(&Value::Null),
|
||||
options.database_type,
|
||||
*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),
|
||||
options.database_type,
|
||||
info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -604,7 +606,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
|
||||
value,
|
||||
options.database_type,
|
||||
&options.column_name,
|
||||
options.column_info.as_ref()
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
)
|
||||
)),
|
||||
DataGridContextFilterMode::LessThanOrEqual => Some(format!(
|
||||
@@ -613,7 +616,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
|
||||
value,
|
||||
options.database_type,
|
||||
&options.column_name,
|
||||
options.column_info.as_ref()
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
)
|
||||
)),
|
||||
DataGridContextFilterMode::GreaterThan => Some(format!(
|
||||
@@ -622,7 +626,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
|
||||
value,
|
||||
options.database_type,
|
||||
&options.column_name,
|
||||
options.column_info.as_ref()
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
)
|
||||
)),
|
||||
DataGridContextFilterMode::GreaterThanOrEqual => Some(format!(
|
||||
@@ -631,7 +636,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
|
||||
value,
|
||||
options.database_type,
|
||||
&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(
|
||||
@@ -640,6 +646,7 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
|
||||
options.database_type,
|
||||
&options.column_name,
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
false,
|
||||
),
|
||||
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.column_name,
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
true,
|
||||
),
|
||||
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.column_name,
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
false,
|
||||
),
|
||||
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.column_name,
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
true,
|
||||
),
|
||||
DataGridContextFilterMode::Equals => Some(format!(
|
||||
@@ -674,7 +684,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
|
||||
value,
|
||||
options.database_type,
|
||||
&options.column_name,
|
||||
options.column_info.as_ref()
|
||||
options.column_info.as_ref(),
|
||||
options.identifier_quote.as_deref(),
|
||||
)
|
||||
)),
|
||||
DataGridContextFilterMode::NotEquals => Some(format!(
|
||||
@@ -683,7 +694,8 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo
|
||||
value,
|
||||
options.database_type,
|
||||
&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>,
|
||||
column_name: &str,
|
||||
column_info: Option<&DataGridColumnInfo>,
|
||||
identifier_quote: Option<&str>,
|
||||
) -> String {
|
||||
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)) {
|
||||
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 {
|
||||
@@ -714,6 +727,7 @@ fn build_data_grid_context_membership_filter_condition(
|
||||
database_type: Option<DatabaseType>,
|
||||
column_name: &str,
|
||||
column_info: Option<&DataGridColumnInfo>,
|
||||
identifier_quote: Option<&str>,
|
||||
negated: bool,
|
||||
) -> Option<String> {
|
||||
if values.is_empty() {
|
||||
@@ -728,7 +742,8 @@ fn build_data_grid_context_membership_filter_condition(
|
||||
has_null = true;
|
||||
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()) {
|
||||
literals.push(literal);
|
||||
}
|
||||
@@ -786,14 +801,17 @@ fn build_data_grid_context_range_filter_condition(
|
||||
database_type: Option<DatabaseType>,
|
||||
column_name: &str,
|
||||
column_info: Option<&DataGridColumnInfo>,
|
||||
identifier_quote: Option<&str>,
|
||||
negated: bool,
|
||||
) -> Option<String> {
|
||||
let end_value = end_value?;
|
||||
if start_value.is_null() || end_value.is_null() {
|
||||
return None;
|
||||
}
|
||||
let start = format_data_grid_context_filter_literal(start_value, database_type, column_name, column_info);
|
||||
let end = format_data_grid_context_filter_literal(end_value, database_type, column_name, column_info);
|
||||
let start =
|
||||
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) {
|
||||
return if negated {
|
||||
Some(format!("({column} < {start} OR {column} > {end})"))
|
||||
@@ -987,7 +1005,12 @@ pub fn build_data_grid_conditional_update_sql(options: DataGridConditionalUpdate
|
||||
let sets = format!(
|
||||
"{} = {}",
|
||||
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(
|
||||
options.database_type,
|
||||
@@ -1328,7 +1351,12 @@ fn build_data_grid_save_statements(
|
||||
Some(format!(
|
||||
"{} = {}",
|
||||
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<_>>()
|
||||
@@ -1433,7 +1461,12 @@ fn build_data_grid_save_statements(
|
||||
let values = insert_pairs
|
||||
.iter()
|
||||
.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<_>>()
|
||||
.join(", ");
|
||||
@@ -1533,7 +1566,12 @@ fn build_data_grid_rollback_statements(
|
||||
let values = insert_pairs
|
||||
.iter()
|
||||
.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<_>>()
|
||||
.join(", ");
|
||||
@@ -1594,7 +1632,8 @@ fn build_data_grid_rollback_statements(
|
||||
format_grid_assignment_sql_literal(
|
||||
row.get(*column_index).unwrap_or(&Value::Null),
|
||||
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)| {
|
||||
let info = column_info_for(metadata_columns, column);
|
||||
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 {
|
||||
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<_>>()
|
||||
@@ -1942,6 +1986,7 @@ fn format_grid_copy_insert_sql_literal(
|
||||
value: &Value,
|
||||
database_type: Option<DatabaseType>,
|
||||
column_info: Option<&DataGridColumnInfo>,
|
||||
identifier_quote: Option<&str>,
|
||||
) -> String {
|
||||
if is_oracle_temporal_literal_database(database_type) {
|
||||
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(
|
||||
value: &Value,
|
||||
database_type: Option<DatabaseType>,
|
||||
column_info: Option<&DataGridColumnInfo>,
|
||||
identifier_quote: Option<&str>,
|
||||
) -> String {
|
||||
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)) {
|
||||
@@ -1968,9 +2014,14 @@ fn format_grid_assignment_sql_literal(
|
||||
}
|
||||
}
|
||||
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 {
|
||||
@@ -2033,15 +2084,27 @@ pub fn format_grid_sql_literal(
|
||||
value: &Value,
|
||||
database_type: Option<DatabaseType>,
|
||||
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 {
|
||||
if value.is_null() {
|
||||
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
|
||||
// is a numeric/boolean type rather than a bit-string type like
|
||||
// PostgreSQL's bit(n).
|
||||
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
|
||||
// is_bit_literal_column already covers); any other column there still
|
||||
// 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();
|
||||
}
|
||||
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() {
|
||||
return number.to_string();
|
||||
}
|
||||
@@ -2106,7 +2181,12 @@ pub fn format_grid_sql_literal(
|
||||
}
|
||||
if database_type == Some(DatabaseType::ManticoreSearch) {
|
||||
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() {
|
||||
@@ -2245,11 +2325,12 @@ fn format_grid_save_sql_literal(
|
||||
value: &Value,
|
||||
database_type: Option<DatabaseType>,
|
||||
column_info: Option<&DataGridColumnInfo>,
|
||||
identifier_quote: Option<&str>,
|
||||
) -> String {
|
||||
if empty_string_saves_as_null(value, column_info) {
|
||||
"NULL".to_string()
|
||||
} 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() })
|
||||
}
|
||||
|
||||
fn is_mysql_bit_literal_column(database_type: Option<DatabaseType>, column_info: Option<&DataGridColumnInfo>) -> bool {
|
||||
is_mysql_datetime_literal_database(database_type)
|
||||
fn is_mysql_bit_literal_column(
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -2526,6 +2620,27 @@ fn format_mysql_bit_literal_text(text: &str) -> Option<String> {
|
||||
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 {
|
||||
matches!(
|
||||
database_type,
|
||||
@@ -2783,9 +2898,12 @@ pub(crate) fn build_column_predicate(
|
||||
if value.is_null() {
|
||||
format!("{ident} IS NULL")
|
||||
} 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 {
|
||||
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 let Some(predicate) = postgres_keyless_json_predicate(database_type, &ident, &literal, column_info) {
|
||||
return predicate;
|
||||
@@ -2807,9 +2925,12 @@ fn build_save_column_predicate(
|
||||
if value.is_null() || empty_string_saves_as_null(value, column_info) {
|
||||
format!("{ident} IS NULL")
|
||||
} 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 {
|
||||
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 let Some(predicate) = postgres_keyless_json_predicate(database_type, &ident, &literal, column_info) {
|
||||
return predicate;
|
||||
@@ -5316,7 +5437,8 @@ mod tests {
|
||||
fn prepares_oracle_clob_update_without_oversized_string_literals() {
|
||||
let clob = column("body", "CLOB", true, None);
|
||||
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
|
||||
.split("TO_CLOB('")
|
||||
.skip(1)
|
||||
@@ -5328,21 +5450,26 @@ mod tests {
|
||||
assert_eq!(chunks[0].len(), ORACLE_LOB_LITERAL_CHUNK_BYTES);
|
||||
assert_eq!(chunks[1].len(), 4205 - ORACLE_LOB_LITERAL_CHUNK_BYTES);
|
||||
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'"
|
||||
);
|
||||
let nclob = column("body", "NCLOB", true, None);
|
||||
assert!(format_grid_assignment_sql_literal(&json!(large_value), Some(DatabaseType::Oracle), Some(&nclob))
|
||||
.starts_with("TO_NCLOB('"));
|
||||
assert!(format_grid_assignment_sql_literal(
|
||||
&json!(large_value),
|
||||
Some(DatabaseType::Oracle),
|
||||
Some(&nclob),
|
||||
None
|
||||
)
|
||||
.starts_with("TO_NCLOB('"));
|
||||
let special_value = format!("{}'\\", "x".repeat(3998));
|
||||
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.contains("''"));
|
||||
assert!(special_literal.contains("\\\\"));
|
||||
let varchar = column("body", "VARCHAR2(5000)", true, None);
|
||||
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.contains("TO_CLOB("));
|
||||
|
||||
@@ -6649,6 +6776,76 @@ mod tests {
|
||||
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]
|
||||
fn postgres_literals_use_escape_strings_for_stable_backslash_semantics() {
|
||||
let value = json!(r#"{"json_raw":"{\"foo\":1,\"bar\":\"sometext\"}"}"#);
|
||||
|
||||
Reference in new issue
Block a user