diff --git a/crates/dbx-core/src/data_grid_extractors/sql.rs b/crates/dbx-core/src/data_grid_extractors/sql.rs index 6d2058976d..6a24447ef1 100644 --- a/crates/dbx-core/src/data_grid_extractors/sql.rs +++ b/crates/dbx-core/src/data_grid_extractors/sql.rs @@ -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')'); diff --git a/crates/dbx-core/src/data_grid_extractors/tests.rs b/crates/dbx-core/src/data_grid_extractors/tests.rs index 58ac66ebdb..e7108fc764 100644 --- a/crates/dbx-core/src/data_grid_extractors/tests.rs +++ b/crates/dbx-core/src/data_grid_extractors/tests.rs @@ -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); diff --git a/crates/dbx-core/src/data_grid_iotdb_sql.rs b/crates/dbx-core/src/data_grid_iotdb_sql.rs index 307e54dc27..39207359f0 100644 --- a/crates/dbx-core/src/data_grid_iotdb_sql.rs +++ b/crates/dbx-core/src/data_grid_iotdb_sql.rs @@ -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::>() diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 1b33e3d206..a9b9bf7fe5 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -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::>() @@ -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, 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, column_name: &str, column_info: Option<&DataGridColumnInfo>, + identifier_quote: Option<&str>, negated: bool, ) -> Option { 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, column_name: &str, column_info: Option<&DataGridColumnInfo>, + identifier_quote: Option<&str>, negated: bool, ) -> Option { 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::>() @@ -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::>() .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::>() .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::>() @@ -1942,6 +1986,7 @@ fn format_grid_copy_insert_sql_literal( value: &Value, database_type: Option, 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, 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, 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, + 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, 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 { Some(Rfc3339Parts { date: date.to_string(), time: time.to_string(), fraction, zone: String::new() }) } -fn is_mysql_bit_literal_column(database_type: Option, column_info: Option<&DataGridColumnInfo>) -> bool { - is_mysql_datetime_literal_database(database_type) +fn is_mysql_bit_literal_column( + database_type: Option, + 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, + 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 { None } +fn format_kingbase_bit_literal_text(text: &str) -> Option { + 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) -> 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\"}"}"#);