diff --git a/tests/oracle/oracle_mcp.md b/docs/en/how-to/connect-ide/oracle_mcp.md similarity index 100% rename from tests/oracle/oracle_mcp.md rename to docs/en/how-to/connect-ide/oracle_mcp.md diff --git a/docs/en/reference/prebuilt-tools.md b/docs/en/reference/prebuilt-tools.md index 3d84034ddb..859ee768a0 100644 --- a/docs/en/reference/prebuilt-tools.md +++ b/docs/en/reference/prebuilt-tools.md @@ -689,8 +689,7 @@ See [Usage Examples](../reference/cli.md#examples). * **Permissions:** * **Database-level permissions** are required to execute Cypher queries. * **Tools:** - * `ORACLE_WALLET`: The path to Oracle DB Wallet file for the databases that support this authentication type - * `USE_OCI`: true or false, The flag if the Oracle Database is deployed in cloud deployment + ## Oracle @@ -698,6 +697,11 @@ See [Usage Examples](../reference/cli.md#examples). * **Environment Variables:** * `ORACLE_HOST`: The hostname or IP address of the Oracle server. * `ORACLE_PORT`: The port number for the Oracle server (Default: 1521). + * `ORACLE_CONNECTION_STRING`: The + * `ORACLE_USER`: The + * `ORACLE_PASSWORD`: The + * `ORACLE_WALLET`: The path to Oracle DB Wallet file for the databases that support this authentication type + * `ORACLE_USE_OCI`: true or false, The flag if the Oracle Database is deployed in cloud deployment * **Permissions:** * Database-level permissions (e.g., `SELECT`, `INSERT`) are required to @@ -710,6 +714,11 @@ See [Usage Examples](../reference/cli.md#examples). * `list_top_sql_by_resource`: Lists top SQL statements by resource usage. * `list_tablespace_usage`: Lists tablespace usage. * `list_invalid_objects`: Lists invalid objects. + * `list_active_sessions`: Lists active database sessions. + * `get_query_plan`: Gets the execution plan for a SQL statement. + * `list_top_sql_by_resource`: Lists top SQL statements by resource usage. + * `list_tablespace_usage`: Lists tablespace usage. + * `list_invalid_objects`: Lists invalid objects. ## Google Cloud Healthcare API * `--prebuilt` value: `cloud-healthcare` diff --git a/internal/prebuiltconfigs/tools/oracledb.yaml b/internal/prebuiltconfigs/tools/oracledb.yaml index 47f441a669..9c52a09540 100644 --- a/internal/prebuiltconfigs/tools/oracledb.yaml +++ b/internal/prebuiltconfigs/tools/oracledb.yaml @@ -15,27 +15,26 @@ sources: oracle-source: kind: "oracle" - connectionString: $ORACLE_CONNECTION_STRING - walletLocation: $ORACLE_WALLET - user: $ORACLE_USER - password: $ORACLE_PASSWORD - useOCI: $USE_OCI:false -tools: - execute_sql: - kind: oracle-execute-sql - source: oracle-source - description: Use this tool to execute a single SQL query or DML statement. + connectionString: ${ORACLE_CONNECTION_STRING} + walletLocation: ${ORACLE_WALLET:} + user: ${ORACLE_USER} + password: ${ORACLE_PASSWORD} + useOCI: ${ORACLE_USE_OCI:false} +tools: + list_tables: kind: oracle-sql source: oracle-source - description: "Lists all user tables in the connected schema, including segment size, row count, and last analyzed date. Filters by a comma-separated list of names. If names are omitted, lists all tables in the current user's schema. SELECT table_name from user_tables;" - + description: "Lists all user tables in the connected schema, including segment size, row count, and last analyzed date. Filters by a comma-separated list of names. If names are omitted, lists all tables in the current user's schema." + statement: SELECT table_name from user_tables; + list_active_sessions: kind: oracle-sql source: oracle-source - description: "List the top N (default 50) currently running database sessions (STATUS='ACTIVE'), showing SID, OS User, Program, and the current SQL statement text. - SELECT + description: "List the top N (default 50) currently running database sessions (STATUS='ACTIVE'), showing SID, OS User, Program, and the current SQL statement text." + statement: | + SELECT s.sid, s.serial#, s.username, @@ -53,18 +52,19 @@ tools: AND s.sql_id = sql.sql_id (+) AND s.audsid != userenv('sessionid') -- Exclude current session ORDER BY s.last_call_et DESC - FETCH FIRST COALESCE(10) ROWS ONLY;" + FETCH FIRST COALESCE(10) ROWS ONLY; get_query_plan: - kind: oracle-execute-sql + kind: oracle-sql source: oracle-source - description: "Generate a full execution plan for a single SQL statement. This can be used to analyze query performance without execution. Requires the SQL statement as input. following is an example EXPLAIN PLAN FOR {{&query}}; - SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());" + description: "Generate a full execution plan for a single SQL statement. This can be used to analyze query performance without execution. Requires the SQL statement as input. following is an example EXPLAIN PLAN FOR {{&query}};" + statement: SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY()); list_top_sql_by_resource: kind: oracle-sql source: oracle-source - description: "List the top N SQL statements from the library cache based on a chosen resource metric (CPU, I/O, or Elapsed Time), following is an example of the sql + description: "List the top N SQL statements from the library cache based on a chosen resource metric (CPU, I/O, or Elapsed Time), following is an example of the sql" + statement: | SELECT sql_id, executions, @@ -74,12 +74,13 @@ tools: elapsed_time / 1000000 AS elapsed_seconds FROM v$sql - FETCH FIRST 5 ROWS ONLY;" + FETCH FIRST 5 ROWS ONLY; list_tablespace_usage: kind: oracle-sql source: oracle-source - description: "List tablespace names, total size, free space, and used percentage to monitor storage utilization. + description: "List tablespace names, total size, free space, and used percentage to monitor storage utilization." + statement: | SELECT t.tablespace_name, TO_CHAR(t.total_bytes / 1024 / 1024, '99,999.00') AS total_mb, @@ -93,7 +94,7 @@ tools: GROUP BY t.tablespace_name, t.total_bytes ORDER BY - used_pct DESC" + used_pct DESC; list_invalid_objects: kind: oracle-sql @@ -111,7 +112,7 @@ tools: status = 'INVALID' AND owner NOT IN ('SYS', 'SYSTEM') -- Exclude system schemas for clarity ORDER BY - owner, object_type, object_name; + owner, object_type, object_name; toolsets: oracle_database_tools: @@ -121,4 +122,4 @@ toolsets: - get_query_plan - list_top_sql_by_resource - list_tablespace_usage - - list_invalid_objects \ No newline at end of file + - list_invalid_objects diff --git a/tests/oracle/oracle_integration_test.go b/tests/oracle/oracle_integration_test.go index 6c73d3ea69..0347e47788 100644 --- a/tests/oracle/oracle_integration_test.go +++ b/tests/oracle/oracle_integration_test.go @@ -9,6 +9,7 @@ import ( "os" "regexp" "strings" + "strconv" "testing" "time" @@ -96,8 +97,8 @@ func TestOracleTools(t *testing.T) { defer teardownTable1(t) // set up data for auth tool - createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getOracleAuthToolInfo(tableNameAuth) - teardownTable2 := setupOracleTable(t, ctx, db, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams) + createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getOracleAuthToolInfo(t, tableNameAuth) + teardownTable2 := setupOracleTable(t, ctx, db, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams...) defer teardownTable2(t) // Write config into a file and pass it to command @@ -150,6 +151,125 @@ func TestOracleTools(t *testing.T) { }) } +func TestOracleConnectionPureGoWithWallet(t *testing.T) { + t.Parallel() + // This test expects the connection to fail because the wallet file won't exist. + // It verifies that the walletLocation parameter is correctly passed to the pure Go driver. + + // Save original env vars and restore them at the end + cleanup := setOracleEnv(t, + oracleHost, oracleUser, oraclePassword, oracleService, oraclePort, // Use existing base connection details + "", // connectionString + "", // tnsAlias + "", // tnsAdmin + "/tmp/nonexistent_wallet", // walletLocation + false, // useOCI + ) + defer cleanup() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cfg := getOracleConfigFromEnv(t) + _, err := cfg.Initialize(ctx, nil) // Pass nil for tracer as it's not critical for this test + + if err == nil { + t.Fatalf("Expected connection to fail with non-existent wallet, but it succeeded") + } + + // Check for error message indicating wallet usage or connection failure related to wallet + // The exact error message might vary depending on the go-ora version and OS. + // We are looking for an error that suggests the wallet path was attempted. + expectedErrorSubstring := "wallet" + if !strings.Contains(strings.ToLower(err.Error()), expectedErrorSubstring) { + t.Errorf("Expected error message to contain '%s' (case-insensitive) but got: %v", expectedErrorSubstring, err) + } + t.Logf("Connection failed as expected (Pure Go with Wallet): %v", err) +} + +func TestOracleConnectionOCI(t *testing.T) { + t.Parallel() + // This test verifies that the useOCI=true parameter is correctly passed to the OCI driver. + // It will likely fail if Oracle Instant Client is not installed or configured. + + // Save original env vars and restore them at the end + cleanup := setOracleEnv(t, + oracleHost, oracleUser, oraclePassword, oracleService, oraclePort, // Use existing base connection details + "", // connectionString + "", // tnsAlias + "", // tnsAdmin + "", // walletLocation + true, // useOCI + ) + defer cleanup() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cfg := getOracleConfigFromEnv(t) + _, err := cfg.Initialize(ctx, nil) + + if err == nil { + t.Fatalf("Expected connection to fail (OCI driver without Instant Client), but it succeeded") + } + + // Check for error message indicating OCI driver usage or connection failure related to OCI. + // Common errors include "OCI environment not initialized", "driver: bad connection", etc. + expectedErrorSubstrings := []string{"oci", "driver", "connection"} + foundExpectedError := false + for _, sub := range expectedErrorSubstrings { + if strings.Contains(strings.ToLower(err.Error()), sub) { + foundExpectedError = true + break + } + } + if !foundExpectedError { + t.Errorf("Expected error message to contain one of %v (case-insensitive) but got: %v", expectedErrorSubstrings, err) + } + t.Logf("Connection failed as expected (OCI Driver): %v", err) +} + +func TestOracleConnectionOCIWithWallet(t *testing.T) { + t.Parallel() + // This test verifies that useOCI=true and tnsAdmin parameters are correctly passed for OCI wallet. + // It will likely fail due to missing tnsnames.ora and wallet files. + + // Save original env vars and restore them at the end + cleanup := setOracleEnv(t, + "", oracleUser, oraclePassword, "", "", // Unset host/port/service for TNS alias, but keep user/pass + "", // connectionString + "MY_TNS_ALIAS", // tnsAlias + "/tmp/nonexistent_tns_admin", // tnsAdmin + "", // walletLocation + true, // useOCI + ) + defer cleanup() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cfg := getOracleConfigFromEnv(t) + _, err := cfg.Initialize(ctx, nil) + + if err == nil { + t.Fatalf("Expected connection to fail (OCI driver with TNS Admin/Wallet), but it succeeded") + } + + // Check for error message indicating TNS Admin/Wallet usage or connection failure. + expectedErrorSubstrings := []string{"tns", "wallet", "oci", "driver", "connection"} + foundExpectedError := false + for _, sub := range expectedErrorSubstrings { + if strings.Contains(strings.ToLower(err.Error()), sub) { + foundExpectedError = true + break + } + } + if !foundExpectedError { + t.Errorf("Expected error message to contain one of %v (case-insensitive) but got: %v", expectedErrorSubstrings, err) + } + t.Logf("Connection failed as expected (OCI Driver with TNS Admin/Wallet): %v", err) +} + func setupOracleTable(t *testing.T, ctx context.Context, pool *sql.DB, createStatement, insertStatement, tableName string, params []any) func(*testing.T) { err := pool.PingContext(ctx) if err != nil { @@ -205,7 +325,7 @@ func getOracleParamToolInfo(tableName string) (string, string, string, string, s } // getOracleAuthToolInfo returns statements and params for my-auth-tool for Oracle SQL -func getOracleAuthToolInfo(tableName string) (string, string, string, []any) { +func getOracleAuthToolInfo(t *testing.T, tableName string) (string, string, string, []any) { createStatement := fmt.Sprintf(`CREATE TABLE %s ("id" NUMBER GENERATED AS IDENTITY PRIMARY KEY, "name" VARCHAR2(255), "email" VARCHAR2(255))`, tableName) // MODIFIED: Use a PL/SQL block for multiple inserts