fix(informix): 支持表格直接编辑
This commit is contained in:
parent
ce69af1de0
commit
05effc8edf
|
|
@ -227,13 +227,7 @@ impl AppState {
|
|||
client
|
||||
.call::<serde_json::Value>(
|
||||
"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();
|
||||
|
|
|
|||
|
|
@ -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<AppState>>, 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<AppState>>, config: ConnectionConfi
|
|||
client
|
||||
.call::<serde_json::Value>(
|
||||
"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)))
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
<Input
|
||||
v-model="form.url_params"
|
||||
class="col-span-3"
|
||||
:placeholder="form.db_type === 'mysql' ? 'charset=utf8mb4' : 'sslmode=disable'"
|
||||
:placeholder="
|
||||
form.db_type === 'mysql'
|
||||
? 'charset=utf8mb4'
|
||||
: form.db_type === 'informix'
|
||||
? 'INFORMIXSERVER=informix;CLIENT_LOCALE=en_US.utf8;DB_LOCALE=en_US.utf8'
|
||||
: 'sslmode=disable'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ export const SCHEMA_AWARE_TYPES = new Set<DatabaseType>([
|
|||
"snowflake",
|
||||
"trino",
|
||||
"db2",
|
||||
"informix",
|
||||
]);
|
||||
|
||||
export const SQL_FILE_UNSUPPORTED_TYPES = new Set<DatabaseType>(["redis", "mongodb", "elasticsearch"]);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,15 @@ const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<Databa
|
|||
transaction: false,
|
||||
},
|
||||
},
|
||||
informix: {
|
||||
tableData: {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: true,
|
||||
deleteRequiresPrimaryKey: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
},
|
||||
},
|
||||
neo4j: {
|
||||
syntheticKey: "neo4j-element-id",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export interface BuildTableSelectSqlOptions {
|
|||
|
||||
export function quoteTableIdentifier(databaseType: DatabaseType | undefined, name: string): string {
|
||||
if (databaseType === "mysql" || databaseType === "hive") return `\`${name.replace(/`/g, "``")}\``;
|
||||
if (databaseType === "informix" && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(name)) return name;
|
||||
if (databaseType === "neo4j") return quoteCypherIdentifier(name);
|
||||
if (databaseType === "sqlserver") return `[${name.replace(/\]/g, "]]")}]`;
|
||||
return `"${name.replace(/"/g, '""')}"`;
|
||||
|
|
|
|||
|
|
@ -98,6 +98,28 @@ test("builds Trino insert statements with schema-qualified identifiers", () => {
|
|||
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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue