mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
+96
-10
@@ -212,10 +212,57 @@ pub async fn install_jdbc_driver_from_maven(
|
||||
let local_repo = plugin_dir.join("maven-cache");
|
||||
std::fs::create_dir_all(&local_repo).map_err(|err| err.to_string())?;
|
||||
|
||||
let mut command = crate::process::new_tokio_command(&resolver);
|
||||
let mut resolved = resolve_maven_artifacts(&resolver, &coordinate, &local_repo, &repositories, &env).await?;
|
||||
if let Some(orai18n) = oracle_orai18n_companion(&resolved) {
|
||||
match resolve_maven_artifacts(&resolver, &orai18n, &local_repo, &repositories, &env).await {
|
||||
Ok(mut companion) => resolved.artifacts.append(&mut companion.artifacts),
|
||||
Err(_) => {
|
||||
// A missing orai18n companion must not block the driver install;
|
||||
// affected charsets surface a dedicated hint at runtime.
|
||||
}
|
||||
}
|
||||
}
|
||||
let root = plugins_root.to_path_buf();
|
||||
tokio::task::spawn_blocking(move || install_jdbc_maven_bundle(&root, &coordinate, &resolved))
|
||||
.await
|
||||
.map_err(|err| err.to_string())??;
|
||||
list_jdbc_drivers(plugins_root)
|
||||
}
|
||||
|
||||
const ORACLE_JDBC_GROUP_ID: &str = "com.oracle.database.jdbc";
|
||||
const ORACLE_NLS_GROUP_ID: &str = "com.oracle.database.nls";
|
||||
const ORAI18N_ARTIFACT_ID: &str = "orai18n";
|
||||
|
||||
/// Oracle's thin driver jar ships converters for a small default charset set;
|
||||
/// multibyte charsets such as ZHS16GBK need the orai18n companion jar, otherwise
|
||||
/// every metadata call that reads dictionary comments fails wholesale. DBeaver
|
||||
/// ships the same pairing.
|
||||
fn oracle_orai18n_companion(resolved: &MavenResolveOutput) -> Option<String> {
|
||||
let ojdbc = resolved
|
||||
.artifacts
|
||||
.iter()
|
||||
.find(|artifact| artifact.group_id == ORACLE_JDBC_GROUP_ID && artifact.artifact_id.starts_with("ojdbc"))?;
|
||||
if resolved
|
||||
.artifacts
|
||||
.iter()
|
||||
.any(|artifact| artifact.group_id == ORACLE_NLS_GROUP_ID && artifact.artifact_id == ORAI18N_ARTIFACT_ID)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(format!("{ORACLE_NLS_GROUP_ID}:{ORAI18N_ARTIFACT_ID}:{}", ojdbc.version))
|
||||
}
|
||||
|
||||
async fn resolve_maven_artifacts(
|
||||
resolver: &Path,
|
||||
coordinate: &str,
|
||||
local_repo: &Path,
|
||||
repositories: &[String],
|
||||
env: &PluginRuntimeEnv,
|
||||
) -> Result<MavenResolveOutput, String> {
|
||||
let mut command = crate::process::new_tokio_command(resolver);
|
||||
env.apply_to(&mut command);
|
||||
command.arg("resolve").arg("--coordinate").arg(&coordinate).arg("--local-repo").arg(&local_repo);
|
||||
for repo in &repositories {
|
||||
command.arg("resolve").arg("--coordinate").arg(coordinate).arg("--local-repo").arg(local_repo);
|
||||
for repo in repositories {
|
||||
command.arg("--repo").arg(repo);
|
||||
}
|
||||
let output = command.output().await.map_err(|err| format!("Failed to run JDBC Maven resolver: {err}"))?;
|
||||
@@ -224,13 +271,7 @@ pub async fn install_jdbc_driver_from_maven(
|
||||
return Err(if stderr.is_empty() { "JDBC Maven resolver failed".to_string() } else { stderr });
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let resolved: MavenResolveOutput =
|
||||
serde_json::from_str(&stdout).map_err(|err| format!("Failed to parse JDBC Maven resolver output: {err}"))?;
|
||||
let root = plugins_root.to_path_buf();
|
||||
tokio::task::spawn_blocking(move || install_jdbc_maven_bundle(&root, &coordinate, &resolved))
|
||||
.await
|
||||
.map_err(|err| err.to_string())??;
|
||||
list_jdbc_drivers(plugins_root)
|
||||
serde_json::from_str(&stdout).map_err(|err| format!("Failed to parse JDBC Maven resolver output: {err}"))
|
||||
}
|
||||
|
||||
pub async fn install_prestosql_jdbc_driver(plugins_root: &Path) -> Result<Vec<JdbcDriverInfo>, String> {
|
||||
@@ -1254,6 +1295,51 @@ mod tests {
|
||||
assert!(!is_safe_bundle_id("../hive"));
|
||||
}
|
||||
|
||||
fn resolved_artifact(group_id: &str, artifact_id: &str, version: &str) -> MavenResolvedArtifact {
|
||||
MavenResolvedArtifact {
|
||||
group_id: group_id.to_string(),
|
||||
artifact_id: artifact_id.to_string(),
|
||||
version: version.to_string(),
|
||||
classifier: String::new(),
|
||||
extension: "jar".to_string(),
|
||||
file: format!("/tmp/{artifact_id}-{version}.jar"),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolved_with(artifacts: Vec<MavenResolvedArtifact>) -> MavenResolveOutput {
|
||||
MavenResolveOutput {
|
||||
coordinate: String::new(),
|
||||
scope: "runtime".to_string(),
|
||||
repositories: vec!["https://repo.maven.apache.org/maven2/".to_string()],
|
||||
artifacts,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_ojdbc_resolves_matching_orai18n_companion() {
|
||||
let resolved = resolved_with(vec![resolved_artifact("com.oracle.database.jdbc", "ojdbc11", "21.9.0.0")]);
|
||||
assert_eq!(oracle_orai18n_companion(&resolved).as_deref(), Some("com.oracle.database.nls:orai18n:21.9.0.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_orai18n_companion_skips_existing_nls_artifact() {
|
||||
let resolved = resolved_with(vec![
|
||||
resolved_artifact("com.oracle.database.jdbc", "ojdbc11", "21.9.0.0"),
|
||||
resolved_artifact("com.oracle.database.nls", "orai18n", "21.9.0.0"),
|
||||
]);
|
||||
assert_eq!(oracle_orai18n_companion(&resolved), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_orai18n_companion_ignores_non_oracle_drivers() {
|
||||
for coordinate in
|
||||
[("org.postgresql", "postgresql"), ("com.oracle.database.xml", "xdb6"), ("mysql", "mysql-connector-java")]
|
||||
{
|
||||
let resolved = resolved_with(vec![resolved_artifact(coordinate.0, coordinate.1, "8.0.33")]);
|
||||
assert_eq!(oracle_orai18n_companion(&resolved), None, "{}", coordinate.1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_offline_jdbc_plugin_and_rewrites_bundle_paths() {
|
||||
let root = std::env::temp_dir().join(format!("dbx-offline-jdbc-test-{}", uuid::Uuid::new_v4()));
|
||||
|
||||
@@ -116,6 +116,8 @@ DBX 不会自动下载数据库厂商驱动。你需要从数据库官网或 Mav
|
||||
|
||||
<Callout type="warn">不同数据库和驱动版本的 URL 参数可能不同。以厂商官方文档为准。如果驱动依赖多个 JAR,请把需要的 JAR 路径都导入或填写进去。</Callout>
|
||||
|
||||
<Callout type="info">Oracle 驱动请同时导入与 `ojdbc` 同版本的 `com.oracle.database.nls:orai18n`。基础 ojdbc jar 不支持 `ZHS16GBK` 等多字节字符集,缺少它时表/字段注释等元数据无法加载。通过 Maven 安装 `com.oracle.database.jdbc:ojdbc*` 时会自动附带同版本的 `orai18n`。使用 DBX 内置 Oracle 连接(默认驱动)则无需额外 JAR,已支持这些字符集。</Callout>
|
||||
|
||||
## 第四步:导入 JDBC 驱动 JAR
|
||||
|
||||
在 DBX 中打开:
|
||||
|
||||
@@ -116,6 +116,8 @@ Common examples:
|
||||
|
||||
<Callout type="warn">JDBC URL parameters vary by database and driver version. Follow the vendor documentation. If a driver depends on multiple JARs, import or list all required JAR paths.</Callout>
|
||||
|
||||
<Callout type="info">For Oracle, also import `com.oracle.database.nls:orai18n` with the same version as `ojdbc`. The base ojdbc jar cannot convert multibyte charsets such as `ZHS16GBK`, so table and column comments fail to load without it. Maven installs of `com.oracle.database.jdbc:ojdbc*` fetch the matching `orai18n` jar automatically. DBX's built-in Oracle connection (default driver) supports these charsets without any extra JAR.</Callout>
|
||||
|
||||
## Step 4: Import JDBC Driver JARs
|
||||
|
||||
In DBX:
|
||||
|
||||
@@ -273,10 +273,11 @@ public final class DbxJdbcPlugin {
|
||||
ObjectNode response = MAPPER.createObjectNode();
|
||||
response.set("id", id.isMissingNode() ? MAPPER.getNodeFactory().numberNode(1) : id);
|
||||
|
||||
JsonNode connection = MAPPER.createObjectNode();
|
||||
try {
|
||||
String method = requireText(request, "method");
|
||||
JsonNode params = request.path("params");
|
||||
JsonNode connection = params.path("connection");
|
||||
connection = params.path("connection");
|
||||
if ("close".equals(method)) {
|
||||
closeSharedConnection();
|
||||
ObjectNode result = MAPPER.createObjectNode();
|
||||
@@ -290,12 +291,31 @@ public final class DbxJdbcPlugin {
|
||||
} catch (Throwable error) {
|
||||
// The plugin protocol boundary must report linkage errors from vendor drivers instead of exiting silently.
|
||||
ObjectNode errorNode = MAPPER.createObjectNode();
|
||||
errorNode.put("message", throwableMessage(error));
|
||||
errorNode.put("message", enrichDriverHint(connection, throwableMessage(error)));
|
||||
response.set("error", errorNode);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private static final Pattern ORACLE_UNSUPPORTED_CHARSET_PATTERN =
|
||||
Pattern.compile("(?i)unsupported charset|不支持的字符集");
|
||||
|
||||
// The base Oracle thin driver jar ships converters for a handful of charsets only;
|
||||
// databases such as ZHS16GBK need orai18n.jar, otherwise every metadata call that
|
||||
// reads dictionary comments fails wholesale.
|
||||
static String enrichDriverHint(JsonNode connection, String message) {
|
||||
if (message == null || !ORACLE_UNSUPPORTED_CHARSET_PATTERN.matcher(message).find()) {
|
||||
return message;
|
||||
}
|
||||
if (!isOracleUrl(jdbcUrl(connection))) {
|
||||
return message;
|
||||
}
|
||||
String hint = message.contains("不支持的字符集")
|
||||
? "。请在 设置 → JDBC 驱动 中为该 Oracle 驱动一并导入同版本的 orai18n.jar,或改用 DBX 内置 Oracle 连接(默认驱动已支持中文多字节字符集)"
|
||||
: ". Import orai18n.jar (same version as the ojdbc driver) next to the Oracle driver under Settings -> JDBC Drivers, or use DBX's built-in Oracle connection instead, whose default driver supports multibyte Chinese charsets";
|
||||
return message.endsWith(hint) ? message : message + hint;
|
||||
}
|
||||
|
||||
private static String throwableMessage(Throwable error) {
|
||||
List<Throwable> causes = new ArrayList<>();
|
||||
Throwable cause = error;
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.util.regex.Pattern;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
final class DbxJdbcPluginTest {
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
@@ -4448,4 +4449,47 @@ final class DbxJdbcPluginTest {
|
||||
""".formatted(method, params);
|
||||
return MAPPER.valueToTree(handleLine.invoke(null, line));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enrichDriverHintAppendsChineseOrlai18nGuidanceForOracleCharsetErrors() {
|
||||
JsonNode connection = MAPPER.createObjectNode()
|
||||
.put("connection_string", "jdbc:oracle:thin:@//db:1521/ORCL");
|
||||
String enriched = DbxJdbcPlugin.enrichDriverHint(
|
||||
connection,
|
||||
"不支持的字符集 (在类路径中添加 orai18n.jar): ZHS16GBK"
|
||||
);
|
||||
assertTrue(enriched.startsWith("不支持的字符集 (在类路径中添加 orai18n.jar): ZHS16GBK"));
|
||||
assertTrue(enriched.contains("orai18n.jar"));
|
||||
assertTrue(enriched.contains("内置 Oracle 连接"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enrichDriverHintAppendsEnglishOrlai18nGuidanceForOracleCharsetErrors() {
|
||||
JsonNode connection = MAPPER.createObjectNode()
|
||||
.put("connection_string", "jdbc:oracle:thin:@//db:1521/ORCL");
|
||||
String enriched = DbxJdbcPlugin.enrichDriverHint(connection, "Unsupported charset: ZHS16GBK");
|
||||
assertTrue(enriched.startsWith("Unsupported charset: ZHS16GBK"));
|
||||
assertTrue(enriched.contains("Settings -> JDBC Drivers"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enrichDriverHintKeepsMessagesForOtherUrlsAndErrors() {
|
||||
JsonNode oracleConnection = MAPPER.createObjectNode()
|
||||
.put("connection_string", "jdbc:oracle:thin:@//db:1521/ORCL");
|
||||
JsonNode mysqlConnection = MAPPER.createObjectNode()
|
||||
.put("connection_string", "jdbc:mysql://db:3306/test");
|
||||
assertEquals("ORA-12505", DbxJdbcPlugin.enrichDriverHint(oracleConnection, "ORA-12505"));
|
||||
assertEquals(
|
||||
"Unsupported charset: ZHS16GBK",
|
||||
DbxJdbcPlugin.enrichDriverHint(mysqlConnection, "Unsupported charset: ZHS16GBK")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void enrichDriverHintDoesNotStackRepeatedHints() {
|
||||
JsonNode connection = MAPPER.createObjectNode()
|
||||
.put("connection_string", "jdbc:oracle:thin:@//db:1521/ORCL");
|
||||
String once = DbxJdbcPlugin.enrichDriverHint(connection, "Unsupported charset: ZHS16GBK");
|
||||
assertEquals(once, DbxJdbcPlugin.enrichDriverHint(connection, once));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user