diff --git a/apps/desktop/src/components/config/DriverStoreDialog.vue b/apps/desktop/src/components/config/DriverStoreDialog.vue
index 4b069732fd..35ed02f7a0 100644
--- a/apps/desktop/src/components/config/DriverStoreDialog.vue
+++ b/apps/desktop/src/components/config/DriverStoreDialog.vue
@@ -659,7 +659,7 @@ onUnmounted(() => {
JRE {{ driver.jre }}
diff --git a/crates/dbx-core/src/agent_manager.rs b/crates/dbx-core/src/agent_manager.rs
index 898966e879..f6af233f23 100644
--- a/crates/dbx-core/src/agent_manager.rs
+++ b/crates/dbx-core/src/agent_manager.rs
@@ -8,7 +8,7 @@ use crate::database_capabilities;
use crate::db::agent_driver::{AgentDriverClient, AgentMethod};
use crate::models::connection::DatabaseType;
-pub const DEFAULT_JRE_KEY: &str = "17";
+pub const DEFAULT_JRE_KEY: &str = "21";
fn default_jre_key() -> String {
DEFAULT_JRE_KEY.to_string()
@@ -64,12 +64,12 @@ mod tests {
#[test]
fn resolves_managed_java_runtime_by_default() {
let manager = test_manager("managed");
- let java = manager.jre_java_path("17");
+ let java = manager.jre_java_path(DEFAULT_JRE_KEY);
touch(&java);
let state = AgentState::default();
- assert_eq!(manager.resolve_java_runtime(&state, "17").unwrap(), java);
+ assert_eq!(manager.resolve_java_runtime(&state, DEFAULT_JRE_KEY).unwrap(), java);
}
#[test]
@@ -85,7 +85,7 @@ mod tests {
..AgentState::default()
};
- assert_eq!(manager.resolve_java_runtime(&state, "17").unwrap(), custom_java);
+ assert_eq!(manager.resolve_java_runtime(&state, DEFAULT_JRE_KEY).unwrap(), custom_java);
}
#[test]
@@ -99,7 +99,7 @@ mod tests {
..AgentState::default()
};
- let err = manager.resolve_java_runtime(&state, "17").unwrap_err();
+ let err = manager.resolve_java_runtime(&state, DEFAULT_JRE_KEY).unwrap_err();
assert!(err.contains("Custom Java runtime does not exist"));
}
diff --git a/crates/dbx-core/src/agent_service.rs b/crates/dbx-core/src/agent_service.rs
index fe7db30d82..9c8b28b9ea 100644
--- a/crates/dbx-core/src/agent_service.rs
+++ b/crates/dbx-core/src/agent_service.rs
@@ -1,7 +1,9 @@
use std::io::Read;
use std::path::{Path, PathBuf};
-use crate::agent_manager::{AgentDriverInfo, AgentManager, AgentRegistry, InstalledDriver, DEFAULT_JRE_KEY};
+use crate::agent_manager::{
+ AgentDriverInfo, AgentManager, AgentRegistry, InstalledDriver, JavaRuntimeMode, DEFAULT_JRE_KEY,
+};
const REGISTRY_PATH: &str = "https://github.com/t8y2/dbx-agents/releases/latest/download/agent-registry.json";
const REGISTRY_R2_PATH: &str = "agents/agent-registry.json";
@@ -56,6 +58,11 @@ pub fn build_agent_list(am: &AgentManager, registry: Option<&AgentRegistry>) ->
.map(|r| r.jre.clone())
.or_else(|| local.map(|l| l.jre.clone()))
.unwrap_or_else(|| DEFAULT_JRE_KEY.to_string());
+ let remote_jre_version = registry.and_then(|r| r.resolve_jre(&jre_key)).map(|j| &j.version);
+ let local_jre_version = local_state.jre_versions.get(&jre_key);
+ let jre_update_available = installed
+ && (!am.is_jre_installed(&jre_key)
+ || remote_jre_version.is_some_and(|version| local_jre_version != Some(version)));
AgentDriverInfo {
db_type: key.to_string(),
label: label.to_string(),
@@ -64,7 +71,7 @@ pub fn build_agent_list(am: &AgentManager, registry: Option<&AgentRegistry>) ->
installed,
installed_version: local.map(|l| l.version.clone()),
update_available: match (local, remote) {
- (Some(l), Some(r)) => l.version != r.version,
+ (Some(l), Some(r)) => l.version != r.version || jre_update_available,
_ => false,
},
jre: jre_key.clone(),
@@ -74,6 +81,17 @@ pub fn build_agent_list(am: &AgentManager, registry: Option<&AgentRegistry>) ->
.collect()
}
+pub fn jre_needs_install(am: &AgentManager, registry: &AgentRegistry, jre_key: &str) -> bool {
+ let state = am.load_state();
+ if state.java_runtime.mode != JavaRuntimeMode::Managed {
+ return false;
+ }
+ if !am.is_jre_installed(jre_key) {
+ return true;
+ }
+ registry.resolve_jre(jre_key).is_some_and(|jre| state.jre_versions.get(jre_key) != Some(&jre.version))
+}
+
pub fn local_agent_jar_candidates(db_type: &str) -> Vec {
let jar_name = format!("dbx-agent-{db_type}.jar");
let relative = PathBuf::from("..").join("dbx-agents").join(db_type).join("build").join("libs").join(&jar_name);
diff --git a/crates/dbx-core/tests/agent_service.rs b/crates/dbx-core/tests/agent_service.rs
index b2c43ae3cf..e1af38c9dd 100644
--- a/crates/dbx-core/tests/agent_service.rs
+++ b/crates/dbx-core/tests/agent_service.rs
@@ -1,7 +1,7 @@
use dbx_core::agent_manager::{
- AgentManager, AgentRegistry, ArtifactInfo, DriverInfo, InstalledDriver, DEFAULT_JRE_KEY,
+ AgentManager, AgentRegistry, ArtifactInfo, DriverInfo, InstalledDriver, JreInfo, DEFAULT_JRE_KEY,
};
-use dbx_core::agent_service::{build_agent_list, github_url_to_r2_path, local_agent_jar_candidates};
+use dbx_core::agent_service::{build_agent_list, github_url_to_r2_path, jre_needs_install, local_agent_jar_candidates};
use dbx_core::agent_service::{is_app_version_compatible, replace_download};
fn test_manager(name: &str) -> AgentManager {
@@ -24,6 +24,15 @@ fn registry_with_driver(db_type: &str, version: &str, jre: &str) -> AgentRegistr
AgentRegistry { jre: None, jres: std::collections::HashMap::new(), drivers }
}
+fn registry_with_jre_driver(db_type: &str, driver_version: &str, jre: &str, jre_version: &str) -> AgentRegistry {
+ let mut registry = registry_with_driver(db_type, driver_version, jre);
+ registry.jres.insert(
+ jre.to_string(),
+ JreInfo { version: jre_version.to_string(), platforms: std::collections::HashMap::new() },
+ );
+ registry
+}
+
#[test]
fn built_in_agent_list_includes_expected_driver_labels() {
let manager = test_manager("labels");
@@ -69,6 +78,56 @@ fn agent_list_marks_installed_driver_update_when_registry_version_differs() {
assert!(h2.update_available);
}
+#[test]
+fn agent_list_marks_update_when_installed_managed_jre_version_differs() {
+ let manager = test_manager("jre-update");
+ let jar_path = manager.driver_jar_path("h2");
+ let java_path = manager.jre_java_path(DEFAULT_JRE_KEY);
+ std::fs::create_dir_all(jar_path.parent().unwrap()).unwrap();
+ std::fs::create_dir_all(java_path.parent().unwrap()).unwrap();
+ std::fs::write(&jar_path, b"jar").unwrap();
+ std::fs::write(&java_path, b"java").unwrap();
+ manager
+ .save_state(&dbx_core::agent_manager::AgentState {
+ jre_versions: [(DEFAULT_JRE_KEY.to_string(), "21.0.10".to_string())].into_iter().collect(),
+ installed_drivers: [(
+ "h2".to_string(),
+ InstalledDriver {
+ version: "0.2.0".to_string(),
+ installed_at: "2026-05-18T00:00:00Z".to_string(),
+ jre: DEFAULT_JRE_KEY.to_string(),
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ..Default::default()
+ })
+ .unwrap();
+ let registry = registry_with_jre_driver("h2", "0.2.0", DEFAULT_JRE_KEY, "21.0.11");
+
+ let agents = build_agent_list(&manager, Some(®istry));
+ let h2 = agents.iter().find(|agent| agent.db_type == "h2").unwrap();
+
+ assert!(h2.update_available);
+}
+
+#[test]
+fn jre_needs_install_when_managed_runtime_version_differs() {
+ let manager = test_manager("jre-needs-install");
+ let java_path = manager.jre_java_path(DEFAULT_JRE_KEY);
+ std::fs::create_dir_all(java_path.parent().unwrap()).unwrap();
+ std::fs::write(&java_path, b"java").unwrap();
+ manager
+ .save_state(&dbx_core::agent_manager::AgentState {
+ jre_versions: [(DEFAULT_JRE_KEY.to_string(), "21.0.10".to_string())].into_iter().collect(),
+ ..Default::default()
+ })
+ .unwrap();
+ let registry = registry_with_jre_driver("h2", "0.2.0", DEFAULT_JRE_KEY, "21.0.11");
+
+ assert!(jre_needs_install(&manager, ®istry, DEFAULT_JRE_KEY));
+}
+
#[test]
fn local_agent_jar_candidates_include_sibling_build_output() {
let candidates = local_agent_jar_candidates("tdengine");
@@ -79,8 +138,8 @@ fn local_agent_jar_candidates_include_sibling_build_output() {
#[test]
fn github_agent_asset_urls_map_to_r2_paths_by_category() {
assert_eq!(
- github_url_to_r2_path("https://github.com/t8y2/dbx-agents/releases/download/v1/jre-17.tar.gz", "jre"),
- "agents/jre/jre-17.tar.gz"
+ github_url_to_r2_path("https://github.com/t8y2/dbx-agents/releases/download/v1/dbx-jre-21.tar.gz", "jre"),
+ "agents/jre/dbx-jre-21.tar.gz"
);
assert_eq!(
github_url_to_r2_path("https://github.com/t8y2/dbx-agents/releases/download/v1/dbx-agent-h2.jar", "driver"),
diff --git a/crates/dbx-web/src/routes/agents.rs b/crates/dbx-web/src/routes/agents.rs
index 1898f5fe0f..e8aca12944 100644
--- a/crates/dbx-web/src/routes/agents.rs
+++ b/crates/dbx-web/src/routes/agents.rs
@@ -9,7 +9,8 @@ use dbx_core::agent_manager::{
};
use dbx_core::agent_service::{
build_agent_list, download_temp_path, fetch_registry, find_local_agent_jar, github_url_to_r2_path,
- import_offline_zip, install_local_agent, invalidate_registry_cache, replace_download, OfflineImportProgress,
+ import_offline_zip, install_local_agent, invalidate_registry_cache, jre_needs_install, replace_download,
+ OfflineImportProgress,
};
use futures::Stream;
use serde::Deserialize;
@@ -258,7 +259,7 @@ async fn install_agent_from_registry(
return Err(format!("Unknown driver type: {db_type}"));
};
let jre_key = &driver.jre;
- let needs_jre = am.load_state().java_runtime.mode == JavaRuntimeMode::Managed && !am.is_jre_installed(jre_key);
+ let needs_jre = jre_needs_install(am, registry, jre_key);
if needs_jre {
let jre_info =
registry.resolve_jre(jre_key).ok_or_else(|| format!("No JRE definition for version: {jre_key}"))?;
@@ -282,7 +283,11 @@ async fn install_agent_from_registry(
)
.await?;
send_install_progress(tx, "jre-extract", 0, 0, Some(db_type), current, total_drivers);
- extract_archive(&jre_archive, &am.jre_dir(jre_key))?;
+ let jre_dir = am.jre_dir(jre_key);
+ if jre_dir.exists() {
+ std::fs::remove_dir_all(&jre_dir).map_err(|err| format!("Failed to remove old JRE: {err}"))?;
+ }
+ extract_archive(&jre_archive, &jre_dir)?;
std::fs::remove_file(&jre_archive).ok();
}
diff --git a/plugins/jdbc/src/main/java/app/dbx/jdbc/DbxJdbcPlugin.java b/plugins/jdbc/src/main/java/app/dbx/jdbc/DbxJdbcPlugin.java
index 0411412682..05bcf67c8f 100644
--- a/plugins/jdbc/src/main/java/app/dbx/jdbc/DbxJdbcPlugin.java
+++ b/plugins/jdbc/src/main/java/app/dbx/jdbc/DbxJdbcPlugin.java
@@ -40,7 +40,7 @@ public final class DbxJdbcPlugin {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final int MAX_ROWS = 10_000;
private static final JdbcDriverQuirks DEFAULT_QUIRKS = new JdbcDriverQuirks(false, false);
- private static final JdbcDriverQuirks YASHAN_QUIRKS = new JdbcDriverQuirks(true, false);
+ private static final JdbcDriverQuirks YASHAN_QUIRKS = new JdbcDriverQuirks(true, true);
private static final JdbcDriverQuirks ORACLE_QUIRKS = new JdbcDriverQuirks(false, true);
private static final List DRIVER_QUIRK_RULES = List.of(
new JdbcDriverQuirkRule("jdbc:yasdb:", YASHAN_QUIRKS),
diff --git a/plugins/jdbc/src/test/java/app/dbx/jdbc/DbxJdbcPluginTest.java b/plugins/jdbc/src/test/java/app/dbx/jdbc/DbxJdbcPluginTest.java
index dfa74f54a2..46fbf0c463 100644
--- a/plugins/jdbc/src/test/java/app/dbx/jdbc/DbxJdbcPluginTest.java
+++ b/plugins/jdbc/src/test/java/app/dbx/jdbc/DbxJdbcPluginTest.java
@@ -74,7 +74,9 @@ final class DbxJdbcPluginTest {
""");
assertEquals(true, DbxJdbcPlugin.driverQuirks(yashan).skipExecutionContext());
+ assertEquals(true, DbxJdbcPlugin.driverQuirks(yashan).useOracleMetadata());
assertEquals(false, DbxJdbcPlugin.driverQuirks(h2).skipExecutionContext());
+ assertEquals(false, DbxJdbcPlugin.driverQuirks(h2).useOracleMetadata());
}
@Test
diff --git a/src-tauri/src/commands/agents.rs b/src-tauri/src/commands/agents.rs
index 58e910890b..899cb35baf 100644
--- a/src-tauri/src/commands/agents.rs
+++ b/src-tauri/src/commands/agents.rs
@@ -7,8 +7,8 @@ use dbx_core::agent_manager::{
};
use dbx_core::agent_service::{
build_agent_list, download_temp_path, fetch_registry, find_local_agent_jar, github_url_to_r2_path,
- import_agent_jar, import_offline_zip, install_local_agent, invalidate_registry_cache, replace_download,
- OfflineImportProgress,
+ import_agent_jar, import_offline_zip, install_local_agent, invalidate_registry_cache, jre_needs_install,
+ replace_download, OfflineImportProgress,
};
use dbx_core::connection::AppState;
@@ -51,7 +51,7 @@ pub async fn install_agent(
return Err(format!("Unknown driver type: {db_type}"));
};
let jre_key = &driver.jre;
- let needs_jre = am.load_state().java_runtime.mode == JavaRuntimeMode::Managed && !am.is_jre_installed(jre_key);
+ let needs_jre = jre_needs_install(am, ®istry, jre_key);
if needs_jre {
let jre_info =
@@ -83,7 +83,11 @@ pub async fn install_agent(
"step": "jre-extract", "downloaded": 0u64, "total": 0u64,
}),
);
- extract_archive(&jre_archive, &am.jre_dir(jre_key))?;
+ let jre_dir = am.jre_dir(jre_key);
+ if jre_dir.exists() {
+ std::fs::remove_dir_all(&jre_dir).map_err(|e| format!("Failed to remove old JRE: {e}"))?;
+ }
+ extract_archive(&jre_archive, &jre_dir)?;
std::fs::remove_file(&jre_archive).ok();
}
@@ -137,7 +141,7 @@ pub async fn upgrade_all_agents(app: tauri::AppHandle, state: State<'_, Arc