Skip to content
Open
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
22 changes: 17 additions & 5 deletions jdbc-bridge/src/main/java/sqlkit/bridge/ConnectionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,23 @@ public class ConnectionManager {
public void connect(String connId, String url, String username,
String password, String driverClass,
List<String> driverJars,
int minPool, int maxPool) throws ClassifiedException, Exception {
int minPool, int maxPool,
boolean credentialsInUrl) throws ClassifiedException, Exception {
if (pools.containsKey(connId)) {
throw new Exception("Connection already exists: " + connId);
}

if (credentialsInUrl) {
StringBuilder sb = new StringBuilder(url);
sb.append(url.contains("?") ? "&" : "?");
sb.append("user=").append(username);
if (password != null && !password.isEmpty()) {
sb.append("&password=").append(password);
}
url = sb.toString();
}
final String jdbcUrl = url;

DriverClassLoader loader = new DriverClassLoader(driverJars);
Class<?> driverCls = Class.forName(driverClass, true, loader);
if (!java.sql.Driver.class.isAssignableFrom(driverCls)) {
Expand All @@ -48,13 +60,13 @@ public java.sql.Connection getConnection() throws java.sql.SQLException {
java.util.Properties info = new java.util.Properties();
if (username != null) info.setProperty("user", username);
if (password != null) info.setProperty("password", password);
return driver.connect(url, info);
return driver.connect(jdbcUrl, info);
}
public java.sql.Connection getConnection(String u, String p) throws java.sql.SQLException {
java.util.Properties info = new java.util.Properties();
info.setProperty("user", u);
info.setProperty("password", p);
return driver.connect(url, info);
if (u != null) info.setProperty("user", u);
if (p != null) info.setProperty("password", p);
return driver.connect(jdbcUrl, info);
}
public java.io.PrintWriter getLogWriter() { return null; }
public void setLogWriter(java.io.PrintWriter out) {}
Expand Down
37 changes: 35 additions & 2 deletions jdbc-bridge/src/main/java/sqlkit/bridge/DriverResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,43 @@ private static String getDriversCacheDir() {
* @param mavenArtifact e.g. "h2"
* @param versionCap Optional max version to cap against. Null means resolve LATEST.
* @param classifier Optional Maven classifier (e.g. "standalone"). Null means no classifier.
* @param downloadUrl Direct download URL for drivers NOT on Maven Central. Null means use Maven.
* @return DriverResult with path to the cached JAR and resolved version
*/
public static DriverResult resolve(String mavenGroup, String mavenArtifact,
String versionCap, String classifier) throws Exception {
public static DriverResult resolve(String mavenGroup, String mavenArtifact,
String versionCap, String classifier,
String downloadUrl) throws Exception {
if (downloadUrl != null && !downloadUrl.isEmpty()) {
return resolveDirect(mavenArtifact, versionCap, downloadUrl);
}
return resolveFromMaven(mavenGroup, mavenArtifact, versionCap, classifier);
}

private static DriverResult resolveDirect(String mavenArtifact, String versionCap, String downloadUrl) throws Exception {
String version = (versionCap != null && !versionCap.isEmpty()) ? versionCap : "1.0.0";
String jarFilename = mavenArtifact + "-" + version + ".jar";
Path destPath = Paths.get(DRIVERS_CACHE, mavenArtifact, jarFilename);

if (!Files.exists(destPath)) {
Files.createDirectories(destPath.getParent());
Request request = new Request.Builder()
.url(downloadUrl)
.addHeader("User-Agent", "SQLKit/1.0")
.build();
Response response = HTTP_CLIENT.newCall(request).execute();
if (!response.isSuccessful()) {
throw new Exception("Failed to download JAR: HTTP " + response.code() + " for " + downloadUrl);
}
byte[] jarBytes = response.body() != null ? response.body().bytes() : new byte[0];
response.close();
Files.write(destPath, jarBytes);
}

return new DriverResult(destPath.toAbsolutePath().toString(), version);
}

private static DriverResult resolveFromMaven(String mavenGroup, String mavenArtifact,
String versionCap, String classifier) throws Exception {
// 1. Fetch maven-metadata.xml
String metadataUrl = String.format("%s/%s/%s/maven-metadata.xml",
MAVEN_CENTRAL, mavenGroup.replace('.', '/'), mavenArtifact);
Expand Down
10 changes: 7 additions & 3 deletions jdbc-bridge/src/main/java/sqlkit/bridge/ProtocolHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ private void handleConnect(JsonNode params, ObjectNode response) throws Exceptio
String driverClass = requiredString(params, "driver_class", null);
int poolMin = params.has("pool_min") ? params.get("pool_min").asInt(1) : 1;
int poolMax = params.has("pool_max") ? params.get("pool_max").asInt(5) : 5;
boolean credentialsInUrl = params.has("credentials_in_url") && !params.get("credentials_in_url").isNull()
&& params.get("credentials_in_url").asBoolean(false);

List<String> driverJars = new ArrayList<>();
if (params.has("driver_jars") && params.get("driver_jars").isArray()) {
Expand All @@ -132,7 +134,7 @@ private void handleConnect(JsonNode params, ObjectNode response) throws Exceptio
// Extract Oracle-specific connection options
String tnsAdminDir = null;
String walletPassword = null;
connectionManager.connect(connId, url, username, password, driverClass, driverJars, poolMin, poolMax);
connectionManager.connect(connId, url, username, password, driverClass, driverJars, poolMin, poolMax, credentialsInUrl);
response.put("result", connId);
}

Expand Down Expand Up @@ -214,8 +216,10 @@ private void handleResolveDriver(JsonNode params, ObjectNode response) throws Ex
? params.get("version_cap").asText() : null;
String classifier = params.has("maven_classifier") && !params.get("maven_classifier").isNull()
? params.get("maven_classifier").asText() : null;

DriverResolver.DriverResult result = DriverResolver.resolve(mavenGroup, mavenArtifact, versionCap, classifier);
String downloadUrl = params.has("download_url") && !params.get("download_url").isNull()
? params.get("download_url").asText() : null;

DriverResolver.DriverResult result = DriverResolver.resolve(mavenGroup, mavenArtifact, versionCap, classifier, downloadUrl);

ObjectNode resultNode = MAPPER.createObjectNode();
resultNode.put("jar_path", result.getJarPath());
Expand Down
1 change: 0 additions & 1 deletion src-tauri/Cargo.lock

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

6 changes: 4 additions & 2 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ hex = "0.4"
rust_xlsxwriter = "0.64"
calamine = "0.22"

# Agent / LLM dependencies (data-studio-agent release)
# Agent / LLM dependencies
# data-studio-agent: local checkout for active development.
# Revert to release when ready: { git = "https://github.com/geek-fun/data-studio-agent.git", tag = "v0.1.2" }
reqwest = { version = "0.12", default-features = false, features = [
"json",
"rustls-tls",
Expand All @@ -86,7 +88,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.2" }
data-studio-agent = { path = "/Users/blankll/Documents/devs/geekfun/data-studio-agent" }

# Archive extraction (JRE downloads)
flate2 = "1.0"
Expand Down
40 changes: 37 additions & 3 deletions src-tauri/src/database/jdbc_bridge/drivers.toml
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,13 @@ version_error_signatures = [

[databases.xugudb]
name = "虚谷 XuguDB"
class_name = "com.xugudb.jdbc.Driver"
class_name = "com.xugu.cloudjdbc.Driver"
maven_group = "com.xugudb"
maven_artifact = "xugudb-jdbc"
jdbc_url_template = "jdbc:xugudb://{host}:{port}/{database}"
maven_artifact = "xugu-jdbc"
jdbc_url_template = "jdbc:xugu://{host}:{port}/{database}"
default_port = 5138
min_jre_version = "11"
credentials_in_url = true
version_error_signatures = [ "driver version not compatible", ]

# ── 南大通用 GBase 8a ──
Expand All @@ -180,6 +181,8 @@ maven_artifact = "gbase-connector-java"
jdbc_url_template = "jdbc:gbase://{host}:{port}/{database}"
default_port = 5258
min_jre_version = "11"
version_cap = "8.3.81.53"
download_url = "https://www.gbase8.cn/wp-content/uploads/2020/10/gbase-connector-java-8.3.81.53-build55.5.7-bin_min_mix.jar"
version_error_signatures = [ "driver version not compatible", ]

# ── Apache Hive ──
Expand Down Expand Up @@ -332,3 +335,34 @@ jdbc_url_template = "jdbc:ucanaccess://{host}/{database}"
default_port = 0
min_jre_version = "11"
version_error_signatures = [ "driver version not compatible", ]

# ── YashanDB (崖山数据库) ──
#
# Official JDBC driver from Yashan Technologies. Published on Maven Central.
# Uses `jdbc:yasdb:` protocol — NOT PG-wire compatible.

[databases.yashandb]
name = "YashanDB (崖山数据库)"
class_name = "com.yashandb.jdbc.Driver"
maven_group = "com.yashandb"
maven_artifact = "yashandb-jdbc"
jdbc_url_template = "jdbc:yasdb://{host}:{port}/{database}"
default_port = 1688
min_jre_version = "11"
version_error_signatures = [ "driver version not compatible", ]

# ── KingbaseES (人大金仓) ──
#
# Official JDBC driver from 电科金仓. Published on Maven Central.
# Uses `jdbc:kingbase8:` protocol — PG-wire compatible but JDBC bridge
# gives access to advanced features (read-write splitting, Oracle-compat mode).

[databases.kingbase]
name = "KingbaseES (人大金仓)"
class_name = "com.kingbase8.Driver"
maven_group = "cn.com.kingbase"
maven_artifact = "kingbase8"
jdbc_url_template = "jdbc:kingbase8://{host}:{port}/{database}"
default_port = 54321
min_jre_version = "11"
version_error_signatures = [ "driver version not compatible", ]
2 changes: 2 additions & 0 deletions src-tauri/src/database/jdbc_bridge/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ pub async fn try_driver(
"maven_artifact": config.maven_artifact,
"version_cap": effective_cap,
"maven_classifier": config.maven_classifier,
"download_url": config.download_url,
});
let resolve_req = JdbcRequest::new(JdbcMethod::ResolveDriver, resolve_params);

Expand Down Expand Up @@ -182,6 +183,7 @@ pub async fn try_driver(
pool_min: 1,
pool_max: 5,
oracle_options: oracle_options.cloned(),
credentials_in_url: config.credentials_in_url,
}) {
Ok(v) => v,
Err(e) => {
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/database/jdbc_bridge/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pub struct ConnectParams {
pub pool_max: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub oracle_options: Option<OracleConnectionOptions>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub credentials_in_url: Option<bool>,
}

fn default_pool_min() -> u32 {
Expand Down Expand Up @@ -112,6 +114,8 @@ pub struct ResolveDriverParams {
pub version_cap: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maven_classifier: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub download_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
31 changes: 31 additions & 0 deletions src-tauri/src/database/jdbc_bridge/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ pub struct DatabaseDriverConfig {
/// When set, the download URL becomes: {artifact}-{version}-{classifier}.jar
#[serde(default)]
pub maven_classifier: Option<String>,
/// Direct download URL for drivers NOT on Maven Central (e.g. GBase 8a).
/// When present, the bridge downloads the JAR directly from this URL
/// instead of resolving via Maven metadata.
#[serde(default)]
pub download_url: Option<String>,
/// When true, append `?user=...&password=...` to the JDBC URL instead of
/// passing credentials via Properties. Some drivers (e.g. XuguDB) require this.
#[serde(default)]
pub credentials_in_url: Option<bool>,
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -190,6 +199,8 @@ fn db_type_to_registry_key(db: DatabaseType) -> Option<&'static str> {
DatabaseType::Cassandra => Some("cassandra"),
DatabaseType::Iris => Some("iris"),
DatabaseType::Access => Some("access"),
DatabaseType::YashanDB => Some("yashandb"),
DatabaseType::KingbaseES => Some("kingbase"),
_ => None,
}
}
Expand Down Expand Up @@ -275,6 +286,8 @@ mod tests {
version_error_signatures: vec![],
version_cap: None,
maven_classifier: None,
download_url: None,
credentials_in_url: None,
};
let url = build_jdbc_url(&config, "localhost", 9092, Some("testdb"));
assert_eq!(url, "jdbc:h2:tcp://localhost:9092/testdb");
Expand All @@ -294,6 +307,8 @@ mod tests {
version_error_signatures: vec![],
version_cap: None,
maven_classifier: None,
download_url: None,
credentials_in_url: None,
};
let url = build_jdbc_url(&config, "localhost", 1521, Some("XEPDB1"));
assert_eq!(url, "jdbc:oracle:thin:@localhost:1521:XEPDB1");
Expand All @@ -314,6 +329,8 @@ mod tests {
version_error_signatures: vec![],
version_cap: None,
maven_classifier: None,
download_url: None,
credentials_in_url: None,
};
let url = build_jdbc_url(&config, "localhost", 9092, None);
assert_eq!(url, "jdbc:h2:tcp://localhost:9092/");
Expand All @@ -334,6 +351,8 @@ mod tests {
version_error_signatures: vec![],
version_cap: None,
maven_classifier: None,
download_url: None,
credentials_in_url: None,
};
let url = build_jdbc_url(&config, "10.0.0.1", 5236, None);
assert_eq!(url, "jdbc:dm://10.0.0.1:5236");
Expand Down Expand Up @@ -397,6 +416,8 @@ mod tests {
version_error_signatures: vec![],
version_cap: None,
maven_classifier: None,
download_url: None,
credentials_in_url: None,
};
let url = build_jdbc_url(&config, "localhost", port, None);
assert_eq!(
Expand Down Expand Up @@ -473,6 +494,14 @@ mod tests {
DriverRegistry::registry_key(DatabaseType::Access),
Some("access")
);
assert_eq!(
DriverRegistry::registry_key(DatabaseType::YashanDB),
Some("yashandb")
);
assert_eq!(
DriverRegistry::registry_key(DatabaseType::KingbaseES),
Some("kingbase")
);
}

#[test]
Expand All @@ -490,6 +519,8 @@ mod tests {
assert!(keys.contains(&"oracle"), "oracle should be in the list");
assert!(keys.contains(&"db2"), "db2 should be in the list");
assert!(keys.contains(&"h2"), "h2 should be in the list");
assert!(keys.contains(&"yashandb"), "yashandb should be in the list");
assert!(keys.contains(&"kingbase"), "kingbase should be in the list");
}

#[test]
Expand Down
Loading
Loading