diff --git a/crates/dbx-core/src/db/oracle_driver.rs b/crates/dbx-core/src/db/oracle_driver.rs index 7cfb96465b..70dc0d0ea3 100644 --- a/crates/dbx-core/src/db/oracle_driver.rs +++ b/crates/dbx-core/src/db/oracle_driver.rs @@ -129,6 +129,44 @@ pub async fn list_tables(conn: &OracleClient, schema: &str) -> Result Result, String> { + let s = quote_literal(schema); + let sql = format!( + "SELECT o.OBJECT_NAME, \ + CASE o.OBJECT_TYPE \ + WHEN 'TABLE' THEN 'TABLE' \ + WHEN 'VIEW' THEN 'VIEW' \ + WHEN 'PROCEDURE' THEN 'PROCEDURE' \ + WHEN 'FUNCTION' THEN 'FUNCTION' \ + ELSE o.OBJECT_TYPE \ + END AS OBJECT_TYPE, \ + c.COMMENTS \ + FROM ALL_OBJECTS o \ + LEFT JOIN ALL_TAB_COMMENTS c ON c.OWNER = o.OWNER AND c.TABLE_NAME = o.OBJECT_NAME \ + WHERE o.OWNER = {s} \ + AND o.OBJECT_TYPE IN ('TABLE','VIEW','PROCEDURE','FUNCTION') \ + AND o.OBJECT_NAME NOT LIKE 'BIN$%' \ + ORDER BY CASE o.OBJECT_TYPE \ + WHEN 'TABLE' THEN 0 \ + WHEN 'VIEW' THEN 1 \ + WHEN 'PROCEDURE' THEN 2 \ + WHEN 'FUNCTION' THEN 3 \ + ELSE 4 \ + END, o.OBJECT_NAME" + ); + let result = conn.query(&sql, &[]).await.map_err(|e| e.to_string())?; + Ok(result + .rows + .iter() + .map(|row| crate::types::ObjectInfo { + name: row.get_string(0).unwrap_or("").to_string(), + object_type: row.get_string(1).unwrap_or("TABLE").to_string(), + schema: Some(schema.to_string()), + comment: row.get_string(2).filter(|s| !s.is_empty()).map(|s| s.to_string()), + }) + .collect()) +} + pub async fn get_columns(conn: &OracleClient, schema: &str, table: &str) -> Result, String> { log::debug!("[oracle] get_columns: schema={schema}, table={table}"); let s = quote_literal(schema); diff --git a/crates/dbx-core/src/db/sqlserver.rs b/crates/dbx-core/src/db/sqlserver.rs index 0e82780ba2..f9a1d54360 100644 --- a/crates/dbx-core/src/db/sqlserver.rs +++ b/crates/dbx-core/src/db/sqlserver.rs @@ -135,6 +135,46 @@ pub async fn list_tables(client: &mut SqlServerClient, schema: &str) -> Result Result, String> { + let sql = format!( + "SELECT o.name, \ + CASE o.type \ + WHEN 'U' THEN 'TABLE' \ + WHEN 'V' THEN 'VIEW' \ + WHEN 'P' THEN 'PROCEDURE' \ + WHEN 'FN' THEN 'FUNCTION' \ + WHEN 'IF' THEN 'FUNCTION' \ + WHEN 'TF' THEN 'FUNCTION' \ + WHEN 'FS' THEN 'FUNCTION' \ + WHEN 'FT' THEN 'FUNCTION' \ + ELSE o.type_desc \ + END AS object_type \ + FROM sys.objects o \ + JOIN sys.schemas s ON s.schema_id = o.schema_id \ + WHERE s.name = '{}' \ + AND o.type IN ('U','V','P','FN','IF','TF','FS','FT') \ + AND o.is_ms_shipped = 0 \ + ORDER BY CASE o.type \ + WHEN 'U' THEN 0 \ + WHEN 'V' THEN 1 \ + WHEN 'P' THEN 2 \ + ELSE 3 \ + END, o.name", + schema.replace('\'', "''") + ); + let stream = client.query(&*sql, &[]).await.map_err(|e| e.to_string())?; + let rows = stream.into_first_result().await.map_err(|e| e.to_string())?; + Ok(rows + .iter() + .map(|row| crate::types::ObjectInfo { + name: row.get::<&str, _>(0).unwrap_or("").to_string(), + object_type: row.get::<&str, _>(1).unwrap_or("TABLE").to_string(), + schema: Some(schema.to_string()), + comment: None, + }) + .collect()) +} + pub async fn get_columns(client: &mut SqlServerClient, schema: &str, table: &str) -> Result, String> { let sql = format!( "SELECT c.COLUMN_NAME, c.DATA_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, \ diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index 45693f3e1f..8477fbb8cf 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -313,6 +313,59 @@ pub async fn list_tables_core( } } +pub async fn list_objects_core( + state: &AppState, + connection_id: &str, + database: &str, + schema: &str, +) -> Result, String> { + let pool_key = state.get_or_create_pool(connection_id, Some(database)).await?; + + { + let connections = state.connections.lock().await; + if let Some(ext_pool) = extract_external(&connections, &pool_key) { + drop(connections); + let cache = ext_pool.cache.clone(); + return tokio::task::spawn_blocking(move || { + let con = cache.lock().map_err(|e| e.to_string())?; + Ok(duckdb_query_tables(&con)? + .into_iter() + .map(|table| db::ObjectInfo { + name: table.name, + object_type: table.table_type, + schema: None, + comment: table.comment, + }) + .collect()) + }) + .await + .map_err(|e| e.to_string())?; + } + if let Some(client) = extract_sqlserver(&connections, &pool_key) { + drop(connections); + let mut client = client.lock().await; + return db::sqlserver::list_objects(&mut client, schema).await; + } + if let Some(pool) = extract_oracle(&connections, &pool_key) { + drop(connections); + let client = pool.client(); + let client = client.lock().await; + return db::oracle_driver::list_objects(&*client, schema).await; + } + } + + Ok(list_tables_core(state, connection_id, database, schema) + .await? + .into_iter() + .map(|table| db::ObjectInfo { + name: table.name, + object_type: table.table_type, + schema: if schema.is_empty() { None } else { Some(schema.to_string()) }, + comment: table.comment, + }) + .collect()) +} + pub async fn get_columns_core( state: &AppState, connection_id: &str, diff --git a/crates/dbx-core/src/types.rs b/crates/dbx-core/src/types.rs index 8e271cae23..b77adc5fd2 100644 --- a/crates/dbx-core/src/types.rs +++ b/crates/dbx-core/src/types.rs @@ -12,6 +12,14 @@ pub struct TableInfo { pub comment: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ObjectInfo { + pub name: String, + pub object_type: String, + pub schema: Option, + pub comment: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ColumnInfo { pub name: String, diff --git a/src-tauri/src/commands/schema.rs b/src-tauri/src/commands/schema.rs index 27159a257b..2f7ff5e518 100644 --- a/src-tauri/src/commands/schema.rs +++ b/src-tauri/src/commands/schema.rs @@ -31,6 +31,16 @@ pub async fn list_tables( dbx_core::schema::list_tables_core(&state, &connection_id, &database, &schema).await } +#[tauri::command] +pub async fn list_objects( + state: State<'_, Arc>, + connection_id: String, + database: String, + schema: String, +) -> Result, String> { + dbx_core::schema::list_objects_core(&state, &connection_id, &database, &schema).await +} + #[tauri::command] pub async fn get_columns( state: State<'_, Arc>, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 15396fa2c6..6b52625059 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -82,6 +82,7 @@ pub fn run() { commands::plugins::uninstall_jdbc_plugin, commands::schema::list_databases, commands::schema::list_tables, + commands::schema::list_objects, commands::schema::list_schemas, commands::schema::get_columns, commands::schema::list_indexes, diff --git a/src-web/src/main.rs b/src-web/src/main.rs index cba7633e70..8a4f6d06e4 100644 --- a/src-web/src/main.rs +++ b/src-web/src/main.rs @@ -85,6 +85,7 @@ async fn main() { .route("/schema/databases", get(routes::schema::list_databases)) .route("/schema/schemas", get(routes::schema::list_schemas)) .route("/schema/tables", get(routes::schema::list_tables)) + .route("/schema/objects", get(routes::schema::list_objects)) .route("/schema/columns", get(routes::schema::list_columns)) .route("/schema/indexes", get(routes::schema::list_indexes)) .route("/schema/foreign-keys", get(routes::schema::list_foreign_keys)) diff --git a/src-web/src/routes/schema.rs b/src-web/src/routes/schema.rs index d7fc783740..c5cf7857a1 100644 --- a/src-web/src/routes/schema.rs +++ b/src-web/src/routes/schema.rs @@ -43,6 +43,17 @@ pub async fn list_tables( Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?)) } +pub async fn list_objects( + State(state): State>, + Query(q): Query, +) -> Result, AppError> { + let database = q.database.as_deref().unwrap_or(""); + let schema = q.schema.as_deref().unwrap_or(""); + let result = + dbx_core::schema::list_objects_core(&state.app, &q.connection_id, database, schema).await.map_err(AppError)?; + Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?)) +} + pub async fn list_columns( State(state): State>, Query(q): Query, diff --git a/src/components/objects/ObjectBrowser.vue b/src/components/objects/ObjectBrowser.vue index df1c5076e2..478c004c33 100644 --- a/src/components/objects/ObjectBrowser.vue +++ b/src/components/objects/ObjectBrowser.vue @@ -1,24 +1,24 @@