From 08024778744cfa5e5bbb5bfa6de3030ed1d5d9b2 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sun, 19 Jul 2026 13:49:33 +0800 Subject: [PATCH] fix(hive): generate valid inserts for qualified columns --- crates/dbx-core/src/data_grid_sql.rs | 279 +++++++++++++++++++++++++-- 1 file changed, 268 insertions(+), 11 deletions(-) diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 29afbd776..8f5c55f38 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -861,6 +861,7 @@ fn validate_data_grid_save(options: &DataGridSaveStatementOptions) -> Option = options .table_meta .columns @@ -886,7 +887,7 @@ fn validate_data_grid_save(options: &DataGridSaveStatementOptions) -> Option Option Ve } for row in &options.new_rows { + if options.database_type == Some(DatabaseType::Hive) { + if let Some(statement) = build_hive_values_insert(options, &table, &save_columns, row, true, true) { + statements.push(data_grid_statement(options.database_type, statement)); + } + continue; + } let insert_pairs: Vec<(&str, &Value)> = save_columns .iter() .enumerate() @@ -1226,6 +1233,12 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) - let Some(row) = options.rows.get(*row_index) else { continue; }; + if options.database_type == Some(DatabaseType::Hive) { + if let Some(statement) = build_hive_values_insert(options, &table, &save_columns, row, false, false) { + statements.push(data_grid_statement(options.database_type, statement)); + } + continue; + } let insert_pairs: Vec<(&str, &Value)> = save_columns .iter() .enumerate() @@ -1365,10 +1378,140 @@ fn build_mysql_insert_rollback_where( } pub(crate) fn effective_columns(options: &DataGridSaveStatementOptions) -> Vec> { - match &options.source_columns { + let columns = match &options.source_columns { Some(source_columns) if source_columns.len() == options.columns.len() => source_columns.clone(), _ => options.columns.iter().map(|column| Some(column.clone())).collect(), + }; + if options.database_type != Some(DatabaseType::Hive) { + return columns; } + columns + .into_iter() + .map(|column| column.map(|column| resolve_hive_target_column(&options.table_meta, &column))) + .collect() +} + +fn resolve_hive_target_column(table_meta: &DataGridTableMeta, result_column: &str) -> String { + let Some(columns) = table_meta.columns.as_deref() else { + return result_column.to_string(); + }; + if let Some(column) = unique_column_info_match(columns, result_column) { + return column.name.clone(); + } + let Some(unqualified) = last_qualified_identifier_component(result_column) else { + return result_column.to_string(); + }; + // Hive JDBC may expose SELECT * labels as `table.column`. Resolve them through + // target metadata, while exact matching above preserves real dotted column names. + unique_column_info_match(columns, &unqualified) + .map_or_else(|| result_column.to_string(), |column| column.name.clone()) +} + +fn unique_column_info_match<'a>(columns: &'a [DataGridColumnInfo], name: &str) -> Option<&'a DataGridColumnInfo> { + if let Some(column) = columns.iter().find(|column| column.name == name) { + return Some(column); + } + let normalized = normalize_column_name(name); + let mut matches = columns.iter().filter(|column| normalize_column_name(&column.name) == normalized); + let first = matches.next()?; + matches.next().is_none().then_some(first) +} + +fn last_qualified_identifier_component(name: &str) -> Option { + let mut quote = None; + let mut component_start = 0; + let mut last_component = None; + let chars = name.char_indices().collect::>(); + let mut index = 0; + while index < chars.len() { + let (byte_index, ch) = chars[index]; + if let Some(end_quote) = quote { + if ch == end_quote { + if chars.get(index + 1).is_some_and(|(_, next)| *next == end_quote) { + index += 2; + continue; + } + quote = None; + } + } else { + match ch { + '`' | '"' => quote = Some(ch), + '[' => quote = Some(']'), + '.' => { + let component = name[component_start..byte_index].trim(); + if component.is_empty() { + return None; + } + last_component = Some(component); + component_start = byte_index + ch.len_utf8(); + } + _ => {} + } + } + index += 1; + } + if quote.is_some() || last_component.is_none() { + return None; + } + unquote_identifier_component(name[component_start..].trim()) +} + +fn unquote_identifier_component(component: &str) -> Option { + if component.is_empty() { + return None; + } + for (open, close) in [('`', '`'), ('"', '"'), ('[', ']')] { + if component.starts_with(open) || component.ends_with(close) { + let inner = component.strip_prefix(open)?.strip_suffix(close)?; + let escaped = format!("{close}{close}"); + return Some(inner.replace(&escaped, &close.to_string())); + } + } + Some(component.to_string()) +} + +fn build_hive_values_insert( + options: &DataGridSaveStatementOptions, + table: &str, + save_columns: &[Option], + row: &[Value], + save_literals: bool, + skip_all_null: bool, +) -> Option { + let metadata_columns = options.table_meta.columns.as_deref().unwrap_or(&[]); + let target_columns = if metadata_columns.is_empty() { + save_columns.iter().filter_map(|column| column.as_deref()).collect::>() + } else { + metadata_columns.iter().map(|column| column.name.as_str()).collect::>() + }; + if target_columns.is_empty() { + return None; + } + let values = target_columns + .iter() + .map(|column| { + let value = find_column_index(Some(DatabaseType::Hive), save_columns, column) + .and_then(|index| row.get(index)) + .unwrap_or(&Value::Null); + (column, value) + }) + .collect::>(); + if skip_all_null && values.iter().all(|(_, value)| value.is_null()) { + return None; + } + let values = values + .into_iter() + .map(|(column, value)| { + let info = column_info_for(metadata_columns, column); + if save_literals { + format_grid_save_sql_literal(value, options.database_type, info) + } else { + format_grid_sql_literal(value, options.database_type, info) + } + }) + .collect::>() + .join(", "); + Some(format!("INSERT INTO TABLE {table} VALUES ({values})")) } fn effective_copy_columns(source_columns: Option<&[Option]>, columns: &[String]) -> Vec> { @@ -1396,13 +1539,6 @@ fn copy_column_info( }) } -fn effective_column(options: &DataGridSaveStatementOptions, index: usize) -> Option<&str> { - match &options.source_columns { - Some(source_columns) if source_columns.len() == options.columns.len() => source_columns.get(index)?.as_deref(), - _ => options.columns.get(index).map(String::as_str), - } -} - fn data_grid_save_execution_schema( database_type: Option, table_meta: &DataGridTableMeta, @@ -2238,7 +2374,7 @@ fn find_column_index(database_type: Option, columns: &[Option