feat(export): honor exclude-primary-keys in SQL script exports
This commit is contained in:
16 files changed
+464
-68
No files matched your search
@@ -3250,16 +3250,17 @@ pub(crate) fn is_neo4j_element_id(database_type: Option<DatabaseType>, name: Opt
|
||||
database_type == Some(DatabaseType::Neo4j) && name == Some(DBX_NEO4J_ELEMENT_ID_COLUMN)
|
||||
}
|
||||
|
||||
pub(crate) fn is_auto_generated_column(column: &DataGridColumnInfo) -> bool {
|
||||
column
|
||||
.extra
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
pub(crate) fn extra_is_auto_generated(extra: &str) -> bool {
|
||||
extra
|
||||
.to_ascii_lowercase()
|
||||
.split(|ch: char| !ch.is_ascii_alphanumeric() && ch != '_')
|
||||
.any(|part| matches!(part, "auto_increment" | "autoincrement" | "identity"))
|
||||
}
|
||||
|
||||
pub(crate) fn is_auto_generated_column(column: &DataGridColumnInfo) -> bool {
|
||||
extra_is_auto_generated(column.extra.as_deref().unwrap_or(""))
|
||||
}
|
||||
|
||||
fn grid_value_is_empty(value: &Value) -> bool {
|
||||
value.is_null() || value.as_str().is_some_and(str::is_empty)
|
||||
}
|
||||
|
||||
@@ -473,6 +473,9 @@ pub struct BuildExportInsertStatementsOptions {
|
||||
pub struct BuildExportSqlInsertOptions {
|
||||
#[serde(flatten)]
|
||||
pub insert: BuildExportInsertStatementsOptions,
|
||||
/// 生成 INSERT 时需要排除的列名(例如导出时不带主键),忽略大小写匹配。
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub exclude_columns: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -1154,10 +1157,24 @@ fn is_export_numeric_literal(text: &str) -> bool {
|
||||
}
|
||||
|
||||
pub fn build_export_insert_statements(options: BuildExportInsertStatementsOptions) -> Result<Vec<String>, String> {
|
||||
build_export_insert_statements_excluding(options, &[])
|
||||
}
|
||||
|
||||
/// 与 [`build_export_insert_statements`] 行为一致,但可以额外按列名排除若干列
|
||||
/// (典型场景:导出 SQL 时不带主键)。列名比较忽略大小写。
|
||||
///
|
||||
/// 行值与空间列仍然按列在 `columns` 中的原始下标取值,所以调用方不需要自己裁剪
|
||||
/// 行数据,也不会出现“列删了、值没删”导致的错位。
|
||||
pub fn build_export_insert_statements_excluding(
|
||||
options: BuildExportInsertStatementsOptions,
|
||||
exclude_columns: &[String],
|
||||
) -> Result<Vec<String>, String> {
|
||||
if options.columns.is_empty() || options.rows.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let excluded_names: HashSet<String> =
|
||||
exclude_columns.iter().map(|column| column.trim().to_ascii_uppercase()).collect();
|
||||
let table = export_qualified_table_name(
|
||||
options.database_type,
|
||||
options.schema.as_deref(),
|
||||
@@ -1172,6 +1189,26 @@ pub fn build_export_insert_statements(options: BuildExportInsertStatementsOption
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, column)| {
|
||||
let column_type = export_column_type(&options.column_types, index, options.database_type, &spatial_columns);
|
||||
let excluded = excluded_names.contains(&column.trim().to_ascii_uppercase());
|
||||
(is_export_insert_column(
|
||||
options.database_type,
|
||||
column,
|
||||
column_type,
|
||||
options.column_extras.get(index).and_then(|value| value.as_deref()),
|
||||
) && !excluded)
|
||||
.then(|| {
|
||||
let sqlserver_unicode_string = options.database_type == Some(DatabaseType::SqlServer)
|
||||
&& column_type.is_some_and(is_sqlserver_unicode_export_type);
|
||||
(index, column, sqlserver_unicode_string)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if insert_columns.is_empty() {
|
||||
// Only fail when the exclusion itself removed the last insertable column;
|
||||
// emptiness caused by other omission rules (e.g. generated columns) keeps
|
||||
// the silent empty result.
|
||||
let had_insertable_without_exclusion = options.columns.iter().enumerate().any(|(index, column)| {
|
||||
let column_type = export_column_type(&options.column_types, index, options.database_type, &spatial_columns);
|
||||
is_export_insert_column(
|
||||
options.database_type,
|
||||
@@ -1179,14 +1216,10 @@ pub fn build_export_insert_statements(options: BuildExportInsertStatementsOption
|
||||
column_type,
|
||||
options.column_extras.get(index).and_then(|value| value.as_deref()),
|
||||
)
|
||||
.then(|| {
|
||||
let sqlserver_unicode_string = options.database_type == Some(DatabaseType::SqlServer)
|
||||
&& column_type.is_some_and(is_sqlserver_unicode_export_type);
|
||||
(index, column, sqlserver_unicode_string)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if insert_columns.is_empty() {
|
||||
});
|
||||
if had_insertable_without_exclusion {
|
||||
return Err("No insertable columns remain after excluding columns from the export.".to_string());
|
||||
}
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let batch_size = if options.database_type.is_some_and(uses_single_row_insert_statements) {
|
||||
@@ -1354,7 +1387,8 @@ fn is_postgres_bytea_export_column(database_type: Option<DatabaseType>, column_t
|
||||
}
|
||||
|
||||
pub fn build_export_sql_insert(options: BuildExportSqlInsertOptions) -> Result<String, String> {
|
||||
build_export_insert_statements(options.insert).map(|statements| statements.join("\n"))
|
||||
build_export_insert_statements_excluding(options.insert, &options.exclude_columns)
|
||||
.map(|statements| statements.join("\n"))
|
||||
}
|
||||
|
||||
pub fn build_database_sql_export(options: BuildDatabaseSqlExportOptions) -> Result<String, String> {
|
||||
@@ -3837,18 +3871,20 @@ mod tests {
|
||||
};
|
||||
use super::{
|
||||
build_database_export_object_source_sql, build_database_sql_export, build_export_insert_statements,
|
||||
create_database_export_writer, database_export_query_options_for_timeout, database_export_select_sql,
|
||||
database_export_total_objects, drop_table_if_exists_sql, ensure_export_destination_dir,
|
||||
export_destination_identity_mismatch, filter_export_table_infos, format_export_sql_literal,
|
||||
format_export_table_ddl, format_mysql_spatial_export_literal, format_xugu_spatial_export_literal,
|
||||
generate_postgres_extension_ddl, generate_postgres_sequence_create_ddl, generate_postgres_sequence_owner_ddl,
|
||||
build_export_insert_statements_excluding, build_export_sql_insert, create_database_export_writer,
|
||||
database_export_query_options_for_timeout, database_export_select_sql, database_export_total_objects,
|
||||
drop_table_if_exists_sql, ensure_export_destination_dir, export_destination_identity_mismatch,
|
||||
filter_export_table_infos, format_export_sql_literal, format_export_table_ddl,
|
||||
format_mysql_spatial_export_literal, format_xugu_spatial_export_literal, generate_postgres_extension_ddl,
|
||||
generate_postgres_sequence_create_ddl, generate_postgres_sequence_owner_ddl,
|
||||
generate_postgres_sequence_setval_sql, is_postgres_extension_member_routine, mysql_database_export_preamble,
|
||||
mysql_view_dependencies_from_rows, mysql_view_dependencies_sql, normalize_export_table_ddl,
|
||||
record_export_destination_identity, record_export_error, replace_database_export_select_list,
|
||||
sort_export_views_by_dependencies, split_postgres_export_table_triggers, write_database_export_rows,
|
||||
BuildDatabaseSqlExportOptions, BuildExportInsertStatementsOptions, DatabaseExportObjectCounts,
|
||||
DatabaseExportRequest, DatabaseExportWriter, DdlNormalizeOptions, ExportedTableSql, PostgresExportExtension,
|
||||
PostgresExportSequence, PostgresExtensionMembers, DATABASE_EXPORT_INSERT_BATCH_SIZE, DATABASE_EXPORT_ROW_LIMIT,
|
||||
BuildDatabaseSqlExportOptions, BuildExportInsertStatementsOptions, BuildExportSqlInsertOptions,
|
||||
DatabaseExportObjectCounts, DatabaseExportRequest, DatabaseExportWriter, DdlNormalizeOptions, ExportedTableSql,
|
||||
PostgresExportExtension, PostgresExportSequence, PostgresExtensionMembers, DATABASE_EXPORT_INSERT_BATCH_SIZE,
|
||||
DATABASE_EXPORT_ROW_LIMIT,
|
||||
};
|
||||
use super::{ExportProgress, LenientExportErrors};
|
||||
use crate::connection::AppState;
|
||||
@@ -5450,6 +5486,78 @@ mod tests {
|
||||
assert_eq!(statements, vec!["INSERT INTO \"public\".\"articles\" (\"id\", \"title\") VALUES (1, 'Hello');"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_insert_export_omits_excluded_columns_and_keeps_values_aligned() {
|
||||
let statements = build_export_insert_statements_excluding(
|
||||
BuildExportInsertStatementsOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
schema: Some("public".to_string()),
|
||||
table_name: Some("users".to_string()),
|
||||
qualified_table_name: None,
|
||||
columns: vec!["id".to_string(), "name".to_string(), "email".to_string()],
|
||||
column_types: vec![Some("integer".to_string()), Some("text".to_string()), Some("text".to_string())],
|
||||
column_extras: Vec::new(),
|
||||
spatial_columns: Vec::new(),
|
||||
spatial_values: Vec::new(),
|
||||
rows: vec![vec![json!(1), json!("Ada"), json!("ada@example.com")]],
|
||||
batch_size: Some(10),
|
||||
},
|
||||
&["id".to_string()],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
statements,
|
||||
vec!["INSERT INTO \"public\".\"users\" (\"name\", \"email\") VALUES ('Ada', 'ada@example.com');"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_insert_export_reports_error_when_excluding_leaves_no_columns() {
|
||||
let result = build_export_insert_statements_excluding(
|
||||
BuildExportInsertStatementsOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
schema: Some("public".to_string()),
|
||||
table_name: Some("user_roles".to_string()),
|
||||
qualified_table_name: None,
|
||||
columns: vec!["user_id".to_string()],
|
||||
column_types: vec![Some("integer".to_string())],
|
||||
column_extras: Vec::new(),
|
||||
spatial_columns: Vec::new(),
|
||||
spatial_values: Vec::new(),
|
||||
rows: vec![vec![json!(1)]],
|
||||
batch_size: Some(10),
|
||||
},
|
||||
&["user_id".to_string()],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result.expect_err("excluding every column must fail instead of writing an empty export"),
|
||||
"No insertable columns remain after excluding columns from the export."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_export_sql_insert_honors_exclude_columns_from_payload() {
|
||||
let options: BuildExportSqlInsertOptions = serde_json::from_value(json!({
|
||||
"databaseType": "postgres",
|
||||
"schema": "public",
|
||||
"tableName": "users",
|
||||
"columns": ["id", "name"],
|
||||
"columnTypes": ["integer", "text"],
|
||||
"rows": [[1, "Ada"]],
|
||||
"excludeColumns": ["id"],
|
||||
"batchSize": 10
|
||||
}))
|
||||
.expect("deserialize export insert payload");
|
||||
|
||||
let sql = build_export_sql_insert(options).expect("build export sql insert");
|
||||
|
||||
assert_eq!(sql, "INSERT INTO \"public\".\"users\" (\"name\") VALUES ('Ada');");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_generated_columns_are_omitted_from_sql_inserts_but_kept_in_ddl() {
|
||||
let ddl = "CREATE TABLE `orders` (`id` bigint AUTO_INCREMENT, `quantity` int, `unit_price` decimal(10,2), `virtual_total` decimal(10,2) GENERATED ALWAYS AS ((`quantity` * `unit_price`)) VIRTUAL, `stored_total` decimal(10,2) GENERATED ALWAYS AS ((`quantity` * `unit_price`)) STORED);";
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::csv_export::{
|
||||
};
|
||||
pub use crate::database_export::ExportStatus;
|
||||
use crate::database_export::{
|
||||
build_export_insert_statements, is_export_cancelled, BuildExportInsertStatementsOptions, SqlInsertMode,
|
||||
build_export_insert_statements_excluding, is_export_cancelled, BuildExportInsertStatementsOptions, SqlInsertMode,
|
||||
};
|
||||
use crate::models::connection::DatabaseType;
|
||||
use crate::query::{
|
||||
@@ -120,6 +120,13 @@ pub struct QueryResultExportRequest {
|
||||
pub auto_filter: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub identifier_quote: Option<String>,
|
||||
/// 导出 SQL 时是否排除主键列(对应前端数据提取设置里的“排除主键”)。
|
||||
#[serde(default)]
|
||||
pub exclude_primary_keys: bool,
|
||||
/// 结果集对应的原表主键列名。查询结果导出由前端从表元数据带过来;
|
||||
/// 没有表元数据(例如自由 SQL 查询)时为空,此时无法排除主键。
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub primary_keys: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct StagedExportTarget {
|
||||
@@ -322,6 +329,8 @@ struct SqlInsertWriter {
|
||||
schema: Option<String>,
|
||||
table_name: String,
|
||||
identifier_quote: Option<String>,
|
||||
/// 生成 INSERT 时需要排除的列名,来自请求里的“不含主键”设置。
|
||||
exclude_columns: Vec<String>,
|
||||
}
|
||||
|
||||
/// Streams query-result JSON rows without retaining the complete result set.
|
||||
@@ -408,6 +417,7 @@ impl SqlInsertWriter {
|
||||
schema: request.schema.clone(),
|
||||
table_name,
|
||||
identifier_quote: request.identifier_quote.clone(),
|
||||
exclude_columns: if request.exclude_primary_keys { request.primary_keys.clone() } else { Vec::new() },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -439,20 +449,23 @@ impl SqlInsertWriter {
|
||||
if self.pending_rows.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let stmts = build_export_insert_statements(BuildExportInsertStatementsOptions {
|
||||
database_type: Some(self.database_type),
|
||||
identifier_quote: self.identifier_quote.clone(),
|
||||
schema: self.schema.clone(),
|
||||
table_name: Some(self.table_name.clone()),
|
||||
qualified_table_name: None,
|
||||
columns: self.columns.clone(),
|
||||
column_types: self.column_types.clone(),
|
||||
column_extras: Vec::new(),
|
||||
spatial_columns: self.spatial_columns.clone(),
|
||||
spatial_values: mem::take(&mut self.pending_spatial_values),
|
||||
rows: mem::take(&mut self.pending_rows),
|
||||
batch_size: Some(self.insert_mode.batch_size(SQL_INSERT_BATCH_SIZE)),
|
||||
})?;
|
||||
let stmts = build_export_insert_statements_excluding(
|
||||
BuildExportInsertStatementsOptions {
|
||||
database_type: Some(self.database_type),
|
||||
identifier_quote: self.identifier_quote.clone(),
|
||||
schema: self.schema.clone(),
|
||||
table_name: Some(self.table_name.clone()),
|
||||
qualified_table_name: None,
|
||||
columns: self.columns.clone(),
|
||||
column_types: self.column_types.clone(),
|
||||
column_extras: Vec::new(),
|
||||
spatial_columns: self.spatial_columns.clone(),
|
||||
spatial_values: mem::take(&mut self.pending_spatial_values),
|
||||
rows: mem::take(&mut self.pending_rows),
|
||||
batch_size: Some(self.insert_mode.batch_size(SQL_INSERT_BATCH_SIZE)),
|
||||
},
|
||||
&self.exclude_columns,
|
||||
)?;
|
||||
let file = self.file.as_mut().ok_or_else(|| "SQL export file already closed".to_string())?;
|
||||
for stmt in &stmts {
|
||||
writeln!(file, "{stmt}").map_err(|e| format!("Failed to write SQL: {e}"))?;
|
||||
@@ -1965,6 +1978,7 @@ async fn try_export_sqlserver_query_result_stream(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn staged_export_target_preserves_existing_destination_on_discard_and_replace_failure() {
|
||||
@@ -2010,6 +2024,60 @@ mod tests {
|
||||
assert!(!stream_export_was_cancelled("network failure", false, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_insert_writer_omits_excluded_primary_key_columns() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let file_path = dir.path().join("users.sql");
|
||||
let request = QueryResultExportRequest {
|
||||
export_id: "export-1".to_string(),
|
||||
connection_id: "conn-1".to_string(),
|
||||
database: "db".to_string(),
|
||||
schema: Some("public".to_string()),
|
||||
catalog: None,
|
||||
sql: "SELECT * FROM users".to_string(),
|
||||
query_base_sql: "SELECT * FROM users".to_string(),
|
||||
setup_sql: Vec::new(),
|
||||
database_type: DatabaseType::Postgres,
|
||||
use_agent_cursor: false,
|
||||
file_path: file_path.to_string_lossy().to_string(),
|
||||
format: "sql".to_string(),
|
||||
include_sql_sheet: false,
|
||||
page_size: 1000,
|
||||
row_limit: None,
|
||||
total_rows: None,
|
||||
timeout_secs: None,
|
||||
keyset_optimization_enabled: false,
|
||||
client_session_id: None,
|
||||
execution_id: None,
|
||||
date_time_format: None,
|
||||
export_table_name: Some("users".to_string()),
|
||||
export_column_types: None,
|
||||
numeric_column_right_align: false,
|
||||
column_comments: None,
|
||||
auto_filter: None,
|
||||
identifier_quote: None,
|
||||
insert_mode: Default::default(),
|
||||
csv_quote_mode: Default::default(),
|
||||
exclude_primary_keys: true,
|
||||
primary_keys: vec!["id".to_string()],
|
||||
};
|
||||
|
||||
let mut writer = SqlInsertWriter::create(&request).expect("create sql insert writer");
|
||||
writer.set_columns(
|
||||
vec!["id".to_string(), "name".to_string()],
|
||||
&["integer".to_string(), "text".to_string()],
|
||||
&[],
|
||||
&request,
|
||||
);
|
||||
writer.write_row(vec![json!(1), json!("Ada")], None).expect("write export row");
|
||||
writer.finish().expect("finish export");
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&file_path).expect("read sql export"),
|
||||
"INSERT INTO \"public\".\"users\" (\"name\") VALUES ('Ada');\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_temp_setup_accepts_only_session_local_table_operations() {
|
||||
let safe = vec![
|
||||
@@ -2070,6 +2138,8 @@ mod tests {
|
||||
column_comments: None,
|
||||
auto_filter: None,
|
||||
identifier_quote: None,
|
||||
exclude_primary_keys: false,
|
||||
primary_keys: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,11 @@ use crate::csv_export::{
|
||||
format_csv_with_quote_mode, format_tsv, push_table_csv_row, push_table_csv_row_with_quote_mode, push_tsv_row,
|
||||
CsvQuoteMode,
|
||||
};
|
||||
use crate::data_grid_sql::extra_is_auto_generated;
|
||||
pub use crate::database_export::ExportStatus;
|
||||
use crate::database_export::{
|
||||
build_export_insert_statements, is_export_cancelled, is_internal_export_column, BuildExportInsertStatementsOptions,
|
||||
SqlInsertMode,
|
||||
build_export_insert_statements_excluding, is_export_cancelled, is_internal_export_column,
|
||||
BuildExportInsertStatementsOptions, SqlInsertMode,
|
||||
};
|
||||
use crate::db::agent_driver::AgentTableReadStartParams;
|
||||
use crate::models::connection::DatabaseType;
|
||||
@@ -42,6 +43,31 @@ pub fn table_export_client_session_id(export_id: &str) -> String {
|
||||
task_client_session_id("table-export", export_id)
|
||||
}
|
||||
|
||||
/// SQL 导出时需要排除的列名:用户选择了“不含主键”时,与 copy-as-INSERT
|
||||
/// 一致只剔除自增/identity 主键;手动赋值的主键保留,否则回放 INSERT 缺值。
|
||||
fn sql_export_excluded_columns(
|
||||
request: &TableExportRequest,
|
||||
primary_keys: &[String],
|
||||
col_names: &[String],
|
||||
column_extras: &[Option<String>],
|
||||
) -> Vec<String> {
|
||||
if !request.exclude_primary_keys {
|
||||
return Vec::new();
|
||||
}
|
||||
primary_keys
|
||||
.iter()
|
||||
.filter(|pk| {
|
||||
col_names
|
||||
.iter()
|
||||
.position(|name| name.eq_ignore_ascii_case(pk))
|
||||
.and_then(|index| column_extras.get(index))
|
||||
.and_then(|extra| extra.as_deref())
|
||||
.is_some_and(extra_is_auto_generated)
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableExportRequest {
|
||||
@@ -65,6 +91,9 @@ pub struct TableExportRequest {
|
||||
pub column_types: Option<Vec<Option<String>>>,
|
||||
#[serde(default)]
|
||||
pub primary_keys: Option<Vec<String>>,
|
||||
/// 导出 SQL 时是否排除主键列(对应前端数据提取设置里的“排除主键”)。
|
||||
#[serde(default)]
|
||||
pub exclude_primary_keys: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub where_input: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -1314,20 +1343,23 @@ async fn try_export_native_table_stream(
|
||||
if pending_rows.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let statements = build_export_insert_statements(BuildExportInsertStatementsOptions {
|
||||
database_type: Some(*db_type),
|
||||
identifier_quote: request.identifier_quote.clone(),
|
||||
schema: request.schema.clone(),
|
||||
table_name: Some(request.table_name.clone()),
|
||||
qualified_table_name: None,
|
||||
columns: col_names.to_vec(),
|
||||
column_types: column_types.to_vec(),
|
||||
column_extras: column_extras.to_vec(),
|
||||
spatial_columns: Vec::new(),
|
||||
spatial_values: Vec::new(),
|
||||
rows: std::mem::take(pending_rows),
|
||||
batch_size: Some(request.insert_mode.batch_size(SQL_INSERT_BATCH_SIZE)),
|
||||
})?;
|
||||
let statements = build_export_insert_statements_excluding(
|
||||
BuildExportInsertStatementsOptions {
|
||||
database_type: Some(*db_type),
|
||||
identifier_quote: request.identifier_quote.clone(),
|
||||
schema: request.schema.clone(),
|
||||
table_name: Some(request.table_name.clone()),
|
||||
qualified_table_name: None,
|
||||
columns: col_names.to_vec(),
|
||||
column_types: column_types.to_vec(),
|
||||
column_extras: column_extras.to_vec(),
|
||||
spatial_columns: Vec::new(),
|
||||
spatial_values: Vec::new(),
|
||||
rows: std::mem::take(pending_rows),
|
||||
batch_size: Some(request.insert_mode.batch_size(SQL_INSERT_BATCH_SIZE)),
|
||||
},
|
||||
&sql_export_excluded_columns(request, primary_keys, col_names, column_extras),
|
||||
)?;
|
||||
if !statements.is_empty() {
|
||||
if wrote_statements {
|
||||
file.write_all(b"\n").map_err(|e| format!("Failed to write SQL: {e}"))?;
|
||||
@@ -2152,20 +2184,23 @@ async fn export_table_data_core_inner(
|
||||
break;
|
||||
}
|
||||
|
||||
let statements = build_export_insert_statements(BuildExportInsertStatementsOptions {
|
||||
database_type: Some(db_type),
|
||||
identifier_quote: request.identifier_quote.clone(),
|
||||
schema: request.schema.clone(),
|
||||
table_name: Some(request.table_name.clone()),
|
||||
qualified_table_name: None,
|
||||
columns: col_names.clone(),
|
||||
column_types: column_types.clone(),
|
||||
column_extras: column_extras.clone(),
|
||||
spatial_columns: result.spatial_columns.clone(),
|
||||
spatial_values: result.spatial_values.clone(),
|
||||
rows: result.rows.clone(),
|
||||
batch_size: Some(request.insert_mode.batch_size(SQL_INSERT_BATCH_SIZE)),
|
||||
})?;
|
||||
let statements = build_export_insert_statements_excluding(
|
||||
BuildExportInsertStatementsOptions {
|
||||
database_type: Some(db_type),
|
||||
identifier_quote: request.identifier_quote.clone(),
|
||||
schema: request.schema.clone(),
|
||||
table_name: Some(request.table_name.clone()),
|
||||
qualified_table_name: None,
|
||||
columns: col_names.clone(),
|
||||
column_types: column_types.clone(),
|
||||
column_extras: column_extras.clone(),
|
||||
spatial_columns: result.spatial_columns.clone(),
|
||||
spatial_values: result.spatial_values.clone(),
|
||||
rows: result.rows.clone(),
|
||||
batch_size: Some(request.insert_mode.batch_size(SQL_INSERT_BATCH_SIZE)),
|
||||
},
|
||||
&sql_export_excluded_columns(request, &primary_keys, &col_names, &column_extras),
|
||||
)?;
|
||||
if !statements.is_empty() {
|
||||
if wrote_statements {
|
||||
file.write_all(b"\n").map_err(|e| format!("Failed to write SQL: {e}"))?;
|
||||
@@ -2226,7 +2261,7 @@ async fn export_table_data_core_inner(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::database_export::{clear_export_cancelled, set_export_cancelled};
|
||||
use crate::database_export::{build_export_insert_statements, clear_export_cancelled, set_export_cancelled};
|
||||
use crate::models::connection::ConnectionConfig;
|
||||
#[cfg(unix)]
|
||||
use crate::plugins::{
|
||||
@@ -2351,6 +2386,7 @@ mod tests {
|
||||
columns: Some(vec!["id".to_string(), "name".to_string()]),
|
||||
column_types: Some(vec![Some("INTEGER".to_string()), Some("VARCHAR".to_string())]),
|
||||
primary_keys: Some(vec!["id".to_string()]),
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count,
|
||||
@@ -2512,6 +2548,7 @@ mod tests {
|
||||
format: "csv".to_string(),
|
||||
insert_mode: Default::default(),
|
||||
csv_quote_mode: CsvQuoteMode::All,
|
||||
exclude_primary_keys: false,
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
@@ -2680,6 +2717,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: Some("WHERE temperature > 1".to_string()),
|
||||
order_by: Some("Time DESC".to_string()),
|
||||
skip_count: true,
|
||||
@@ -2739,6 +2777,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: true,
|
||||
@@ -2776,6 +2815,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: true,
|
||||
@@ -2808,6 +2848,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: true,
|
||||
@@ -2848,6 +2889,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: true,
|
||||
@@ -2900,6 +2942,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: Some("WHERE status = 'active'".to_string()),
|
||||
order_by: None,
|
||||
skip_count: false,
|
||||
@@ -2946,6 +2989,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: false,
|
||||
@@ -3020,6 +3064,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: false,
|
||||
@@ -3076,6 +3121,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: true,
|
||||
@@ -3134,6 +3180,7 @@ mod tests {
|
||||
columns: None,
|
||||
column_types: None,
|
||||
primary_keys: None,
|
||||
exclude_primary_keys: false,
|
||||
where_input: None,
|
||||
order_by: None,
|
||||
skip_count: false,
|
||||
|
||||
Reference in new issue
Block a user