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::{
|
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<_>>()
|
||||||
|
|||||||
@@ -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\"}"}"#);
|
||||||
|
|||||||
Reference in new issue
Block a user