From 2eee29c0bf17e1184880dae5804cc37b1b4b5aa2 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Tue, 18 Aug 2026 06:32:44 +0000 Subject: [PATCH] fix(grid): deduplicate copied SQL IN tuples Closes #6542 --- .../dbx-core/src/data_grid_extractors/sql.rs | 26 ++++++---- .../src/data_grid_extractors/tests.rs | 49 +++++++++++++++++++ 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/crates/dbx-core/src/data_grid_extractors/sql.rs b/crates/dbx-core/src/data_grid_extractors/sql.rs index 6b9f994a26..f387c27a1b 100644 --- a/crates/dbx-core/src/data_grid_extractors/sql.rs +++ b/crates/dbx-core/src/data_grid_extractors/sql.rs @@ -10,7 +10,7 @@ use crate::data_grid_sql::{ DataGridCopyUpdateStatementOptions, DataGridTableMeta, }; use serde_json::Value; -use std::collections::HashSet; +use std::collections::{hash_map::Entry, HashMap, HashSet}; use std::io::Write; pub(super) fn write_sql_in_list( @@ -18,23 +18,31 @@ pub(super) fn write_sql_in_list( output: &mut dyn Write, ) -> Result<(), DataGridExtractError> { write_bytes(output, b"(")?; - for (row_index, row) in context.request.rows.iter().enumerate() { - if row_index > 0 { - write_bytes(output, b", ")?; - } + let mut seen_rows = HashMap::, ()>::new(); + let mut has_output = false; + for row in &context.request.rows { + let mut tuple = Vec::new(); if context.selected_columns.len() > 1 { - write_bytes(output, b"(")?; + tuple.push(b'('); } for (column_index, source_index) in context.selected_source_indexes.iter().enumerate() { if column_index > 0 { - write_bytes(output, b", ")?; + tuple.extend_from_slice(b", "); } let value = &row[*source_index]; let info = context.selected_column_info[column_index]; - write_bytes(output, format_grid_sql_literal(value, context.request.database_type, info).as_bytes())?; + tuple.extend_from_slice(format_grid_sql_literal(value, context.request.database_type, info).as_bytes()); } if context.selected_columns.len() > 1 { - write_bytes(output, b")")?; + tuple.push(b')'); + } + if let Entry::Vacant(entry) = seen_rows.entry(tuple) { + if has_output { + write_bytes(output, b", ")?; + } + write_bytes(output, entry.key())?; + entry.insert(()); + has_output = true; } } write_bytes(output, b")") diff --git a/crates/dbx-core/src/data_grid_extractors/tests.rs b/crates/dbx-core/src/data_grid_extractors/tests.rs index 5ddbd849a1..569a0fdc71 100644 --- a/crates/dbx-core/src/data_grid_extractors/tests.rs +++ b/crates/dbx-core/src/data_grid_extractors/tests.rs @@ -187,6 +187,55 @@ fn extracts_multi_column_sql_in_as_row_value_tuples() { assert_eq!(result.text, "((1, 'Ada'), (2, 'Grace, Hopper'))"); } +#[test] +fn sql_in_list_deduplicates_single_selected_values() { + let mut request = request(DataGridExtractorId::SqlInList); + request.selected_column_indexes = vec![0]; + request.rows = vec![ + vec![json!(1), json!("first")], + vec![json!(1), json!("unselected value differs")], + vec![json!(2), json!("last")], + ]; + + let result = extract_data_grid_selection(request).expect("SQL IN extraction"); + + assert_eq!(result.text, "(1, 2)"); +} + +#[test] +fn sql_in_list_deduplicates_multi_column_tuples_in_first_seen_order() { + let mut request = request(DataGridExtractorId::SqlInList); + request.rows = vec![ + vec![json!(2), json!("Grace")], + vec![json!(1), json!("Ada")], + vec![json!(2), json!("Grace")], + vec![json!(1), json!("Lovelace")], + vec![json!(1), json!("Ada")], + ]; + + let result = extract_data_grid_selection(request).expect("SQL IN extraction"); + + assert_eq!(result.text, "((2, 'Grace'), (1, 'Ada'), (1, 'Lovelace'))"); +} + +#[test] +fn sql_in_list_deduplicates_only_identical_rendered_literals() { + let mut request = request(DataGridExtractorId::SqlInList); + request.selected_column_indexes = vec![0]; + request.rows = vec![ + vec![json!(1)], + vec![json!("1")], + vec![Value::Null], + vec![Value::Null], + vec![json!("O'Reilly")], + vec![json!("O'Reilly")], + ]; + + let result = extract_data_grid_selection(request).expect("SQL IN extraction"); + + assert_eq!(result.text, "(1, '1', NULL, 'O''Reilly')"); +} + #[test] fn preserves_duplicate_json_columns_without_overwriting_values() { let mut request = request(DataGridExtractorId::Json);