diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index c81fbd84e..0bfa7f8f5 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -227,13 +227,7 @@ impl AppState { client .call::( "connect", - serde_json::json!({ - "host": host, - "port": port, - "database": db_config.effective_database().unwrap_or(""), - "username": db_config.username, - "password": db_config.password, - }), + agent_connect_params(&db_config, &host, port, db_config.effective_database().unwrap_or("")), ) .await?; PoolKind::Agent(Arc::new(tokio::sync::Mutex::new(client))) @@ -387,6 +381,17 @@ pub fn redacted_connection_url_for_endpoint(config: &ConnectionConfig, host: &st } } +pub fn agent_connect_params(config: &ConnectionConfig, host: &str, port: u16, database: &str) -> serde_json::Value { + serde_json::json!({ + "host": host, + "port": port, + "database": database, + "username": config.username, + "password": config.password, + "url_params": config.url_params.as_deref().unwrap_or(""), + }) +} + pub async fn probe_connection_endpoint(config: &ConnectionConfig, host: &str, port: u16) -> Result<(), String> { if config.db_type == DatabaseType::MongoDb && config.connection_string.as_deref().is_some_and(|value| !value.is_empty()) @@ -415,7 +420,7 @@ async fn detect_ob_oracle_mode(config: &ConnectionConfig, pool: &sqlx::mysql::My #[cfg(test)] mod tests { - use super::{database_connection_config, metadata_connection_config, AppState}; + use super::{agent_connect_params, database_connection_config, metadata_connection_config, AppState}; use crate::models::connection::{ConnectionConfig, DatabaseType, ProxyType}; use crate::schema; use crate::storage::Storage; @@ -458,6 +463,23 @@ mod tests { } } + #[test] + fn agent_connect_params_include_url_params() { + let mut config = mysql_config(Some("testdb")); + config.username = "informix".to_string(); + config.password = "in4mix".to_string(); + config.url_params = Some("INFORMIXSERVER=informix;CLIENT_LOCALE=en_US.utf8".to_string()); + + let params = agent_connect_params(&config, "172.26.128.159", 20013, "testdb"); + + assert_eq!(params["host"], "172.26.128.159"); + assert_eq!(params["port"], 20013); + assert_eq!(params["database"], "testdb"); + assert_eq!(params["username"], "informix"); + assert_eq!(params["password"], "in4mix"); + assert_eq!(params["url_params"], "INFORMIXSERVER=informix;CLIENT_LOCALE=en_US.utf8"); + } + async fn test_app_state() -> (AppState, std::path::PathBuf) { let dir = std::env::temp_dir().join(format!("dbx-core-test-{}", uuid::Uuid::new_v4())); std::fs::create_dir_all(&dir).unwrap(); diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index 1dc79816f..8f8bfb005 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use tauri::State; pub use dbx_core::connection::{ - connection_url_for_endpoint, expand_tilde, metadata_connection_config, probe_connection_endpoint, - redacted_connection_url_for_endpoint, AppState, MysqlMode, PoolKind, + agent_connect_params, connection_url_for_endpoint, expand_tilde, metadata_connection_config, + probe_connection_endpoint, redacted_connection_url_for_endpoint, AppState, MysqlMode, PoolKind, }; use dbx_core::database_capabilities; use dbx_core::db; @@ -114,13 +114,7 @@ pub async fn test_connection(state: State<'_, Arc>, config: Connection &config.db_type, config.driver_profile.as_deref(), "test_connection", - serde_json::json!({ - "host": host, - "port": port, - "database": config.database.as_deref().unwrap_or(""), - "username": config.username, - "password": config.password, - }), + agent_connect_params(&config, &host, port, config.database.as_deref().unwrap_or("")), ) .await?; Ok("Connection successful".to_string()) @@ -213,13 +207,7 @@ pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfi client .call::( "connect", - serde_json::json!({ - "host": host, - "port": port, - "database": db_config.effective_database().unwrap_or(""), - "username": db_config.username, - "password": db_config.password, - }), + agent_connect_params(&db_config, &host, port, db_config.effective_database().unwrap_or("")), ) .await?; PoolKind::Agent(std::sync::Arc::new(tokio::sync::Mutex::new(client))) diff --git a/src/components/connection/ConnectionDialog.vue b/src/components/connection/ConnectionDialog.vue index 1f568d517..7c8d12ccc 100644 --- a/src/components/connection/ConnectionDialog.vue +++ b/src/components/connection/ConnectionDialog.vue @@ -1166,6 +1166,7 @@ function openExternalUrl(url: string) { form.db_type === 'mysql' || form.db_type === 'postgres' || form.db_type === 'redshift' || + form.db_type === 'informix' || form.db_type === 'kingbase' || form.db_type === 'vastbase' || form.db_type === 'goldendb' @@ -1176,7 +1177,13 @@ function openExternalUrl(url: string) { diff --git a/src/lib/databaseCapabilitySets.ts b/src/lib/databaseCapabilitySets.ts index 3e77c5749..a98543724 100644 --- a/src/lib/databaseCapabilitySets.ts +++ b/src/lib/databaseCapabilitySets.ts @@ -14,7 +14,6 @@ export const SCHEMA_AWARE_TYPES = new Set([ "snowflake", "trino", "db2", - "informix", ]); export const SQL_FILE_UNSUPPORTED_TYPES = new Set(["redis", "mongodb", "elasticsearch"]); diff --git a/src/lib/databaseTableDataCapabilities.ts b/src/lib/databaseTableDataCapabilities.ts index ec22ec2ac..9e91c919c 100644 --- a/src/lib/databaseTableDataCapabilities.ts +++ b/src/lib/databaseTableDataCapabilities.ts @@ -42,6 +42,15 @@ const DATABASE_CAPABILITY_OVERRIDES: Partial { assert.deepEqual(statements, ['INSERT INTO "tiny"."nation" ("nationkey", "name") VALUES (100, \'Atlantis\');']); }); +test("builds Informix grid save statements without delimited identifiers", () => { + const statements = buildDataGridSaveStatements({ + databaseType: "informix", + tableMeta: { + schema: "testdb", + tableName: "dbx_grid_edit_probe", + primaryKeys: ["id"], + }, + columns: ["id", "name"], + rows: [[1, "before"]], + dirtyRows: [[0, [[1, "after"]]]], + deletedRows: [0], + newRows: [[2, "new"]], + }); + + assert.deepEqual(statements, [ + "UPDATE dbx_grid_edit_probe SET name = 'after' WHERE id = 1;", + "DELETE FROM dbx_grid_edit_probe WHERE id = 1;", + "INSERT INTO dbx_grid_edit_probe (id, name) VALUES (2, 'new');", + ]); +}); + test("uses Oracle ROWID as a synthetic key without writing it as a normal column", () => { const statements = buildDataGridSaveStatements({ databaseType: "oracle", diff --git a/tests/tableEditing.test.ts b/tests/tableEditing.test.ts index f7975b27d..6f456c528 100644 --- a/tests/tableEditing.test.ts +++ b/tests/tableEditing.test.ts @@ -39,6 +39,7 @@ test("does not synthesize ROWID for non-Oracle keyless tables", () => { test("allows Hive table data editing even without declared primary keys", () => { assert.equal(isTableDataEditable("hive", []), true); assert.equal(isTableDataEditable("trino", []), true); + assert.equal(isTableDataEditable("informix", []), true); assert.equal(isTableDataEditable("mysql", []), false); assert.equal(isTableDataEditable("postgres", ["id"]), true); }); @@ -55,6 +56,8 @@ test("allows existing row edits for Hive only when the table is transactional", assert.equal(canEditExistingTableRows("hive", undefined), false); assert.equal(canEditExistingTableRows("trino", undefined, []), false); assert.equal(canEditExistingTableRows("trino", undefined, ["id"]), true); + assert.equal(canEditExistingTableRows("informix", undefined, []), false); + assert.equal(canEditExistingTableRows("informix", undefined, ["id"]), true); assert.equal(canEditExistingTableRows("postgres", undefined), true); }); diff --git a/tests/tableSelectSql.test.ts b/tests/tableSelectSql.test.ts index 6ca3ae158..217b6b356 100644 --- a/tests/tableSelectSql.test.ts +++ b/tests/tableSelectSql.test.ts @@ -50,6 +50,18 @@ test("builds Hive table data queries with backtick identifiers", () => { assert.equal(sql, "SELECT * FROM `departments` ORDER BY `dept id` ASC LIMIT 100;"); }); +test("builds Informix table data queries without database-qualified delimited identifiers", () => { + const sql = buildTableSelectSql({ + databaseType: "informix", + schema: "testdb", + tableName: "dbx_grid_edit_probe", + primaryKeys: ["id"], + limit: 100, + }); + + assert.equal(sql, "SELECT * FROM dbx_grid_edit_probe ORDER BY id ASC LIMIT 100;"); +}); + test("expands Hive table data queries into aliased table columns", () => { const sql = buildTableSelectSql({ databaseType: "hive",