feat(agents): remove legacy Oracle Java agents
This commit is contained in:
parent
7486d35a0f
commit
6c232899b4
|
|
@ -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]* //')
|
||||
|
|
|
|||
|
|
@ -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/<db_type>/` 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/<db_type>/` or shared Java agent protocol code, rebuild the target agent and replace the runtime JAR used by the local DBX app:
|
||||
|
||||
```bash
|
||||
./gradlew :<db_type>:shadowJar
|
||||
|
|
@ -74,6 +72,8 @@ cp agents/drivers/<db_type>/build/libs/*-all.jar ~/.dbx/agents/drivers/<db_type>
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<DatabaseInfo> listDatabases() {
|
||||
return unchecked(() -> {
|
||||
List<DatabaseInfo> 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<String> listSchemas() {
|
||||
return unchecked(() -> {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (DatabaseInfo database : listDatabases()) {
|
||||
result.add(database.getName());
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listSchemas(List<String> visibleSchemas) {
|
||||
if (visibleSchemas == null) {
|
||||
return listSchemas();
|
||||
}
|
||||
if (visibleSchemas.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return unchecked(() -> {
|
||||
List<String> 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<TableInfo> listTables(String schema) {
|
||||
return unchecked(() -> {
|
||||
List<TableInfo> 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<ObjectInfo> listObjects(String schema) {
|
||||
return unchecked(() -> {
|
||||
List<ObjectInfo> 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<ColumnInfo> getColumns(String schema, String table) {
|
||||
return unchecked(() -> {
|
||||
List<ColumnInfo> 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<IndexInfo> listIndexes(String schema, String table) {
|
||||
return unchecked(() -> {
|
||||
List<IndexInfo> 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<ForeignKeyInfo> listForeignKeys(String schema, String table) {
|
||||
return unchecked(() -> {
|
||||
List<ForeignKeyInfo> 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<TriggerInfo> listTriggers(String schema, String table) {
|
||||
return unchecked(() -> {
|
||||
List<TriggerInfo> 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<String> 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<String> splitNonEmpty(String value, String delimiter) {
|
||||
List<String> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ObjectInfo> 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<String, List<List<String>>> 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<List<String>> 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<List<String>> 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> T proxy(Class<T> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<DatabaseInfo> 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<DatabaseInfo> queryDatabaseInfos(String sql) throws SQLException {
|
||||
List<DatabaseInfo> 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<DatabaseInfo> 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<String> listSchemas() {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (DatabaseInfo database : listDatabases()) {
|
||||
result.add(database.getName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listSchemas(List<String> visibleSchemas) {
|
||||
if (visibleSchemas == null) {
|
||||
return listSchemas();
|
||||
}
|
||||
if (visibleSchemas.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return unchecked(() -> {
|
||||
List<String> 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<TableInfo> listTables(String schema) {
|
||||
return unchecked(() -> {
|
||||
try {
|
||||
List<TableInfo> 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<ObjectInfo> listObjects(String schema) {
|
||||
return unchecked(() -> {
|
||||
try {
|
||||
List<ObjectInfo> 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<ColumnInfo> 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<ColumnInfo> 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<IndexInfo> 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<IndexInfo> 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<String> 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<ForeignKeyInfo> 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<ForeignKeyInfo> 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<TriggerInfo> 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<TriggerInfo> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<ConnectionConfig> {
|
||||
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"));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -127,13 +127,6 @@ pub async fn call_daemon_method_with_timeout<T: DeserializeOwned + Send + 'stati
|
|||
|
||||
fn runtime_agent_key_candidates(db_type: &DatabaseType, driver_profile: Option<&str>) -> Option<Vec<&'static str>> {
|
||||
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])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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::<serde_json::Value>(
|
||||
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::<serde_json::Value>(
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -1104,9 +1104,7 @@ fn agent_java_args(jar_path: &str) -> Vec<String> {
|
|||
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");
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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::<serde_json::Value>(
|
||||
&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::<serde_json::Value>(
|
||||
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::<serde_json::Value>(
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue