Skip to content

Commit 3aca578

Browse files
luzfcbFabio C. Barrionuevo da Luz-FABIOCBL
authored andcommitted
Initial Oracle Support on Explain feature
Introduce comprehensive support for Oracle Database to the SQL Explain panel in django-debug-toolbar. Unlike databases that support single-step integrated query analysis (such as PostgreSQL's "EXPLAIN ANALYZE"), Oracle requires a multi-step orchestration workflow to execute an explain plan and format its output securely without session pollution. Key Implementations & Changes: 1. Core Oracle Explain Orchestration: - Implement `OracleExplainPlanHelper` to encapsulate the multi-step explain plan lifecycle. - Generate temporary execution nodes under a session-safe, unique `STATEMENT_ID` (`dt_<uuid>`) within the session's `PLAN_TABLE`. - Query `DBMS_XPLAN.DISPLAY` with `ADVANCED +ADAPTIVE` flags to retrieve structured, human-readable execution plans. - Clean up temporary rows from `PLAN_TABLE` at the end of execution. - Suppress Query Block Registry (QBR) XML output on legacy Oracle connections (< 21) to prevent memory exhaustion and infinite recursion bugs on terminal client markdown parsers. - Include a graceful schema health audit fallback that queries `ALL_TABLES` and `ALL_INDEXES` metadata to format and append table and index statistics (sizes, status, performance counters) as aligned ASCII tables, sanitizing permission/database errors. 2. Query Tracking and Integration: - Update `debug_toolbar/panels/sql/forms.py` to hook `SQLSelectForm` and delegate select-explain execution to the new Oracle helper when connected to an Oracle database. - Update `debug_toolbar/panels/sql/tracking.py` to escape and filter out toolbar queries using vendor-specific quoting (`quote_name`) to prevent the toolbar's internal queries from showing up in and polluting SQL panels. 3. Docker & Infrastructure Support: - Introduce `docker-compose.yml` to provision local PostgreSQL, MariaDB, and Oracle Database Free containers to facilitate comprehensive multi-backend integration testing. - Update `.github/workflows/test.yml` and `tox.ini` to define and run tests against Oracle, and also ensuring coverage databases do not collide during parallel test execution. 4. Comprehensive Test Coverage: - Introduce `tests/panels/test_sql_oracle.py` with rigorous unit tests mocking and verifying the helper's edge cases, legacy vs. modern QBR handling, error boundary sanitization, and fallback options. - Add integration tests verifying correct explain output and formatting across both synchronous and asynchronous contexts. Fixes #227
1 parent f95e032 commit 3aca578

18 files changed

Lines changed: 1246 additions & 11 deletions

File tree

.github/workflows/test.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,77 @@ jobs:
261261
path: .coverage.sqlite.${{ matrix.python-version }}*
262262
include-hidden-files: true
263263

264+
oracle:
265+
runs-on: ubuntu-latest
266+
strategy:
267+
fail-fast: false
268+
max-parallel: 5
269+
matrix:
270+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
271+
272+
services:
273+
oracle:
274+
image: gvenzl/oracle-free:23.26.2-slim-faststart
275+
env:
276+
ORACLE_PASSWORD: oracle_secret_pass
277+
ports:
278+
- 1521:1521
279+
options: >-
280+
--health-cmd "healthcheck.sh"
281+
--health-interval 10s
282+
--health-timeout 5s
283+
--health-retries 10
284+
--health-start-period 60s
285+
--health-start-interval 5s
286+
steps:
287+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
288+
with:
289+
persist-credentials: false
290+
291+
- name: Set up Python ${{ matrix.python-version }}
292+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
293+
with:
294+
python-version: ${{ matrix.python-version }}
295+
allow-prereleases: true
296+
297+
- name: Get pip cache dir
298+
id: pip-cache
299+
run: |
300+
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
301+
302+
- name: Cache Pip Dependencies
303+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
304+
with:
305+
path: ${{ steps.pip-cache.outputs.dir }}
306+
key:
307+
${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/tox.ini') }}
308+
restore-keys: |
309+
${{ matrix.python-version }}-v1-
310+
lookup-only: true
311+
312+
- name: Install dependencies
313+
run: |
314+
python -m pip install --upgrade pip
315+
python -m pip install --upgrade tox tox-gh-actions
316+
317+
- name: Test with tox
318+
run: tox
319+
env:
320+
DB_BACKEND: oracle
321+
DB_NAME: localhost:1521/FREEPDB1
322+
DB_USER: system
323+
DB_PASSWORD: oracle_secret_pass
324+
DB_HOST: ""
325+
DB_PORT: ""
326+
COVERAGE_FILE: ".coverage.oracle.${{ matrix.python-version }}"
327+
328+
- name: Store coverage file
329+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
330+
with:
331+
name: coverage-oracle-${{ matrix.python-version }}
332+
path: .coverage.oracle.${{ matrix.python-version }}*
333+
include-hidden-files: true
334+
264335
lint:
265336
runs-on: ubuntu-latest
266337
strategy:
@@ -306,6 +377,7 @@ jobs:
306377
- mysql
307378
- postgres
308379
- sqlite
380+
- oracle
309381
permissions:
310382
pull-requests: write
311383
contents: write

debug_toolbar/panels/sql/forms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ def explain(self):
7979
cursor.execute(f"EXPLAIN QUERY PLAN {sql}", params)
8080
elif vendor == "postgresql":
8181
cursor.execute(f"EXPLAIN ANALYZE {sql}", params)
82+
elif vendor == "oracle":
83+
from debug_toolbar.panels.sql.oracle_helper import (
84+
OracleExplainPlanHelper,
85+
)
86+
87+
helper = OracleExplainPlanHelper(cursor)
88+
return helper.execute(sql, params)
8289
else:
8390
cursor.execute(f"EXPLAIN {sql}", params)
8491
headers = [d[0] for d in cursor.description]

0 commit comments

Comments
 (0)