From 36d8fabc8f167bd5acbc3dbe2494a6f65ddf3cac Mon Sep 17 00:00:00 2001 From: zipg Date: Thu, 16 Jul 2026 15:20:41 +0800 Subject: [PATCH] fix(kingbase): use connection identifier quotes in MySQL mode --- apps/desktop/src/components/grid/DataGrid.vue | 6 + .../src/composables/useDataGridEditor.ts | 1 + .../src/lib/dataGrid/dataGridColumnFilter.ts | 6 +- apps/desktop/src/lib/dataGrid/dataGridSql.ts | 5 + crates/dbx-core/src/data_grid_sql.rs | 273 ++++++++++++++++-- crates/dbx-core/src/data_grid_tdengine_sql.rs | 1 + crates/dbx-core/src/sql_dialect.rs | 2 +- .../dbx-core/src/sql_dialect/table_select.rs | 2 +- .../tests/mysql_cross_database_editing.rs | 1 + .../app-tests/dataGridColumnFilter.test.ts | 19 ++ 10 files changed, 284 insertions(+), 32 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 40127bec1..3f39fbfe1 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -855,6 +855,7 @@ async function loadServerFilterValues(columnIndex: number, searchValue: string) const columnInfo = tableMeta.columns.find((column) => column.name === columnName); const sql = await buildDataGridColumnDistinctValuesSql({ databaseType: resolvedDatabaseType.value, + identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId), catalog: tableMeta.catalog, database: tableMeta.database, schema: tableMeta.schema, @@ -1179,6 +1180,7 @@ async function applyServerColumnFilter(draft: LocalColumnFilterDraft) { } const condition = await buildColumnValuesFilterCondition({ databaseType: resolvedDatabaseType.value, + identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId), columnName, columnInfo: props.tableMeta?.columns.find((column) => column.name === columnName), values, @@ -1209,6 +1211,7 @@ async function applyTypedLocalFilterValue() { const columnInfo = props.tableMeta?.columns.find((column) => column.name === columnName); const condition = await buildColumnValueFilterCondition({ databaseType: resolvedDatabaseType.value, + identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId), columnName, columnInfo, rawValue: localFilterTypedValue.value, @@ -1301,6 +1304,7 @@ async function buildStructuredWhereFromRules(rules: StructuredFilterRule[]): Pro condition: (await buildDataGridContextFilterCondition({ databaseType: resolvedDatabaseType.value, + identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId), columnName: rule.columnName, columnInfo, mode: rule.mode, @@ -3682,6 +3686,7 @@ async function prefetchDetailSqlCondition() { try { const condition = await buildDataGridContextFilterCondition({ databaseType: resolvedDatabaseType.value, + identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId), columnName: detail.column, columnInfo: props.tableMeta?.columns.find((column) => column.name === detail.column), mode: "equals", @@ -3977,6 +3982,7 @@ async function contextFilterCondition(mode: FilterMode): Promise return ( (await buildDataGridContextFilterCondition({ databaseType: resolvedDatabaseType.value, + identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId), columnName: contextColumn.value, columnInfo: props.tableMeta?.columns.find((column) => column.name === contextColumn.value), mode, diff --git a/apps/desktop/src/composables/useDataGridEditor.ts b/apps/desktop/src/composables/useDataGridEditor.ts index fb3b24730..b9cff29d0 100644 --- a/apps/desktop/src/composables/useDataGridEditor.ts +++ b/apps/desktop/src/composables/useDataGridEditor.ts @@ -1088,6 +1088,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) { if (!tableMeta.value) return null; return { databaseType: resolvedDatabaseType.value, + identifierQuote: connectionStore.connectionIdentifierQuote?.(connectionId.value), tableMeta: tableMeta.value, columns: result.value.columns, sourceColumns: sourceColumns.value, diff --git a/apps/desktop/src/lib/dataGrid/dataGridColumnFilter.ts b/apps/desktop/src/lib/dataGrid/dataGridColumnFilter.ts index e463caa74..bbf68acb3 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridColumnFilter.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridColumnFilter.ts @@ -3,9 +3,10 @@ import type { DataGridColumnInfo, DataGridContextFilterMode, GridCellValue } fro import { buildDataGridColumnValueFilterCondition, buildDataGridColumnValuesFilterCondition } from "@/lib/dataGrid/dataGridSql"; import { normalizeWhereInput } from "@/lib/table/tableSelectSql"; -export function buildColumnValueFilterCondition(options: { databaseType?: DatabaseType; columnName: string; columnInfo?: Pick; rawValue: string }): Promise { +export function buildColumnValueFilterCondition(options: { databaseType?: DatabaseType; identifierQuote?: string; columnName: string; columnInfo?: Pick; rawValue: string }): Promise { return buildDataGridColumnValueFilterCondition({ databaseType: options.databaseType, + identifierQuote: options.identifierQuote, columnName: options.columnName, columnInfo: options.columnInfo ? { @@ -18,9 +19,10 @@ export function buildColumnValueFilterCondition(options: { databaseType?: Databa }); } -export function buildColumnValuesFilterCondition(options: { databaseType?: DatabaseType; columnName: string; columnInfo?: Pick; values: GridCellValue[] }): Promise { +export function buildColumnValuesFilterCondition(options: { databaseType?: DatabaseType; identifierQuote?: string; columnName: string; columnInfo?: Pick; values: GridCellValue[] }): Promise { return buildDataGridColumnValuesFilterCondition({ databaseType: options.databaseType, + identifierQuote: options.identifierQuote, columnName: options.columnName, columnInfo: options.columnInfo ? { diff --git a/apps/desktop/src/lib/dataGrid/dataGridSql.ts b/apps/desktop/src/lib/dataGrid/dataGridSql.ts index 7ec7fb94f..e7f0ed9bc 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridSql.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridSql.ts @@ -23,6 +23,7 @@ export interface DataGridColumnInfo { export interface DataGridSaveStatementOptions { databaseType?: DatabaseType; + identifierQuote?: string; tableMeta: DataGridTableMeta; columns: string[]; sourceColumns?: Array; @@ -57,6 +58,7 @@ export type DataGridContextFilterMode = "equals" | "not-equals" | "is-null" | "i export interface DataGridContextFilterConditionOptions { databaseType?: DatabaseType; + identifierQuote?: string; columnName: string; mode: DataGridContextFilterMode; value: GridCellValue; @@ -67,6 +69,7 @@ export interface DataGridContextFilterConditionOptions { export interface DataGridColumnValueFilterConditionOptions { databaseType?: DatabaseType; + identifierQuote?: string; columnName: string; columnInfo?: DataGridColumnInfo; rawValue: string; @@ -74,6 +77,7 @@ export interface DataGridColumnValueFilterConditionOptions { export interface DataGridColumnValuesFilterConditionOptions { databaseType?: DatabaseType; + identifierQuote?: string; columnName: string; columnInfo?: DataGridColumnInfo; values: GridCellValue[]; @@ -81,6 +85,7 @@ export interface DataGridColumnValuesFilterConditionOptions { export interface DataGridColumnDistinctValuesSqlOptions { databaseType?: DatabaseType; + identifierQuote?: string; catalog?: string; database?: string; schema?: string; diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 9608c9832..29afbd776 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -65,6 +65,8 @@ pub struct DataGridColumnInfo { pub struct DataGridSaveStatementOptions { #[serde(default, skip_serializing_if = "Option::is_none")] pub database_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub identifier_quote: Option, pub table_meta: DataGridTableMeta, pub columns: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -158,6 +160,8 @@ fn supports_data_grid_context_filter_mode( pub struct DataGridContextFilterConditionOptions { #[serde(default, skip_serializing_if = "Option::is_none")] pub database_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub identifier_quote: Option, pub column_name: String, pub mode: DataGridContextFilterMode, pub value: Value, @@ -174,6 +178,8 @@ pub struct DataGridContextFilterConditionOptions { pub struct DataGridColumnValueFilterConditionOptions { #[serde(default, skip_serializing_if = "Option::is_none")] pub database_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub identifier_quote: Option, pub column_name: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub column_info: Option, @@ -185,6 +191,8 @@ pub struct DataGridColumnValueFilterConditionOptions { pub struct DataGridColumnValuesFilterConditionOptions { #[serde(default, skip_serializing_if = "Option::is_none")] pub database_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub identifier_quote: Option, pub column_name: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub column_info: Option, @@ -198,6 +206,8 @@ pub struct DataGridColumnDistinctValuesSqlOptions { #[serde(default, skip_serializing_if = "Option::is_none")] pub database_type: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub identifier_quote: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub catalog: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub database: Option, @@ -310,12 +320,13 @@ pub fn build_data_grid_copy_update_statements(options: DataGridCopyUpdateStateme return Vec::new(); } - let table = crate::sql_dialect::qualified_table_name_with_catalog( + let table = data_grid_qualified_table_name( options.database_type, options.table_meta.catalog.as_deref(), options.table_meta.schema.as_deref(), options.table_meta.database.as_deref(), &options.table_meta.table_name, + None, ); let mut statements = Vec::new(); for row in &options.rows { @@ -327,7 +338,7 @@ pub fn build_data_grid_copy_update_statements(options: DataGridCopyUpdateStateme .map(|(column, index)| { format!( "{} = {}", - quote_ident(options.database_type, column), + data_grid_identifier(options.database_type, column, None), format_grid_sql_literal( row.get(*index).unwrap_or(&Value::Null), options.database_type, @@ -350,6 +361,7 @@ pub fn build_data_grid_copy_update_statements(options: DataGridCopyUpdateStateme row.get(primary_key_indexes[index]).unwrap_or(&Value::Null), column_info_for(column_info, primary_key), false, + None, ) }) .collect::>() @@ -459,8 +471,13 @@ pub fn build_data_grid_context_filter_condition(options: DataGridContextFilterCo return None; } - let column = column_filter_ref(options.database_type, &options.column_name); - let like_column = column_like_filter_ref(options.database_type, &options.column_name, options.column_info.as_ref()); + let column = column_filter_ref(options.database_type, &options.column_name, options.identifier_quote.as_deref()); + let like_column = column_like_filter_ref( + options.database_type, + &options.column_name, + options.column_info.as_ref(), + options.identifier_quote.as_deref(), + ); let value = &options.value; match options.mode { DataGridContextFilterMode::IsNull => Some(format!("{column} IS NULL")), @@ -634,7 +651,7 @@ pub fn build_data_grid_column_value_filter_condition( if text.is_empty() { return None; } - let column = column_filter_ref(options.database_type, &options.column_name); + let column = column_filter_ref(options.database_type, &options.column_name, options.identifier_quote.as_deref()); if text.eq_ignore_ascii_case("null") { return Some(format!("{column} IS NULL")); } @@ -649,7 +666,7 @@ pub fn build_data_grid_column_values_filter_condition( return None; } - let column = column_filter_ref(options.database_type, &options.column_name); + let column = column_filter_ref(options.database_type, &options.column_name, options.identifier_quote.as_deref()); let mut has_null = false; let mut literals = Vec::new(); let mut seen_literals = HashSet::new(); @@ -687,14 +704,15 @@ pub fn build_data_grid_column_distinct_values_sql(options: DataGridColumnDistinc } let limit = data_grid_column_distinct_values_limit(options.limit); - let table = crate::sql_dialect::qualified_table_name_with_catalog( + let table = data_grid_qualified_table_name( options.database_type, options.catalog.as_deref(), options.schema.as_deref(), options.database.as_deref(), &options.table_name, + options.identifier_quote.as_deref(), ); - let column = column_filter_ref(options.database_type, &options.column_name); + let column = column_filter_ref(options.database_type, &options.column_name, options.identifier_quote.as_deref()); let mut predicates = Vec::new(); let predicate = crate::sql_dialect::normalize_where_input(options.where_input.as_deref()); if !predicate.is_empty() { @@ -780,14 +798,20 @@ fn data_grid_column_distinct_values_search_predicate( if !options.column_info.as_ref().map(|column| is_textual_column_type(&column.data_type)).unwrap_or(true) && !is_postgres_like_pattern_database(options.database_type) { - let column = column_filter_ref(options.database_type, &options.column_name); + let column = + column_filter_ref(options.database_type, &options.column_name, options.identifier_quote.as_deref()); let value = parse_typed_filter_value(search, options.database_type, options.column_info.as_ref()); return Some(format!( "{column} = {}", format_grid_sql_literal(&value, options.database_type, options.column_info.as_ref()) )); } - let column = column_like_filter_ref(options.database_type, &options.column_name, options.column_info.as_ref()); + let column = column_like_filter_ref( + options.database_type, + &options.column_name, + options.column_info.as_ref(), + options.identifier_quote.as_deref(), + ); let pattern = Value::String(format!("%{search}%")); Some(format!("{column} LIKE {}", format_grid_sql_literal(&pattern, options.database_type, None))) } @@ -795,7 +819,7 @@ fn data_grid_column_distinct_values_search_predicate( fn build_neo4j_data_grid_column_distinct_values_sql(options: &DataGridColumnDistinctValuesSqlOptions) -> String { let limit = data_grid_column_distinct_values_limit(options.limit); let label = quote_ident(Some(DatabaseType::Neo4j), &options.table_name); - let column = column_filter_ref(Some(DatabaseType::Neo4j), &options.column_name); + let column = column_filter_ref(Some(DatabaseType::Neo4j), &options.column_name, None); let mut predicates = Vec::new(); let predicate = crate::sql_dialect::normalize_where_input(options.where_input.as_deref()); if !predicate.is_empty() { @@ -1038,12 +1062,13 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve let save_columns = effective_columns(options); let column_info = options.table_meta.columns.as_deref().unwrap_or(&[]); - let table = crate::sql_dialect::qualified_table_name_with_catalog( + let table = data_grid_qualified_table_name( options.database_type, options.table_meta.catalog.as_deref(), options.table_meta.schema.as_deref(), options.table_meta.database.as_deref(), &options.table_meta.table_name, + options.identifier_quote.as_deref(), ); let mut statements = Vec::new(); let primary_key_set: Vec = @@ -1067,7 +1092,7 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve } Some(format!( "{} = {}", - quote_ident(options.database_type, column), + 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)) )) }) @@ -1082,6 +1107,7 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve &save_columns, row, column_info, + options.identifier_quote.as_deref(), ); statements.push(data_grid_statement( options.database_type, @@ -1099,6 +1125,7 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve &save_columns, row, column_info, + options.identifier_quote.as_deref(), ); statements.push(data_grid_statement( options.database_type, @@ -1134,7 +1161,7 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve } let columns = insert_pairs .iter() - .map(|(column, _)| quote_ident(options.database_type, column)) + .map(|(column, _)| data_grid_identifier(options.database_type, column, options.identifier_quote.as_deref())) .collect::>() .join(", "); let values = insert_pairs @@ -1166,10 +1193,13 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) - let save_columns = effective_columns(options); let column_info = options.table_meta.columns.as_deref().unwrap_or(&[]); - let table = qualified_table_name( + let table = data_grid_qualified_table_name( options.database_type, + options.table_meta.catalog.as_deref(), options.table_meta.schema.as_deref(), + options.table_meta.database.as_deref(), &options.table_meta.table_name, + options.identifier_quote.as_deref(), ); let mut statements = Vec::new(); @@ -1177,7 +1207,13 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) - let where_clause = if options.database_type == Some(DatabaseType::Mysql) { build_mysql_insert_rollback_where(options, &save_columns, row, column_info) } else { - let where_clause = build_save_row_where(options.database_type, &save_columns, row, column_info); + let where_clause = build_save_row_where( + options.database_type, + &save_columns, + row, + column_info, + options.identifier_quote.as_deref(), + ); (!where_clause.is_empty()).then_some(where_clause) }; if let Some(where_clause) = where_clause { @@ -1204,7 +1240,7 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) - .collect(); let columns = insert_pairs .iter() - .map(|(column, _)| quote_ident(options.database_type, column)) + .map(|(column, _)| data_grid_identifier(options.database_type, column, options.identifier_quote.as_deref())) .collect::>() .join(", "); let values = insert_pairs @@ -1250,7 +1286,7 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) - .map(|((column_index, _), column)| { format!( "{} = {}", - quote_ident(options.database_type, column), + data_grid_identifier(options.database_type, column, options.identifier_quote.as_deref()), format_grid_sql_literal( row.get(*column_index).unwrap_or(&Value::Null), options.database_type, @@ -1269,6 +1305,7 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) - &save_columns, &after_row, column_info, + options.identifier_quote.as_deref(), )]; predicates.extend(writable_changes.iter().map(|((_, value), column)| { build_save_column_predicate( @@ -1277,6 +1314,7 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) - value, column_info_for(column_info, column), true, + options.identifier_quote.as_deref(), ) })); statements.push(data_grid_statement( @@ -1316,7 +1354,14 @@ fn build_mysql_insert_rollback_where( } } - Some(build_primary_key_where(options.database_type, &options.table_meta.primary_keys, columns, row, column_info)) + Some(build_primary_key_where( + options.database_type, + &options.table_meta.primary_keys, + columns, + row, + column_info, + options.identifier_quote.as_deref(), + )) } pub(crate) fn effective_columns(options: &DataGridSaveStatementOptions) -> Vec> { @@ -1905,9 +1950,10 @@ fn build_primary_key_where( columns: &[Option], row: &[Value], column_info: &[DataGridColumnInfo], + identifier_quote: Option<&str>, ) -> String { if primary_keys.is_empty() && uses_keyless_row_predicate(database_type) { - return build_row_where(database_type, columns, row, column_info); + return build_row_where(database_type, columns, row, column_info, identifier_quote); } primary_keys .iter() @@ -1915,7 +1961,14 @@ fn build_primary_key_where( let value = row .get(find_column_index(database_type, columns, primary_key).unwrap_or(usize::MAX)) .unwrap_or(&Value::Null); - build_column_predicate(database_type, primary_key, value, column_info_for(column_info, primary_key), false) + build_column_predicate( + database_type, + primary_key, + value, + column_info_for(column_info, primary_key), + false, + identifier_quote, + ) }) .collect::>() .join(" AND ") @@ -1926,6 +1979,7 @@ fn build_row_where( columns: &[Option], row: &[Value], column_info: &[DataGridColumnInfo], + identifier_quote: Option<&str>, ) -> String { columns .iter() @@ -1941,6 +1995,7 @@ fn build_row_where( row.get(index).unwrap_or(&Value::Null), column_info_for(column_info, column), true, + identifier_quote, )) }) .collect::>() @@ -1952,6 +2007,7 @@ fn build_save_row_where( columns: &[Option], row: &[Value], column_info: &[DataGridColumnInfo], + identifier_quote: Option<&str>, ) -> String { columns .iter() @@ -1967,6 +2023,7 @@ fn build_save_row_where( row.get(index).unwrap_or(&Value::Null), column_info_for(column_info, column), true, + identifier_quote, )) }) .collect::>() @@ -1979,8 +2036,9 @@ fn build_column_predicate( value: &Value, column_info: Option<&DataGridColumnInfo>, use_binary_text_comparison: bool, + identifier_quote: Option<&str>, ) -> String { - let ident = predicate_ident(database_type, column); + let ident = predicate_ident(database_type, column, identifier_quote); if value.is_null() { format!("{ident} IS NULL") } else if use_binary_text_comparison && uses_mysql_binary_text_predicate(database_type, value, column_info) { @@ -1996,8 +2054,9 @@ fn build_save_column_predicate( value: &Value, column_info: Option<&DataGridColumnInfo>, use_binary_text_comparison: bool, + identifier_quote: Option<&str>, ) -> String { - let ident = predicate_ident(database_type, column); + let ident = predicate_ident(database_type, column, identifier_quote); 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) { @@ -2243,11 +2302,11 @@ fn clickhouse_no_mutable_columns_error() -> String { "ClickHouse primary or partition key columns cannot be updated. Change a non-key column before saving.".to_string() } -fn predicate_ident(database_type: Option, name: &str) -> String { +fn predicate_ident(database_type: Option, name: &str, identifier_quote: Option<&str>) -> String { if is_oracle_row_id(database_type, Some(name)) { "ROWIDTOCHAR(ROWID)".to_string() } else { - quote_ident(database_type, name) + data_grid_identifier(database_type, name, identifier_quote) } } @@ -2263,8 +2322,27 @@ pub(crate) fn qualified_table_name( crate::sql_dialect::qualified_table_name(database_type, schema, table_name) } -fn column_filter_ref(database_type: Option, column_name: &str) -> String { - let quoted = quote_ident(database_type, column_name); +fn data_grid_identifier(database_type: Option, name: &str, identifier_quote: Option<&str>) -> String { + crate::sql_dialect::quote_table_data_identifier(database_type, name, identifier_quote) +} + +fn data_grid_qualified_table_name( + database_type: Option, + catalog: Option<&str>, + schema: Option<&str>, + database: Option<&str>, + table_name: &str, + identifier_quote: Option<&str>, +) -> String { + if database_type == Some(DatabaseType::Kingbase) { + crate::sql_dialect::table_data_qualified_table_name(database_type, schema, table_name, identifier_quote) + } else { + crate::sql_dialect::qualified_table_name_with_catalog(database_type, catalog, schema, database, table_name) + } +} + +fn column_filter_ref(database_type: Option, column_name: &str, identifier_quote: Option<&str>) -> String { + let quoted = data_grid_identifier(database_type, column_name, identifier_quote); if database_type == Some(DatabaseType::Neo4j) { format!("n.{quoted}") } else { @@ -2276,8 +2354,9 @@ fn column_like_filter_ref( database_type: Option, column_name: &str, column_info: Option<&DataGridColumnInfo>, + identifier_quote: Option<&str>, ) -> String { - let column = column_filter_ref(database_type, column_name); + let column = column_filter_ref(database_type, column_name, identifier_quote); if is_postgres_like_pattern_database(database_type) && column_info.map(|column_info| !is_textual_column_type(&column_info.data_type)).unwrap_or(true) { @@ -2705,9 +2784,35 @@ mod tests { #[test] fn builds_filter_conditions() { + assert_eq!( + build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { + database_type: Some(DatabaseType::Kingbase), + identifier_quote: Some("`".to_string()), + column_name: "file_name".to_string(), + mode: DataGridContextFilterMode::Equals, + value: json!("34-B-0048"), + values: Vec::new(), + end_value: None, + column_info: Some(column("file_name", "varchar", false, None)), + }) + .as_deref(), + Some("`file_name` = '34-B-0048'") + ); + assert_eq!( + build_data_grid_column_value_filter_condition(DataGridColumnValueFilterConditionOptions { + database_type: Some(DatabaseType::Kingbase), + identifier_quote: Some("`".to_string()), + column_name: "file_name".to_string(), + column_info: Some(column("file_name", "varchar", false, None)), + raw_value: "34-B-0048".to_string(), + }) + .as_deref(), + Some("`file_name` = '34-B-0048'") + ); assert_eq!( build_data_grid_column_value_filter_condition(DataGridColumnValueFilterConditionOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, column_name: "id".to_string(), column_info: Some(column("id", "int", false, None)), raw_value: "49436".to_string(), @@ -2718,6 +2823,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, column_name: "status".to_string(), mode: DataGridContextFilterMode::Like, value: json!("active"), @@ -2731,6 +2837,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, column_name: "update_date".to_string(), mode: DataGridContextFilterMode::Like, value: json!("128"), @@ -2744,6 +2851,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, column_name: "created_at".to_string(), mode: DataGridContextFilterMode::NotLike, value: json!("2026"), @@ -2757,6 +2865,7 @@ mod tests { assert_eq!( build_data_grid_column_value_filter_condition(DataGridColumnValueFilterConditionOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, column_name: "active".to_string(), column_info: Some(column("active", "bitn", false, None)), raw_value: "false".to_string(), @@ -2771,6 +2880,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, column_name: "id".to_string(), mode: DataGridContextFilterMode::In, value: Value::Null, @@ -2784,6 +2894,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, column_name: "status".to_string(), mode: DataGridContextFilterMode::In, value: Value::Null, @@ -2797,6 +2908,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, column_name: "status".to_string(), mode: DataGridContextFilterMode::NotIn, value: Value::Null, @@ -2810,6 +2922,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Neo4j), + identifier_quote: None, column_name: "name".to_string(), mode: DataGridContextFilterMode::In, value: Value::Null, @@ -2823,6 +2936,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::Between, value: json!(10), @@ -2836,6 +2950,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::NotBetween, value: json!(10), @@ -2853,6 +2968,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Neo4j), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::In, value: Value::Null, @@ -2866,6 +2982,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Neo4j), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::NotIn, value: Value::Null, @@ -2879,6 +2996,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Neo4j), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::Between, value: json!(10), @@ -2892,6 +3010,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Neo4j), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::NotBetween, value: json!(10), @@ -2926,6 +3045,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(database_type), + identifier_quote: None, column_name: "score".to_string(), mode, value, @@ -2946,6 +3066,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::In, value: Value::Null, @@ -2958,6 +3079,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::Between, value: json!(10), @@ -2970,6 +3092,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::Between, value: Value::Null, @@ -2982,6 +3105,7 @@ mod tests { assert_eq!( build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, column_name: "score".to_string(), mode: DataGridContextFilterMode::NotBetween, value: json!(10), @@ -3012,6 +3136,7 @@ mod tests { assert_eq!( build_data_grid_column_values_filter_condition(DataGridColumnValuesFilterConditionOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, column_name: "status".to_string(), column_info: Some(column("status", "varchar", true, None)), values: vec![json!("active"), json!("pending"), Value::Null, json!("active")], @@ -3022,6 +3147,7 @@ mod tests { assert_eq!( build_data_grid_column_values_filter_condition(DataGridColumnValuesFilterConditionOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, column_name: "id".to_string(), column_info: Some(column("id", "int", false, None)), values: vec![json!(42)], @@ -3036,6 +3162,7 @@ mod tests { let values = (0..=1000).map(|value| json!(value)).collect::>(); let in_condition = build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Oracle), + identifier_quote: None, column_name: "id".to_string(), mode: DataGridContextFilterMode::In, value: Value::Null, @@ -3049,6 +3176,7 @@ mod tests { let not_in_condition = build_data_grid_context_filter_condition(DataGridContextFilterConditionOptions { database_type: Some(DatabaseType::Oracle), + identifier_quote: None, column_name: "id".to_string(), mode: DataGridContextFilterMode::NotIn, value: Value::Null, @@ -3071,6 +3199,7 @@ mod tests { assert_eq!( build_data_grid_column_value_filter_condition(DataGridColumnValueFilterConditionOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, column_name: "flags".to_string(), column_info: Some(bit), raw_value: "true".to_string(), @@ -3093,6 +3222,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, catalog: None, database: None, schema: Some("public".to_string()), @@ -3109,6 +3239,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, catalog: None, database: None, schema: None, @@ -3125,6 +3256,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, catalog: None, database: None, schema: None, @@ -3141,6 +3273,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::Oracle), + identifier_quote: None, catalog: None, database: None, schema: Some("APP".to_string()), @@ -3157,6 +3290,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::Firebird), + identifier_quote: None, catalog: None, database: None, schema: None, @@ -3174,6 +3308,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::Doris), + identifier_quote: None, catalog: Some("iceberg_catalog".to_string()), database: None, schema: Some("sales".to_string()), @@ -3190,6 +3325,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::StarRocks), + identifier_quote: None, catalog: Some("hive_catalog".to_string()), database: None, schema: None, @@ -3207,6 +3343,7 @@ mod tests { assert_eq!( build_data_grid_column_distinct_values_sql(DataGridColumnDistinctValuesSqlOptions { database_type: Some(DatabaseType::Doris), + identifier_quote: None, catalog: Some("internal".to_string()), database: None, schema: None, @@ -3443,6 +3580,7 @@ mod tests { fn prepares_sqlserver_bigint_update_from_numeric_string() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3467,6 +3605,7 @@ mod tests { fn prepares_kingbase_update_when_source_primary_key_case_differs() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Kingbase), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3503,6 +3642,7 @@ mod tests { fn rejects_existing_row_save_when_primary_key_is_missing() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Kingbase), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3536,6 +3676,7 @@ mod tests { fn rejects_existing_row_save_when_primary_key_value_is_null() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Kingbase), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3585,6 +3726,7 @@ mod tests { fn rejects_postgres_save_when_only_case_different_column_is_returned() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3619,6 +3761,7 @@ mod tests { fn rejects_kingbase_save_when_case_only_primary_key_match_is_ambiguous() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Kingbase), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3644,10 +3787,40 @@ mod tests { assert!(result.rollback_statements.is_empty()); } + #[test] + fn kingbase_mysql_compat_save_uses_connection_identifier_quote() { + 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("gc".to_string()), + table_name: "docfileinfo".to_string(), + primary_keys: vec!["id".to_string()], + columns: Some(vec![column("id", "integer", false, None), column("file_name", "varchar", false, None)]), + }, + columns: vec!["id".to_string(), "file_name".to_string()], + source_columns: None, + rows: vec![vec![json!(1), json!("old")]], + dirty_rows: vec![(0, vec![(1, json!("34-B-0048"))])], + deleted_rows: vec![], + new_rows: vec![], + }); + + assert_eq!(result.validation_error, None); + assert_eq!(result.statements, vec!["UPDATE `gc`.`docfileinfo` SET `file_name` = '34-B-0048' WHERE `id` = 1;"]); + assert!(result + .rollback_statements + .iter() + .all(|statement| statement.contains("`gc`.`docfileinfo`") && !statement.contains('"'))); + } + #[test] fn postgres_save_uses_exact_quoted_primary_key_for_update_delete_and_rollback() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3684,6 +3857,7 @@ mod tests { fn prepares_oracle_timestamp_insert_from_iso_grid_value() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Oracle), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3716,6 +3890,7 @@ mod tests { fn prepares_oceanbase_oracle_lob_deletes_with_synthetic_rowid() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::OceanbaseOracle), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3761,6 +3936,7 @@ mod tests { fn prepares_oceanbase_oracle_lob_delete_with_declared_primary_key() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::OceanbaseOracle), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3790,6 +3966,7 @@ mod tests { fn rejects_oceanbase_oracle_keyless_lob_writes_without_rowid() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::OceanbaseOracle), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3818,6 +3995,7 @@ mod tests { fn preserves_oceanbase_oracle_keyless_predicates_for_comparable_columns() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::OceanbaseOracle), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3866,6 +4044,7 @@ mod tests { fn prepares_sqlserver_bitn_updates_with_numeric_literals() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3898,6 +4077,7 @@ mod tests { fn saves_empty_nullable_mysql_numeric_cell_as_null() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3926,6 +4106,7 @@ mod tests { fn keeps_empty_nullable_mysql_text_cell_as_empty_string() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3954,6 +4135,7 @@ mod tests { fn preserves_mysql_text_cell_line_breaks() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -3982,6 +4164,7 @@ mod tests { fn prepares_sqlserver_save_statements() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::SqlServer), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4012,6 +4195,7 @@ mod tests { fn prepares_tdengine_child_table_delete_from_stable_row() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4043,6 +4227,7 @@ mod tests { fn rejects_tdengine_composite_key_delete_from_same_timestamp_stable_rows() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4083,6 +4268,7 @@ mod tests { fn prepares_tdengine_delete_from_direct_child_table_row() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4114,6 +4300,7 @@ mod tests { fn prepares_tdengine_overwrite_for_direct_child_table_row() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4149,6 +4336,7 @@ mod tests { fn prepares_tdengine_composite_key_overwrite_and_rollback_for_same_timestamp_child_rows() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4187,6 +4375,7 @@ mod tests { fn prepares_tdengine_stable_insert_with_child_table_identity() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4232,6 +4421,7 @@ mod tests { fn skips_tdengine_composite_key_insert_rollback_for_same_timestamp_rows() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4275,6 +4465,7 @@ mod tests { fn rejects_tdengine_stable_insert_without_child_table_identity() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4303,6 +4494,7 @@ mod tests { fn rejects_tdengine_delete_without_child_table_identity() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4330,6 +4522,7 @@ mod tests { fn rejects_tdengine_existing_row_edit_when_composite_key_is_missing() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4362,6 +4555,7 @@ mod tests { fn rejects_tdengine_existing_row_identity_changes() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Tdengine), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4391,6 +4585,7 @@ mod tests { fn prepares_databend_save_statements() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Databend), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4423,6 +4618,7 @@ mod tests { id_column.is_primary_key = true; let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::ClickHouse), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4457,6 +4653,7 @@ mod tests { id_column.is_primary_key = true; let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::ClickHouse), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4489,6 +4686,7 @@ mod tests { event_date_column.is_primary_key = false; let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::ClickHouse), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4517,6 +4715,7 @@ mod tests { fn rejects_clickhouse_partition_key_only_update() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::ClickHouse), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4607,6 +4806,7 @@ mod tests { let save = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Doris), + identifier_quote: None, table_meta, columns: vec!["id".to_string(), "status".to_string()], source_columns: None, @@ -4630,6 +4830,7 @@ mod tests { fn prepares_databend_keyless_save_statements_with_row_predicate() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Databend), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4659,6 +4860,7 @@ mod tests { fn prepares_oscar_keyless_save_statements_with_schema_qualified_row_predicate() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Oscar), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4688,6 +4890,7 @@ mod tests { fn skips_expression_only_source_columns() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4722,6 +4925,7 @@ mod tests { fn formats_mysql_temporal_columns_by_target_type() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4774,6 +4978,7 @@ mod tests { fn mysql_primary_key_text_predicates_do_not_use_binary_comparison() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4800,6 +5005,7 @@ mod tests { fn mysql_row_text_predicates_use_binary_comparison_for_width_sensitive_edits() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4832,6 +5038,7 @@ mod tests { fn prepares_manticore_save_statements_without_trailing_semicolons() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::ManticoreSearch), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4863,6 +5070,7 @@ mod tests { fn validates_duplicate_inserted_primary_keys() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Postgres), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4901,6 +5109,7 @@ mod tests { fn prepare_data_grid_save_skips_sqlite_autoincrement_pk_validation() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Sqlite), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4928,6 +5137,7 @@ mod tests { fn prepare_data_grid_save_includes_explicit_sqlite_pk_value() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Sqlite), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4958,6 +5168,7 @@ mod tests { fn prepare_data_grid_save_omits_empty_mysql_auto_increment_value() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -4985,6 +5196,7 @@ mod tests { fn prepare_data_grid_save_omits_mysql_not_null_column_for_before_insert_trigger() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -5014,6 +5226,7 @@ mod tests { fn prepare_data_grid_save_uses_mysql_default_row_insert_for_trigger_only_rows() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -5042,6 +5255,7 @@ mod tests { fn prepare_data_grid_save_uses_known_mysql_primary_key_for_trigger_rollback() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -5071,6 +5285,7 @@ mod tests { fn prepare_data_grid_save_still_rejects_mysql_null_update() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -5098,6 +5313,7 @@ mod tests { fn prepare_data_grid_save_omits_empty_kingbase_sqlserver_identity_value() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Kingbase), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, @@ -5125,6 +5341,7 @@ mod tests { fn prepare_data_grid_save_still_validates_other_not_null_columns_in_sqlite() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Sqlite), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, diff --git a/crates/dbx-core/src/data_grid_tdengine_sql.rs b/crates/dbx-core/src/data_grid_tdengine_sql.rs index 9dfb34bab..c3cab6510 100644 --- a/crates/dbx-core/src/data_grid_tdengine_sql.rs +++ b/crates/dbx-core/src/data_grid_tdengine_sql.rs @@ -188,6 +188,7 @@ fn build_tdengine_delete_statement( save_columns, row, options.table_meta.columns.as_deref().unwrap_or(&[]), + None, ); if where_clause.is_empty() { return None; diff --git a/crates/dbx-core/src/sql_dialect.rs b/crates/dbx-core/src/sql_dialect.rs index f4fd81ef3..f619f0b2c 100644 --- a/crates/dbx-core/src/sql_dialect.rs +++ b/crates/dbx-core/src/sql_dialect.rs @@ -14,6 +14,6 @@ pub use identifiers::{ normalize_where_input, qualified_table_name, qualified_table_name_with_catalog, quote_table_identifier, }; pub(crate) use identifiers::{parse_sqlserver_linked_schema_ref, qualified_transfer_table, quote_transfer_identifier}; -pub(crate) use table_select::table_data_qualified_table_name; pub use table_select::{build_count_table_sql, build_table_data_select_sql, build_table_select_sql}; +pub(crate) use table_select::{quote_table_data_identifier, table_data_qualified_table_name}; pub use types::*; diff --git a/crates/dbx-core/src/sql_dialect/table_select.rs b/crates/dbx-core/src/sql_dialect/table_select.rs index 322501894..e4b58a7b8 100644 --- a/crates/dbx-core/src/sql_dialect/table_select.rs +++ b/crates/dbx-core/src/sql_dialect/table_select.rs @@ -171,7 +171,7 @@ pub(crate) fn table_data_qualified_table_name( .unwrap_or(table) } -fn quote_table_data_identifier( +pub(crate) fn quote_table_data_identifier( database_type: Option, name: &str, identifier_quote: Option<&str>, diff --git a/crates/dbx-core/tests/mysql_cross_database_editing.rs b/crates/dbx-core/tests/mysql_cross_database_editing.rs index 05ca62a97..a60a7214b 100644 --- a/crates/dbx-core/tests/mysql_cross_database_editing.rs +++ b/crates/dbx-core/tests/mysql_cross_database_editing.rs @@ -54,6 +54,7 @@ fn mysql_cross_database_query_flow_preserves_target_database() { let save = prepare_data_grid_save(DataGridSaveStatementOptions { database_type: Some(DatabaseType::Mysql), + identifier_quote: None, table_meta: DataGridTableMeta { catalog: None, database: None, diff --git a/packages/app-tests/dataGridColumnFilter.test.ts b/packages/app-tests/dataGridColumnFilter.test.ts index 428946cef..10694deaf 100644 --- a/packages/app-tests/dataGridColumnFilter.test.ts +++ b/packages/app-tests/dataGridColumnFilter.test.ts @@ -178,3 +178,22 @@ test("passes list and range values through the shared context filter API", async endValue: 20, }); }); + +test("passes the connection identifier quote to Kingbase filters", async () => { + installFilterFetchMock(); + await buildDataGridContextFilterCondition({ + databaseType: "kingbase", + identifierQuote: "`", + columnName: "file_name", + mode: "equals", + value: "34-B-0048", + }); + + assert.deepEqual(lastContextFilterOptions, { + databaseType: "kingbase", + identifierQuote: "`", + columnName: "file_name", + mode: "equals", + value: "34-B-0048", + }); +});