Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ SqlKit supports **70+ databases** across four adapter strategies:
|----------|-----------|
| **Native** (Rust) | PostgreSQL, MySQL, SQL Server, SQLite |
| **PG-wire compat** | CockroachDB, Redshift, YugabyteDB, TimescaleDB, QuestDB, Vastbase, YashanDB, KingbaseES, GaussDB, HighGo, UXDB, OpenGauss, GBase8c, Greenplum, EnterpriseDB, CrateDB, Materialize, AlloyDB, CloudSQLPG, FujitsuPG |
| **MySQL-wire compat** | MariaDB, TiDB, OceanBase, TDSQL, PolarDB, DM8, Doris, SelectDB, StarRocks, Databend, GoldenDB, ManticoreSearch, SingleStore, CloudSQLMySQL |
| **JDBC bridge** | Oracle, DuckDB, Firebird, DB2, H2, Snowflake, TDengine, Derby, Hive, Databricks, Hana, Teradata, Vertica, Exasol, BigQuery, Informix, Kylin, Cassandra, Iris, Access, DM8Oracle, XuguDB, GBase8a |
| **MySQL-wire compat** | MariaDB, TiDB, OceanBase, TDSQL, PolarDB, Doris, SelectDB, StarRocks, Databend, GoldenDB, ManticoreSearch, SingleStore, CloudSQLMySQL |
| **JDBC bridge** | Oracle, DuckDB, Firebird, DB2, H2, Snowflake, TDengine, Derby, Hive, Databricks, Hana, Teradata, Vertica, Exasol, BigQuery, Informix, Kylin, Cassandra, Iris, Access, Dameng, XuguDB, GBase8a |
| **HTTP bridge** | ClickHouse, Trino, Presto, RQLite, Turso |

### Product-Grade Editor
Expand Down
4 changes: 2 additions & 2 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ SqlKit 支持 **70+ 种数据库**,通过四种适配策略覆盖:
|------|--------|
| **原生** (Rust) | PostgreSQL、MySQL、SQL Server、SQLite |
| **PG 协议兼容** | CockroachDB、Redshift、YugabyteDB、TimescaleDB、QuestDB、Vastbase、YashanDB、KingbaseES、GaussDB、HighGo、UXDB、OpenGauss、GBase8c、Greenplum、EnterpriseDB、CrateDB、Materialize、AlloyDB、CloudSQLPG、FujitsuPG |
| **MySQL 协议兼容** | MariaDB、TiDB、OceanBase、TDSQL、PolarDB、DM8、Doris、SelectDB、StarRocks、Databend、GoldenDB、ManticoreSearch、SingleStore、CloudSQLMySQL |
| **JDBC 桥接** | Oracle、DuckDB、Firebird、DB2、H2、Snowflake、TDengine、Derby、Hive、Databricks、Hana、Teradata、Vertica、Exasol、BigQuery、Informix、Kylin、Cassandra、Iris、Access、DM8Oracle、XuguDB、GBase8a |
| **MySQL 协议兼容** | MariaDB、TiDB、OceanBase、TDSQL、PolarDB、Doris、SelectDB、StarRocks、Databend、GoldenDB、ManticoreSearch、SingleStore、CloudSQLMySQL |
| **JDBC 桥接** | Oracle、DuckDB、Firebird、DB2、H2、Snowflake、TDengine、Derby、Hive、Databricks、Hana、Teradata、Vertica、Exasol、BigQuery、Informix、Kylin、Cassandra、Iris、Access、Dameng、XuguDB、GBase8a |
| **HTTP 桥接** | ClickHouse、Trino、Presto、RQLite、Turso |

### 专业级编辑器
Expand Down
45 changes: 44 additions & 1 deletion jdbc-bridge/src/main/java/sqlkit/bridge/MetadataProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,57 @@ public class MetadataProvider {

/**
* List all databases (catalogs) on the server.
*
* For Oracle, getCatalogs() returns empty because Oracle doesn't use
* JDBC catalogs in the traditional sense. Fall back to querying the
* current container/PDB name via SYS_CONTEXT, then try listing all
* PDBs if connected to a CDB.
*/
public static List<String> listDatabases(Connection conn) throws Exception {
List<String> databases = new ArrayList<>();
try (ResultSet rs = conn.getMetaData().getCatalogs()) {
while (rs.next()) {
databases.add(rs.getString("TABLE_CAT"));
String cat = rs.getString("TABLE_CAT");
if (cat != null && !cat.isEmpty()) {
databases.add(cat);
}
}
}

// Oracle fallback: getCatalogs() returns empty for Oracle JDBC.
// Try SYS_CONTEXT first (works in any Oracle container),
// then v$pdbs (only works in CDB$ROOT).
if (databases.isEmpty()) {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM DUAL")) {
if (rs.next()) {
String conName = rs.getString(1);
if (conName != null && !conName.isEmpty()) {
databases.add(conName);
}
}
} catch (SQLException e) {
// Fall through to v$pdbs
}
}

if (databases.isEmpty()) {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT name FROM v$pdbs")) {
while (rs.next()) {
String name = rs.getString(1);
if (name != null && !name.isEmpty()) {
databases.add(name);
}
}
} catch (SQLException e) {
// Driver does not support Oracle-specific queries;
// leave databases empty (frontend will fall back to
// the configured connection database).
}
}

return databases;
}

Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ http = "1"
log = "0.4"
futures = "0.3"
rand = "0.8"
data-studio-agent = { git = "https://github.com/geek-fun/data-studio-agent.git", tag = "v0.1.1" }
data-studio-agent = { git = "https://github.com/geek-fun/data-studio-agent.git", tag = "v0.1.2" }

# Archive extraction (JRE downloads)
flate2 = "1.0"
Expand Down
230 changes: 221 additions & 9 deletions src-tauri/src/capabilities/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,36 @@ async fn execute_on_adapter(adapter: &ActiveConnection, sql: &str) -> Result<Que
.execute_query(sql)
.await
.map_err(|e| e.to_string()),
_ => todo!(),
ActiveConnection::ClickHouse(a) => a
.lock()
.await
.execute_query(sql)
.await
.map_err(|e| e.to_string()),
ActiveConnection::JdbcBridge(a) => a
.lock()
.await
.execute_query(sql)
.await
.map_err(|e| e.to_string()),
ActiveConnection::HttpSql(a) => a
.lock()
.await
.execute_query(sql)
.await
.map_err(|e| e.to_string()),
ActiveConnection::Rqlite(a) => a
.lock()
.await
.execute_query(sql)
.await
.map_err(|e| e.to_string()),
ActiveConnection::Turso(a) => a
.lock()
.await
.execute_query(sql)
.await
.map_err(|e| e.to_string()),
}
}

Expand Down Expand Up @@ -202,7 +231,36 @@ impl CapabilityHandler for ListDatabasesHandler {
.list_databases()
.await
.map_err(|e| e.to_string())?,
_ => todo!(),
ActiveConnection::ClickHouse(a) => a
.lock()
.await
.list_databases()
.await
.map_err(|e| e.to_string())?,
ActiveConnection::JdbcBridge(a) => a
.lock()
.await
.list_databases()
.await
.map_err(|e| e.to_string())?,
ActiveConnection::HttpSql(a) => a
.lock()
.await
.list_databases()
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Rqlite(a) => a
.lock()
.await
.list_databases()
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Turso(a) => a
.lock()
.await
.list_databases()
.await
.map_err(|e| e.to_string())?,
};
serde_json::to_string(&dbs).map_err(|e| e.to_string())
}
Expand Down Expand Up @@ -238,7 +296,36 @@ impl CapabilityHandler for ListSchemasHandler {
.list_schemas(database)
.await
.map_err(|e| e.to_string())?,
_ => todo!(),
ActiveConnection::ClickHouse(a) => a
.lock()
.await
.list_schemas(database)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::JdbcBridge(a) => a
.lock()
.await
.list_schemas(database)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::HttpSql(a) => a
.lock()
.await
.list_schemas(database)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Rqlite(a) => a
.lock()
.await
.list_schemas(database)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Turso(a) => a
.lock()
.await
.list_schemas(database)
.await
.map_err(|e| e.to_string())?,
};
serde_json::to_string(&schemas).map_err(|e| e.to_string())
}
Expand Down Expand Up @@ -280,7 +367,36 @@ impl CapabilityHandler for ListTablesHandler {
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
_ => todo!(),
ActiveConnection::ClickHouse(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::JdbcBridge(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::HttpSql(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Rqlite(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Turso(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
};
serde_json::to_string(&tables).map_err(|e| e.to_string())
}
Expand Down Expand Up @@ -323,10 +439,48 @@ impl CapabilityHandler for GetSchemaHandler {
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
_ => todo!(),
ActiveConnection::ClickHouse(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::JdbcBridge(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::HttpSql(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Rqlite(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Turso(a) => a
.lock()
.await
.list_tables(database, schema)
.await
.map_err(|e| e.to_string())?,
};

const MAX_SCHEMA_TABLES: usize = 30;
let tables: Vec<_> = tables.into_iter().take(MAX_SCHEMA_TABLES).collect();

let mut schema_lines: Vec<String> = Vec::new();
if tables.len() >= MAX_SCHEMA_TABLES {
schema_lines.push(format!(
"-- Showing first {} tables. Specify a schema filter for complete results.\n",
MAX_SCHEMA_TABLES
));
}
for table in &tables {
let cols = match &adapter {
ActiveConnection::Postgres(a) => a
Expand All @@ -353,7 +507,36 @@ impl CapabilityHandler for GetSchemaHandler {
.list_columns(database, schema, &table.name)
.await
.map_err(|e| e.to_string())?,
_ => todo!(),
ActiveConnection::ClickHouse(a) => a
.lock()
.await
.list_columns(database, schema, &table.name)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::JdbcBridge(a) => a
.lock()
.await
.list_columns(database, schema, &table.name)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::HttpSql(a) => a
.lock()
.await
.list_columns(database, schema, &table.name)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Rqlite(a) => a
.lock()
.await
.list_columns(database, schema, &table.name)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Turso(a) => a
.lock()
.await
.list_columns(database, schema, &table.name)
.await
.map_err(|e| e.to_string())?,
};

let schema_name = table.schema.as_deref().unwrap_or("public");
Expand Down Expand Up @@ -425,7 +608,36 @@ impl CapabilityHandler for DescribeTableHandler {
.list_columns(database, schema, table)
.await
.map_err(|e| e.to_string())?,
_ => todo!(),
ActiveConnection::ClickHouse(a) => a
.lock()
.await
.list_columns(database, schema, table)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::JdbcBridge(a) => a
.lock()
.await
.list_columns(database, schema, table)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::HttpSql(a) => a
.lock()
.await
.list_columns(database, schema, table)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Rqlite(a) => a
.lock()
.await
.list_columns(database, schema, table)
.await
.map_err(|e| e.to_string())?,
ActiveConnection::Turso(a) => a
.lock()
.await
.list_columns(database, schema, table)
.await
.map_err(|e| e.to_string())?,
};
serde_json::to_string(&cols).map_err(|e| e.to_string())
}
Expand Down Expand Up @@ -504,7 +716,7 @@ pub fn register_sql_tools(reg: &mut CapabilityRegistry) {

reg.register(Capability {
name: "sqlkit__list_tables",
description: "List all tables in a database schema.",
description: "List all tables in a database schema. Returns table names, types, and row counts — fast and lightweight. Use this to check if tables exist or browse available objects. For full column details, use sqlkit__describe_table or sqlkit__get_schema.",
handler: Arc::new(ListTablesHandler),
input_schema: json!({"type": "object", "properties": {
"connection_id": connection_id_schema(),
Expand All @@ -518,7 +730,7 @@ pub fn register_sql_tools(reg: &mut CapabilityRegistry) {

reg.register(Capability {
name: "sqlkit__get_schema",
description: "Get the full database schema (all tables and columns) as DDL-like text. Use this before writing queries to understand the structure.",
description: "Get the full database schema (all tables and all columns) as DDL-like text. SLOW on databases with many objects. Prefer sqlkit__list_tables for browsing and sqlkit__describe_table for single-table details.",
handler: Arc::new(GetSchemaHandler),
input_schema: json!({"type": "object", "properties": {
"connection_id": connection_id_schema(),
Expand Down
Loading
Loading