From 6c232899b43bb4d757d1b0221b8dcd923c675be8 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sun, 28 Jun 2026 16:11:22 +0800 Subject: [PATCH] feat(agents): remove legacy Oracle Java agents --- .github/workflows/agents-release.yml | 89 +-- agents/README.md | 10 +- agents/build.gradle | 2 +- agents/docs/agent-authoring.md | 4 +- agents/docs/release-checklist.md | 13 +- agents/drivers/oracle-10g/build.gradle | 11 - .../dbx/agent/oracle10g/Oracle10gAgent.java | 593 --------------- .../agent/oracle10g/Oracle10gAgentTest.java | 235 ------ agents/drivers/oracle-legacy/build.gradle | 19 - .../agent/oraclelegacy/OracleLegacyAgent.java | 10 - agents/drivers/oracle/build.gradle | 11 - .../com/dbx/agent/oracle/OracleAgent.java | 686 ------------------ .../com/dbx/agent/oracle/OracleAgentTest.java | 113 --- agents/scripts/validate_agents.py | 23 +- agents/scripts/validate_agents_test.py | 37 +- agents/settings.gradle | 4 +- agents/versions.json | 2 - crates/dbx-core/src/agent_connection.rs | 35 +- crates/dbx-core/src/agent_manager.rs | 5 +- crates/dbx-core/src/agent_runtime.rs | 7 - crates/dbx-core/src/connection.rs | 78 +- crates/dbx-core/src/db/agent_driver.rs | 11 +- .../app-tests/agentDriverInstallHint.test.ts | 18 +- src-tauri/src/commands/connection.rs | 62 +- 24 files changed, 66 insertions(+), 2012 deletions(-) delete mode 100644 agents/drivers/oracle-10g/build.gradle delete mode 100644 agents/drivers/oracle-10g/src/main/java/com/dbx/agent/oracle10g/Oracle10gAgent.java delete mode 100644 agents/drivers/oracle-10g/src/test/java/com/dbx/agent/oracle10g/Oracle10gAgentTest.java delete mode 100644 agents/drivers/oracle-legacy/build.gradle delete mode 100644 agents/drivers/oracle-legacy/src/main/java/com/dbx/agent/oraclelegacy/OracleLegacyAgent.java delete mode 100644 agents/drivers/oracle/build.gradle delete mode 100644 agents/drivers/oracle/src/main/java/com/dbx/agent/oracle/OracleAgent.java delete mode 100644 agents/drivers/oracle/src/test/java/com/dbx/agent/oracle/OracleAgentTest.java diff --git a/.github/workflows/agents-release.yml b/.github/workflows/agents-release.yml index 3f4dfb8ed..c1fa253e5 100644 --- a/.github/workflows/agents-release.yml +++ b/.github/workflows/agents-release.yml @@ -61,20 +61,16 @@ jobs: echo "Skipping automatic module version bump for migrated first release; versions.json was carried over from dbx-agents." else for dir in $(echo "$VERSIONS" | python3 -c "import sys,json; [print(k) for k in json.load(sys.stdin)]"); do - if [ -d "agents/drivers/$dir" ] || [ -d "agents/$dir" ] || [ "$dir" = "common" ]; then - if [ -d "agents/drivers/$dir" ]; then - CHECK_DIR="drivers/$dir" - else - CHECK_DIR="$dir" - fi - EXTRA_CHECK_DIR="" - if [ "$dir" = "oracle" ]; then - EXTRA_CHECK_DIR="drivers/oracle-go" - fi - DIFF_PATHS=("agents/$CHECK_DIR/") - if [ -n "$EXTRA_CHECK_DIR" ]; then - DIFF_PATHS+=("agents/$EXTRA_CHECK_DIR/") - fi + if [ "$dir" = "oracle" ] && [ -d "agents/drivers/oracle-go" ]; then + CHECK_DIR="drivers/oracle-go" + elif [ -d "agents/drivers/$dir" ]; then + CHECK_DIR="drivers/$dir" + elif [ -d "agents/$dir" ] || [ "$dir" = "common" ]; then + CHECK_DIR="$dir" + else + continue + fi + DIFF_PATHS=("agents/$CHECK_DIR/") # common changes affect all modules if git diff --quiet "$PREV_TAG"..HEAD -- agents/common/ 2>/dev/null; then COMMON_CHANGED=false @@ -99,7 +95,6 @@ jobs: CHANGED=true echo " $dir: $OLD_VER -> $NEW_VER" fi - fi done fi @@ -290,50 +285,8 @@ jobs: name: jre-${{ matrix.jre-key }} path: "dbx-jre-*.tar.gz" - build-jre8: - runs-on: ubuntu-latest - steps: - - name: Download Adoptium JRE 8 for all platforms - shell: bash - run: | - ADOPTIUM="https://api.adoptium.net/v3/binary/latest/8/ga" - declare -A PLATFORMS=( - ["linux-x64"]="linux/x64" - ["linux-aarch64"]="linux/aarch64" - ["macos-x64"]="mac/x64" - ["windows-x64"]="windows/x64" - ) - for platform in "${!PLATFORMS[@]}"; do - os_arch="${PLATFORMS[$platform]}" - echo "=== Downloading JRE 8 for $platform ($os_arch) ===" - if [[ "$platform" == windows-* ]]; then - curl -L -f -o jre8-$platform.zip "$ADOPTIUM/$os_arch/jre/hotspot/normal/eclipse?project=jdk" - mkdir -p jre8-extract - unzip -q jre8-$platform.zip -d jre8-extract - mv jre8-extract/*/ dbx-jre || mv jre8-extract/* dbx-jre - rm -rf jre8-extract jre8-$platform.zip - else - curl -L -f -o jre8-$platform.tar.gz "$ADOPTIUM/$os_arch/jre/hotspot/normal/eclipse?project=jdk" - mkdir -p dbx-jre - tar xzf jre8-$platform.tar.gz -C dbx-jre --strip-components=1 - rm -f jre8-$platform.tar.gz - fi - tar czf dbx-jre-8-$platform.tar.gz dbx-jre - rm -rf dbx-jre - done - - if [ -f dbx-jre-8-macos-x64.tar.gz ]; then - cp dbx-jre-8-macos-x64.tar.gz dbx-jre-8-macos-aarch64.tar.gz - fi - - ls -lh dbx-jre-8-*.tar.gz - - uses: actions/upload-artifact@v4 - with: - name: jre-8 - path: "dbx-jre-8-*.tar.gz" - release: - needs: [bump-versions, build-agents, build-oracle-native, build-xugu-native, build-jre, build-jre8] + needs: [bump-versions, build-agents, build-oracle-native, build-xugu-native, build-jre] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -445,13 +398,11 @@ jobs: detect_jre_key() { local name="$1" case "$name" in - oracle-10g) echo "8" ;; *) echo "21" ;; esac } JRE21_PLATFORMS=$(generate_jre_platforms "21") - JRE8_PLATFORMS=$(generate_jre_platforms "8") DRIVERS="" for f in release/dbx-agent-*.jar; do @@ -466,7 +417,7 @@ jobs: [ -n "$DRIVERS" ] && DRIVERS="${DRIVERS},"$'\n' DRIVERS="${DRIVERS}$(generate_jar_entry "$name" "$label" "$f" "$jre_key" "$version" "$external_driver" "$native_json")" done - for name in xugu; do + for name in oracle xugu; do [ -f "release/dbx-agent-${name}.jar" ] && continue native_json=$(generate_native_platforms "$name") [ -z "$native_json" ] && continue @@ -485,12 +436,6 @@ jobs: "platforms": { ${JRE21_PLATFORMS} } - }, - "8": { - "version": "8u442", - "platforms": { - ${JRE8_PLATFORMS} - } } }, "drivers": { @@ -517,7 +462,7 @@ jobs: for f in release/dbx-agent-*.jar; do DRIVER_NAMES+=("$(basename "$f" .jar | sed 's/dbx-agent-//')") done - for name in xugu; do + for name in oracle xugu; do [ -f "release/dbx-agent-${name}.jar" ] && continue compgen -G "release/dbx-agent-${name}-*" > /dev/null && DRIVER_NAMES+=("$name") done @@ -525,6 +470,7 @@ jobs: native_only_label() { local name="$1" case "$name" in + oracle) echo "Oracle" ;; xugu) echo "虚谷 XuguDB" ;; *) echo "$name" ;; esac @@ -544,15 +490,14 @@ jobs: fi NOTES="${NOTES}### ${label} (${old_ver} → ${new_ver})"$'\n' - if [ -d "agents/drivers/$name" ]; then + if [ "$name" = "oracle" ]; then + LOG_PATH="agents/drivers/oracle-go/" + elif [ -d "agents/drivers/$name" ]; then LOG_PATH="agents/drivers/$name/" else LOG_PATH="agents/$name/" fi LOG_PATHS=("$LOG_PATH") - if [ "$name" = "oracle" ]; then - LOG_PATHS+=("agents/drivers/oracle-go/") - fi while IFS= read -r line; do [ -n "$line" ] && NOTES="${NOTES}- ${line}"$'\n' done < <(git log --oneline "$PREV_TAG"..HEAD -- "${LOG_PATHS[@]}" | sed 's/^[0-9a-f]* //') diff --git a/agents/README.md b/agents/README.md index 924d3c796..cb76146a0 100644 --- a/agents/README.md +++ b/agents/README.md @@ -24,8 +24,6 @@ Each agent runs as a standalone process and communicates with DBX via stdin/stdo | gbase8a | GBase 8a | External GBase 8a JDBC | | gbase8s | GBase 8s | External GBase 8s JDBC | | oracle | Oracle 10g+ | go-ora native agent | -| oracle-legacy | Oracle 11g/12c/18c/19c | ojdbc8 (compatibility fallback) | -| oracle-10g | Oracle 10g | ojdbc6 (compatibility fallback, JRE 8) | | h2 | H2 | H2 JDBC | | snowflake | Snowflake | Snowflake JDBC | | trino | Trino (Presto) | Trino JDBC | @@ -48,11 +46,11 @@ Each agent runs as a standalone process and communicates with DBX via stdin/stdo ## Multi-JRE Support -Most Java agents target JRE 21. Native agents, such as `oracle` and `xugu`, do not require a JRE. Agents that still require legacy Java runtimes (e.g. compatibility fallback `oracle-10g` uses JRE 8) declare their JRE version in the registry. DBX downloads and manages multiple JRE installations automatically. +Most Java agents target JRE 21. Native agents, such as `oracle` and `xugu`, do not require a JRE. DBX downloads and manages the JRE 21 installation automatically for Java agents. ## Build -Requires JDK 8 and 21 (Gradle toolchain auto-downloads if needed). +Requires JDK 21 (Gradle toolchain auto-downloads if needed). ```bash ./gradlew shadowJar @@ -64,7 +62,7 @@ Output JARs are in `drivers/{module}/build/libs/`. Native agents build from `dri ### Local DBX Runtime Test -When changing `agents/drivers//` or shared Java agent protocol code, rebuild the target agent and replace the runtime JAR used by the local DBX app: +When changing a Java agent under `agents/drivers//` or shared Java agent protocol code, rebuild the target agent and replace the runtime JAR used by the local DBX app: ```bash ./gradlew ::shadowJar @@ -74,6 +72,8 @@ cp agents/drivers//build/libs/*-all.jar ~/.dbx/agents/drivers/ Restart DBX or disconnect and reconnect the database so the new agent process loads the replacement JAR. +Native agents such as `oracle` and `xugu` use the `agent` executable in the driver directory instead of `agent.jar`. + ## Development - Agent authoring guide: [docs/agent-authoring.md](docs/agent-authoring.md) diff --git a/agents/build.gradle b/agents/build.gradle index 4891f9e63..b3dff3914 100644 --- a/agents/build.gradle +++ b/agents/build.gradle @@ -2,7 +2,7 @@ plugins { id 'com.gradleup.shadow' version '9.0.0-beta12' apply false } -def java8Projects = ['common', 'test-support', 'oracle-10g'] as Set +def java8Projects = ['common', 'test-support'] as Set def infrastructureProjects = ['common', 'test-support'] as Set def legacyStandaloneProjects = ['mongodb'] as Set def agentProjects = subprojects.findAll { !infrastructureProjects.contains(it.name) } diff --git a/agents/docs/agent-authoring.md b/agents/docs/agent-authoring.md index ec6b6ede7..5aecf95be 100644 --- a/agents/docs/agent-authoring.md +++ b/agents/docs/agent-authoring.md @@ -182,7 +182,7 @@ The root `build.gradle` convention derives the archive name from the module name Default to: ```groovy -def java8Projects = ['common', 'test-support', 'oracle-10g'] as Set +def java8Projects = ['common', 'test-support'] as Set subprojects { java { @@ -193,7 +193,7 @@ subprojects { } ``` -Most agents use JRE 21. Only use JRE 8 for drivers that require it, such as legacy Oracle 10g support. If an agent needs a special runtime, update the root Gradle convention, the release workflow JRE detection logic, and document the reason in the module. +Most agents use JRE 21. If an agent needs a special runtime, update the root Gradle convention, the release workflow JRE detection logic, and document the reason in the module. ## Tests diff --git a/agents/docs/release-checklist.md b/agents/docs/release-checklist.md index dfd898cfd..f84b33ac3 100644 --- a/agents/docs/release-checklist.md +++ b/agents/docs/release-checklist.md @@ -113,15 +113,9 @@ Current external-driver agents include BigQuery and SunDB. ## 6. JRE Selection -Most agents are built for the default JRE key `21`, backed by JDK 21 in the release workflow. +Java agents are built for the default JRE key `21`, backed by JDK 21 in the release workflow. Native agents do not require a JRE. -Special case: - -- `oracle-10g` uses JRE key `8`. - -If another agent needs a different runtime: - -- Update the release workflow JRE detection logic. +If another agent needs a different runtime, update the release workflow JRE detection logic. - Document why in the module or release notes. - Verify DBX can download the matching runtime artifact. @@ -183,8 +177,7 @@ After the GitHub release finishes: - Download or inspect `agent-registry.json`. - Confirm every expected agent appears under `drivers`. - Confirm labels preserve spaces, for example `Google BigQuery`. -- Confirm `oracle-10g` uses JRE key `8`. -- Confirm other agents use JRE key `21`. +- Confirm Java agents use JRE key `21`. - Confirm `external_driver_required` is correct. - Confirm every jar URL, sha256, and size is present. - Spot-check at least one agent jar manifest: diff --git a/agents/drivers/oracle-10g/build.gradle b/agents/drivers/oracle-10g/build.gradle deleted file mode 100644 index a38986ab8..000000000 --- a/agents/drivers/oracle-10g/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.oracle.database.jdbc:ojdbc6:11.2.0.4' - implementation 'com.oracle.database.nls:orai18n:11.2.0.4' -} - -tasks.named('shadowJar') { - manifest { - attributes('Agent-Label': 'Oracle 10g', 'Main-Class': 'com.dbx.agent.oracle10g.Oracle10gAgent') - } -} diff --git a/agents/drivers/oracle-10g/src/main/java/com/dbx/agent/oracle10g/Oracle10gAgent.java b/agents/drivers/oracle-10g/src/main/java/com/dbx/agent/oracle10g/Oracle10gAgent.java deleted file mode 100644 index 565070159..000000000 --- a/agents/drivers/oracle-10g/src/main/java/com/dbx/agent/oracle10g/Oracle10gAgent.java +++ /dev/null @@ -1,593 +0,0 @@ -package com.dbx.agent.oracle10g; - -import com.dbx.agent.BaseDatabaseAgent; -import com.dbx.agent.ColumnInfo; -import com.dbx.agent.ConnectParams; -import com.dbx.agent.DatabaseInfo; -import com.dbx.agent.ExecuteQueryOptions; -import com.dbx.agent.ForeignKeyInfo; -import com.dbx.agent.IndexInfo; -import com.dbx.agent.JdbcExecutor; -import com.dbx.agent.JdbcIdentifiers; -import com.dbx.agent.JsonRpcServer; -import com.dbx.agent.ObjectInfo; -import com.dbx.agent.ObjectSource; -import com.dbx.agent.QueryPageOptions; -import com.dbx.agent.QueryPageResult; -import com.dbx.agent.QueryResult; -import com.dbx.agent.TableInfo; -import com.dbx.agent.TriggerInfo; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class Oracle10gAgent extends BaseDatabaseAgent { - private static final List SYSTEM_SCHEMAS = Arrays.asList( - "SYS", "SYSTEM", "SYSMAN", "DBSNMP", "SYSBACKUP", "SYSDG", "SYSKM", "OUTLN", - "AUDSYS", "LBACSYS", "DVF", "DVSYS", "APPQOSSYS", "CTXSYS", "MDSYS", "MDDATA", - "ORDSYS", "ORDDATA", "ORDPLUGINS", "XDB", "ANONYMOUS", "DIP", "EXFSYS", - "GSMADMIN_INTERNAL", "GSMCATUSER", "GSMUSER", "OJVMSYS", "OLAPSYS", - "ORACLE_OCM", "SI_INFORMTN_SCHEMA", "WMSYS", "XS$NULL", "DBSFWUSER", - "REMOTE_SCHEDULER_AGENT", "PDBADMIN", "DGPDB_INT", "OPS$ORACLE", - "GGSYS", "FLOWS_FILES", "APEX_PUBLIC_USER" - ); - private static final Pattern OFFSET_FETCH_RE = Pattern.compile( - "(.+?)\\s+OFFSET\\s+(\\d+)\\s+ROWS?\\s+FETCH\\s+(FIRST|NEXT)\\s+(\\d+)\\s+ROWS?\\s+ONLY", - Pattern.CASE_INSENSITIVE - ); - private static final Pattern FETCH_ONLY_RE = Pattern.compile( - "(.+?)\\s+FETCH\\s+(FIRST|NEXT)\\s+(\\d+)\\s+ROWS?\\s+ONLY", - Pattern.CASE_INSENSITIVE - ); - private static final Pattern PLSQL_OBJECT_DDL_RE = Pattern.compile( - "^\\s*CREATE\\s+(?:OR\\s+REPLACE\\s+)?(?:(?:NON)?EDITIONABLE\\s+)?(?:PROCEDURE|FUNCTION|PACKAGE(?:\\s+BODY)?|TRIGGER|TYPE(?:\\s+BODY)?)\\b", - Pattern.CASE_INSENSITIVE - ); - - private Connection connection; - - @Override - public Connection getConnection() { - return connection; - } - - public static String rewriteFetchFirst(String sql) { - Matcher offsetFetch = OFFSET_FETCH_RE.matcher(sql); - if (offsetFetch.matches()) { - String innerSql = offsetFetch.group(1); - long offset = Long.parseLong(offsetFetch.group(2)); - long limit = Long.parseLong(offsetFetch.group(4)); - long upper = offset + limit; - return "SELECT * FROM (SELECT a.*, ROWNUM rn__ FROM (" + innerSql + ") a WHERE ROWNUM <= " + upper + ") WHERE rn__ > " + offset; - } - - Matcher fetchOnly = FETCH_ONLY_RE.matcher(sql); - if (fetchOnly.matches()) { - String innerSql = fetchOnly.group(1); - long limit = Long.parseLong(fetchOnly.group(3)); - return "SELECT * FROM (" + innerSql + ") WHERE ROWNUM <= " + limit; - } - - return sql; - } - - @Override - public String setSchemaSQL(String schema) { - return "ALTER SESSION SET CURRENT_SCHEMA = " + JdbcIdentifiers.INSTANCE.doubleQuote(schema); - } - - @Override - public void connect(ConnectParams params) { - uncheckedVoid(() -> { - Class.forName("oracle.jdbc.OracleDriver"); - connection = DriverManager.getConnection(buildUrl(params), connectionProperties(params)); - }); - } - - @Override - public boolean testConnection(ConnectParams params) { - return unchecked(() -> { - Class.forName("oracle.jdbc.OracleDriver"); - try (Connection conn = DriverManager.getConnection(buildUrl(params), connectionProperties(params))) { - return conn.isValid(5); - } - }); - } - - @Override - public List listDatabases() { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = listDatabasesSql(0); - try (Statement stmt = requireConnected().createStatement(); - ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - result.add(new DatabaseInfo(rs.getString(1))); - } - } - return result; - }); - } - - static String listDatabasesSql(int visibleSchemaCount) { - StringBuilder sql = new StringBuilder() - .append("SELECT owner FROM (") - .append(" SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AS owner FROM DUAL") - .append(" UNION") - .append(" SELECT DISTINCT owner FROM all_tables") - .append(" UNION") - .append(" SELECT DISTINCT owner FROM all_views") - .append(" )") - .append(" WHERE owner IS NOT NULL") - .append(" AND owner NOT IN (").append(quotedSystemSchemas()).append(")") - .append(" AND owner NOT LIKE 'APEX_%'") - .append(" AND owner NOT LIKE 'FLOWS_%'"); - if (visibleSchemaCount > 0) { - sql.append(" AND owner IN ("); - for (int i = 0; i < visibleSchemaCount; i++) { - if (i > 0) sql.append(","); - sql.append("?"); - } - sql.append(")"); - } - sql.append(" ORDER BY owner"); - return sql.toString(); - } - - @Override - public List listSchemas() { - return unchecked(() -> { - List result = new ArrayList<>(); - for (DatabaseInfo database : listDatabases()) { - result.add(database.getName()); - } - return result; - }); - } - - @Override - public List listSchemas(List visibleSchemas) { - if (visibleSchemas == null) { - return listSchemas(); - } - if (visibleSchemas.isEmpty()) { - return Collections.emptyList(); - } - return unchecked(() -> { - List result = new ArrayList<>(); - try (PreparedStatement stmt = requireConnected().prepareStatement(listDatabasesSql(visibleSchemas.size()))) { - for (int i = 0; i < visibleSchemas.size(); i++) { - stmt.setString(i + 1, visibleSchemas.get(i)); - } - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(rs.getString(1)); - } - } - } - return result; - }); - } - - @Override - public List listTables(String schema) { - return unchecked(() -> { - List result = new ArrayList<>(); - try (PreparedStatement stmt = requireConnected().prepareStatement(listTablesSql())) { - stmt.setString(1, schema); - stmt.setString(2, schema); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new TableInfo(rs.getString(1), rs.getString(2), rs.getString(3))); - } - } - } - return result; - }); - } - - static String listTablesSql() { - return "SELECT OBJECT_NAME, TABLE_TYPE, COMMENTS" - + " FROM (" - + " SELECT t.TABLE_NAME AS OBJECT_NAME," - + " 'TABLE' AS TABLE_TYPE," - + " CAST(NULL AS VARCHAR2(4000)) AS COMMENTS" - + " FROM ALL_TABLES t" - + " WHERE t.OWNER = ?" - + " AND t.NESTED = 'NO'" - + " UNION ALL" - + " SELECT o.OBJECT_NAME," - + " 'VIEW' AS TABLE_TYPE," - + " CAST(NULL AS VARCHAR2(4000)) AS COMMENTS" - + " FROM ALL_OBJECTS o" - + " WHERE o.OWNER = ?" - + " AND o.OBJECT_TYPE = 'VIEW'" - + " ) ORDER BY OBJECT_NAME"; - } - - @Override - public List listObjects(String schema) { - return unchecked(() -> { - List result = new ArrayList<>(); - try (PreparedStatement stmt = requireConnected().prepareStatement(listObjectsSql())) { - stmt.setString(1, schema); - stmt.setString(2, schema); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new ObjectInfo(rs.getString(1), rs.getString(2), schema, null)); - } - } - } - return result; - }); - } - - static String listObjectsSql() { - return "SELECT OBJECT_NAME, OBJECT_TYPE" - + " FROM (" - + " SELECT t.TABLE_NAME AS OBJECT_NAME," - + " 'TABLE' AS OBJECT_TYPE" - + " FROM ALL_TABLES t" - + " WHERE t.OWNER = ?" - + " AND t.NESTED = 'NO'" - + " UNION ALL" - + " SELECT o.OBJECT_NAME," - + " o.OBJECT_TYPE" - + " FROM ALL_OBJECTS o" - + " WHERE o.OWNER = ?" - + " AND o.OBJECT_TYPE IN ('VIEW', 'PROCEDURE', 'FUNCTION')" - + " ) ORDER BY CASE OBJECT_TYPE WHEN 'TABLE' THEN 0 WHEN 'VIEW' THEN 1 WHEN 'PROCEDURE' THEN 2 ELSE 3 END, OBJECT_NAME"; - } - - @Override - public ObjectSource getObjectSource(String schema, String name, String objectType) { - return unchecked(() -> { - String upperType = objectType.toUpperCase(Locale.ROOT); - String source; - if ("VIEW".equals(upperType)) { - // ALL_VIEWS is more reliable than DBMS_METADATA.GET_DDL, - // which fails on XE editions where XSL stylesheets are missing. - String sql = "SELECT TEXT FROM ALL_VIEWS WHERE OWNER = ? AND VIEW_NAME = ?"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, name); - try (ResultSet rs = stmt.executeQuery()) { - source = rs.next() ? coalesce(rs.getString(1)) : ""; - } - } - } else { - String dbmsType; - switch (upperType) { - case "PROCEDURE": - dbmsType = "PROCEDURE"; - break; - case "FUNCTION": - dbmsType = "FUNCTION"; - break; - default: - throw new IllegalArgumentException("Unsupported object type: " + objectType); - } - String sql = "SELECT DBMS_METADATA.GET_DDL(?, ?, ?) FROM DUAL"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, dbmsType); - stmt.setString(2, name); - stmt.setString(3, schema); - try (ResultSet rs = stmt.executeQuery()) { - source = rs.next() ? coalesce(rs.getString(1)) : ""; - } - } - } - return new ObjectSource(name, objectType, schema, source); - }); - } - - @Override - public String getTableDdl(String schema, String table) { - return unchecked(() -> { - String sql = "SELECT DBMS_METADATA.GET_DDL(?, ?, ?) FROM DUAL"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, "TABLE"); - stmt.setString(2, table); - stmt.setString(3, schema); - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - return coalesce(rs.getString(1)); - } - } - } - throw new IllegalArgumentException("Table not found: " + schema + "." + table); - }); - } - - @Override - public List getColumns(String schema, String table) { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT c.COLUMN_NAME, c.DATA_TYPE, c.NULLABLE, c.DATA_PRECISION, c.DATA_SCALE," - + " c.DATA_LENGTH, c.CHAR_LENGTH, cc.COMMENTS," - + " CASE WHEN pk.COLUMN_NAME IS NULL THEN 0 ELSE 1 END AS IS_PK" - + " FROM ALL_TAB_COLUMNS c" - + " LEFT JOIN ALL_COL_COMMENTS cc" - + " ON cc.OWNER = c.OWNER AND cc.TABLE_NAME = c.TABLE_NAME AND cc.COLUMN_NAME = c.COLUMN_NAME" - + " LEFT JOIN (" - + " SELECT cols.COLUMN_NAME FROM ALL_CONS_COLUMNS cols" - + " JOIN ALL_CONSTRAINTS cons" - + " ON cols.CONSTRAINT_NAME = cons.CONSTRAINT_NAME AND cols.OWNER = cons.OWNER" - + " WHERE cons.CONSTRAINT_TYPE = 'P' AND cons.OWNER = ? AND cons.TABLE_NAME = ?" - + " ) pk ON pk.COLUMN_NAME = c.COLUMN_NAME" - + " WHERE c.OWNER = ? AND c.TABLE_NAME = ?" - + " ORDER BY c.COLUMN_ID"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - stmt.setString(3, schema); - stmt.setString(4, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - String name = rs.getString("COLUMN_NAME"); - String baseType = rs.getString("DATA_TYPE"); - Integer numPrec = intObject(rs, "DATA_PRECISION"); - Integer numScale = intObject(rs, "DATA_SCALE"); - Integer dataLen = intObject(rs, "DATA_LENGTH"); - Integer charLen = intObject(rs, "CHAR_LENGTH"); - String dataType = formatDataType(baseType, numPrec, numScale, dataLen, charLen); - - result.add(new ColumnInfo( - name, - dataType, - "Y".equals(rs.getString("NULLABLE")), - null, - rs.getInt("IS_PK") == 1, - null, - rs.getString("COMMENTS"), - numPrec, - numScale, - charLen - )); - } - } - } - return result; - }); - } - - @Override - public List listIndexes(String schema, String table) { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT i.INDEX_NAME," - + " RTRIM(XMLAGG(XMLELEMENT(e, ic.COLUMN_NAME || ',') ORDER BY ic.COLUMN_POSITION).EXTRACT('//text()').GETSTRINGVAL(), ',') AS COLUMNS," - + " i.UNIQUENESS," - + " CASE WHEN c.CONSTRAINT_TYPE = 'P' THEN 1 ELSE 0 END AS IS_PK," - + " i.INDEX_TYPE" - + " FROM ALL_INDEXES i" - + " JOIN ALL_IND_COLUMNS ic ON i.INDEX_NAME = ic.INDEX_NAME AND i.OWNER = ic.INDEX_OWNER AND i.TABLE_OWNER = ic.TABLE_OWNER" - + " LEFT JOIN ALL_CONSTRAINTS c ON i.INDEX_NAME = c.INDEX_NAME AND i.TABLE_OWNER = c.OWNER" - + " AND c.CONSTRAINT_TYPE = 'P'" - + " WHERE i.TABLE_OWNER = ? AND i.TABLE_NAME = ?" - + " GROUP BY i.INDEX_NAME, i.UNIQUENESS, c.CONSTRAINT_TYPE, i.INDEX_TYPE" - + " ORDER BY i.INDEX_NAME"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new IndexInfo( - rs.getString(1), - splitNonEmpty(coalesce(rs.getString(2)), ","), - "UNIQUE".equals(rs.getString(3)), - "1".equals(rs.getString(4)), - null, - rs.getString(5), - null, - null - )); - } - } - } - return result; - }); - } - - @Override - public List listForeignKeys(String schema, String table) { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT c.CONSTRAINT_NAME, cc.COLUMN_NAME, rc.TABLE_NAME, rcc.COLUMN_NAME" - + " FROM ALL_CONSTRAINTS c" - + " JOIN ALL_CONS_COLUMNS cc ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME AND c.OWNER = cc.OWNER" - + " JOIN ALL_CONSTRAINTS rc ON c.R_CONSTRAINT_NAME = rc.CONSTRAINT_NAME AND c.R_OWNER = rc.OWNER" - + " JOIN ALL_CONS_COLUMNS rcc ON rc.CONSTRAINT_NAME = rcc.CONSTRAINT_NAME AND rc.OWNER = rcc.OWNER" - + " WHERE c.CONSTRAINT_TYPE = 'R' AND c.OWNER = ? AND c.TABLE_NAME = ?" - + " ORDER BY c.CONSTRAINT_NAME"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new ForeignKeyInfo(rs.getString(1), rs.getString(2), rs.getString(3), rs.getString(4))); - } - } - } - return result; - }); - } - - @Override - public List listTriggers(String schema, String table) { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT TRIGGER_NAME, TRIGGERING_EVENT, TRIGGER_TYPE" - + " FROM ALL_TRIGGERS" - + " WHERE OWNER = ? AND TABLE_NAME = ?" - + " ORDER BY TRIGGER_NAME"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new TriggerInfo(rs.getString(1), rs.getString(2), rs.getString(3))); - } - } - } - return result; - }); - } - - @Override - public QueryResult executeQuery(String sql, String schema, ExecuteQueryOptions options) { - return JdbcExecutor.INSTANCE.execute( - requireConnected(), - prepareExecutableSql(sql), - schema, - this::setSchemaSQL, - options.getMaxRows(), - options.getFetchSize(), - options.getTimeoutSecs(), - this::stringResultValue - ); - } - - @Override - public QueryPageResult executeQueryPage(String sql, String schema, QueryPageOptions options) { - return JdbcExecutor.INSTANCE.executePage( - requireConnected(), - prepareExecutableSql(sql), - schema, - this::setSchemaSQL, - options, - this::stringResultValue - ); - } - - @Override - public QueryPageResult startTableRead(String sql, String schema, QueryPageOptions options) { - return JdbcExecutor.INSTANCE.startTableRead( - requireConnected(), - prepareExecutableSql(sql), - schema, - this::setSchemaSQL, - options, - this::stringResultValue - ); - } - - @Override - public void disconnect() { - uncheckedVoid(() -> { - if (connection != null) { - connection.close(); - } - connection = null; - }); - } - - private Object stringResultValue(ResultSet rs, int index, int sqlType) { - return unchecked(() -> { - return JdbcExecutor.stringResultValue(rs, index, sqlType); - }); - } - - static String buildUrl(ConnectParams params) { - String connectionString = params.getConnection_string(); - if (connectionString != null && !connectionString.trim().isEmpty()) { - return connectionString; - } - return "jdbc:oracle:thin:@" + params.getHost() + ":" + params.getPort() + "/" + serviceName(params); - } - - private static Properties connectionProperties(ConnectParams params) { - Properties props = new Properties(); - props.put("user", params.getUsername()); - props.put("password", params.getPassword()); - if (params.getDatabase().toUpperCase(Locale.ROOT).startsWith("SYSDBA:")) { - props.put("internal_logon", "SYSDBA"); - } - return props; - } - - private static String serviceName(ConnectParams params) { - String serviceName = params.getDatabase(); - if (serviceName.toUpperCase(Locale.ROOT).startsWith("SYSDBA:")) { - return serviceName.substring(7); - } - return serviceName; - } - - private static String quotedSystemSchemas() { - List quoted = new ArrayList<>(); - for (String schema : SYSTEM_SCHEMAS) { - quoted.add("'" + schema + "'"); - } - return String.join(",", quoted); - } - - private static String trimTrailingSemicolons(String sql) { - String trimmed = sql; - while (trimmed.endsWith(";")) { - trimmed = trimmed.substring(0, trimmed.length() - 1); - } - return trimmed; - } - - static String prepareExecutableSql(String sql) { - String trimmed = sql.trim(); - if (PLSQL_OBJECT_DDL_RE.matcher(trimmed).find()) { - return trimmed; - } - return rewriteFetchFirst(trimTrailingSemicolons(trimmed)); - } - - private static String formatDataType(String base, Integer numPrec, Integer numScale, Integer dataLen, Integer charLen) { - switch (base.toUpperCase(Locale.ROOT)) { - case "VARCHAR2": - case "NVARCHAR2": - case "CHAR": - case "NCHAR": - Integer length = charLen != null ? charLen : dataLen; - return length != null ? base + "(" + length + ")" : base; - case "NUMBER": - if (numPrec != null && numScale != null && numScale > 0) { - return base + "(" + numPrec + "," + numScale + ")"; - } - return numPrec != null && numPrec > 0 ? base + "(" + numPrec + ")" : base; - case "RAW": - return dataLen != null ? "RAW(" + dataLen + ")" : "RAW"; - default: - return base; - } - } - - private static Integer intObject(ResultSet rs, String column) throws Exception { - Object value = rs.getObject(column); - return value == null ? null : ((Number) value).intValue(); - } - - private static List splitNonEmpty(String value, String delimiter) { - List result = new ArrayList<>(); - for (String part : value.split(delimiter)) { - if (!part.isEmpty()) { - result.add(part); - } - } - return result; - } - - private static String coalesce(String value) { - return value == null ? "" : value; - } - - public static void main(String[] args) { - new JsonRpcServer(new Oracle10gAgent()).run(); - } -} diff --git a/agents/drivers/oracle-10g/src/test/java/com/dbx/agent/oracle10g/Oracle10gAgentTest.java b/agents/drivers/oracle-10g/src/test/java/com/dbx/agent/oracle10g/Oracle10gAgentTest.java deleted file mode 100644 index e62b7d2a0..000000000 --- a/agents/drivers/oracle-10g/src/test/java/com/dbx/agent/oracle10g/Oracle10gAgentTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.dbx.agent.oracle10g; - -import com.dbx.agent.test.TestSupport; -import com.dbx.agent.BaseDatabaseAgent; -import com.dbx.agent.ConnectParams; -import com.dbx.agent.DatabaseAgent; -import com.dbx.agent.ObjectInfo; -import com.dbx.agent.ObjectSource; -import com.dbx.agent.test.JdbcAgentFake; -import com.dbx.agent.test.JdbcFakeExecutionBehaviorTest; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Proxy; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class Oracle10gAgentTest extends JdbcFakeExecutionBehaviorTest { - @Override - protected DatabaseAgent createAgent() { - return new Oracle10gAgent(); - } - - @Override - protected String resultSetSql() { - return "CALL DBMS_XPLAN.DISPLAY_CURSOR()"; - } - - @Test - void agentInheritsBaseDatabaseAgent() { - Assertions.assertTrue(createAgent() instanceof BaseDatabaseAgent); - } - - @Test - void buildUrlUsesExplicitConnectionString() { - ConnectParams params = new ConnectParams( - "oracle.example.com", - 1521, - "ORCL", - "scott", - "tiger", - "", - "jdbc:oracle:thin:@oracle.example.com:1521:ORCL", - false - ); - - Assertions.assertEquals("jdbc:oracle:thin:@oracle.example.com:1521:ORCL", Oracle10gAgent.buildUrl(params)); - } - - @Test - void prepareExecutableSqlKeepsPlsqlObjectTerminator() { - String sql = "CREATE OR REPLACE PROCEDURE APP_PROC AS BEGIN NULL; END;"; - - Assertions.assertEquals(sql, Oracle10gAgent.prepareExecutableSql(sql)); - } - - @Test - void prepareExecutableSqlTrimsPlainStatementTerminator() { - Assertions.assertEquals("SELECT 1 FROM DUAL", Oracle10gAgent.prepareExecutableSql("SELECT 1 FROM DUAL;")); - } - - @Test - void prepareExecutableSqlStillRewritesFetchFirst() { - Assertions.assertEquals( - "SELECT * FROM (SELECT * FROM EMP) WHERE ROWNUM <= 10", - Oracle10gAgent.prepareExecutableSql("SELECT * FROM EMP FETCH FIRST 10 ROWS ONLY;") - ); - } - - @Test - void listsTablesViewsProceduresAndFunctions() { - Oracle10gAgent agent = new Oracle10gAgent(); - TestSupport.setPrivateConnection(agent, objectListConnection()); - - List objects = agent.listObjects("APP"); - - Assertions.assertEquals( - Arrays.asList("TABLE", "VIEW", "PROCEDURE", "FUNCTION"), - objects.stream().map(ObjectInfo::getObject_type).collect(Collectors.toList()) - ); - Assertions.assertEquals( - Arrays.asList("APP_TABLE", "APP_VIEW", "APP_PROC", "APP_FUNC"), - objects.stream().map(ObjectInfo::getName).collect(Collectors.toList()) - ); - } - - @Test - void listTablesSqlUsesSplitDictionaryQuery() { - String sql = Oracle10gAgent.listTablesSql().toUpperCase(Locale.ROOT); - - Assertions.assertTrue(sql.contains("ALL_TABLES"), sql); - Assertions.assertTrue(sql.contains("ALL_OBJECTS"), sql); - Assertions.assertTrue(sql.contains("UNION ALL"), sql); - Assertions.assertFalse(sql.contains("ALL_TAB_COMMENTS"), sql); - } - - @Test - void listDatabasesSqlCanApplyVisibleSchemaFilter() { - String sql = Oracle10gAgent.listDatabasesSql(2).toUpperCase(Locale.ROOT); - - Assertions.assertTrue(sql.contains("ALL_TABLES"), sql); - Assertions.assertTrue(sql.contains("ALL_VIEWS"), sql); - Assertions.assertTrue(sql.contains("OWNER IN (?,?)"), sql); - } - - @Test - void listObjectsSqlUsesSplitDictionaryQuery() { - String sql = Oracle10gAgent.listObjectsSql().toUpperCase(Locale.ROOT); - - Assertions.assertTrue(sql.contains("ALL_TABLES"), sql); - Assertions.assertTrue(sql.contains("ALL_OBJECTS"), sql); - Assertions.assertTrue(sql.contains("UNION ALL"), sql); - Assertions.assertFalse(sql.contains("ALL_TAB_COMMENTS"), sql); - } - - @Test - void loadsRoutineSourceFromDbmsMetadata() { - Oracle10gAgent agent = new Oracle10gAgent(); - TestSupport.setPrivateConnection( - agent, - metadataConnection(sql -> Arrays.asList( - Arrays.asList("CREATE OR REPLACE PROCEDURE APP_PROC AS BEGIN NULL; END;") - )) - ); - - ObjectSource source = agent.getObjectSource("APP", "APP_PROC", "PROCEDURE"); - - Assertions.assertEquals("APP_PROC", source.getName()); - Assertions.assertEquals("PROCEDURE", source.getObject_type()); - Assertions.assertEquals("APP", source.getSchema()); - Assertions.assertEquals("CREATE OR REPLACE PROCEDURE APP_PROC AS BEGIN NULL; END;", source.getSource()); - } - - private static Connection objectListConnection() { - return metadataConnection(sql -> { - if (sql.contains("'PROCEDURE'")) { - return Arrays.asList( - Arrays.asList("APP_TABLE", "TABLE"), - Arrays.asList("APP_VIEW", "VIEW"), - Arrays.asList("APP_PROC", "PROCEDURE"), - Arrays.asList("APP_FUNC", "FUNCTION") - ); - } - return Arrays.asList( - Arrays.asList("APP_TABLE", "TABLE", ""), - Arrays.asList("APP_VIEW", "VIEW", "") - ); - }); - } - - private static Connection metadataConnection(Function>> rowsForSql) { - return proxy(Connection.class, (unused, method, args) -> { - switch (method.getName()) { - case "prepareStatement": - return metadataStatement(rowsForSql.apply((String) args[0])); - case "close": - return null; - case "isClosed": - return false; - default: - return defaultValue(method.getReturnType()); - } - }); - } - - private static PreparedStatement metadataStatement(List> rows) { - return proxy(PreparedStatement.class, (unused, method, args) -> { - switch (method.getName()) { - case "executeQuery": - return metadataResultSet(rows); - case "setString": - case "close": - return null; - default: - return defaultValue(method.getReturnType()); - } - }); - } - - private static ResultSet metadataResultSet(List> rows) { - int[] index = {-1}; - return proxy(ResultSet.class, (unused, method, args) -> { - switch (method.getName()) { - case "next": - index[0] += 1; - return index[0] < rows.size(); - case "getString": - return rows.get(index[0]).get(((Integer) args[0]) - 1); - case "close": - return null; - default: - return defaultValue(method.getReturnType()); - } - }); - } - - @SuppressWarnings("unchecked") - private static T proxy(Class type, InvocationHandler handler) { - return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, handler); - } - - private static Object defaultValue(Class type) { - if (type == Boolean.TYPE) { - return false; - } - if (type == Byte.TYPE) { - return (byte) 0; - } - if (type == Short.TYPE) { - return (short) 0; - } - if (type == Integer.TYPE) { - return 0; - } - if (type == Long.TYPE) { - return 0L; - } - if (type == Float.TYPE) { - return 0F; - } - if (type == Double.TYPE) { - return 0.0D; - } - if (type == Character.TYPE) { - return '\0'; - } - return null; - } -} diff --git a/agents/drivers/oracle-legacy/build.gradle b/agents/drivers/oracle-legacy/build.gradle deleted file mode 100644 index f43773d5e..000000000 --- a/agents/drivers/oracle-legacy/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -sourceSets { - main { - java { - srcDirs = ['src/main/java', '../oracle/src/main/java'] - } - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.oracle.database.jdbc:ojdbc8:19.26.0.0' - implementation 'com.oracle.database.nls:orai18n:19.26.0.0' -} - -tasks.named('shadowJar') { - manifest { - attributes('Agent-Label': 'Oracle Legacy', 'Main-Class': 'com.dbx.agent.oraclelegacy.OracleLegacyAgent') - } -} diff --git a/agents/drivers/oracle-legacy/src/main/java/com/dbx/agent/oraclelegacy/OracleLegacyAgent.java b/agents/drivers/oracle-legacy/src/main/java/com/dbx/agent/oraclelegacy/OracleLegacyAgent.java deleted file mode 100644 index d2fc003ea..000000000 --- a/agents/drivers/oracle-legacy/src/main/java/com/dbx/agent/oraclelegacy/OracleLegacyAgent.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.dbx.agent.oraclelegacy; - -import com.dbx.agent.JsonRpcServer; -import com.dbx.agent.oracle.OracleAgent; - -public final class OracleLegacyAgent extends OracleAgent { - public static void main(String[] args) { - new JsonRpcServer(new OracleLegacyAgent()).run(); - } -} diff --git a/agents/drivers/oracle/build.gradle b/agents/drivers/oracle/build.gradle deleted file mode 100644 index 32455273d..000000000 --- a/agents/drivers/oracle/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.oracle.database.jdbc:ojdbc17:23.26.2.0.0' - implementation 'com.oracle.database.nls:orai18n:23.26.2.0.0' -} - -tasks.named('shadowJar') { - manifest { - attributes('Agent-Label': 'Oracle', 'Main-Class': 'com.dbx.agent.oracle.OracleAgent') - } -} diff --git a/agents/drivers/oracle/src/main/java/com/dbx/agent/oracle/OracleAgent.java b/agents/drivers/oracle/src/main/java/com/dbx/agent/oracle/OracleAgent.java deleted file mode 100644 index 3cacc98c1..000000000 --- a/agents/drivers/oracle/src/main/java/com/dbx/agent/oracle/OracleAgent.java +++ /dev/null @@ -1,686 +0,0 @@ -package com.dbx.agent.oracle; - -import com.dbx.agent.BaseDatabaseAgent; -import com.dbx.agent.ColumnInfo; -import com.dbx.agent.ConnectParams; -import com.dbx.agent.DatabaseInfo; -import com.dbx.agent.ExecuteQueryOptions; -import com.dbx.agent.ForeignKeyInfo; -import com.dbx.agent.IndexInfo; -import com.dbx.agent.JdbcExecutor; -import com.dbx.agent.JdbcIdentifiers; -import com.dbx.agent.JsonRpcServer; -import com.dbx.agent.ObjectInfo; -import com.dbx.agent.ObjectSource; -import com.dbx.agent.QueryPageOptions; -import com.dbx.agent.QueryPageResult; -import com.dbx.agent.QueryResult; -import com.dbx.agent.TableInfo; -import com.dbx.agent.TriggerInfo; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class OracleAgent extends BaseDatabaseAgent { - private static final Set SYSTEM_SCHEMAS = Set.of( - "SYS", "SYSTEM", "SYSMAN", "DBSNMP", "SYSBACKUP", "SYSDG", "SYSKM", "OUTLN", - "AUDSYS", "LBACSYS", "DVF", "DVSYS", "APPQOSSYS", "CTXSYS", "MDSYS", "MDDATA", - "ORDSYS", "ORDDATA", "ORDPLUGINS", "XDB", "ANONYMOUS", "DIP", "EXFSYS", - "GSMADMIN_INTERNAL", "GSMCATUSER", "GSMUSER", "OJVMSYS", "OLAPSYS", - "ORACLE_OCM", "SI_INFORMTN_SCHEMA", "WMSYS", "XS$NULL", "DBSFWUSER", - "REMOTE_SCHEDULER_AGENT", "PDBADMIN", "DGPDB_INT", "OPS$ORACLE", - "GGSYS", "FLOWS_FILES", "APEX_PUBLIC_USER", "GSMROOTUSER", "SYSRAC" - ); - - private static final Pattern OFFSET_FETCH_RE = Pattern.compile( - "(.+?)\\s+OFFSET\\s+(\\d+)\\s+ROWS?\\s+FETCH\\s+(FIRST|NEXT)\\s+(\\d+)\\s+ROWS?\\s+ONLY", - Pattern.CASE_INSENSITIVE - ); - private static final Pattern FETCH_ONLY_RE = Pattern.compile( - "(.+?)\\s+FETCH\\s+(FIRST|NEXT)\\s+(\\d+)\\s+ROWS?\\s+ONLY", - Pattern.CASE_INSENSITIVE - ); - private static final Pattern PLSQL_OBJECT_DDL_RE = Pattern.compile( - "^\\s*CREATE\\s+(?:OR\\s+REPLACE\\s+)?(?:(?:NON)?EDITIONABLE\\s+)?(?:PROCEDURE|FUNCTION|PACKAGE(?:\\s+BODY)?|TRIGGER|TYPE(?:\\s+BODY)?)\\b", - Pattern.CASE_INSENSITIVE - ); - - private Connection connection; - - @Override - public Connection getConnection() { - return connection; - } - - public static String rewriteFetchFirst(String sql) { - Matcher offsetMatcher = OFFSET_FETCH_RE.matcher(sql); - if (offsetMatcher.matches()) { - String innerSql = offsetMatcher.group(1); - long offset = Long.parseLong(offsetMatcher.group(2)); - long limit = Long.parseLong(offsetMatcher.group(4)); - long upper = offset + limit; - return "SELECT * FROM (SELECT a.*, ROWNUM rn__ FROM (" + innerSql + ") a WHERE ROWNUM <= " + upper - + ") WHERE rn__ > " + offset; - } - - Matcher fetchMatcher = FETCH_ONLY_RE.matcher(sql); - if (fetchMatcher.matches()) { - String innerSql = fetchMatcher.group(1); - long limit = Long.parseLong(fetchMatcher.group(3)); - return "SELECT * FROM (" + innerSql + ") WHERE ROWNUM <= " + limit; - } - - return sql; - } - - @Override - public String setSchemaSQL(String schema) { - return "ALTER SESSION SET CURRENT_SCHEMA = " + JdbcIdentifiers.INSTANCE.doubleQuote(schema); - } - - @Override - public void connect(ConnectParams params) { - uncheckedVoid(() -> { - Class.forName("oracle.jdbc.OracleDriver"); - String serviceName = params.getDatabase(); - Properties props = connectionProperties(params); - - if (serviceName.toUpperCase(Locale.ROOT).startsWith("SYSDBA:")) { - serviceName = serviceName.substring(7); - props.setProperty("internal_logon", "SYSDBA"); - } - - connection = DriverManager.getConnection(buildUrl(params, serviceName), props); - try (var stmt = connection.createStatement()) { - stmt.execute("ALTER SESSION SET NLS_LANGUAGE='AMERICAN'"); - } - }); - } - - @Override - public boolean testConnection(ConnectParams params) { - return unchecked(() -> { - Class.forName("oracle.jdbc.OracleDriver"); - String serviceName = params.getDatabase(); - Properties props = connectionProperties(params); - - if (serviceName.toUpperCase(Locale.ROOT).startsWith("SYSDBA:")) { - serviceName = serviceName.substring(7); - props.setProperty("internal_logon", "SYSDBA"); - } - - try (Connection conn = DriverManager.getConnection(buildUrl(params, serviceName), props)) { - return conn.isValid(5); - } - }); - } - - @Override - public List listDatabases() { - return unchecked(() -> { - try { - return queryDatabaseInfos(listDatabasesSql()); - } catch (SQLException e) { - if (isPgaLimitError(e)) { - return currentSchemaDatabase(); - } - throw e; - } - }); - } - - static String listDatabasesSql() { - return listDatabasesSql(0); - } - - static String listDatabasesSql(int visibleSchemaCount) { - String placeholders = SYSTEM_SCHEMAS.stream() - .map(schema -> "'" + schema + "'") - .collect(Collectors.joining(",")); - String visibleSchemaClause = visibleSchemaCount <= 0 ? "" : " AND username IN (" + "?,".repeat(visibleSchemaCount).replaceAll(",$", "") + ")\n"; - return """ - SELECT username AS owner - FROM all_users - WHERE username IS NOT NULL - AND username NOT IN (%s) - AND username NOT LIKE 'APEX_%%' - AND username NOT LIKE 'FLOWS_%%' - AND username NOT LIKE '%%$%%' - %s - ORDER BY CASE - WHEN username = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') THEN 0 - WHEN username = SYS_CONTEXT('USERENV', 'SESSION_USER') THEN 1 - ELSE 2 - END, username - """.formatted(placeholders, visibleSchemaClause).stripIndent().trim(); - } - - static boolean isPgaLimitError(SQLException error) { - for (Throwable current = error; current != null; current = current.getCause()) { - if (current instanceof SQLException sqlException) { - if (sqlException.getErrorCode() == 4036 || String.valueOf(sqlException.getMessage()).contains("ORA-04036")) { - return true; - } - SQLException next = sqlException.getNextException(); - while (next != null) { - if (next.getErrorCode() == 4036 || String.valueOf(next.getMessage()).contains("ORA-04036")) { - return true; - } - next = next.getNextException(); - } - } else if (String.valueOf(current.getMessage()).contains("ORA-04036")) { - return true; - } - } - return false; - } - - private List queryDatabaseInfos(String sql) throws SQLException { - List result = new ArrayList<>(); - try (var stmt = requireConnected().createStatement(); - ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - result.add(new DatabaseInfo(rs.getString(1))); - } - } - return result; - } - - private List currentSchemaDatabase() throws SQLException { - try (var stmt = requireConnected().createStatement(); - ResultSet rs = stmt.executeQuery("SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') FROM DUAL")) { - if (rs.next()) { - String schema = rs.getString(1); - if (schema != null && !schema.isBlank()) { - return List.of(new DatabaseInfo(schema)); - } - } - } - return List.of(); - } - - @Override - public List listSchemas() { - List result = new ArrayList<>(); - for (DatabaseInfo database : listDatabases()) { - result.add(database.getName()); - } - return result; - } - - @Override - public List listSchemas(List visibleSchemas) { - if (visibleSchemas == null) { - return listSchemas(); - } - if (visibleSchemas.isEmpty()) { - return List.of(); - } - return unchecked(() -> { - List result = new ArrayList<>(); - try (var stmt = requireConnected().prepareStatement(listDatabasesSql(visibleSchemas.size()))) { - for (int i = 0; i < visibleSchemas.size(); i++) { - stmt.setString(i + 1, visibleSchemas.get(i)); - } - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(rs.getString(1)); - } - } - } - return result; - }); - } - - @Override - public List listTables(String schema) { - return unchecked(() -> { - try { - List result = new ArrayList<>(); - try (var stmt = requireConnected().prepareStatement(listTablesSql())) { - stmt.setString(1, schema); - stmt.setString(2, schema); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new TableInfo(rs.getString(1), rs.getString(2), rs.getString(3))); - } - } - } - return result; - } catch (SQLException e) { - if (isPgaLimitError(e)) { - return List.of(); - } - throw e; - } - }); - } - - static String listTablesSql() { - return """ - SELECT OBJECT_NAME, TABLE_TYPE, COMMENTS - FROM ( - SELECT t.TABLE_NAME AS OBJECT_NAME, - 'TABLE' AS TABLE_TYPE, - CAST(NULL AS VARCHAR2(4000)) AS COMMENTS - FROM ALL_TABLES t - WHERE t.OWNER = ? - AND t.NESTED = 'NO' - UNION ALL - SELECT o.OBJECT_NAME, - 'VIEW' AS TABLE_TYPE, - CAST(NULL AS VARCHAR2(4000)) AS COMMENTS - FROM ALL_OBJECTS o - WHERE o.OWNER = ? - AND o.OBJECT_TYPE = 'VIEW' - ) - ORDER BY OBJECT_NAME - """.stripIndent().trim(); - } - - @Override - public List listObjects(String schema) { - return unchecked(() -> { - try { - List result = new ArrayList<>(); - try (var stmt = requireConnected().prepareStatement(listObjectsSql())) { - stmt.setString(1, schema); - stmt.setString(2, schema); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new ObjectInfo(rs.getString(1), rs.getString(2), schema, null)); - } - } - } - return result; - } catch (SQLException e) { - if (isPgaLimitError(e)) { - return List.of(); - } - throw e; - } - }); - } - - static String listObjectsSql() { - return """ - SELECT OBJECT_NAME, OBJECT_TYPE - FROM ( - SELECT t.TABLE_NAME AS OBJECT_NAME, - 'TABLE' AS OBJECT_TYPE - FROM ALL_TABLES t - WHERE t.OWNER = ? - AND t.NESTED = 'NO' - UNION ALL - SELECT o.OBJECT_NAME, - o.OBJECT_TYPE - FROM ALL_OBJECTS o - WHERE o.OWNER = ? - AND o.OBJECT_TYPE IN ('VIEW', 'PROCEDURE', 'FUNCTION') - ) - ORDER BY CASE OBJECT_TYPE WHEN 'TABLE' THEN 0 WHEN 'VIEW' THEN 1 WHEN 'PROCEDURE' THEN 2 ELSE 3 END, OBJECT_NAME - """.stripIndent().trim(); - } - - @Override - public ObjectSource getObjectSource(String schema, String name, String objectType) { - return unchecked(() -> { - String upperType = objectType.toUpperCase(Locale.ROOT); - String source = ""; - if ("VIEW".equals(upperType)) { - // ALL_VIEWS is more reliable than DBMS_METADATA.GET_DDL, - // which fails on XE editions where XSL stylesheets are missing. - try (var stmt = requireConnected().prepareStatement( - "SELECT TEXT FROM ALL_VIEWS WHERE OWNER = ? AND VIEW_NAME = ?")) { - stmt.setString(1, schema); - stmt.setString(2, name); - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - String value = rs.getString(1); - source = value == null ? "" : value; - } - } - } - } else { - String dbmsType = switch (upperType) { - case "PROCEDURE" -> "PROCEDURE"; - case "FUNCTION" -> "FUNCTION"; - default -> throw new IllegalArgumentException("Unsupported object type: " + objectType); - }; - try (var stmt = requireConnected().prepareStatement( - "SELECT DBMS_METADATA.GET_DDL(?, ?, ?) FROM DUAL")) { - stmt.setString(1, dbmsType); - stmt.setString(2, name); - stmt.setString(3, schema); - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - String value = rs.getString(1); - source = value == null ? "" : value; - } - } - } - } - return new ObjectSource(name, objectType, schema, source); - }); - } - - @Override - public String getTableDdl(String schema, String table) { - return unchecked(() -> { - try (var stmt = requireConnected().prepareStatement("SELECT DBMS_METADATA.GET_DDL(?, ?, ?) FROM DUAL")) { - stmt.setString(1, "TABLE"); - stmt.setString(2, table); - stmt.setString(3, schema); - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - String value = rs.getString(1); - return value == null ? "" : value; - } - } - } - throw new IllegalArgumentException("Table not found: " + schema + "." + table); - }); - } - - @Override - public List getColumns(String schema, String table) { - return unchecked(() -> { - String sql = """ - SELECT c.COLUMN_NAME, c.DATA_TYPE, c.NULLABLE, c.DATA_PRECISION, c.DATA_SCALE, - c.DATA_LENGTH, c.CHAR_LENGTH, cc.COMMENTS, - CASE WHEN pk.COLUMN_NAME IS NULL THEN 0 ELSE 1 END AS IS_PK - FROM ALL_TAB_COLUMNS c - LEFT JOIN ALL_COL_COMMENTS cc - ON cc.OWNER = c.OWNER AND cc.TABLE_NAME = c.TABLE_NAME AND cc.COLUMN_NAME = c.COLUMN_NAME - LEFT JOIN ( - SELECT cols.COLUMN_NAME FROM ALL_CONS_COLUMNS cols - JOIN ALL_CONSTRAINTS cons - ON cols.CONSTRAINT_NAME = cons.CONSTRAINT_NAME AND cols.OWNER = cons.OWNER - WHERE cons.CONSTRAINT_TYPE = 'P' AND cons.OWNER = ? AND cons.TABLE_NAME = ? - ) pk ON pk.COLUMN_NAME = c.COLUMN_NAME - WHERE c.OWNER = ? AND c.TABLE_NAME = ? - ORDER BY c.COLUMN_ID - """.stripIndent().trim(); - - List result = new ArrayList<>(); - try (var stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - stmt.setString(3, schema); - stmt.setString(4, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - String name = rs.getString("COLUMN_NAME"); - String baseType = rs.getString("DATA_TYPE"); - Integer numPrec = intOrNull(rs, "DATA_PRECISION"); - Integer numScale = intOrNull(rs, "DATA_SCALE"); - Integer dataLen = intOrNull(rs, "DATA_LENGTH"); - Integer charLen = intOrNull(rs, "CHAR_LENGTH"); - String dataType = formatDataType(baseType, numPrec, numScale, dataLen, charLen); - - result.add(new ColumnInfo( - name, - dataType, - "Y".equals(rs.getString("NULLABLE")), - null, - rs.getInt("IS_PK") == 1, - null, - rs.getString("COMMENTS"), - numPrec, - numScale, - charLen - )); - } - } - } - return result; - }); - } - - @Override - public List listIndexes(String schema, String table) { - return unchecked(() -> { - String sql = """ - SELECT i.INDEX_NAME, - LISTAGG(ic.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY ic.COLUMN_POSITION) AS COLUMNS, - i.UNIQUENESS, - CASE WHEN c.CONSTRAINT_TYPE = 'P' THEN 1 ELSE 0 END AS IS_PK, - i.INDEX_TYPE - FROM ALL_INDEXES i - JOIN ALL_IND_COLUMNS ic ON i.INDEX_NAME = ic.INDEX_NAME AND i.OWNER = ic.INDEX_OWNER AND i.TABLE_OWNER = ic.TABLE_OWNER - LEFT JOIN ALL_CONSTRAINTS c ON i.INDEX_NAME = c.INDEX_NAME AND i.TABLE_OWNER = c.OWNER - AND c.CONSTRAINT_TYPE = 'P' - WHERE i.TABLE_OWNER = ? AND i.TABLE_NAME = ? - GROUP BY i.INDEX_NAME, i.UNIQUENESS, c.CONSTRAINT_TYPE, i.INDEX_TYPE - ORDER BY i.INDEX_NAME - """.stripIndent().trim(); - - List result = new ArrayList<>(); - try (var stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - String colsStr = rs.getString(2); - List columns = new ArrayList<>(); - if (colsStr != null && !colsStr.isEmpty()) { - columns.addAll(List.of(colsStr.split(","))); - } - result.add(new IndexInfo( - rs.getString(1), - columns, - "UNIQUE".equals(rs.getString(3)), - "1".equals(rs.getString(4)), - null, - rs.getString(5), - null, - null - )); - } - } - } - return result; - }); - } - - @Override - public List listForeignKeys(String schema, String table) { - return unchecked(() -> { - String sql = """ - SELECT c.CONSTRAINT_NAME, cc.COLUMN_NAME, rc.TABLE_NAME, rcc.COLUMN_NAME - FROM ALL_CONSTRAINTS c - JOIN ALL_CONS_COLUMNS cc ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME AND c.OWNER = cc.OWNER - JOIN ALL_CONSTRAINTS rc ON c.R_CONSTRAINT_NAME = rc.CONSTRAINT_NAME AND c.R_OWNER = rc.OWNER - JOIN ALL_CONS_COLUMNS rcc ON rc.CONSTRAINT_NAME = rcc.CONSTRAINT_NAME AND rc.OWNER = rcc.OWNER - WHERE c.CONSTRAINT_TYPE = 'R' AND c.OWNER = ? AND c.TABLE_NAME = ? - ORDER BY c.CONSTRAINT_NAME - """.stripIndent().trim(); - - List result = new ArrayList<>(); - try (var stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new ForeignKeyInfo( - rs.getString(1), - rs.getString(2), - rs.getString(3), - rs.getString(4) - )); - } - } - } - return result; - }); - } - - @Override - public List listTriggers(String schema, String table) { - return unchecked(() -> { - String sql = """ - SELECT TRIGGER_NAME, TRIGGERING_EVENT, TRIGGER_TYPE - FROM ALL_TRIGGERS - WHERE OWNER = ? AND TABLE_NAME = ? - ORDER BY TRIGGER_NAME - """.stripIndent().trim(); - - List result = new ArrayList<>(); - try (var stmt = requireConnected().prepareStatement(sql)) { - stmt.setString(1, schema); - stmt.setString(2, table); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new TriggerInfo(rs.getString(1), rs.getString(2), rs.getString(3))); - } - } - } - return result; - }); - } - - @Override - public QueryResult executeQuery(String sql, String schema, ExecuteQueryOptions options) { - return JdbcExecutor.INSTANCE.execute( - requireConnected(), - prepareExecutableSql(sql), - schema, - this::setSchemaSQL, - options.getMaxRows(), - options.getFetchSize(), - options.getTimeoutSecs(), - OracleAgent::stringResultValue - ); - } - - @Override - public QueryPageResult executeQueryPage(String sql, String schema, QueryPageOptions options) { - return JdbcExecutor.INSTANCE.executePage( - requireConnected(), - prepareExecutableSql(sql), - schema, - this::setSchemaSQL, - options, - OracleAgent::stringResultValue - ); - } - - @Override - public QueryPageResult startTableRead(String sql, String schema, QueryPageOptions options) { - return JdbcExecutor.INSTANCE.startTableRead( - requireConnected(), - prepareExecutableSql(sql), - schema, - this::setSchemaSQL, - options, - OracleAgent::stringResultValue - ); - } - - @Override - public void disconnect() { - uncheckedVoid(() -> { - if (connection != null) { - connection.close(); - } - connection = null; - }); - } - - private static Properties connectionProperties(ConnectParams params) { - Properties props = new Properties(); - props.setProperty("user", params.getUsername()); - props.setProperty("password", params.getPassword()); - props.setProperty("oracle.jdbc.defaultNChar", "true"); - return props; - } - - static String buildUrl(ConnectParams params) { - return buildUrl(params, serviceName(params)); - } - - private static String buildUrl(ConnectParams params, String serviceName) { - String connectionString = params.getConnection_string(); - if (connectionString != null && !connectionString.trim().isEmpty()) { - return connectionString; - } - return "jdbc:oracle:thin:@" + params.getHost() + ":" + params.getPort() + "/" + serviceName; - } - - private static String serviceName(ConnectParams params) { - String serviceName = params.getDatabase(); - if (serviceName.toUpperCase(Locale.ROOT).startsWith("SYSDBA:")) { - return serviceName.substring(7); - } - return serviceName; - } - - private static String formatDataType( - String base, - Integer numPrec, - Integer numScale, - Integer dataLen, - Integer charLen - ) { - return switch (base.toUpperCase(Locale.ROOT)) { - case "VARCHAR2", "NVARCHAR2", "CHAR", "NCHAR" -> { - Integer len = charLen == null ? dataLen : charLen; - yield len == null ? base : base + "(" + len + ")"; - } - case "NUMBER" -> { - if (numPrec != null && numScale != null && numScale > 0) { - yield base + "(" + numPrec + "," + numScale + ")"; - } - if (numPrec != null && numPrec > 0) { - yield base + "(" + numPrec + ")"; - } - yield base; - } - case "RAW" -> dataLen == null ? "RAW" : "RAW(" + dataLen + ")"; - default -> base; - }; - } - - private static Object stringResultValue(ResultSet rs, int index, int sqlType) { - try { - return JdbcExecutor.stringResultValue(rs, index, sqlType); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - private static Integer intOrNull(ResultSet rs, String column) throws Exception { - Object value = rs.getObject(column); - return value instanceof Number ? ((Number) value).intValue() : null; - } - - private static String trimEndSemicolons(String sql) { - String trimmed = sql; - while (trimmed.endsWith(";")) { - trimmed = trimmed.substring(0, trimmed.length() - 1); - } - return trimmed; - } - - static String prepareExecutableSql(String sql) { - String trimmed = sql.trim(); - if (PLSQL_OBJECT_DDL_RE.matcher(trimmed).find()) { - return trimmed; - } - return rewriteFetchFirst(trimEndSemicolons(trimmed)); - } - - public static void main(String[] args) { - new JsonRpcServer(new OracleAgent()).run(); - } -} diff --git a/agents/drivers/oracle/src/test/java/com/dbx/agent/oracle/OracleAgentTest.java b/agents/drivers/oracle/src/test/java/com/dbx/agent/oracle/OracleAgentTest.java deleted file mode 100644 index 55ad3282d..000000000 --- a/agents/drivers/oracle/src/test/java/com/dbx/agent/oracle/OracleAgentTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.dbx.agent.oracle; - -import com.dbx.agent.DatabaseAgent; -import com.dbx.agent.ConnectParams; -import com.dbx.agent.test.JdbcFakeExecutionBehaviorTest; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.sql.SQLException; -import java.util.List; -import java.util.Locale; - -class OracleAgentTest extends JdbcFakeExecutionBehaviorTest { - @Override - protected DatabaseAgent createAgent() { - return new OracleAgent(); - } - - @Override - protected String resultSetSql() { - return "CALL DBMS_XPLAN.DISPLAY_CURSOR()"; - } - - @Test - void buildUrlUsesExplicitConnectionString() { - ConnectParams params = new ConnectParams( - "oracle.example.com", - 1521, - "ORCL", - "scott", - "tiger", - "", - "jdbc:oracle:thin:@oracle.example.com:1521:ORCL", - false - ); - - Assertions.assertEquals("jdbc:oracle:thin:@oracle.example.com:1521:ORCL", OracleAgent.buildUrl(params)); - } - - @Test - void prepareExecutableSqlKeepsPlsqlObjectTerminator() { - String sql = "CREATE OR REPLACE PROCEDURE APP_PROC AS BEGIN NULL; END;"; - - Assertions.assertEquals(sql, OracleAgent.prepareExecutableSql(sql)); - } - - @Test - void prepareExecutableSqlTrimsPlainStatementTerminator() { - Assertions.assertEquals("SELECT 1 FROM DUAL", OracleAgent.prepareExecutableSql("SELECT 1 FROM DUAL;")); - } - - @Test - void prepareExecutableSqlStillRewritesFetchFirst() { - Assertions.assertEquals( - "SELECT * FROM (SELECT * FROM EMP) WHERE ROWNUM <= 10", - OracleAgent.prepareExecutableSql("SELECT * FROM EMP FETCH FIRST 10 ROWS ONLY;") - ); - } - - @Test - void listDatabasesSqlUsesUserDictionaryInsteadOfObjectDictionary() { - String sql = OracleAgent.listDatabasesSql().toUpperCase(Locale.ROOT); - - Assertions.assertTrue(sql.contains("ALL_USERS")); - Assertions.assertFalse(sql.contains("ALL_TABLES")); - Assertions.assertFalse(sql.contains("ALL_VIEWS")); - } - - @Test - void listDatabasesSqlCanApplyVisibleSchemaFilter() { - String sql = OracleAgent.listDatabasesSql(2).toUpperCase(Locale.ROOT); - - Assertions.assertTrue(sql.contains("ALL_USERS"), sql); - Assertions.assertTrue(sql.contains("USERNAME IN (?,?)"), sql); - Assertions.assertFalse(sql.contains("ALL_TABLES"), sql); - } - - @Test - void listSchemasWithEmptyVisibleFilterSkipsMetadataQuery() { - OracleAgent agent = new OracleAgent(); - - Assertions.assertEquals(List.of(), agent.listSchemas(List.of())); - } - - @Test - void listTablesSqlUsesSplitDictionaryQuery() { - String sql = OracleAgent.listTablesSql().toUpperCase(Locale.ROOT); - - Assertions.assertTrue(sql.contains("ALL_TABLES"), sql); - Assertions.assertTrue(sql.contains("ALL_OBJECTS"), sql); - Assertions.assertTrue(sql.contains("UNION ALL"), sql); - Assertions.assertFalse(sql.contains("ALL_TAB_COMMENTS"), sql); - } - - @Test - void listObjectsSqlUsesSplitDictionaryQuery() { - String sql = OracleAgent.listObjectsSql().toUpperCase(Locale.ROOT); - - Assertions.assertTrue(sql.contains("ALL_TABLES"), sql); - Assertions.assertTrue(sql.contains("ALL_OBJECTS"), sql); - Assertions.assertTrue(sql.contains("UNION ALL"), sql); - Assertions.assertFalse(sql.contains("ALL_TAB_COMMENTS"), sql); - } - - @Test - void detectsPgaLimitError() { - Assertions.assertTrue(OracleAgent.isPgaLimitError(new SQLException( - "ORA-04036: PGA memory used by the instance exceeds PGA_AGGREGATE_LIMIT", - "72000", - 4036 - ))); - } -} diff --git a/agents/scripts/validate_agents.py b/agents/scripts/validate_agents.py index 8d2398dcd..fe12530b3 100644 --- a/agents/scripts/validate_agents.py +++ b/agents/scripts/validate_agents.py @@ -11,9 +11,11 @@ SOURCE_GLOBS = ("*/src/main/**/*.java", "drivers/*/src/main/**/*.java") KOTLIN_FILE_SUFFIXES = (".kt", ".kts") KOTLIN_SCAN_EXCLUDED_PARTS = {".git", ".gradle", "build"} DEFAULT_AGENT_JRE_KEY = "21" -LEGACY_ORACLE_JRE_KEY = "8" NON_JDBC_AGENT_MODULES = {"mongodb", "etcd", "zookeeper"} -NATIVE_ONLY_AGENT_MODULES = {"xugu"} +NATIVE_ONLY_AGENT_MODULES = { + "oracle": "drivers/oracle-go", + "xugu": "drivers/xugu", +} JDBC_ARCHITECTURE_ALLOWLIST = { "access": "custom Access metadata and URL behavior pending migration", "dameng": "custom Dameng metadata and DDL pending migration", @@ -23,9 +25,6 @@ JDBC_ARCHITECTURE_ALLOWLIST = { "goldendb": "custom GoldenDB metadata pending migration", "informix": "custom Informix metadata pending migration", "neo4j": "custom Neo4j transaction/query behavior pending migration", - "oracle": "custom Oracle metadata and connection properties pending migration", - "oracle-legacy": "custom Oracle legacy metadata and connection properties pending migration", - "oracle-10g": "custom Oracle 10g metadata and Java 8 runtime pending migration", "sundb": "custom SunDB metadata pending migration", "tdengine": "custom TDengine WebSocket JDBC behavior pending migration", } @@ -69,11 +68,13 @@ def included_agent_modules(root: Path) -> set[str]: def agent_modules(root: Path) -> set[str]: - native = {name for name in NATIVE_ONLY_AGENT_MODULES if module_dir(root, name).exists()} + native = {name for name, path in NATIVE_ONLY_AGENT_MODULES.items() if (root / path).exists()} return included_agent_modules(root) | native def module_dir(root: Path, module: str) -> Path: + if module in NATIVE_ONLY_AGENT_MODULES: + return root / NATIVE_ONLY_AGENT_MODULES[module] nested = root / "drivers" / module if nested.exists(): return nested @@ -162,7 +163,7 @@ def validate_manifest_fields(root: Path, modules: set[str]) -> list[str]: def validate_jdbc_architecture(root: Path, modules: set[str]) -> list[str]: problems: list[str] = [] - for module in sorted(modules - NON_JDBC_AGENT_MODULES - NATIVE_ONLY_AGENT_MODULES): + for module in sorted(modules - NON_JDBC_AGENT_MODULES - set(NATIVE_ONLY_AGENT_MODULES)): source = main_class_source(root, module) if source is None or not source.exists(): continue @@ -227,13 +228,9 @@ def validate_release_runtime_keys(root: Path) -> list[str]: rf'java-version:\s*"{DEFAULT_AGENT_JRE_KEY}"', f"release workflow must build the default JRE from Java {DEFAULT_AGENT_JRE_KEY}", ), - ( - rf'oracle-10g\)\s*echo\s*"{LEGACY_ORACLE_JRE_KEY}"', - f"oracle-10g must keep JRE key {LEGACY_ORACLE_JRE_KEY}", - ), ( rf'\*\)\s*echo\s*"{DEFAULT_AGENT_JRE_KEY}"', - f"non-legacy agents must use JRE key {DEFAULT_AGENT_JRE_KEY}", + f"agents must use JRE key {DEFAULT_AGENT_JRE_KEY}", ), ( rf'"{DEFAULT_AGENT_JRE_KEY}":\s*\{{\s*"version":\s*"{DEFAULT_AGENT_JRE_KEY}\.', @@ -249,7 +246,7 @@ def validate_release_runtime_keys(root: Path) -> list[str]: problems.append(message) forbidden_patterns = [ (r'jre-key:\s*"17"', "release workflow must not build Java 21 under JRE key 17"), - (r'\*\)\s*echo\s*"17"', "non-legacy agents must not use JRE key 17"), + (r'\*\)\s*echo\s*"17"', "agents must not use JRE key 17"), (r'"17":\s*\{\s*"version":\s*"21\.', "registry must not publish Java 21 under JRE key 17"), ] for pattern, message in forbidden_patterns: diff --git a/agents/scripts/validate_agents_test.py b/agents/scripts/validate_agents_test.py index 8c6dedbb1..a53ad8911 100644 --- a/agents/scripts/validate_agents_test.py +++ b/agents/scripts/validate_agents_test.py @@ -127,33 +127,22 @@ class ValidateAgentsTest(unittest.TestCase): problems, ) - def test_jdbc_architecture_allows_documented_migration_exceptions(self): + def test_versions_include_native_only_modules(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) - module = root / "oracle" - source = module / "src/main/java/com/dbx/agent/oracle/OracleAgent.java" - source.parent.mkdir(parents=True) - (module / "build.gradle").write_text( - textwrap.dedent( - """ - tasks.named('shadowJar') { - manifest { - attributes( - 'Agent-Label': 'Oracle', - 'Main-Class': 'com.dbx.agent.oracle.OracleAgent' - ) - } - } - """ - ), + (root / "settings.gradle").write_text( + "def infrastructureModules = ['common', 'test-support']\n" + "def driverModules = ['h2']\n" + "include(*(infrastructureModules + driverModules))\n", encoding="utf-8", ) - source.write_text( - "package com.dbx.agent.oracle; public final class OracleAgent extends BaseDatabaseAgent {}\n", + (root / "drivers/oracle-go").mkdir(parents=True) + (root / "versions.json").write_text( + json.dumps({"h2": "0.1.0", "oracle": "0.1.0"}), encoding="utf-8", ) - self.assertEqual([], validate_agents.validate_jdbc_architecture(root, {"oracle"})) + self.assertEqual([], validate_agents.validate_versions(root)) def test_authoring_template_must_use_shared_foundation(self): with tempfile.TemporaryDirectory() as tmp: @@ -316,7 +305,7 @@ class ValidateAgentsTest(unittest.TestCase): problems, ) - def test_release_runtime_keys_match_java_21_default_and_oracle_8_exception(self): + def test_release_runtime_keys_match_java_21_default(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) workflow = root / ".github/workflows/release.yml" @@ -331,7 +320,6 @@ class ValidateAgentsTest(unittest.TestCase): java-version: "21" detect_jre_key() { case "$name" in - oracle-10g) echo "8" ;; *) echo "21" ;; esac } @@ -362,7 +350,6 @@ class ValidateAgentsTest(unittest.TestCase): java-version: "21" detect_jre_key() { case "$name" in - oracle-10g) echo "8" ;; *) echo "17" ;; esac } @@ -383,11 +370,11 @@ class ValidateAgentsTest(unittest.TestCase): self.assertEqual( [ "release workflow must build the default JRE with key 21", - "non-legacy agents must use JRE key 21", + "agents must use JRE key 21", "registry must publish Java 21 under JRE key 21", "native-only registry entries must publish a legacy jar placeholder for older DBX clients", "release workflow must not build Java 21 under JRE key 17", - "non-legacy agents must not use JRE key 17", + "agents must not use JRE key 17", "registry must not publish Java 21 under JRE key 17", ], validate_agents.validate_release_runtime_keys(root), diff --git a/agents/settings.gradle b/agents/settings.gradle index 4e1b3ae1a..d198956a1 100644 --- a/agents/settings.gradle +++ b/agents/settings.gradle @@ -3,8 +3,8 @@ rootProject.name = 'dbx-agents' def infrastructureModules = ['common', 'test-support'] def driverModules = [ 'access', 'dameng', 'kingbase', 'vastbase', 'goldendb', 'databend', 'databricks', 'saphana', - 'teradata', 'vertica', 'firebird', 'exasol', 'oceanbase-oracle', 'gbase8a', 'gbase8s', 'oracle', - 'oracle-legacy', 'oracle-10g', 'bigquery', 'kylin', 'sundb', 'h2', 'snowflake', 'trino', 'hive', + 'teradata', 'vertica', 'firebird', 'exasol', 'oceanbase-oracle', 'gbase8a', 'gbase8s', + 'bigquery', 'kylin', 'sundb', 'h2', 'snowflake', 'trino', 'hive', 'db2', 'informix', 'neo4j', 'cassandra', 'gaussdb', 'mongodb', 'highgo', 'tdengine', 'yashandb', 'iris', 'iotdb', 'etcd', 'zookeeper' ] diff --git a/agents/versions.json b/agents/versions.json index 4282dedc1..93c203fda 100644 --- a/agents/versions.json +++ b/agents/versions.json @@ -20,8 +20,6 @@ "neo4j": "0.1.25", "oceanbase-oracle": "0.1.20", "oracle": "0.1.29", - "oracle-legacy": "0.1.16", - "oracle-10g": "0.1.28", "snowflake": "0.1.25", "saphana": "0.1.20", "sundb": "0.1.25", diff --git a/crates/dbx-core/src/agent_connection.rs b/crates/dbx-core/src/agent_connection.rs index d2887334e..dc76d24b3 100644 --- a/crates/dbx-core/src/agent_connection.rs +++ b/crates/dbx-core/src/agent_connection.rs @@ -251,9 +251,6 @@ pub fn oracle_alternate_connect_configs(config: &ConnectionConfig, err: &str) -> if config.db_type != DatabaseType::Oracle { return Vec::new(); } - if config.driver_profile.as_deref() == Some("oracle-10g") { - return Vec::new(); - } if config.connection_string.as_deref().is_some_and(|value| !value.trim().is_empty()) { return Vec::new(); } @@ -402,21 +399,6 @@ fn postgres_like_agent_jdbc_connection_string( append_agent_url_params(base, config.url_params.as_deref()) } -pub fn should_retry_oracle_with_10g_driver(config: &ConnectionConfig, err: &str) -> bool { - !oracle_auth_fallback_profiles(config, err).is_empty() -} - -pub fn oracle_auth_fallback_profiles(config: &ConnectionConfig, err: &str) -> Vec<&'static str> { - if config.db_type != DatabaseType::Oracle { - return Vec::new(); - } - let normalized = err.to_lowercase(); - if !normalized.contains("ora-28040") && !normalized.contains("no matching authentication protocol") { - return Vec::new(); - } - Vec::new() -} - pub fn oracle_alternate_connect_config(config: &ConnectionConfig, err: &str) -> Option { oracle_alternate_connect_configs(config, err).into_iter().next() } @@ -749,10 +731,9 @@ mod tests { } #[test] - fn oracle_listener_error_hint_skips_other_databases() { + fn oracle_listener_error_hint_skips_non_oracle_databases() { let err = "Agent RPC error (-1): ORA-12541: TNS:no listener"; let mut cfg = config(DatabaseType::Oracle, Some("ORCL")); - cfg.driver_profile = Some("oracle-legacy".to_string()); assert!(oracle_error_with_driver_hint(&cfg, err).contains("Service Name")); @@ -909,20 +890,6 @@ mod tests { assert!(oracle_alternate_connect_config(&cfg, "ORA-12541: TNS:no listener").is_some()); } - #[test] - fn oracle_auth_errors_do_not_switch_driver_profiles() { - let mut cfg = config(DatabaseType::Oracle, Some("ORCL")); - cfg.driver_profile = Some("oracle".to_string()); - - assert!(oracle_auth_fallback_profiles(&cfg, "ORA-28040: No matching authentication protocol").is_empty()); - - cfg.driver_profile = Some("oracle-legacy".to_string()); - assert!(oracle_auth_fallback_profiles(&cfg, "ORA-28040: No matching authentication protocol").is_empty()); - - cfg.driver_profile = Some("oracle-10g".to_string()); - assert!(oracle_auth_fallback_profiles(&cfg, "ORA-28040: No matching authentication protocol").is_empty()); - } - #[test] fn oracle_custom_connection_string_skips_alternate_descriptor_retry() { let mut cfg = config(DatabaseType::Oracle, Some("ORCL")); diff --git a/crates/dbx-core/src/agent_manager.rs b/crates/dbx-core/src/agent_manager.rs index 554548c2b..c8c366304 100644 --- a/crates/dbx-core/src/agent_manager.rs +++ b/crates/dbx-core/src/agent_manager.rs @@ -215,8 +215,7 @@ mod tests { assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, None), Some("oracle")); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Gbase, Some("gbase8s")), Some("gbase8s")); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Gbase, None), Some("gbase8a")); - manager.stop_daemon_by_key("oracle-legacy").await; - manager.stop_daemon_by_key("oracle-10g").await; + manager.stop_daemon_by_key("oracle").await; manager.stop_daemon_by_key("gbase8s").await; } @@ -486,7 +485,7 @@ impl AgentManager { if flat.exists() { return flat; } - // macOS Adoptium JRE 8 uses Contents/Home/ layout + // Some macOS runtimes are unpacked with a Contents/Home/ layout. let macos = dir.join("Contents").join("Home").join("bin").join(java_name); if macos.exists() { return macos; diff --git a/crates/dbx-core/src/agent_runtime.rs b/crates/dbx-core/src/agent_runtime.rs index 85697673d..4a72bc5dc 100644 --- a/crates/dbx-core/src/agent_runtime.rs +++ b/crates/dbx-core/src/agent_runtime.rs @@ -127,13 +127,6 @@ pub async fn call_daemon_method_with_timeout) -> Option> { let primary = db_type_to_agent_key(db_type, driver_profile)?; - if *db_type == DatabaseType::Oracle { - return match driver_profile { - Some("oracle-legacy") => Some(vec![primary, "oracle-legacy"]), - Some("oracle-10g") => Some(vec![primary, "oracle-10g"]), - _ => Some(vec![primary]), - }; - } Some(vec![primary]) } diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index a4ac772cf..1de5ec1cc 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -11,8 +11,7 @@ use mysql_async::Row as MysqlRow; use crate::agent_connection::{ agent_connect_params, h2_file_path_from_jdbc_url, is_h2_file_connection, mongo_legacy_error_with_auth_hint, mongo_uses_legacy_driver, oracle_alternate_connect_config_labels, oracle_alternate_connect_configs, - oracle_auth_fallback_profiles, oracle_error_with_driver_hint, should_retry_mongo_with_legacy_driver, - should_retry_oracle_with_10g_driver, trino_like_jdbc_connection_string, + oracle_error_with_driver_hint, should_retry_mongo_with_legacy_driver, trino_like_jdbc_connection_string, }; use crate::agent_manager::{JavaRuntimeMode, DEFAULT_JRE_KEY}; use crate::database_capabilities; @@ -899,7 +898,7 @@ impl AppState { let connect_result = client .call_method_with_timeout::( AgentMethod::Connect, - connect_params.clone(), + connect_params, Some(agent_connect_timeout(&db_config)), ) .await; @@ -947,45 +946,6 @@ impl AppState { fallback_errors.join("\n") )); } - } else if should_retry_oracle_with_10g_driver(&db_config, &err) { - log::warn!( - "Oracle connect failed with profile {:?}: {}. Retrying with legacy Oracle profiles.", - db_config.driver_profile, - err - ); - let mut fallback_errors = Vec::new(); - let mut connected_client = None; - for profile in oracle_auth_fallback_profiles(&db_config, &err) { - match self.agent_manager.spawn(&db_config.db_type, Some(profile)).await { - Ok(mut fallback_client) => { - match fallback_client - .call_method_with_timeout::( - AgentMethod::Connect, - connect_params.clone(), - Some(agent_connect_timeout(&db_config)), - ) - .await - { - Ok(_) => { - connected_client = Some(fallback_client); - break; - } - Err(fallback_err) => { - fallback_errors.push(format!("{profile}: {fallback_err}")); - } - } - } - Err(fallback_err) => { - fallback_errors.push(format!("{profile}: {fallback_err}")); - } - } - } - client = connected_client.ok_or_else(|| { - format!( - "{err}\n\nFallback with legacy Oracle drivers failed: {}", - fallback_errors.join("\n") - ) - })?; } else { return Err(oracle_error_with_driver_hint(&db_config, &err)); } @@ -2398,7 +2358,7 @@ mod tests { }; use crate::agent_connection::{ agent_connect_params, mongo_legacy_error_with_auth_hint, mongo_uses_legacy_driver, - oracle_alternate_connect_config, should_retry_mongo_with_legacy_driver, should_retry_oracle_with_10g_driver, + oracle_alternate_connect_config, should_retry_mongo_with_legacy_driver, }; use crate::agent_manager::{AgentState, JavaRuntimeConfig, JavaRuntimeMode, DEFAULT_JRE_KEY}; use crate::database_capabilities; @@ -2692,33 +2652,6 @@ mod tests { assert_eq!(params["connection_string"], "jdbc:oracle:thin:@//127.0.0.1:11521/ORCL"); } - #[test] - fn oracle_retry_guard_only_triggers_for_non_10g_listener_errors() { - let mut config = mysql_config(Some("ORCL")); - config.db_type = DatabaseType::Oracle; - config.driver_profile = Some("oracle".to_string()); - - assert!(!should_retry_oracle_with_10g_driver( - &config, - "Agent RPC error (-1): ORA-28040: No matching authentication protocol" - )); - assert!(!should_retry_oracle_with_10g_driver(&config, "Agent RPC error (-1): ORA-12541: TNS:no listener")); - assert!(!should_retry_oracle_with_10g_driver(&config, "host xxx port 1521 中没有监听程序")); - - config.driver_profile = Some("oracle-10g".to_string()); - assert!(!should_retry_oracle_with_10g_driver(&config, "Agent RPC error (-1): ORA-12541: TNS:no listener")); - assert!(!should_retry_oracle_with_10g_driver( - &config, - "Agent RPC error (-1): ORA-28040: No matching authentication protocol" - )); - - config.driver_profile = Some("oracle".to_string()); - assert!(!should_retry_oracle_with_10g_driver( - &config, - "Agent RPC error (-1): ORA-01017: invalid username/password" - )); - } - #[test] fn oracle_listener_errors_can_retry_with_alternate_connect_descriptor() { let mut config = mysql_config(Some("ORCL")); @@ -2747,15 +2680,12 @@ mod tests { } #[test] - fn oracle_alternate_descriptor_retry_skips_non_listener_errors_and_10g_profiles() { + fn oracle_alternate_descriptor_retry_skips_non_listener_errors() { let mut config = mysql_config(Some("ORCL")); config.db_type = DatabaseType::Oracle; config.driver_profile = Some("oracle".to_string()); assert!(oracle_alternate_connect_config(&config, "ORA-01017: invalid username/password").is_none()); - - config.driver_profile = Some("oracle-10g".to_string()); - assert!(oracle_alternate_connect_config(&config, "ORA-12514: listener does not know service").is_none()); } #[test] diff --git a/crates/dbx-core/src/db/agent_driver.rs b/crates/dbx-core/src/db/agent_driver.rs index 00b3e2440..b82e5e298 100644 --- a/crates/dbx-core/src/db/agent_driver.rs +++ b/crates/dbx-core/src/db/agent_driver.rs @@ -1104,9 +1104,7 @@ fn agent_java_args(jar_path: &str) -> Vec { args.push("-Djava.net.preferIPv4Stack=true".to_string()); } - if !agent_jar_path_matches_key(jar_path, "oracle-10g") { - args.push("--add-opens=java.sql/java.sql=ALL-UNNAMED".to_string()); - } + args.push("--add-opens=java.sql/java.sql=ALL-UNNAMED".to_string()); args.extend(["-XX:TieredStopAtLevel=1", "-XX:+UseSerialGC", "-jar", jar_path].into_iter().map(str::to_string)); @@ -1286,13 +1284,6 @@ mod tests { assert!(args.iter().any(|arg| arg == "--add-opens=java.sql/java.sql=ALL-UNNAMED")); } - #[test] - fn agent_java_args_skip_module_flags_for_oracle_10g_profile() { - let args = agent_java_args("/tmp/dbx/drivers/oracle-10g/agent.jar"); - - assert!(!args.iter().any(|arg| arg == "--add-opens=java.sql/java.sql=ALL-UNNAMED")); - } - #[test] fn agent_java_args_disable_ambient_proxy_settings() { let args = agent_java_args("/tmp/dbx-agent-opengauss.jar"); diff --git a/packages/app-tests/agentDriverInstallHint.test.ts b/packages/app-tests/agentDriverInstallHint.test.ts index 5d5b8cc17..681f5ac11 100644 --- a/packages/app-tests/agentDriverInstallHint.test.ts +++ b/packages/app-tests/agentDriverInstallHint.test.ts @@ -26,11 +26,7 @@ test("uses the unified Oracle driver for legacy Oracle profiles", () => { assert.equal( showAgentDriverInstallHint( "oracle", - [ - { db_type: "oracle", installed: false }, - { db_type: "oracle-legacy", installed: false }, - { db_type: "oracle-10g", installed: true }, - ], + [{ db_type: "oracle", installed: false }], "oracle-10g", ), true, @@ -38,11 +34,7 @@ test("uses the unified Oracle driver for legacy Oracle profiles", () => { assert.equal( showAgentDriverInstallHint( "oracle", - [ - { db_type: "oracle", installed: true }, - { db_type: "oracle-legacy", installed: false }, - { db_type: "oracle-10g", installed: false }, - ], + [{ db_type: "oracle", installed: true }], "oracle", ), false, @@ -50,11 +42,7 @@ test("uses the unified Oracle driver for legacy Oracle profiles", () => { assert.equal( showAgentDriverInstallHint( "oracle", - [ - { db_type: "oracle", installed: true }, - { db_type: "oracle-legacy", installed: true }, - { db_type: "oracle-10g", installed: false }, - ], + [{ db_type: "oracle", installed: true }], "oracle-legacy", ), false, diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index d662e9128..c7d103917 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -4,8 +4,7 @@ use tauri::State; pub use dbx_core::agent_connection::{ agent_connect_params, mongo_legacy_error_with_auth_hint, mongo_uses_legacy_driver, oracle_alternate_connect_config, - oracle_auth_fallback_profiles, oracle_error_with_driver_hint, should_retry_mongo_with_legacy_driver, - should_retry_oracle_with_10g_driver, + oracle_error_with_driver_hint, should_retry_mongo_with_legacy_driver, }; pub use dbx_core::connection::{ agent_connect_timeout, connect_bare_metadata_pool, connect_mysql_metadata_pool, connection_url_for_endpoint, @@ -67,7 +66,7 @@ async fn test_agent_connection( &config.db_type, config.driver_profile.as_deref(), AgentMethod::TestConnection, - connect_params.clone(), + connect_params, Some(agent_connect_timeout(config)), ) .await; @@ -92,34 +91,6 @@ async fn test_agent_connection( .map_err(|alternate_err| { format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}") })?; - } else if should_retry_oracle_with_10g_driver(config, &err) { - let mut fallback_errors = Vec::new(); - let mut connected = false; - for profile in oracle_auth_fallback_profiles(config, &err) { - match state - .agent_manager - .call_daemon_method_with_timeout::( - &config.db_type, - Some(profile), - AgentMethod::TestConnection, - connect_params.clone(), - Some(agent_connect_timeout(config)), - ) - .await - { - Ok(_) => { - connected = true; - break; - } - Err(fallback_err) => fallback_errors.push(format!("{profile}: {fallback_err}")), - } - } - if !connected { - return Err(format!( - "{err}\n\nFallback with legacy Oracle drivers failed: {}", - fallback_errors.join("\n") - )); - } } else { return Err(oracle_error_with_driver_hint(config, &err)); } @@ -139,7 +110,7 @@ async fn connect_agent_pool( let connect_result = client .call_method_with_timeout::( AgentMethod::Connect, - connect_params.clone(), + connect_params, Some(agent_connect_timeout(config)), ) .await; @@ -161,33 +132,6 @@ async fn connect_agent_pool( .map_err(|alternate_err| { format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}") })?; - } else if should_retry_oracle_with_10g_driver(config, &err) { - let mut fallback_errors = Vec::new(); - let mut connected_client = None; - for profile in oracle_auth_fallback_profiles(config, &err) { - match state.agent_manager.spawn(&config.db_type, Some(profile)).await { - Ok(mut fallback_client) => { - match fallback_client - .call_method_with_timeout::( - AgentMethod::Connect, - connect_params.clone(), - Some(agent_connect_timeout(config)), - ) - .await - { - Ok(_) => { - connected_client = Some(fallback_client); - break; - } - Err(fallback_err) => fallback_errors.push(format!("{profile}: {fallback_err}")), - } - } - Err(fallback_err) => fallback_errors.push(format!("{profile}: {fallback_err}")), - } - } - client = connected_client.ok_or_else(|| { - format!("{err}\n\nFallback with legacy Oracle drivers failed: {}", fallback_errors.join("\n")) - })?; } else { return Err(oracle_error_with_driver_hint(config, &err)); }