fix(import): route oracle jdbc imports through insert all

Closes #8309
This commit is contained in:
t8y2
2026-09-08 01:53:50 +08:00
parent d2de504248
commit 4f2c67e34e
2 changed files with 123 additions and 0 deletions
+63
View File
@@ -11096,6 +11096,69 @@ mod tests {
);
}
#[tokio::test]
async fn oracle_jdbc_import_maps_xls_rows_to_insert_all() {
let dir = tempfile::tempdir().unwrap();
let storage = Storage::open(&dir.path().join("storage.db")).await.unwrap();
let state = AppState::new(storage);
let connection_id = "oracle-jdbc-import";
let config: ConnectionConfig = serde_json::from_value(serde_json::json!({
"id": connection_id,
"name": "Oracle over JDBC",
"db_type": "jdbc",
"host": "",
"port": 0,
"username": "",
"password": "",
"connection_string": "jdbc:oracle:thin:@localhost:1521:ORCL",
"jdbc_driver_class": "oracle.jdbc.driver.OracleDriver"
}))
.unwrap();
state.configs.write().await.insert(connection_id.to_string(), config);
let db_type = crate::transfer::get_db_type(&state, connection_id).await.unwrap();
assert_eq!(db_type, DatabaseType::Oracle);
let mappings = vec![
TableImportColumnMapping {
source_column: "id".to_string(),
target_column: "id".to_string(),
target_data_type: None,
},
TableImportColumnMapping {
source_column: "name".to_string(),
target_column: "name".to_string(),
target_data_type: None,
},
];
let data = ParsedImportFile {
columns: vec!["id".to_string(), "name".to_string()],
rows: vec![
vec![serde_json::json!(1), serde_json::json!("Ada")],
vec![serde_json::json!(2), serde_json::json!("Grace")],
],
total_rows: 2,
effective_encoding: None,
};
let batches = build_import_insert_batches(
&data,
&mappings,
&[("id".to_string(), "NUMBER".to_string()), ("name".to_string(), "VARCHAR2(64)".to_string())],
"events",
"APP",
&db_type,
500,
)
.unwrap();
assert_eq!(batches.len(), 1);
assert_eq!(batches[0].row_count, 2);
assert!(batches[0].sql.starts_with("INSERT ALL\nINTO "));
assert!(batches[0].sql.ends_with("SELECT 1 FROM dual"));
assert!(!batches[0].sql.contains("),\n("));
}
fn kingbase_date_import_sql(oracle_mode: bool) -> String {
let mappings = vec![TableImportColumnMapping {
source_column: "created_at".to_string(),
+60
View File
@@ -5349,6 +5349,16 @@ fn effective_transfer_database_type(config: &ConnectionConfig) -> DatabaseType {
return DatabaseType::Jdbc;
}
// Oracle behind a generic JDBC connection: Oracle rejects MySQL-style
// multi-row VALUES lists with ORA-00933, so reuse the native Oracle
// transfer dialect (INSERT ALL, TO_DATE literals, row-capped batches).
let is_oracle =
config.connection_string.as_deref().is_some_and(|url| url.to_ascii_lowercase().contains("jdbc:oracle:"))
|| config.jdbc_driver_class.as_deref().is_some_and(|class| class.to_ascii_lowercase().contains("oracle"));
if is_oracle {
return DatabaseType::Oracle;
}
let jdbc_identity = [
config.driver_profile.as_deref().unwrap_or(""),
config.connection_string.as_deref().unwrap_or(""),
@@ -12896,6 +12906,56 @@ SELECT 1 FROM dual"#
assert!(statements[0].ends_with("SELECT 1 FROM dual"));
}
#[test]
fn oracle_jdbc_url_routes_multi_row_insert_through_insert_all() {
let config =
jdbc_transfer_config("jdbc:oracle:thin:@localhost:1521:ORCL", "oracle.jdbc.driver.OracleDriver", "");
assert_eq!(effective_transfer_database_type(&config), DatabaseType::Oracle);
// Mixed-case URL and driver class alone must be recognized too.
assert_eq!(
effective_transfer_database_type(&jdbc_transfer_config("JDBC:Oracle:thin:@localhost:1521:ORCL", "", "")),
DatabaseType::Oracle
);
assert_eq!(
effective_transfer_database_type(&jdbc_transfer_config("", "oracle.jdbc.OracleDriver", "")),
DatabaseType::Oracle
);
let sql = generate_insert_typed(
&[String::from("id"), String::from("name")],
&[Some(String::from("number")), Some(String::from("varchar2(64)"))],
&[vec![json!(1), json!("Ada")], vec![json!(2), json!("Grace")]],
"INSTR_CATEGORY",
"APP",
&effective_transfer_database_type(&config),
None,
);
assert!(sql.starts_with("INSERT ALL\nINTO "));
assert!(sql.ends_with("SELECT 1 FROM dual"));
assert!(!sql.contains("),\n("));
}
#[test]
fn non_oracle_jdbc_url_keeps_multi_row_values_insert() {
let config = jdbc_transfer_config("jdbc:mysql://localhost:3306/dbx_test", "com.mysql.cj.jdbc.Driver", "");
assert_eq!(effective_transfer_database_type(&config), DatabaseType::Jdbc);
let sql = generate_insert_typed(
&[String::from("id"), String::from("name")],
&[Some(String::from("int")), Some(String::from("varchar(64)"))],
&[vec![json!(1), json!("Ada")], vec![json!(2), json!("Grace")]],
"instr_category",
"",
&effective_transfer_database_type(&config),
None,
);
assert!(sql.starts_with("INSERT INTO "));
assert!(sql.contains("),\n("));
assert!(!sql.contains("INSERT ALL"));
}
#[test]
fn transfer_write_sql_batches_split_large_insert_statements() {
let rows = (0..4).map(|index| vec![json!(index), json!("x".repeat(180 * 1024))]).collect::<Vec<_>>();