diff --git a/.gitignore b/.gitignore index aee837885..2daf81360 100644 --- a/.gitignore +++ b/.gitignore @@ -52,8 +52,6 @@ tmp/ # Logs *.log -# Agent JDBC driver JARs -agents/*/libs/*.jar # Generated changelog data releases-*.json diff --git a/agents/.gitignore b/agents/.gitignore index f797c2c63..e1afb2aa4 100644 --- a/agents/.gitignore +++ b/agents/.gitignore @@ -1,4 +1,5 @@ -*/libs/*.jar +**/libs/*.jar +!drivers/oscar/libs/oscarJDBC8.jar gradle.properties build/ */bin/ diff --git a/agents/drivers/oscar/build.gradle b/agents/drivers/oscar/build.gradle new file mode 100644 index 000000000..88d88c8d8 --- /dev/null +++ b/agents/drivers/oscar/build.gradle @@ -0,0 +1,12 @@ +dependencies { + implementation files('libs/oscarJDBC8.jar') +} + +tasks.named('shadowJar') { + manifest { + attributes( + 'Agent-Label': '\u795e\u901a OSCAR', + 'Main-Class': 'com.dbx.agent.oscar.OscarAgent' + ) + } +} diff --git a/agents/drivers/oscar/libs/oscarJDBC8.jar b/agents/drivers/oscar/libs/oscarJDBC8.jar new file mode 100644 index 000000000..7d1a6c4e1 Binary files /dev/null and b/agents/drivers/oscar/libs/oscarJDBC8.jar differ diff --git a/agents/drivers/oscar/src/main/java/com/dbx/agent/oscar/OscarAgent.java b/agents/drivers/oscar/src/main/java/com/dbx/agent/oscar/OscarAgent.java new file mode 100644 index 000000000..ea247c817 --- /dev/null +++ b/agents/drivers/oscar/src/main/java/com/dbx/agent/oscar/OscarAgent.java @@ -0,0 +1,21 @@ +package com.dbx.agent.oscar; + +import com.dbx.agent.ConfiguredJdbcAgent; +import com.dbx.agent.JdbcAgentProfile; +import com.dbx.agent.JsonRpcServer; + +public final class OscarAgent extends ConfiguredJdbcAgent { + public static final JdbcAgentProfile OSCAR_PROFILE = new JdbcAgentProfile( + "com.oscar.Driver", + "jdbc:oscar://{host}:{port}/{database}", + 2003 + ); + + public OscarAgent() { + super(OSCAR_PROFILE); + } + + public static void main(String[] args) { + new JsonRpcServer(new OscarAgent()).run(); + } +} diff --git a/agents/drivers/oscar/src/test/java/com/dbx/agent/oscar/OscarAgentTest.java b/agents/drivers/oscar/src/test/java/com/dbx/agent/oscar/OscarAgentTest.java new file mode 100644 index 000000000..953e99134 --- /dev/null +++ b/agents/drivers/oscar/src/test/java/com/dbx/agent/oscar/OscarAgentTest.java @@ -0,0 +1,16 @@ +package com.dbx.agent.oscar; + +import com.dbx.agent.JdbcAgentProfile; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class OscarAgentTest { + @Test + void declaresOscarJdbcProfile() { + JdbcAgentProfile profile = OscarAgent.OSCAR_PROFILE; + + Assertions.assertEquals("com.oscar.Driver", profile.getDriverClass()); + Assertions.assertEquals("jdbc:oscar://{host}:{port}/{database}", profile.getUrlTemplate()); + Assertions.assertEquals(2003, profile.getDefaultPort()); + } +} diff --git a/agents/settings.gradle b/agents/settings.gradle index 38a228596..5d0be1832 100644 --- a/agents/settings.gradle +++ b/agents/settings.gradle @@ -5,7 +5,7 @@ def driverModules = [ 'access', 'dameng', 'kingbase', 'vastbase', 'goldendb', 'databend', 'databricks', 'saphana', 'teradata', 'vertica', 'firebird', 'exasol', 'oceanbase-oracle', 'gbase8a', 'gbase8s', 'bigquery', 'kylin', 'sundb', 'h2', 'snowflake', 'trino', 'hive', - 'db2', 'informix', 'neo4j', 'cassandra', 'mongodb', 'highgo', 'tdengine', 'yashandb', + 'db2', 'informix', 'neo4j', 'cassandra', 'mongodb', 'highgo', 'tdengine', 'yashandb', 'oscar', 'iris', 'iotdb', 'etcd', 'zookeeper', 'kafka' ] diff --git a/agents/versions.json b/agents/versions.json index b4b9b2b7e..7e3dccd35 100644 --- a/agents/versions.json +++ b/agents/versions.json @@ -30,6 +30,7 @@ "mongodb": "0.1.24", "highgo": "0.1.23", "yashandb": "0.1.21", + "oscar": "0.1.0", "xugu": "0.1.16", "iris": "0.1.16", "iotdb": "0.1.13", diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 80e6e46d8..a2322339e 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -570,6 +570,7 @@ const driverProfiles: Record< }, kylin: { type: "kylin", port: 7070, user: "ADMIN", label: "Apache Kylin", icon: "kylin" }, sundb: { type: "sundb", port: 22000, user: "root", label: "SunDB", icon: "sundb" }, + oscar: { type: "oscar", port: 2003, user: "SYSDBA", label: "神通 OSCAR", icon: "oscar" }, jdbc: { type: "jdbc", port: 0, user: "", label: "JDBC", icon: "jdbc" }, tdengine: { type: "tdengine", port: 6041, user: "root", label: "TDengine", icon: "tdengine" }, xugu: { type: "xugu", port: 5138, user: "", label: "虚谷 XuguDB", icon: "xugu" }, @@ -1360,6 +1361,7 @@ const iconTypeMap: Record = { bigquery: "bigquery", kylin: "kylin", sundb: "sundb", + oscar: "oscar", influxdb: "influxdb", jdbc: "jdbc", custom_mysql: "mysql", @@ -1426,6 +1428,7 @@ const dbOptions: DbOption[] = [ { value: "bigquery", label: "BigQuery" }, { value: "kylin", label: "Kylin" }, { value: "sundb", label: "SunDB" }, + { value: "oscar", label: "神通 OSCAR" }, { value: "xugu", label: "虚谷 XuguDB" }, { value: "iotdb", label: "Apache IoTDB" }, { value: "etcd", label: "etcd" }, diff --git a/apps/desktop/src/components/icons/DatabaseIcon.vue b/apps/desktop/src/components/icons/DatabaseIcon.vue index 34d303b03..1d54d319a 100644 --- a/apps/desktop/src/components/icons/DatabaseIcon.vue +++ b/apps/desktop/src/components/icons/DatabaseIcon.vue @@ -92,7 +92,9 @@ const assetIcons: Record = { zookeeper: "zookeeper", }; -const letterIcons: Record = {}; +const letterIcons: Record = { + oscar: { letter: "O", color: "#2563eb" }, +}; const normalizedType = computed(() => props.dbType.toLowerCase().replace(/[\s-]+/g, "_")); const assetName = computed(() => assetIcons[normalizedType.value]); diff --git a/apps/desktop/src/lib/__tests__/connectionAttemptTimeout.spec.ts b/apps/desktop/src/lib/__tests__/connectionAttemptTimeout.spec.ts index 88ce1f546..1a8bebe35 100644 --- a/apps/desktop/src/lib/__tests__/connectionAttemptTimeout.spec.ts +++ b/apps/desktop/src/lib/__tests__/connectionAttemptTimeout.spec.ts @@ -18,6 +18,10 @@ describe("connectionAttemptTimeout", () => { expect(connectionAttemptTimeoutMs({ db_type: "prestosql", connect_timeout_secs: 5, transport_layers: [] })).toBe(AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS * 1000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS); }); + it("uses the startup floor for generic JDBC plugin connections", () => { + expect(connectionAttemptTimeoutMs({ db_type: "jdbc", connect_timeout_secs: 5, transport_layers: [] })).toBe(AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS * 1000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS); + }); + it("uses the startup floor for ZooKeeper agent connections", () => { expect(connectionAttemptTimeoutMs({ db_type: "zookeeper", connect_timeout_secs: 5, transport_layers: [] })).toBe(AGENT_DRIVER_MIN_CONNECT_TIMEOUT_SECS * 1000 + CONNECTION_ATTEMPT_TIMEOUT_BUFFER_MS); }); diff --git a/apps/desktop/src/lib/connectionAttemptTimeout.ts b/apps/desktop/src/lib/connectionAttemptTimeout.ts index 2bd4c0f93..23d914dbf 100644 --- a/apps/desktop/src/lib/connectionAttemptTimeout.ts +++ b/apps/desktop/src/lib/connectionAttemptTimeout.ts @@ -27,6 +27,7 @@ const DRIVER_STARTUP_FLOOR_TYPES = new Set([ "snowflake", "trino", "prestosql", + "jdbc", "hive", "db2", "informix", @@ -35,6 +36,7 @@ const DRIVER_STARTUP_FLOOR_TYPES = new Set([ "bigquery", "kylin", "sundb", + "oscar", "tdengine", "xugu", "iotdb", diff --git a/apps/desktop/src/lib/connectionPresentation.ts b/apps/desktop/src/lib/connectionPresentation.ts index 431f25374..ab35c6b98 100644 --- a/apps/desktop/src/lib/connectionPresentation.ts +++ b/apps/desktop/src/lib/connectionPresentation.ts @@ -142,6 +142,9 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string { case "tdengine": return "tdengine://user:password@host:6041/database"; + case "oscar": + return "oscar://user:password@host:2003/database"; + case "xugu": return "xugu://user:password@host:5138/database"; diff --git a/apps/desktop/src/lib/connectionUrl.ts b/apps/desktop/src/lib/connectionUrl.ts index bda71045c..91d40508b 100644 --- a/apps/desktop/src/lib/connectionUrl.ts +++ b/apps/desktop/src/lib/connectionUrl.ts @@ -58,6 +58,7 @@ const SCHEME_PROFILES: Record = { questdb: { type: "questdb", profile: "questdb", label: "QuestDB", defaultPort: 8812 }, tdengine: { type: "tdengine", profile: "tdengine", label: "TDengine", defaultPort: 6041 }, "taos-ws": { type: "tdengine", profile: "tdengine", label: "TDengine", defaultPort: 6041 }, + oscar: { type: "oscar", profile: "oscar", label: "神通 OSCAR", defaultPort: 2003 }, xugu: { type: "xugu", profile: "xugu", label: "XuguDB", defaultPort: 5138 }, iotdb: { type: "iotdb", profile: "iotdb", label: "Apache IoTDB", defaultPort: 6667 }, iris: { type: "iris", profile: "iris", label: "IRIS", defaultPort: 1972 }, diff --git a/apps/desktop/src/lib/databaseCapabilitySets.ts b/apps/desktop/src/lib/databaseCapabilitySets.ts index e13e07ee0..c763708c0 100644 --- a/apps/desktop/src/lib/databaseCapabilitySets.ts +++ b/apps/desktop/src/lib/databaseCapabilitySets.ts @@ -30,6 +30,7 @@ export const SCHEMA_AWARE_TYPES = new Set([ "db2", "informix", "xugu", + "oscar", "iotdb", "iris", "duckdb", @@ -64,6 +65,7 @@ export const TREE_SCHEMA_TYPES = new Set([ "h2", "informix", "xugu", + "oscar", "iotdb", "iris", "duckdb", diff --git a/apps/desktop/src/lib/databaseTableDataCapabilities.ts b/apps/desktop/src/lib/databaseTableDataCapabilities.ts index 6998d02af..5fb2a4e20 100644 --- a/apps/desktop/src/lib/databaseTableDataCapabilities.ts +++ b/apps/desktop/src/lib/databaseTableDataCapabilities.ts @@ -83,6 +83,7 @@ const NAVICAT_STYLE_TABLE_DATA_TYPES = new Set([ "informix", "bigquery", "sundb", + "oscar", "databend", ]); diff --git a/apps/desktop/src/lib/tableStructureCapabilities.ts b/apps/desktop/src/lib/tableStructureCapabilities.ts index d7fa0329f..5c0678525 100644 --- a/apps/desktop/src/lib/tableStructureCapabilities.ts +++ b/apps/desktop/src/lib/tableStructureCapabilities.ts @@ -282,6 +282,7 @@ const capabilityByType: Partial starrocks: mysqlCapabilities, goldendb: mysqlCapabilities, sundb: mysqlCapabilities, + oscar: unsupportedCapabilities, databend: mysqlCapabilities, gbase: gbaseCapabilities, postgres: postgresCapabilities, diff --git a/apps/desktop/src/lib/tableStructureEditorState.ts b/apps/desktop/src/lib/tableStructureEditorState.ts index 9af8600ed..4987a6995 100644 --- a/apps/desktop/src/lib/tableStructureEditorState.ts +++ b/apps/desktop/src/lib/tableStructureEditorState.ts @@ -276,6 +276,7 @@ const DATA_TYPE_OPTION_ALIASES: Partial> = { starrocks: "mysql", goldendb: "mysql", sundb: "mysql", + oscar: "oracle", gbase: "mysql", gaussdb: "postgres", kwdb: "postgres", diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 298eff1bb..cbf5e5fca 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -518,6 +518,7 @@ export const useConnectionStore = defineStore("connection", () => { bigquery: "BigQuery", kylin: "Kylin", sundb: "SunDB", + oscar: "神通 OSCAR", influxdb: "InfluxDB", }; diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index 9f7bf1516..ee6c83020 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -51,6 +51,7 @@ export type DatabaseType = | "bigquery" | "kylin" | "sundb" + | "oscar" | "tdengine" | "xugu" | "iotdb" diff --git a/crates/dbx-core/assets/database-drivers.manifest.json b/crates/dbx-core/assets/database-drivers.manifest.json index 2b91f28c9..52c26b9b2 100644 --- a/crates/dbx-core/assets/database-drivers.manifest.json +++ b/crates/dbx-core/assets/database-drivers.manifest.json @@ -1517,6 +1517,36 @@ "driverManagement": true } }, + { + "dbType": "oscar", + "label": "神通 OSCAR", + "runtimeMode": "agent", + "mcpMode": "bridge", + "agentKey": "oscar", + "singleConnectionPool": true, + "metadataConnectionScoped": false, + "skipTcpProbe": true, + "defaultPort": 2003, + "supportLevel": "browse", + "capabilities": { + "queryExecution": true, + "metadataBrowse": true, + "objectBrowser": true, + "objectSource": false, + "schemaSearch": true, + "diagram": false, + "tableDataEdit": true, + "tableStructureEdit": false, + "tableImport": false, + "dataTransfer": false, + "sqlFileExecution": true, + "databaseCreate": false, + "fieldLineage": false, + "sqlExplain": false, + "userAdmin": false, + "driverManagement": true + } + }, { "dbType": "tdengine", "label": "TDengine", diff --git a/crates/dbx-core/src/agent_catalog.rs b/crates/dbx-core/src/agent_catalog.rs index f6fbc6037..1528c96dd 100644 --- a/crates/dbx-core/src/agent_catalog.rs +++ b/crates/dbx-core/src/agent_catalog.rs @@ -223,6 +223,13 @@ const AGENT_CATALOG: &[AgentCatalogEntry] = &[ store_visible: true, profiles: &[], }, + AgentCatalogEntry { + db_type: DatabaseType::Oscar, + key: "oscar", + label: "神通 OSCAR", + store_visible: true, + profiles: &[], + }, AgentCatalogEntry { db_type: DatabaseType::Yashandb, key: "yashandb", diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index ae9b95a59..d9baf52f5 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -125,6 +125,7 @@ macro_rules! agent_connection_pool_database_type { | DatabaseType::Bigquery | DatabaseType::Kylin | DatabaseType::Sundb + | DatabaseType::Oscar | DatabaseType::Tdengine | DatabaseType::Xugu | DatabaseType::Iotdb diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 86b8eb26e..893d02702 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -1881,6 +1881,7 @@ fn uses_keyless_row_predicate(database_type: Option) -> bool { | DatabaseType::Vastbase | DatabaseType::Goldendb | DatabaseType::Yashandb + | DatabaseType::Oscar | DatabaseType::Databricks | DatabaseType::SapHana | DatabaseType::Teradata @@ -2548,6 +2549,33 @@ mod tests { ); } + #[test] + fn prepares_oscar_keyless_save_statements_with_schema_qualified_row_predicate() { + let result = prepare_data_grid_save(DataGridSaveStatementOptions { + database_type: Some(DatabaseType::Oscar), + table_meta: DataGridTableMeta { + schema: Some("SYSDBA".to_string()), + table_name: "PEOPLE".to_string(), + primary_keys: vec![], + columns: Some(vec![column("ID", "INTEGER", true, None), column("NAME", "VARCHAR", true, None)]), + }, + columns: vec!["ID".to_string(), "NAME".to_string()], + source_columns: None, + rows: vec![vec![json!(1), json!("Ada")]], + dirty_rows: vec![(0, vec![(1, json!("Linus"))])], + deleted_rows: vec![0], + new_rows: vec![], + }); + + assert_eq!( + result.statements, + vec![ + "UPDATE \"SYSDBA\".\"PEOPLE\" SET \"NAME\" = 'Linus' WHERE \"ID\" = 1 AND \"NAME\" = 'Ada';", + "DELETE FROM \"SYSDBA\".\"PEOPLE\" WHERE \"ID\" = 1 AND \"NAME\" = 'Ada';", + ] + ); + } + #[test] fn skips_expression_only_source_columns() { let result = prepare_data_grid_save(DataGridSaveStatementOptions { diff --git a/crates/dbx-core/src/database_capabilities.rs b/crates/dbx-core/src/database_capabilities.rs index 2456ba19b..a8cb923a6 100644 --- a/crates/dbx-core/src/database_capabilities.rs +++ b/crates/dbx-core/src/database_capabilities.rs @@ -24,6 +24,7 @@ pub fn is_single_connection_pool(db_type: &DatabaseType) -> bool { | DatabaseType::Vastbase | DatabaseType::Goldendb | DatabaseType::Yashandb + | DatabaseType::Oscar | DatabaseType::Firebird | DatabaseType::Iris | DatabaseType::OceanbaseOracle diff --git a/crates/dbx-core/src/models/connection.rs b/crates/dbx-core/src/models/connection.rs index ea57784b9..53cf53dca 100644 --- a/crates/dbx-core/src/models/connection.rs +++ b/crates/dbx-core/src/models/connection.rs @@ -312,6 +312,7 @@ pub enum DatabaseType { Bigquery, Kylin, Sundb, + Oscar, Tdengine, Xugu, Iotdb, @@ -642,6 +643,7 @@ impl ConnectionConfig { DatabaseType::Kingbase | DatabaseType::Vastbase => Some("postgres"), DatabaseType::Highgo => Some("highgo"), DatabaseType::Yashandb => Some("yasdb"), + DatabaseType::Oscar => Some("osrdb"), DatabaseType::Firebird => Some("employee"), DatabaseType::H2 => Some("test"), DatabaseType::Informix => Some("sysmaster"), @@ -796,6 +798,7 @@ impl ConnectionConfig { DatabaseType::Bigquery => format!("bigquery://{host}/{db_part}"), DatabaseType::Kylin => format!("kylin://{host}:{port}{db_part}"), DatabaseType::Sundb => format!("sundb://{host}:{port}{db_part}"), + DatabaseType::Oscar => format!("oscar://{host}:{port}{db_part}"), DatabaseType::Tdengine => format!("tdengine://{host}:{port}{db_part}"), DatabaseType::Xugu => format!("xugu://{host}:{port}{db_part}"), DatabaseType::Iotdb => { @@ -998,6 +1001,9 @@ impl ConnectionConfig { DatabaseType::Sundb => { format!("sundb://{}:{}@{host}:{port}{db_part}", username, password) } + DatabaseType::Oscar => { + format!("oscar://{}:{}@{host}:{port}{db_part}", username, password) + } DatabaseType::Tdengine => { format!("tdengine://{}:{}@{host}:{port}{db_part}", username, password) } @@ -2171,6 +2177,14 @@ mod tests { assert_eq!(config.connection_url(), "yashandb://sys:secret@10.1.2.3:2883/yasdb"); } + #[test] + fn oscar_url_defaults_to_osrdb_database() { + let mut config = mysql_config("SYSDBA", "secret", None); + config.db_type = DatabaseType::Oscar; + + assert_eq!(config.connection_url(), "oscar://SYSDBA:secret@10.1.2.3:2883/osrdb"); + } + #[test] fn mongodb_form_url_without_params_defaults_auth_source_to_admin() { let config = mongodb_config("root", "secret", Some("admin")); diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index a6b8e4a17..a638f3d50 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -535,7 +535,11 @@ async fn list_databases_once(state: &AppState, connection_id: &str) -> Result>("listDatabases", serde_json::json!({ "connection": config.as_ref() })) + .invoke_with_timeout::>( + "listDatabases", + serde_json::json!({ "connection": config.as_ref() }), + agent_metadata_timeout(Some(config.as_ref())), + ) .await; } if let Some(client) = extract_pool!(&connections, connection_id, ClickHouse) { @@ -644,9 +648,10 @@ pub async fn list_data_types_core( let session = session.clone(); drop(connections); return session - .invoke::>( + .invoke_with_timeout::>( "listDataTypes", serde_json::json!({ "connection": config.as_ref(), "database": database }), + agent_metadata_timeout(Some(config.as_ref())), ) .await .map(deduplicate_data_type_names); @@ -697,9 +702,10 @@ async fn list_schemas_once( let session = session.clone(); drop(connections); return session - .invoke::>( + .invoke_with_timeout::>( "listSchemas", serde_json::json!({ "connection": config.as_ref(), "database": database }), + agent_metadata_timeout(Some(config.as_ref())), ) .await; } @@ -1117,9 +1123,10 @@ async fn list_tables_once( .map(|tables| filter_table_infos(tables, filter, limit, offset, object_types)); } return session - .invoke::>( + .invoke_with_timeout::>( "listTables", serde_json::json!({ "connection": config.as_ref(), "database": database, "schema": schema }), + agent_metadata_timeout(Some(config.as_ref())), ) .await .map(|tables| filter_table_infos(tables, filter, limit, offset, object_types)); @@ -1376,7 +1383,7 @@ async fn external_driver_presto_like_tables( schema: &str, ) -> Result, String> { let result: db::QueryResult = session - .invoke( + .invoke_with_timeout( "executeQuery", serde_json::json!({ "connection": config, @@ -1387,6 +1394,7 @@ async fn external_driver_presto_like_tables( "fetchSize": 1000, "timeoutSecs": 60 }), + agent_metadata_timeout(Some(config)), ) .await?; Ok(presto_like_tables_from_query_result(&result)) @@ -2417,9 +2425,10 @@ async fn list_objects_once( return external_driver_presto_like_objects(session, config.as_ref(), database, schema).await; } return session - .invoke::>( + .invoke_with_timeout::>( "listObjects", serde_json::json!({ "connection": config.as_ref(), "database": database, "schema": schema }), + agent_metadata_timeout(Some(config.as_ref())), ) .await; } @@ -2528,9 +2537,10 @@ async fn list_completion_objects_once( let session = session.clone(); drop(connections); return session - .invoke::>( + .invoke_with_timeout::>( "listObjects", serde_json::json!({ "connection": config.as_ref(), "database": database, "schema": schema }), + agent_metadata_timeout(Some(config.as_ref())), ) .await .map(filter_completion_objects); @@ -2703,7 +2713,7 @@ pub async fn get_columns_core( let session = session.clone(); drop(connections); let columns = session - .invoke::>( + .invoke_with_timeout::>( "getColumns", serde_json::json!({ "connection": config.as_ref(), @@ -2711,6 +2721,7 @@ pub async fn get_columns_core( "schema": schema, "table": table, }), + agent_metadata_timeout(Some(config.as_ref())), ) .await?; return Ok(deduplicate_column_infos(columns)); @@ -3558,7 +3569,7 @@ async fn get_object_source_once( let session = session.clone(); drop(connections); let result: db::ObjectSource = session - .invoke( + .invoke_with_timeout( "getObjectSource", serde_json::json!({ "connection": config.as_ref(), @@ -3567,6 +3578,7 @@ async fn get_object_source_once( "name": name, "object_type": &object_type, }), + agent_metadata_timeout(Some(config.as_ref())), ) .await?; return Ok(result); diff --git a/crates/dbx-core/src/sql.rs b/crates/dbx-core/src/sql.rs index a03ee324a..ca3bba1a3 100644 --- a/crates/dbx-core/src/sql.rs +++ b/crates/dbx-core/src/sql.rs @@ -174,6 +174,7 @@ impl SqlDialectProfile { | DatabaseType::Dameng | DatabaseType::Gaussdb | DatabaseType::Yashandb + | DatabaseType::Oscar | DatabaseType::OceanbaseOracle ) } @@ -2520,6 +2521,7 @@ DELIMITER ;"; DatabaseType::Dameng, DatabaseType::Gaussdb, DatabaseType::Yashandb, + DatabaseType::Oscar, DatabaseType::OceanbaseOracle, ] { let profile = SqlDialectProfile::for_database_type(db_type); diff --git a/crates/dbx-core/src/sql_dialect/capabilities.rs b/crates/dbx-core/src/sql_dialect/capabilities.rs index 6913ff2ee..caf08cac8 100644 --- a/crates/dbx-core/src/sql_dialect/capabilities.rs +++ b/crates/dbx-core/src/sql_dialect/capabilities.rs @@ -35,6 +35,7 @@ pub fn is_schema_aware(database_type: DatabaseType) -> bool { | DatabaseType::Highgo | DatabaseType::Vastbase | DatabaseType::Yashandb + | DatabaseType::Oscar | DatabaseType::Databricks | DatabaseType::SapHana | DatabaseType::Teradata @@ -72,6 +73,12 @@ pub fn pagination_strategy(database_type: Option, context: Paginat TablePaginationStrategy::FetchFirst } Some(DatabaseType::Oracle) => TablePaginationStrategy::Unbounded, + Some(DatabaseType::Oscar) + if matches!(context, PaginationContext::TablePreview | PaginationContext::BoundedRead) => + { + TablePaginationStrategy::Rownum + } + Some(DatabaseType::Oscar) => TablePaginationStrategy::Unbounded, Some(DatabaseType::Dameng) => TablePaginationStrategy::FetchFirst, Some(DatabaseType::Db2) => TablePaginationStrategy::Db2FetchFirst, Some(DatabaseType::SqlServer) => TablePaginationStrategy::SqlServerTop, diff --git a/crates/dbx-core/src/sql_dialect/tests.rs b/crates/dbx-core/src/sql_dialect/tests.rs index 4f4a7fa66..f8c640378 100644 --- a/crates/dbx-core/src/sql_dialect/tests.rs +++ b/crates/dbx-core/src/sql_dialect/tests.rs @@ -37,6 +37,7 @@ fn qualifies_schema_only_for_schema_aware_databases() { qualified_table_name(Some(DatabaseType::Xugu), Some("DBX_TEST"), "PRODUCTS"), "\"DBX_TEST\".\"PRODUCTS\"" ); + assert_eq!(qualified_table_name(Some(DatabaseType::Oscar), Some("SYSDBA"), "EMPLOYEE"), "\"SYSDBA\".\"EMPLOYEE\""); assert_eq!(qualified_table_name(Some(DatabaseType::Jdbc), Some("cbsdw_dwd"), "dwd_test_df"), "dwd_test_df"); assert_eq!(qualified_table_name(Some(DatabaseType::Iotdb), Some("root.test"), "device2"), "root.test.device2"); assert_eq!( @@ -64,10 +65,19 @@ fn maps_table_pagination_strategy_by_database_type() { assert_eq!(table_pagination_strategy(Some(DatabaseType::OceanbaseOracle)), TablePaginationStrategy::Rownum); assert_eq!(table_pagination_strategy(Some(DatabaseType::Questdb)), TablePaginationStrategy::QuestDbLimit); assert_eq!(table_pagination_strategy(Some(DatabaseType::Oracle)), TablePaginationStrategy::Rownum); + assert_eq!(table_pagination_strategy(Some(DatabaseType::Oscar)), TablePaginationStrategy::Rownum); assert_eq!( pagination_strategy(Some(DatabaseType::Oracle), PaginationContext::BoundedRead), TablePaginationStrategy::FetchFirst ); + assert_eq!( + pagination_strategy(Some(DatabaseType::Oscar), PaginationContext::BoundedRead), + TablePaginationStrategy::Rownum + ); + assert_eq!( + pagination_strategy(Some(DatabaseType::Oscar), PaginationContext::UserQuery), + TablePaginationStrategy::Unbounded + ); assert_eq!( pagination_strategy(Some(DatabaseType::Oracle), PaginationContext::UserQuery), TablePaginationStrategy::Unbounded diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 588647b14..7b1e44171 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -68,7 +68,7 @@ function formatRedisCommandToolResult(result: RedisCommandResult) { } export const DBX_CONNECTION_TYPE_DESCRIPTION = - "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, weaviate, chromadb, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, prestosql, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, zookeeper, jdbc, access, mq"; + "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, weaviate, chromadb, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, prestosql, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, oscar, tdengine, iotdb, xugu, zookeeper, jdbc, access, mq"; const FILE_CAPABLE_CONNECTION_TYPES = new Set(["sqlite", "duckdb", "access", "h2"]); interface McpScope { @@ -185,10 +185,7 @@ export function createDbxMcpServer(backend: Backend, options: { isWebMode?: bool if (error) return error; const scopedConfig = config!; if (scopedConfig.db_type === "redis") { - return toolError( - "REDIS_COMMAND_REQUIRED", - "Redis connections do not accept SQL through dbx_execute_query. Use dbx_execute_redis_command with a Redis command such as GET key or INFO.", - ); + return toolError("REDIS_COMMAND_REQUIRED", "Redis connections do not accept SQL through dbx_execute_query. Use dbx_execute_redis_command with a Redis command such as GET key or INFO."); } if (scopedConfig.db_type !== "mongodb") { const safety = evaluateSqlSafety(sql, { ...sqlSafetyFromEnv(), allowMultipleStatements: true }); @@ -288,6 +285,7 @@ export function createDbxMcpServer(backend: Backend, options: { isWebMode?: bool kwdb: 26257, rqlite: 4001, tdengine: 6041, + oscar: 2003, iotdb: 6667, xugu: 5138, }; diff --git a/packages/node-core/src/diagnostics.ts b/packages/node-core/src/diagnostics.ts index d69bf4c34..1c11ef628 100644 --- a/packages/node-core/src/diagnostics.ts +++ b/packages/node-core/src/diagnostics.ts @@ -57,6 +57,7 @@ export const BRIDGE_REQUIRED_TYPES = [ "bigquery", "kylin", "sundb", + "oscar", "xugu", "jdbc", "access", diff --git a/src-tauri/windows/nsis/installer.nsi b/src-tauri/windows/nsis/installer.nsi index 5e9a9faf2..565f16a66 100644 --- a/src-tauri/windows/nsis/installer.nsi +++ b/src-tauri/windows/nsis/installer.nsi @@ -63,6 +63,7 @@ ${StrLoc} !define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}" !define ESTIMATEDSIZE "{{estimated_size}}" !define STARTMENUFOLDER "{{start_menu_folder}}" +!searchreplace WEBVIEW2LOADERSRCPATH "${MAINBINARYSRCPATH}" "\${MAINBINARYNAME}.exe" "\WebView2Loader.dll" Var PassiveMode Var UpdateMode @@ -643,6 +644,7 @@ Section Install ; Copy main executable File "${MAINBINARYSRCPATH}" + File /a "/oname=WebView2Loader.dll" "${WEBVIEW2LOADERSRCPATH}" ; Copy resources {{#each resources_dirs}} @@ -781,6 +783,7 @@ Section Uninstall ; Delete the app directory and its content from disk ; Copy main executable Delete "$INSTDIR\${MAINBINARYNAME}.exe" + Delete "$INSTDIR\WebView2Loader.dll" ; Delete resources {{#each resources}}