From 3695c6c58cbc2e59d82cb67c8fc0629eecd89463 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sat, 15 Nov 2025 03:40:42 +0000 Subject: [PATCH 01/20] build: add docker dev environment with CLAUDE.md - Add docker-compose setup with Ubuntu-based dev container - Add CLAUDE.md with git policy and build instructions - Configure build with libxml and uuid support - Update .gitignore for docker_library --- .gitignore | 3 ++- CLAUDE.md | 47 ++++++++++++++++++++++++++++++++++++ Dockerfile | 59 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 12 +++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md create mode 100644 Dockerfile create mode 100644 docker-compose.yaml diff --git a/.gitignore b/.gitignore index 794e35b73cb..0e520f791ef 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ lib*.pc /pgsql.sln.cache /Debug/ /Release/ -/tmp_install/ +/tmp_install +docker_library/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..a40d36ed905 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,47 @@ +# Build Instructions + +## Git Commit Policy (MANDATORY) + +**Commit message format:** +``` +: + +[optional body explaining why/what changed] +``` + +**RULES:** +- NO "Generated with Claude Code" footer +- NO "Co-Authored-By: Claude" line +- NO mention of "Claude" or "Happy" anywhere +- Keep messages short (1-5 lines preferred) +- Types: feat, fix, refactor, chore, docs, build, test + +## Quick Start + +1. **Start the development container** + ```bash + docker compose up -d + ``` + +2. **Configure the build** + ```bash + docker compose exec dev ./configure --prefix=/home/ivorysql/ivorysql --enable-debug --enable-cassert --with-uuid=e2fs --with-libxml + ``` + +3. **Build the project** + ```bash + docker compose exec dev make -j\$(nproc) + ``` + +## Running Tests + +```bash +# PostgreSQL tests (228 tests) +docker compose exec dev make check + +# Oracle compatibility tests (234 tests) +docker compose exec dev make oracle-check + +# Both test suites +docker compose exec dev make all-check +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..e36939281a5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,59 @@ +# IvorySQL Build and Test Container - Modern Ubuntu +# Alternative to CentOS with same capabilities + +FROM ubuntu:22.04 + +# Prevent interactive prompts +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies matching the workflow requirements +RUN apt-get update && apt-get install -y \ + # Build tools + build-essential git lcov bison flex pkg-config cppcheck \ + # Core dependencies + libkrb5-dev libssl-dev libldap-dev libpam-dev \ + libxml2-dev libxslt-dev libreadline-dev libedit-dev \ + zlib1g-dev uuid-dev libossp-uuid-dev libuuid1 e2fsprogs \ + # ICU support + libicu-dev \ + # Language support + python3-dev tcl-dev libperl-dev gettext \ + # Perl test modules + libipc-run-perl libtime-hires-perl libtest-simple-perl \ + # LLVM/Clang + llvm clang \ + # LZ4 compression + liblz4-dev \ + # System libraries + libselinux1-dev libsystemd-dev \ + # GSSAPI + libgssapi-krb5-2 \ + # Locale support + locales \ + # For dev containers + sudo tini \ + && rm -rf /var/lib/apt/lists/* + +# Set up locale +RUN locale-gen en_US.UTF-8 +ENV LANG=en_US.UTF-8 \ + LANGUAGE=en_US:en \ + LC_ALL=en_US.UTF-8 + +# Create ivorysql user with matching host UID/GID (1000:1000) +# and grant sudo privileges without password +ARG USER_UID=1000 +ARG USER_GID=1000 +RUN groupadd -g ${USER_GID} ivorysql || true && \ + useradd -m -u ${USER_UID} -g ${USER_GID} -d /home/ivorysql -s /bin/bash ivorysql && \ + echo "ivorysql ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +# Set working directory +WORKDIR /home/ivorysql/IvorySQL + +# Switch to ivorysql user for builds +USER ivorysql + +# Default command +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["/bin/bash"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000000..07d26ac9a17 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,12 @@ +services: + # Ubuntu-based development environment (recommended) + dev: + build: + context: . + dockerfile: Dockerfile + image: ivorysql-dev + container_name: ivorysql-dev + volumes: + - .:/home/ivorysql/IvorySQL:rw + working_dir: /home/ivorysql/IvorySQL + command: ["sleep", "infinity"] From 3bff4a0db49bc4ec59283f1e474b1ede639ab133 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sat, 15 Nov 2025 04:41:02 +0000 Subject: [PATCH 02/20] chore: update CLAUDE.md for background context --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index a40d36ed905..6e9066f4480 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,3 +1,8 @@ +# Background + +This project is a work of [IvorySQL](https://github.com/IvorySQL/IvorySQL). Changes can be reviewed by comparing HEAD with upstream/master. + + # Build Instructions ## Git Commit Policy (MANDATORY) From 49e7ac598dc8fa752030728fc97ecdcff1807afa Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 16 Nov 2025 17:04:28 +0000 Subject: [PATCH 03/20] docs: add manual testing guide with Oracle port 1521 --- CLAUDE.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 6e9066f4480..5fb08d7ef73 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,3 +50,68 @@ docker compose exec dev make oracle-check # Both test suites docker compose exec dev make all-check ``` + +## Manual Testing with Oracle Compatibility + +When you need to test SQL manually (e.g., testing PL/iSQL packages): + +1. **Initialize a test database in Oracle mode** + ```bash + docker compose exec dev bash -c " + export PATH=/home/ivorysql/ivorysql/bin:\$PATH + export LD_LIBRARY_PATH=/home/ivorysql/ivorysql/lib:\$LD_LIBRARY_PATH + cd /home/ivorysql + rm -rf test_oracle + initdb -D test_oracle --auth=trust -m oracle + " + ``` + +2. **Start the database server** + ```bash + docker compose exec dev bash -c " + export PATH=/home/ivorysql/ivorysql/bin:\$PATH + export LD_LIBRARY_PATH=/home/ivorysql/ivorysql/lib:\$LD_LIBRARY_PATH + pg_ctl -D /home/ivorysql/test_oracle -l /home/ivorysql/test_oracle/logfile start + " + ``` + + **Note:** Oracle mode servers listen on **both ports**: + - Port 5432 (PostgreSQL default) + - Port 1521 (Oracle default) + +3. **Create a test database** + ```bash + docker compose exec dev bash -c " + export PATH=/home/ivorysql/ivorysql/bin:\$PATH + export LD_LIBRARY_PATH=/home/ivorysql/ivorysql/lib:\$LD_LIBRARY_PATH + createdb testdb + " + ``` + +4. **Connect and test (use Oracle port 1521)** + ```bash + # Interactive connection + docker compose exec dev bash -c " + export PATH=/home/ivorysql/ivorysql/bin:\$PATH + export LD_LIBRARY_PATH=/home/ivorysql/ivorysql/lib:\$LD_LIBRARY_PATH + psql -h localhost -p 1521 -d testdb + " + + # Run a SQL file + docker compose exec dev bash -c " + export PATH=/home/ivorysql/ivorysql/bin:\$PATH + export LD_LIBRARY_PATH=/home/ivorysql/ivorysql/lib:\$LD_LIBRARY_PATH + psql -h localhost -p 1521 -d testdb -f /path/to/your/file.sql + " + ``` + +5. **Stop the test server when done** + ```bash + docker compose exec dev bash -c " + export PATH=/home/ivorysql/ivorysql/bin:\$PATH + export LD_LIBRARY_PATH=/home/ivorysql/ivorysql/lib:\$LD_LIBRARY_PATH + pg_ctl -D /home/ivorysql/test_oracle stop -m fast + " + ``` + +**Important:** Always use port **1521** when testing Oracle PL/SQL compatibility features (packages, PL/iSQL procedures, etc.) From c915b751599519bf18e5c160a61ed2352a170efa Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Wed, 26 Nov 2025 13:39:11 +0000 Subject: [PATCH 04/20] chore: add oracle free container --- CLAUDE.md | 39 +++++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 4 ++++ 2 files changed, 43 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 5fb08d7ef73..732c4b15a8a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -115,3 +115,42 @@ When you need to test SQL manually (e.g., testing PL/iSQL packages): ``` **Important:** Always use port **1521** when testing Oracle PL/SQL compatibility features (packages, PL/iSQL procedures, etc.) + +## Testing Against Real Oracle Database + +A real Oracle Database Free container is available for validating Oracle compatibility. + +### Container Information + +- **Container name:** `ivorysql-oracle-1` +- **Image:** `container-registry.oracle.com/database/free:23.26.0.0-lite` +- **Version:** Oracle 23.26 Free +- **Status:** Managed by docker-compose (starts with `docker compose up -d`) + +### Connecting to Oracle + +**Interactive SQL*Plus session:** +```bash +docker exec -it ivorysql-oracle-1 sqlplus / as sysdba +``` + +**Run SQL from command line:** +```bash +docker exec ivorysql-oracle-1 bash -c "echo 'SELECT * FROM dual;' | sqlplus -s / as sysdba" +``` + +**Run inline SQL script:** +```bash +docker exec ivorysql-oracle-1 bash -c "cat << 'EOF' | sqlplus -s / as sysdba +SET SERVEROUTPUT ON; +DECLARE + result NUMBER; +BEGIN + SELECT (100 - 50) * 0.01 INTO result FROM dual; + DBMS_OUTPUT.PUT_LINE('Result: ' || result); +END; +/ +EXIT; +EOF +" +``` diff --git a/docker-compose.yaml b/docker-compose.yaml index 07d26ac9a17..f77476bb4e2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,3 +10,7 @@ services: - .:/home/ivorysql/IvorySQL:rw working_dir: /home/ivorysql/IvorySQL command: ["sleep", "infinity"] + oracle: + image: container-registry.oracle.com/database/free:23.26.0.0-lite + environment: + ORACLE_PWD: orapwd From 2b6385f3c9d8fdd8fc8f71a8be850c9ed2282dae Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Thu, 27 Nov 2025 19:51:14 +0000 Subject: [PATCH 05/20] chore: .devcontainer --- Dockerfile => .devcontainer/Dockerfile | 0 .devcontainer/devcontainer.json | 22 ++++++++++++++++++++++ docker-compose.yaml | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) rename Dockerfile => .devcontainer/Dockerfile (100%) create mode 100644 .devcontainer/devcontainer.json diff --git a/Dockerfile b/.devcontainer/Dockerfile similarity index 100% rename from Dockerfile rename to .devcontainer/Dockerfile diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..bb9789a76c1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +{ + "name": "IvorySQL Dev", + "dockerComposeFile": "../docker-compose.yaml", + "service": "dev", + "workspaceFolder": "/home/ivorysql/IvorySQL", + "remoteUser": "ivorysql", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "C_Cpp.default.configurationProvider": "ms-vscode.makefile-tools" + }, + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.makefile-tools", + "twxs.cmake", + "mhutchie.git-graph", + "eamodio.gitlens" + ] + } + } +} diff --git a/docker-compose.yaml b/docker-compose.yaml index f77476bb4e2..868e18a11e6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,7 +3,7 @@ services: dev: build: context: . - dockerfile: Dockerfile + dockerfile: .devcontainer/Dockerfile image: ivorysql-dev container_name: ivorysql-dev volumes: From 289eed539aa10e0b2313a452c12385cf59adc64c Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Fri, 28 Nov 2025 09:22:50 +0000 Subject: [PATCH 06/20] chore: docker_library as git submodule --- .gitignore | 1 - .gitmodules | 4 ++++ docker_library | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 160000 docker_library diff --git a/.gitignore b/.gitignore index 0e520f791ef..bac11f33086 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,3 @@ lib*.pc /Debug/ /Release/ /tmp_install -docker_library/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..0ff10d881f5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "docker_library"] + path = docker_library + url = https://github.com/rophy/ivorysql-docker_library + branch = develop diff --git a/docker_library b/docker_library new file mode 160000 index 00000000000..dc98006a6d5 --- /dev/null +++ b/docker_library @@ -0,0 +1 @@ +Subproject commit dc98006a6d51bddd78446d1ef246424db822761c From f921e1c1bc9621c0293a3e60355798220620c35f Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Fri, 28 Nov 2025 12:03:19 +0000 Subject: [PATCH 07/20] chore: scripts/build-docker.sh --- scripts/build-docker.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100755 scripts/build-docker.sh diff --git a/scripts/build-docker.sh b/scripts/build-docker.sh new file mode 100755 index 00000000000..94697c69dbb --- /dev/null +++ b/scripts/build-docker.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +# Build Docker image using local source code via git archive + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +DOCKER_CONTEXT="$PROJECT_ROOT/docker_library/5/bookworm" + +echo "Creating source archive from git HEAD..." +cd "$PROJECT_ROOT" +git archive --format=tar.gz HEAD -o "$DOCKER_CONTEXT/ivorysql.tar.gz" + +echo "Source archive created: $DOCKER_CONTEXT/ivorysql.tar.gz" +echo "Building Docker image..." +cd "$DOCKER_CONTEXT" +docker build -t ivorysql:5.0-local . + +echo "Done! Image built as ivorysql:5.0-local" From 100b97555aa7e82cae548e38341e4711a8227680 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 16 Nov 2025 17:04:28 +0000 Subject: [PATCH 08/20] docs: add manual testing guide with Oracle port 1521 --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index 732c4b15a8a..eee3d635a59 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -154,3 +154,4 @@ EXIT; EOF " ``` + From f9ceedd815f3800dbba5410bd67056677fcde444 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Fri, 28 Nov 2025 20:30:36 +0000 Subject: [PATCH 09/20] chore: remove image and container_name from docker-compose So that docker-compose works for multiple git worktrees --- docker-compose.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 868e18a11e6..91465c70505 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,8 +4,6 @@ services: build: context: . dockerfile: .devcontainer/Dockerfile - image: ivorysql-dev - container_name: ivorysql-dev volumes: - .:/home/ivorysql/IvorySQL:rw working_dir: /home/ivorysql/IvorySQL From eb3665797abf9f6c52d34283677dcdbc62c8832b Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sat, 29 Nov 2025 05:41:03 +0000 Subject: [PATCH 10/20] docs: add test framework documentation to CLAUDE.md --- CLAUDE.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index eee3d635a59..662ed729214 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,19 +38,67 @@ This project is a work of [IvorySQL](https://github.com/IvorySQL/IvorySQL). Chan docker compose exec dev make -j\$(nproc) ``` -## Running Tests +## Test Framework Overview + +IvorySQL uses **pg_regress** (PostgreSQL's regression test framework) with multiple test suites: + +### Test Suites + +1. **PostgreSQL Compatibility Tests** (`src/test/regress/`) + - **Runner**: `pg_regress` + - Tests standard PostgreSQL features + +2. **Oracle Compatibility Tests** (`src/oracle_test/regress/`) + - **Runner**: `ora_pg_regress` + - **Key tests**: + - `ora_plisql.sql` - PL/iSQL language tests + - `ora_package.sql` - Oracle package tests + +3. **PL/iSQL Language Tests** (`src/pl/plisql/src/`) + - Tests PL/iSQL procedural language internals + - **Examples**: `plisql_array`, `plisql_control`, `plisql_dbms_output`, etc. + - Command: `cd src/pl/plisql/src && make oracle-check` + +4. **Oracle Extension Tests** (`contrib/ivorysql_ora/`) + - Tests Oracle compatibility packages (DBMS_UTILITY, datatypes, functions) + - Test files: `sql/*.sql` and expected outputs in `expected/*.out` + - Tests defined in `ORA_REGRESS` variable in Makefile + - Command: `cd contrib/ivorysql_ora && make installcheck` + +### Test Pattern + +All tests follow the same pattern: +1. SQL input files in `sql/` directory +2. Expected outputs in `expected/` directory (`.out` files) +3. Runner executes SQL and compares actual vs expected output + +### Running Tests ```bash -# PostgreSQL tests (228 tests) +# PostgreSQL tests docker compose exec dev make check -# Oracle compatibility tests (234 tests) +# Oracle compatibility tests docker compose exec dev make oracle-check # Both test suites docker compose exec dev make all-check + +# Specific contrib module (e.g., ivorysql_ora) +docker compose exec dev bash -c "cd contrib/ivorysql_ora && make installcheck" + +# PL/iSQL language tests +docker compose exec dev bash -c "cd src/pl/plisql/src && make oracle-check" ``` +### Adding New Tests + +**For Oracle packages (like DBMS_UTILITY):** +1. Create `contrib/ivorysql_ora/sql/.sql` +2. Create `contrib/ivorysql_ora/expected/.out` +3. Add `` to `ORA_REGRESS` in `contrib/ivorysql_ora/Makefile` +4. Run `make installcheck` to verify + ## Manual Testing with Oracle Compatibility When you need to test SQL manually (e.g., testing PL/iSQL packages): From 30708694bb4c31c712dcac3cbe377102438edc32 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Thu, 27 Nov 2025 19:51:14 +0000 Subject: [PATCH 11/20] chore: .devcontainer; oracle free container is optional --- docker-compose.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 91465c70505..725a0e68e70 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,4 @@ services: - # Ubuntu-based development environment (recommended) dev: build: context: . @@ -8,7 +7,10 @@ services: - .:/home/ivorysql/IvorySQL:rw working_dir: /home/ivorysql/IvorySQL command: ["sleep", "infinity"] + + # docker compose --profile ora up -d oracle: + profiles: [ora] image: container-registry.oracle.com/database/free:23.26.0.0-lite environment: ORACLE_PWD: orapwd From 3a841e3d42d8481953a5584afabb0b812f63c1b5 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 30 Nov 2025 05:19:25 +0000 Subject: [PATCH 12/20] docs: update oracle container startup instructions --- CLAUDE.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 662ed729214..7bd6def5230 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -173,7 +173,21 @@ A real Oracle Database Free container is available for validating Oracle compati - **Container name:** `ivorysql-oracle-1` - **Image:** `container-registry.oracle.com/database/free:23.26.0.0-lite` - **Version:** Oracle 23.26 Free -- **Status:** Managed by docker-compose (starts with `docker compose up -d`) +- **Status:** Optional; requires `--profile ora` flag to start +- **Memory:** Requires minimum 3GB RAM + +**IMPORTANT - Check before starting:** +The Oracle container requires 3GB+ RAM. Multiple git worktrees can share one instance. +**ALWAYS** check for existing Oracle containers before starting a new one: +```bash +docker ps --filter "ancestor=container-registry.oracle.com/database/free:23.26.0.0-lite" +``` +If an Oracle container is already running, use `docker exec` to connect to it directly. Do NOT start another instance. + +**Starting the Oracle container (only if none exists):** +```bash +docker compose --profile ora up -d +``` ### Connecting to Oracle From 8182776e01eb164077a57969ef36d93e60df0ad3 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 30 Nov 2025 11:14:48 +0000 Subject: [PATCH 13/20] chore: update CLAUDE.md with git branch instructions --- CLAUDE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 7bd6def5230..5d76634f541 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,15 @@ This project is a work of [IvorySQL](https://github.com/IvorySQL/IvorySQL). Chan - Keep messages short (1-5 lines preferred) - Types: feat, fix, refactor, chore, docs, build, test +## Git Branch Convention (IMPORTANT) + +- `master` should be based on upstream/master and match origin +- `test/dev-container` is upstream/master plus docker-compose for dev-containers and CLAUDE.md +- `test/*` should be based on `test/dev-container` plus commits for a single feature +- `feat/*` should be based on upstream/master, plus commits cherry-picked from `test/*` for submitting pull requests to upstream + +When you want to test something and noticed you don't see containers, verify the active branch you're on. + ## Quick Start 1. **Start the development container** From 53fe5ebfdbe54e8e63aaec5d5c301e1ef4fd4287 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 30 Nov 2025 15:35:48 +0000 Subject: [PATCH 14/20] chore: add gdb debugging support to dev container - Install gdb in Dockerfile - Enable SYS_PTRACE capability for debugging - Document gdb usage in CLAUDE.md --- .devcontainer/Dockerfile | 2 ++ CLAUDE.md | 25 ++++++++++++++++++++++++- docker-compose.yaml | 5 +++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e36939281a5..af9384491eb 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -32,6 +32,8 @@ RUN apt-get update && apt-get install -y \ locales \ # For dev containers sudo tini \ + # Debugging tools + gdb \ && rm -rf /var/lib/apt/lists/* # Set up locale diff --git a/CLAUDE.md b/CLAUDE.md index 5d76634f541..2f495fdd18c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,9 +44,32 @@ When you want to test something and noticed you don't see containers, verify the 3. **Build the project** ```bash - docker compose exec dev make -j\$(nproc) + docker compose exec dev make -j4 ``` +## Debugging with GDB + +The dev container includes gdb with ptrace enabled. + +**Debug initdb:** +```bash +docker compose exec dev bash -c " +export PATH=/home/ivorysql/IvorySQL/tmp_install/home/ivorysql/ivorysql/bin:\$PATH +export LD_LIBRARY_PATH=/home/ivorysql/IvorySQL/tmp_install/home/ivorysql/ivorysql/lib:\$LD_LIBRARY_PATH +rm -rf /tmp/testdb +gdb -ex 'break main' -ex 'run -D /tmp/testdb' initdb +" +``` + +**Debug postgres backend:** +```bash +# Start server, then attach to a backend process +docker compose exec dev bash -c " +export PATH=/home/ivorysql/ivorysql/bin:\$PATH +gdb -p +" +``` + ## Test Framework Overview IvorySQL uses **pg_regress** (PostgreSQL's regression test framework) with multiple test suites: diff --git a/docker-compose.yaml b/docker-compose.yaml index 725a0e68e70..1b0414aca7c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,6 +7,11 @@ services: - .:/home/ivorysql/IvorySQL:rw working_dir: /home/ivorysql/IvorySQL command: ["sleep", "infinity"] + # Enable ptrace for gdb debugging + cap_add: + - SYS_PTRACE + security_opt: + - seccomp:unconfined # docker compose --profile ora up -d oracle: From 5d5114754d781513973c64b5fec9cf0c56d5cc95 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Mon, 1 Dec 2025 00:11:46 +0000 Subject: [PATCH 15/20] docs: add build verification instructions Add critical reminder to verify installed files are newer than source after make install. Includes examples for rebuilding initdb and reinstalling extension SQL files. --- CLAUDE.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 2f495fdd18c..bab1cfa4165 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,6 +47,40 @@ When you want to test something and noticed you don't see containers, verify the docker compose exec dev make -j4 ``` +4. **Install the build** + ```bash + docker compose exec dev make install + ``` + +## Build Verification (CRITICAL) + +**ALWAYS verify that installed files are newer than source files after `make install`.** + +The build system may not rebuild files if timestamps are stale. After `make install`, verify: + +```bash +# Check if binary is newer than source +docker compose exec dev stat -c "%Y %n" /home/ivorysql/ivorysql/bin/ /home/ivorysql/IvorySQL/src//.c + +# Check if installed extension SQL is updated +docker compose exec dev grep "" /home/ivorysql/ivorysql/share/postgresql/extension/.sql +``` + +**If installed files are older than source:** +1. Use `touch` on source files to update timestamps +2. Or run `make clean` in the specific subdirectory before rebuilding +3. Then run `make && make install` again + +**Example - rebuilding initdb:** +```bash +docker compose exec dev bash -c "cd /home/ivorysql/IvorySQL/src/bin/initdb && make clean && make && make install" +``` + +**Example - reinstalling an extension SQL file:** +```bash +docker compose exec dev bash -c "cd /home/ivorysql/IvorySQL/src/pl/plisql/src && rm -f /home/ivorysql/ivorysql/share/postgresql/extension/plisql--1.0.sql && make install" +``` + ## Debugging with GDB The dev container includes gdb with ptrace enabled. From 1ce7aa8cf01368286d646fc9035c7e6283774e94 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 30 Nov 2025 23:02:20 +0000 Subject: [PATCH 16/20] feat: add DBMS_UTILITY package with FORMAT_ERROR_BACKTRACE Implement Oracle-compatible DBMS_UTILITY package in PL/iSQL extension: - Add FORMAT_ERROR_BACKTRACE function that returns PL/iSQL call stack during exception handling, matching Oracle's behavior - Implement pl_dbms_utility.c with stack trace formatting logic - Register package in plisql--1.0.sql extension script - Add regression tests for the new functionality --- src/bin/initdb/initdb.c | 3 + src/pl/plisql/src/Makefile | 3 +- src/pl/plisql/src/expected/dbms_utility.out | 227 +++++++++++++++++++ src/pl/plisql/src/pl_dbms_utility.c | 203 +++++++++++++++++ src/pl/plisql/src/pl_exec.c | 49 ++++ src/pl/plisql/src/plisql--1.0.sql | 30 +++ src/pl/plisql/src/plisql.h | 5 + src/pl/plisql/src/sql/dbms_utility.sql | 233 ++++++++++++++++++++ 8 files changed, 752 insertions(+), 1 deletion(-) create mode 100644 src/pl/plisql/src/expected/dbms_utility.out create mode 100644 src/pl/plisql/src/pl_dbms_utility.c create mode 100644 src/pl/plisql/src/sql/dbms_utility.sql diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 41f8c83307a..b1f8b834ee1 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -2091,7 +2091,10 @@ load_plpgsql(FILE *cmdfd) static void load_plisql(FILE *cmdfd) { + /* Switch to oracle mode to allow CREATE PACKAGE in extension SQL */ + PG_CMD_PUTS("set ivorysql.compatible_mode to oracle;\n\n"); PG_CMD_PUTS("CREATE EXTENSION plisql;\n\n"); + PG_CMD_PUTS("set ivorysql.compatible_mode to pg;\n\n"); } static void diff --git a/src/pl/plisql/src/Makefile b/src/pl/plisql/src/Makefile index 2730b93f830..5e550e5644c 100755 --- a/src/pl/plisql/src/Makefile +++ b/src/pl/plisql/src/Makefile @@ -40,6 +40,7 @@ rpath = OBJS = \ $(WIN32RES) \ pl_comp.o \ + pl_dbms_utility.o \ pl_exec.o \ pl_funcs.o \ pl_gram.o \ @@ -58,7 +59,7 @@ REGRESS = plisql_array plisql_call plisql_control plisql_copy plisql_domain \ plisql_record plisql_cache plisql_simple plisql_transaction \ plisql_trap plisql_trigger plisql_varprops plisql_nested_subproc \ plisql_nested_subproc2 plisql_out_parameter plisql_type_rowtype \ - plisql_exception + plisql_exception dbms_utility # where to find ora_gen_keywordlist.pl and subsidiary files TOOLSDIR = $(top_srcdir)/src/tools diff --git a/src/pl/plisql/src/expected/dbms_utility.out b/src/pl/plisql/src/expected/dbms_utility.out new file mode 100644 index 00000000000..583cf7a1646 --- /dev/null +++ b/src/pl/plisql/src/expected/dbms_utility.out @@ -0,0 +1,227 @@ +-- +-- Tests for DBMS_UTILITY package +-- +-- Test 1: FORMAT_ERROR_BACKTRACE - Basic exception in procedure +CREATE OR REPLACE PROCEDURE test_basic_error AS + v_backtrace VARCHAR2(4000); +BEGIN + RAISE EXCEPTION 'Test error'; +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Backtrace: %', v_backtrace; +END; +/ +CALL test_basic_error(); +INFO: Backtrace: ORA-06512: at "PUBLIC.TEST_BASIC_ERROR", line 3 + +DROP PROCEDURE test_basic_error; +-- Test 2: FORMAT_ERROR_BACKTRACE - Nested procedure calls +CREATE OR REPLACE PROCEDURE test_level3 AS +BEGIN + RAISE EXCEPTION 'Error at level 3'; +END; +/ +CREATE OR REPLACE PROCEDURE test_level2 AS +BEGIN + test_level3(); +END; +/ +CREATE OR REPLACE PROCEDURE test_level1 AS + v_backtrace VARCHAR2(4000); +BEGIN + test_level2(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Backtrace: %', v_backtrace; +END; +/ +CALL test_level1(); +INFO: Backtrace: ORA-06512: at "PUBLIC.TEST_LEVEL3", line 2 +ORA-06512: at "PUBLIC.TEST_LEVEL2", line 2 +ORA-06512: at "PUBLIC.TEST_LEVEL1", line 3 + +DROP PROCEDURE test_level1; +DROP PROCEDURE test_level2; +DROP PROCEDURE test_level3; +-- Test 3: FORMAT_ERROR_BACKTRACE - Deeply nested calls +CREATE OR REPLACE PROCEDURE test_deep5 AS +BEGIN + RAISE EXCEPTION 'Error at deepest level'; +END; +/ +CREATE OR REPLACE PROCEDURE test_deep4 AS +BEGIN + test_deep5(); +END; +/ +CREATE OR REPLACE PROCEDURE test_deep3 AS +BEGIN + test_deep4(); +END; +/ +CREATE OR REPLACE PROCEDURE test_deep2 AS +BEGIN + test_deep3(); +END; +/ +CREATE OR REPLACE PROCEDURE test_deep1 AS + v_backtrace VARCHAR2(4000); +BEGIN + test_deep2(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Deep backtrace: %', v_backtrace; +END; +/ +CALL test_deep1(); +INFO: Deep backtrace: ORA-06512: at "PUBLIC.TEST_DEEP5", line 2 +ORA-06512: at "PUBLIC.TEST_DEEP4", line 2 +ORA-06512: at "PUBLIC.TEST_DEEP3", line 2 +ORA-06512: at "PUBLIC.TEST_DEEP2", line 2 +ORA-06512: at "PUBLIC.TEST_DEEP1", line 3 + +DROP PROCEDURE test_deep1; +DROP PROCEDURE test_deep2; +DROP PROCEDURE test_deep3; +DROP PROCEDURE test_deep4; +DROP PROCEDURE test_deep5; +-- Test 4: FORMAT_ERROR_BACKTRACE - Function calls +CREATE OR REPLACE FUNCTION test_func_error RETURN NUMBER AS +BEGIN + RAISE EXCEPTION 'Error in function'; + RETURN 1; +END; +/ +CREATE OR REPLACE PROCEDURE test_func_caller AS + v_result NUMBER; + v_backtrace VARCHAR2(4000); +BEGIN + v_result := test_func_error(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Function backtrace: %', v_backtrace; +END; +/ +CALL test_func_caller(); +INFO: Function backtrace: ORA-06512: at "PUBLIC.TEST_FUNC_ERROR", line 2 +ORA-06512: at "PUBLIC.TEST_FUNC_CALLER", line 4 + +DROP PROCEDURE test_func_caller; +DROP FUNCTION test_func_error; +-- Test 5: FORMAT_ERROR_BACKTRACE - Anonymous block +DO $$ +DECLARE + v_backtrace VARCHAR2(4000); +BEGIN + RAISE EXCEPTION 'Error in anonymous block'; +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Anonymous block backtrace: %', v_backtrace; +END; +$$; +INFO: Anonymous block backtrace: ORA-06512: at line 5 + +-- Test 6: FORMAT_ERROR_BACKTRACE - No exception (should return empty) +CREATE OR REPLACE PROCEDURE test_no_error AS + v_backtrace VARCHAR2(4000); +BEGIN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'No error - backtrace: [%]', v_backtrace; +END; +/ +CALL test_no_error(); +INFO: No error - backtrace: [] +DROP PROCEDURE test_no_error; +-- Test 7: FORMAT_ERROR_BACKTRACE - Multiple exception levels +CREATE OR REPLACE PROCEDURE test_multi_inner AS +BEGIN + RAISE EXCEPTION 'Inner error'; +END; +/ +CREATE OR REPLACE PROCEDURE test_multi_middle AS +BEGIN + BEGIN + test_multi_inner(); + EXCEPTION + WHEN OTHERS THEN + RAISE INFO 'Caught at middle level'; + RAISE; + END; +END; +/ +CREATE OR REPLACE PROCEDURE test_multi_outer AS + v_backtrace VARCHAR2(4000); +BEGIN + test_multi_middle(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Outer backtrace: %', v_backtrace; +END; +/ +CALL test_multi_outer(); +INFO: Caught at middle level +INFO: Outer backtrace: ORA-06512: at "PUBLIC.TEST_MULTI_INNER", line 2 +ORA-06512: at "PUBLIC.TEST_MULTI_MIDDLE", line 3 +ORA-06512: at "PUBLIC.TEST_MULTI_OUTER", line 3 + +DROP PROCEDURE test_multi_outer; +DROP PROCEDURE test_multi_middle; +DROP PROCEDURE test_multi_inner; +-- Test 8: FORMAT_ERROR_BACKTRACE - Package procedure +CREATE OR REPLACE PACKAGE test_pkg IS + PROCEDURE pkg_error; + PROCEDURE pkg_caller; +END test_pkg; +/ +CREATE OR REPLACE PACKAGE BODY test_pkg IS + PROCEDURE pkg_error IS + BEGIN + RAISE EXCEPTION 'Error in package procedure'; + END pkg_error; + PROCEDURE pkg_caller IS + v_backtrace VARCHAR2(4000); + BEGIN + pkg_error(); + EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Package backtrace: %', v_backtrace; + END pkg_caller; +END test_pkg; +/ +CALL test_pkg.pkg_caller(); +INFO: Package backtrace: ORA-06512: at "PUBLIC.PKG_ERROR", line 3 +ORA-06512: at "PUBLIC.PKG_CALLER", line 8 + +DROP PACKAGE test_pkg; +-- Test 9: FORMAT_ERROR_BACKTRACE - Schema-qualified calls +CREATE SCHEMA test_schema; +CREATE OR REPLACE PROCEDURE test_schema.schema_error AS +BEGIN + RAISE EXCEPTION 'Error in schema procedure'; +END; +/ +CREATE OR REPLACE PROCEDURE test_schema.schema_caller AS + v_backtrace VARCHAR2(4000); +BEGIN + test_schema.schema_error(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Schema-qualified backtrace: %', v_backtrace; +END; +/ +CALL test_schema.schema_caller(); +INFO: Schema-qualified backtrace: ORA-06512: at "PUBLIC.TEST_SCHEMA.SCHEMA_ERROR", line 2 +ORA-06512: at "PUBLIC.TEST_SCHEMA.SCHEMA_CALLER", line 3 + +DROP SCHEMA test_schema CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to function test_schema.schema_error() +drop cascades to function test_schema.schema_caller() diff --git a/src/pl/plisql/src/pl_dbms_utility.c b/src/pl/plisql/src/pl_dbms_utility.c new file mode 100644 index 00000000000..52d9733d3ac --- /dev/null +++ b/src/pl/plisql/src/pl_dbms_utility.c @@ -0,0 +1,203 @@ +/*------------------------------------------------------------------------- + * Copyright 2025 IvorySQL Global Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * pl_dbms_utility.c + * + * This file contains the implementation of Oracle's DBMS_UTILITY package + * functions. These functions are part of the PL/iSQL language runtime + * because they need access to PL/iSQL internals (exception context, etc.) + * + * Portions Copyright (c) 2025, IvorySQL Global Development Team + * + * src/pl/plisql/src/pl_dbms_utility.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "fmgr.h" +#include "funcapi.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_namespace.h" +#include "utils/builtins.h" +#include "utils/elog.h" +#include "utils/formatting.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "mb/pg_wchar.h" +#include "plisql.h" + +PG_FUNCTION_INFO_V1(ora_format_error_backtrace); + +/* + * Transform a single line from PostgreSQL error context format to Oracle format. + * + * PostgreSQL format examples: + * "PL/pgSQL function test_level3() line 3 at RAISE" + * "PL/iSQL function test_level3() line 3 at RAISE" + * "PL/iSQL function inline_code_block line 5 at CALL" + * "SQL statement \"CALL test_level3()\"" + * + * Oracle format: + * "ORA-06512: at \"SCHEMA.TEST_LEVEL3\", line 3" + * "ORA-06512: at line 5" + * + * Returns true if line was transformed and appended, false if line should be skipped. + */ +static bool +transform_and_append_line(StringInfo result, const char *line) +{ + const char *p = line; + const char *func_start; + const char *func_end; + const char *line_num_start; + const char *line_marker; + int line_num; + char *func_name; + char *schema_name; + char *func_upper; + char *schema_upper; + int i; + + /* Skip SQL statement lines */ + if (strncmp(line, "SQL statement", 13) == 0) + return false; + + /* Look for "PL/pgSQL function" or "PL/iSQL function" */ + if (strncmp(p, "PL/pgSQL function ", 18) == 0) + p += 18; + else if (strncmp(p, "PL/iSQL function ", 17) == 0) + p += 17; + else + return false; /* Unknown format, skip */ + + func_start = p; + + /* Find the end of the function name (before the opening parenthesis or space for inline blocks) */ + func_end = strchr(p, '('); + if (!func_end) + { + /* No parenthesis - might be inline_code_block which has format "inline_code_block line N" */ + func_end = strstr(p, " line "); + if (!func_end) + return false; + } + + /* Extract function name */ + func_name = pnstrdup(func_start, func_end - func_start); + + /* Check if this is an inline/anonymous block */ + if (strcmp(func_name, "inline_code_block") == 0) + { + /* For anonymous blocks, just show line number */ + /* Find " line " */ + line_marker = strstr(func_end, " line "); + if (!line_marker) + return false; + + line_num_start = line_marker + 6; /* Skip " line " */ + line_num = atoi(line_num_start); + + appendStringInfo(result, "ORA-06512: at line %d\n", line_num); + pfree(func_name); + return true; + } + + /* For named functions/procedures, lookup schema */ + /* Find " line " */ + line_marker = strstr(func_end, " line "); + if (!line_marker) + return false; + + line_num_start = line_marker + 6; /* Skip " line " */ + line_num = atoi(line_num_start); + + /* For now, just use PUBLIC as the default schema */ + /* TODO: Look up the actual schema from pg_proc catalog */ + schema_name = pstrdup("PUBLIC"); + + /* Convert function name to uppercase for Oracle compatibility */ + /* Use simple ASCII uppercase conversion */ + func_upper = pstrdup(func_name); + for (i = 0; func_upper[i]; i++) + func_upper[i] = pg_toupper((unsigned char) func_upper[i]); + + schema_upper = pstrdup(schema_name); + for (i = 0; schema_upper[i]; i++) + schema_upper[i] = pg_toupper((unsigned char) schema_upper[i]); + + /* Format: ORA-06512: at "SCHEMA.FUNCTION", line N */ + appendStringInfo(result, "ORA-06512: at \"%s.%s\", line %d\n", + schema_upper, func_upper, line_num); + + pfree(func_name); + pfree(func_upper); + pfree(schema_upper); + if (schema_name) + pfree(schema_name); + + return true; +} + +/* + * ora_format_error_backtrace - FORMAT_ERROR_BACKTRACE implementation + * + * Returns formatted error backtrace string in Oracle format. + * Returns NULL if not in exception handler context. + * + * This Oracle-compatible function automatically retrieves the exception + * context from PL/iSQL's session storage, which is set when entering + * an exception handler. + */ +Datum +ora_format_error_backtrace(PG_FUNCTION_ARGS) +{ + const char *pg_context; + char *context_copy; + char *line; + char *saveptr; + StringInfoData result; + + /* Get the current exception context from PL/iSQL session storage */ + pg_context = plisql_get_current_exception_context(); + + /* If no context available (not in exception handler), return NULL */ + if (pg_context == NULL || pg_context[0] == '\0') + PG_RETURN_NULL(); + + initStringInfo(&result); + + /* Make a copy since strtok_r modifies the string */ + context_copy = pstrdup(pg_context); + + /* Parse and transform each line */ + line = strtok_r(context_copy, "\n", &saveptr); + while (line != NULL) + { + /* Skip empty lines */ + if (line[0] != '\0') + transform_and_append_line(&result, line); + + line = strtok_r(NULL, "\n", &saveptr); + } + + pfree(context_copy); + + /* Oracle always ends with a newline - don't remove it */ + /* The transform_and_append_line function already adds newlines */ + + PG_RETURN_TEXT_P(cstring_to_text(result.data)); +} diff --git a/src/pl/plisql/src/pl_exec.c b/src/pl/plisql/src/pl_exec.c index 55d671c7844..c0a4c7d9662 100644 --- a/src/pl/plisql/src/pl_exec.c +++ b/src/pl/plisql/src/pl_exec.c @@ -114,6 +114,14 @@ static SimpleEcontextStackEntry *simple_econtext_stack = NULL; */ static ResourceOwner shared_simple_eval_resowner = NULL; +/* + * Session-level storage for the current exception context string. + * This is set when entering an exception handler and can be retrieved + * by DBMS_UTILITY.FORMAT_ERROR_BACKTRACE and similar functions. + * The memory is allocated in TopMemoryContext to survive across function calls. + */ +static char *plisql_current_exception_context = NULL; + /* * Memory management within a plisql function generally works with three * contexts: @@ -2087,6 +2095,22 @@ exec_stmt_block(PLiSQL_execstate * estate, PLiSQL_stmt_block * block) */ estate->cur_error = edata; + /* + * Store the exception context string in session-level storage + * so that DBMS_UTILITY.FORMAT_ERROR_BACKTRACE and similar + * functions can access it. This provides Oracle compatibility. + */ + if (plisql_current_exception_context) + { + pfree(plisql_current_exception_context); + plisql_current_exception_context = NULL; + } + if (edata->context) + { + plisql_current_exception_context = + MemoryContextStrdup(TopMemoryContext, edata->context); + } + estate->err_text = NULL; rc = exec_stmts(estate, exception->action); @@ -2102,6 +2126,16 @@ exec_stmt_block(PLiSQL_execstate * estate, PLiSQL_stmt_block * block) */ estate->cur_error = save_cur_error; + /* + * Clear the session-level exception context now that we've finished + * handling the exception. + */ + if (plisql_current_exception_context) + { + pfree(plisql_current_exception_context); + plisql_current_exception_context = NULL; + } + /* If no match found, re-throw the error */ if (e == NULL) ReThrowError(edata); @@ -10001,3 +10035,18 @@ plisql_anonymous_return_out_parameter(PLiSQL_execstate * estate, PLiSQL_function return; } + +/* + * plisql_get_current_exception_context + * + * Returns the current exception context string if we're in an exception handler, + * otherwise returns NULL. This is used by Oracle-compatible functions like + * DBMS_UTILITY.FORMAT_ERROR_BACKTRACE. + * + * The returned string is managed by PL/iSQL and should not be freed by the caller. + */ +const char * +plisql_get_current_exception_context(void) +{ + return plisql_current_exception_context; +} diff --git a/src/pl/plisql/src/plisql--1.0.sql b/src/pl/plisql/src/plisql--1.0.sql index 1a1c005d93e..63a09b10937 100755 --- a/src/pl/plisql/src/plisql--1.0.sql +++ b/src/pl/plisql/src/plisql--1.0.sql @@ -21,3 +21,33 @@ ALTER LANGUAGE plisql OWNER TO @extowner@; COMMENT ON LANGUAGE plisql IS 'PL/iSQL procedural language'; + +-- +-- DBMS_UTILITY Package +-- +-- Oracle-compatible utility functions that require access to PL/iSQL internals. +-- These are installed as part of the PL/iSQL language extension. +-- + +-- C function wrapper for FORMAT_ERROR_BACKTRACE +CREATE FUNCTION sys.ora_format_error_backtrace() RETURNS TEXT + AS 'MODULE_PATHNAME', 'ora_format_error_backtrace' + LANGUAGE C VOLATILE STRICT; + +COMMENT ON FUNCTION sys.ora_format_error_backtrace() IS 'Internal function for DBMS_UTILITY.FORMAT_ERROR_BACKTRACE'; + +-- +-- DBMS_UTILITY Package Definition +-- + +CREATE OR REPLACE PACKAGE dbms_utility IS + FUNCTION FORMAT_ERROR_BACKTRACE RETURN TEXT; +END dbms_utility; + +CREATE OR REPLACE PACKAGE BODY dbms_utility IS + FUNCTION FORMAT_ERROR_BACKTRACE RETURN TEXT IS + BEGIN + RETURN sys.ora_format_error_backtrace(); + END; +END dbms_utility; + diff --git a/src/pl/plisql/src/plisql.h b/src/pl/plisql/src/plisql.h index 7ab6c2c958d..15bff544b0c 100755 --- a/src/pl/plisql/src/plisql.h +++ b/src/pl/plisql/src/plisql.h @@ -1458,4 +1458,9 @@ extern void plisql_recover_yylex_global_proper(void *yylex_data); extern int plisql_yyparse(PLiSQL_stmt_block * *plisql_parse_result_p, yyscan_t yyscanner); +/* + * Externs in pl_exec.c for exception context access + */ +extern PGDLLEXPORT const char *plisql_get_current_exception_context(void); + #endif /* PLISQL_H */ diff --git a/src/pl/plisql/src/sql/dbms_utility.sql b/src/pl/plisql/src/sql/dbms_utility.sql new file mode 100644 index 00000000000..c9225baabdb --- /dev/null +++ b/src/pl/plisql/src/sql/dbms_utility.sql @@ -0,0 +1,233 @@ +-- +-- Tests for DBMS_UTILITY package +-- + +-- Test 1: FORMAT_ERROR_BACKTRACE - Basic exception in procedure +CREATE OR REPLACE PROCEDURE test_basic_error AS + v_backtrace VARCHAR2(4000); +BEGIN + RAISE EXCEPTION 'Test error'; +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Backtrace: %', v_backtrace; +END; +/ + +CALL test_basic_error(); + +DROP PROCEDURE test_basic_error; + +-- Test 2: FORMAT_ERROR_BACKTRACE - Nested procedure calls +CREATE OR REPLACE PROCEDURE test_level3 AS +BEGIN + RAISE EXCEPTION 'Error at level 3'; +END; +/ + +CREATE OR REPLACE PROCEDURE test_level2 AS +BEGIN + test_level3(); +END; +/ + +CREATE OR REPLACE PROCEDURE test_level1 AS + v_backtrace VARCHAR2(4000); +BEGIN + test_level2(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Backtrace: %', v_backtrace; +END; +/ + +CALL test_level1(); + +DROP PROCEDURE test_level1; +DROP PROCEDURE test_level2; +DROP PROCEDURE test_level3; + +-- Test 3: FORMAT_ERROR_BACKTRACE - Deeply nested calls +CREATE OR REPLACE PROCEDURE test_deep5 AS +BEGIN + RAISE EXCEPTION 'Error at deepest level'; +END; +/ + +CREATE OR REPLACE PROCEDURE test_deep4 AS +BEGIN + test_deep5(); +END; +/ + +CREATE OR REPLACE PROCEDURE test_deep3 AS +BEGIN + test_deep4(); +END; +/ + +CREATE OR REPLACE PROCEDURE test_deep2 AS +BEGIN + test_deep3(); +END; +/ + +CREATE OR REPLACE PROCEDURE test_deep1 AS + v_backtrace VARCHAR2(4000); +BEGIN + test_deep2(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Deep backtrace: %', v_backtrace; +END; +/ + +CALL test_deep1(); + +DROP PROCEDURE test_deep1; +DROP PROCEDURE test_deep2; +DROP PROCEDURE test_deep3; +DROP PROCEDURE test_deep4; +DROP PROCEDURE test_deep5; + +-- Test 4: FORMAT_ERROR_BACKTRACE - Function calls +CREATE OR REPLACE FUNCTION test_func_error RETURN NUMBER AS +BEGIN + RAISE EXCEPTION 'Error in function'; + RETURN 1; +END; +/ + +CREATE OR REPLACE PROCEDURE test_func_caller AS + v_result NUMBER; + v_backtrace VARCHAR2(4000); +BEGIN + v_result := test_func_error(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Function backtrace: %', v_backtrace; +END; +/ + +CALL test_func_caller(); + +DROP PROCEDURE test_func_caller; +DROP FUNCTION test_func_error; + +-- Test 5: FORMAT_ERROR_BACKTRACE - Anonymous block +DO $$ +DECLARE + v_backtrace VARCHAR2(4000); +BEGIN + RAISE EXCEPTION 'Error in anonymous block'; +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Anonymous block backtrace: %', v_backtrace; +END; +$$; + +-- Test 6: FORMAT_ERROR_BACKTRACE - No exception (should return empty) +CREATE OR REPLACE PROCEDURE test_no_error AS + v_backtrace VARCHAR2(4000); +BEGIN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'No error - backtrace: [%]', v_backtrace; +END; +/ + +CALL test_no_error(); + +DROP PROCEDURE test_no_error; + +-- Test 7: FORMAT_ERROR_BACKTRACE - Multiple exception levels +CREATE OR REPLACE PROCEDURE test_multi_inner AS +BEGIN + RAISE EXCEPTION 'Inner error'; +END; +/ + +CREATE OR REPLACE PROCEDURE test_multi_middle AS +BEGIN + BEGIN + test_multi_inner(); + EXCEPTION + WHEN OTHERS THEN + RAISE INFO 'Caught at middle level'; + RAISE; + END; +END; +/ + +CREATE OR REPLACE PROCEDURE test_multi_outer AS + v_backtrace VARCHAR2(4000); +BEGIN + test_multi_middle(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Outer backtrace: %', v_backtrace; +END; +/ + +CALL test_multi_outer(); + +DROP PROCEDURE test_multi_outer; +DROP PROCEDURE test_multi_middle; +DROP PROCEDURE test_multi_inner; + +-- Test 8: FORMAT_ERROR_BACKTRACE - Package procedure +CREATE OR REPLACE PACKAGE test_pkg IS + PROCEDURE pkg_error; + PROCEDURE pkg_caller; +END test_pkg; +/ + +CREATE OR REPLACE PACKAGE BODY test_pkg IS + PROCEDURE pkg_error IS + BEGIN + RAISE EXCEPTION 'Error in package procedure'; + END pkg_error; + + PROCEDURE pkg_caller IS + v_backtrace VARCHAR2(4000); + BEGIN + pkg_error(); + EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Package backtrace: %', v_backtrace; + END pkg_caller; +END test_pkg; +/ + +CALL test_pkg.pkg_caller(); + +DROP PACKAGE test_pkg; + +-- Test 9: FORMAT_ERROR_BACKTRACE - Schema-qualified calls +CREATE SCHEMA test_schema; + +CREATE OR REPLACE PROCEDURE test_schema.schema_error AS +BEGIN + RAISE EXCEPTION 'Error in schema procedure'; +END; +/ + +CREATE OR REPLACE PROCEDURE test_schema.schema_caller AS + v_backtrace VARCHAR2(4000); +BEGIN + test_schema.schema_error(); +EXCEPTION + WHEN OTHERS THEN + v_backtrace := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Schema-qualified backtrace: %', v_backtrace; +END; +/ + +CALL test_schema.schema_caller(); + +DROP SCHEMA test_schema CASCADE; From 7c65c0d7dc9f021c6e664750af3d0ac1e203a7a7 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 30 Nov 2025 23:02:24 +0000 Subject: [PATCH 17/20] docs: add DBMS_UTILITY architecture design documentation --- design/dbms_utility/ARCHITECTURE.md | 323 +++++++++++++++++++++ design/dbms_utility/DEPENDENCY_ANALYSIS.md | 310 ++++++++++++++++++++ design/dbms_utility/EXISTING_PACKAGES.md | 110 +++++++ design/dbms_utility/README.md | 94 ++++++ 4 files changed, 837 insertions(+) create mode 100644 design/dbms_utility/ARCHITECTURE.md create mode 100644 design/dbms_utility/DEPENDENCY_ANALYSIS.md create mode 100644 design/dbms_utility/EXISTING_PACKAGES.md create mode 100644 design/dbms_utility/README.md diff --git a/design/dbms_utility/ARCHITECTURE.md b/design/dbms_utility/ARCHITECTURE.md new file mode 100644 index 00000000000..3cbaaca173b --- /dev/null +++ b/design/dbms_utility/ARCHITECTURE.md @@ -0,0 +1,323 @@ +# DBMS_UTILITY Architecture Discussion + +## Context + +IvorySQL is implementing Oracle-compatible DBMS packages. This document discusses the architectural decisions for where DBMS package implementations should live in the codebase. + +## Current State + +### Existing Built-in Packages + +As of Nov 2025, IvorySQL upstream has **ZERO** built-in DBMS packages. + +**DBMS_UTILITY** (current work) +- Location: `contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c` (to be refactored) +- Status: In development +- Functions: FORMAT_ERROR_BACKTRACE (and more planned) + +This is the **first** built-in Oracle DBMS package being implemented for IvorySQL. + +### IvorySQL Module Structure + +IvorySQL has two key modules that compile into separate shared libraries: + +1. **PL/iSQL Language Runtime** (`src/pl/plisql/src/`) + - Compiles to: `plisql.so` + - Purpose: PL/iSQL procedural language implementation + - Components: Parser, compiler, executor, exception handling + - Exports: Language handler, validator functions + - Key files: `pl_handler.c`, `pl_exec.c`, `pl_comp.c`, `plisql.h` + +2. **Oracle Compatibility Extension** (`contrib/ivorysql_ora/`) + - Compiles to: `ivorysql_ora.so` + - Purpose: Oracle-compatible datatypes, functions, and packages + - Components: Datatypes (CHAR, VARCHAR2, DATE, etc.), built-in functions, DBMS packages + - Key files: Various C implementations merged into one extension + +## Architectural Question + +**Where should DBMS package implementations live when they need access to PL/iSQL internals?** + +### Two Possible Architectures + +#### Architecture 1: Contrib-based (Current Implementation) + +``` +contrib/ivorysql_ora/ +├── src/builtin_functions/ +│ ├── dbms_utility.c ← C implementation with PL/iSQL internals access +│ └── dbms_utility--1.0.sql ← SQL package specification and body +└── Makefile + ├── OBJS += dbms_utility.o + └── PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plisql/src ← Cross-dependency! +``` + +**Key characteristic:** Extension code depends on language internals (cross-module dependency). + +#### Architecture 2: PL/iSQL-based (Proposed) + +``` +src/pl/plisql/src/ +├── dbms_utility.c ← C implementation as part of language +├── plisql.h ← Exports function declarations +└── Makefile + └── OBJS += dbms_utility.o + +contrib/ivorysql_ora/ +└── src/builtin_functions/ + └── dbms_utility--1.0.sql ← SQL package wrapper calling plisql functions +``` + +**Key characteristic:** Language provides native functions; extension wraps them in Oracle syntax. + +## Analysis + +### Current Cross-Module Dependency + +The current implementation requires `contrib/ivorysql_ora` to access PL/iSQL internals: + +```c +// In contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c +#include "plisql.h" // From src/pl/plisql/src/ + +// Accesses: +// - PLiSQL_execstate (exception context) +// - Error stack information +// - PL/iSQL internal data structures +``` + +```makefile +# In contrib/ivorysql_ora/Makefile (line 79) +PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plisql/src +``` + +This creates a **layering violation**: an extension (contrib) depends on a core language module's private headers. + +### Trade-offs Comparison + +| Aspect | Architecture 1 (Contrib) | Architecture 2 (PL/iSQL) | +|--------|-------------------------|--------------------------| +| **Dependency Direction** | Extension → Language (bad) | Language is self-contained (good) | +| **Code Organization** | All Oracle features in one place | Split: internals in language, wrappers in extension | +| **Versioning** | Can version extension independently | DBMS packages tied to language version | +| **Maintainability** | Cross-module changes harder | Changes contained in language module | +| **Encapsulation** | Violates module boundaries | Respects module boundaries | +| **Optionality** | Can load/unload extension | Always available with language | +| **Consistency** | All DBMS packages in same location | Some in language, some in extension | + +### PostgreSQL Precedent + +PostgreSQL's PL/pgSQL follows a similar pattern: + +``` +src/pl/plpgsql/src/ +├── pl_exec.c ← Language implementation +└── (exports functions that extensions can call) + +contrib/extensions/ +└── (use plpgsql functions without accessing internals) +``` + +**Key insight:** Extensions don't include `plpgsql.h` internals. If they need internal features, those features are exported as public functions. + +## Recommendations + +### Recommendation 1: Split by Coupling Level + +**For DBMS packages that NEED PL/iSQL internals (like DBMS_UTILITY.FORMAT_ERROR_BACKTRACE):** + +Put C implementation in `src/pl/plisql/src/`: +``` +src/pl/plisql/src/ +├── pl_dbms_utility.c ← Native implementation +├── plisql.h ← Export declarations +└── Makefile (add to OBJS) + +contrib/ivorysql_ora/ +└── dbms_utility--1.0.sql ← CREATE PACKAGE wrapper +``` + +**For DBMS packages that DON'T need PL/iSQL internals:** + +Keep in `contrib/ivorysql_ora/`: +``` +contrib/ivorysql_ora/ +└── src/builtin_functions/ + ├── .c ← Self-contained implementation + └── --1.0.sql ← CREATE PACKAGE wrapper +``` + +### Recommendation 2: Clean Layering + +Follow this dependency hierarchy: +``` +1. PL/iSQL Language (src/pl/plisql/src/) + ↓ provides functions +2. Oracle Extension (contrib/ivorysql_ora/) + ↓ uses language functions, adds Oracle syntax +3. User Code +``` + +**Never:** Extension → Language internals (violates encapsulation) + +### Recommendation 3: File Naming Convention + +If moving DBMS packages to `src/pl/plisql/src/`, use clear naming: +- `pl_dbms_utility.c` (part of language, for DBMS_UTILITY) +- `pl_exec.c` (language execution, existing) + +This distinguishes "language internals" from "language-provided DBMS packages." + +## Decision: Architecture 2 - PL/iSQL-based + +**Decision Date:** 2025-11-30 + +After detailed analysis, we chose **Architecture 2: PL/iSQL-based** for DBMS_UTILITY. + +### Rationale + +1. **Original Design Intent:** In upstream IvorySQL, `plisql` and `ivorysql_ora` are **independent modules** with no cross-dependencies. Both depend on core headers (`src/include/`), but neither depends on the other. + +2. **Our Change Broke This:** By adding `#include "plisql.h"` in `contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c`, we introduced a cross-module dependency that didn't exist before. + +3. **DBMS_UTILITY Doesn't Need Oracle Types:** The C implementation only uses PostgreSQL native types (`TEXT`). It doesn't require `VARCHAR2` or other Oracle types at compile time or load time. + +4. **Load Order Analysis:** + ``` + initdb -m oracle + │ + ├─→ preload_ora_misc.sql (1st - basic objects, NO plisql yet) + │ + ├─→ CREATE EXTENSION plisql (2nd - loads plisql--1.0.sql) + │ + └─→ CREATE EXTENSION ivorysql_ora (3rd - loads ivorysql_ora--1.0.sql) + ``` + + Since DBMS_UTILITY doesn't need Oracle types, it can be fully defined at step 2 (`plisql--1.0.sql`), before `ivorysql_ora` loads. + +5. **plisql--1.0.sql is Currently Empty:** IvorySQL's `plisql--1.0.sql` is almost empty because the language is pre-defined in system catalogs (`pg_proc.dat`, `pg_language.dat`). This file is the right place to add DBMS package definitions. + +### Final Structure + +``` +src/pl/plisql/src/ +├── pl_dbms_utility.c ← C implementation (needs plisql.h internals) +├── plisql.h ← Already has what we need +├── plisql--1.0.sql ← CREATE FUNCTION + CREATE PACKAGE (all in one file!) +└── Makefile ← Add pl_dbms_utility.o to OBJS + +src/bin/initdb/initdb.c +└── load_plisql() ← Wraps CREATE EXTENSION in Oracle mode +``` + +**Key Insight: SET commands work in extension SQL files!** + +Extension SQL files CAN temporarily switch `compatible_mode` to enable Oracle syntax. +This is much cleaner than hardcoding SQL in initdb.c. + +**plisql--1.0.sql implementation:** +```sql +-- C function wrapper (works in PG mode) +CREATE FUNCTION sys.ora_format_error_backtrace() RETURNS TEXT + AS 'MODULE_PATHNAME', 'ora_format_error_backtrace' + LANGUAGE C VOLATILE STRICT; + +-- Switch to Oracle mode for CREATE PACKAGE syntax +SET ivorysql.compatible_mode TO oracle; + +CREATE OR REPLACE PACKAGE dbms_utility IS + FUNCTION FORMAT_ERROR_BACKTRACE RETURN TEXT; +END dbms_utility; + +CREATE OR REPLACE PACKAGE BODY dbms_utility IS + FUNCTION FORMAT_ERROR_BACKTRACE RETURN TEXT IS + BEGIN + RETURN sys.ora_format_error_backtrace(); + END; +END dbms_utility; + +-- Switch back to PG mode +SET ivorysql.compatible_mode TO pg; +``` + +**initdb.c load_plisql() implementation:** +```c +static void +load_plisql(FILE *cmdfd) +{ + /* Switch to oracle mode to allow CREATE PACKAGE in extension SQL */ + PG_CMD_PUTS("set ivorysql.compatible_mode to oracle;\n\n"); + PG_CMD_PUTS("CREATE EXTENSION plisql;\n\n"); + PG_CMD_PUTS("set ivorysql.compatible_mode to pg;\n\n"); +} +``` + +**Why this approach is better:** +- ✅ Package definition in SQL file (proper extension versioning) +- ✅ No hardcoded SQL in C code +- ✅ Syntax highlighting and easier maintenance +- ✅ Self-contained extension +- ✅ Works with CREATE/DROP EXTENSION after initdb + +### Benefits + +1. **No Cross-Module Dependency:** Everything DBMS_UTILITY needs is within `src/pl/plisql/src/` +2. **Clean Architecture:** Respects original module boundaries +3. **Built-in at initdb:** Available immediately after `CREATE EXTENSION plisql` +4. **Single Compilation Unit:** C code compiles into `plisql.so` + +### Trade-offs Accepted + +1. **Split Location:** DBMS packages that need PL/iSQL internals live in `plisql`, others may stay in `ivorysql_ora` +2. **TEXT vs VARCHAR2:** Package returns `TEXT` instead of `VARCHAR2` (compatible in Oracle mode) + +## Guidelines for Future DBMS Packages + +Based on this decision: + +| Package Needs | Location | +|--------------|----------| +| PL/iSQL internals (exception context, call stack, etc.) | `src/pl/plisql/src/` | +| Only Oracle datatypes, no PL/iSQL internals | `contrib/ivorysql_ora/` | +| Both PL/iSQL internals AND Oracle types | Split: C in plisql, SQL wrapper in ivorysql_ora | + +## Answered Questions + +1. **Extension Loading:** DBMS_UTILITY will be created when `CREATE EXTENSION plisql` runs (during `initdb -m oracle`). No separate extension needed. + +2. **Future Packages:** Follow the guidelines table above based on dependencies. + +## Implementation Status + +1. ✅ Document architecture decision (this document) +2. ✅ Implement C function in `src/pl/plisql/src/pl_dbms_utility.c` +3. ✅ Register C function in `src/pl/plisql/src/plisql--1.0.sql` +4. ✅ Create DBMS_UTILITY package in `plisql--1.0.sql` (using SET compatible_mode) +5. ✅ Update initdb.c to wrap extension load in Oracle mode +6. ✅ Add regression tests in `src/pl/plisql/src/sql/dbms_utility.sql` +7. ✅ All 17 plisql oracle-check tests passing + +## Key Discovery (2025-11-30) + +**Original concern:** Package creation was hardcoded in `initdb.c` instead of in extension SQL files. + +**Investigation:** We tested whether `SET ivorysql.compatible_mode` works within extension SQL files. + +**Result:** ✅ **It works!** Extension SQL files CAN temporarily switch compatible_mode to enable Oracle syntax. + +**Solution:** +1. Move CREATE PACKAGE statements from `initdb.c` to `plisql--1.0.sql` +2. Wrap them with `SET ivorysql.compatible_mode TO oracle/pg` +3. Update `load_plisql()` in initdb.c to ensure extension loads in Oracle mode context + +**Benefits:** +- Cleaner code (SQL in .sql files, not C strings) +- Proper extension versioning support +- Easier maintenance and testing +- Self-contained extension + +--- + +**Document Status:** Implementation complete (refactored to use extension SQL approach) +**Last Updated:** 2025-11-30 +**Authors:** Rophy Tsai diff --git a/design/dbms_utility/DEPENDENCY_ANALYSIS.md b/design/dbms_utility/DEPENDENCY_ANALYSIS.md new file mode 100644 index 00000000000..f5f20564a2f --- /dev/null +++ b/design/dbms_utility/DEPENDENCY_ANALYSIS.md @@ -0,0 +1,310 @@ +# Cross-Module Dependency Analysis + +## The Problem + +DBMS_UTILITY's `FORMAT_ERROR_BACKTRACE` function needs to access PL/iSQL's exception context, creating a cross-module dependency. + +## Current Implementation + +### Module Structure + +IvorySQL has two separate compilation units: + +``` +src/pl/plisql/src/ → plisql.so (language runtime) +contrib/ivorysql_ora/ → ivorysql_ora.so (Oracle compatibility extension) +``` + +These compile to **separate shared libraries** that are loaded independently. + +### Dependency Chain + +``` +User SQL: + DBMS_UTILITY.FORMAT_ERROR_BACKTRACE + +↓ Calls (via package body) + +C Function (in ivorysql_ora.so): + sys.ora_format_error_backtrace() + +↓ Needs access to + +PL/iSQL Internals (in plisql.so): + PLiSQL_execstate->err_text + Error stack information + Exception context +``` + +### Current Implementation Files + +**contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c:** +```c +#include "postgres.h" +#include "plisql.h" // ← From src/pl/plisql/src/plisql.h + +PG_FUNCTION_INFO_V1(ora_format_error_backtrace); + +Datum +ora_format_error_backtrace(PG_FUNCTION_ARGS) +{ + // Needs access to: + PLiSQL_execstate *estate = ...; // PL/iSQL execution state + char *err_text = estate->err_text; // Exception context + // ... +} +``` + +**contrib/ivorysql_ora/Makefile (line 79):** +```makefile +# Include path for PL/iSQL headers (needed for DBMS_UTILITY.FORMAT_ERROR_BACKTRACE) +PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plisql/src +``` + +## Why This Is Problematic + +### 1. Layering Violation + +``` +┌─────────────────────────────────┐ +│ User Code │ +└─────────────────┬───────────────┘ + │ +┌─────────────────▼───────────────┐ +│ contrib/ivorysql_ora │ ← Extension layer +│ (Oracle compatibility) │ +└─────────────────┬───────────────┘ + │ + │ ❌ SHOULD NOT ACCESS INTERNALS + │ +┌─────────────────▼───────────────┐ +│ src/pl/plisql/src │ ← Language layer +│ (PL/iSQL runtime internals) │ +└─────────────────────────────────┘ +``` + +**Principle:** Higher-level modules (extensions) should NOT depend on lower-level module internals. + +### 2. Encapsulation Break + +`plisql.h` contains **private implementation details**: +- `PLiSQL_execstate` structure layout +- Internal error handling mechanisms +- Memory management details + +These are **not intended as a public API**. Changes to PL/iSQL internals will break `ivorysql_ora`. + +### 3. Maintenance Burden + +**Scenario:** PL/iSQL team refactors exception handling +```c +// Before (in plisql.h) +typedef struct PLiSQL_execstate { + char *err_text; +} PLiSQL_execstate; + +// After (refactored) +typedef struct PLiSQL_execstate { + ErrorData *error_data; // Changed! +} PLiSQL_execstate; +``` + +**Result:** `dbms_utility.c` breaks because it depends on internal structure. + +### 4. Binary Compatibility + +Two separate shared libraries (`plisql.so` and `ivorysql_ora.so`) must maintain **ABI compatibility**: +- If `plisql.so` is upgraded, `ivorysql_ora.so` may break +- Versioning becomes complex +- Testing matrix increases (all combinations of versions) + +## Dependency Patterns in PostgreSQL + +### How PL/pgSQL Handles This + +PostgreSQL's PL/pgSQL **does NOT** expose internals to extensions. Instead: + +**Option 1: Public API Functions** +```c +// In src/pl/plpgsql/src/pl_funcs.c (exported) +Datum plpgsql_get_error_info(PG_FUNCTION_ARGS) { + // Access internals safely + return internal_data; +} +``` + +Extensions call public functions, not access internals directly. + +**Option 2: Callback Mechanism** +```c +// Language registers callbacks that extensions can use +void register_error_callback(ErrorCallbackFunc func); +``` + +**Option 3: Shared State in Core** +```c +// In src/backend/utils/error/elog.c (core PostgreSQL) +ErrorData *current_error_data; // Accessible to all +``` + +All modules access shared state in core, not each other's internals. + +### What IvorySQL Should Do + +**Current (Bad):** +``` +ivorysql_ora.so → #include "plisql.h" → Access internals directly +``` + +**Better (Good):** +``` +ivorysql_ora.so → Call plisql_get_exception_context() → plisql.so handles internals +``` + +## Proposed Solutions + +### Solution 1: Move DBMS_UTILITY to PL/iSQL + +**Rationale:** If it needs PL/iSQL internals, it IS part of PL/iSQL. + +``` +src/pl/plisql/src/ +├── pl_dbms_utility.c ← Native implementation (can access internals) +├── plisql--1.0.sql ← Export as sys.ora_format_error_backtrace() +└── plisql.h ← No need to expose, internal use only + +contrib/ivorysql_ora/ +└── dbms_utility--1.0.sql ← CREATE PACKAGE wrapper (calls plisql functions) +``` + +**Pros:** +- ✅ No cross-module dependency +- ✅ Clean encapsulation +- ✅ Natural architecture: "internal to language" + +**Cons:** +- ❌ Splits DBMS packages across two locations +- ❌ Makes DBMS_UTILITY part of core (harder to make optional) + +### Solution 2: Create Public API in PL/iSQL + +**Rationale:** PL/iSQL exports exception info through a stable API. + +```c +// In src/pl/plisql/src/pl_public_api.c (NEW FILE) +PG_FUNCTION_INFO_V1(plisql_get_exception_context); + +Datum plisql_get_exception_context(PG_FUNCTION_ARGS) { + // Access internal PLiSQL_execstate + // Return formatted error info + return error_context_string; +} +``` + +```c +// In contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c +// NO #include "plisql.h" needed! + +Datum ora_format_error_backtrace(PG_FUNCTION_ARGS) { + // Call public API + return DirectFunctionCall0(plisql_get_exception_context); +} +``` + +**Pros:** +- ✅ Keeps all DBMS packages in one location (contrib) +- ✅ Stable API (internals can change without breaking extension) +- ✅ Follows PostgreSQL patterns + +**Cons:** +- ❌ More boilerplate (need public API layer) +- ❌ PL/iSQL must maintain backward compatibility for API + +### Solution 3: Use Core PostgreSQL Error System + +**Rationale:** PostgreSQL core already tracks errors; use that instead. + +```c +// In contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c +#include "utils/elog.h" // Core PostgreSQL, not plisql.h + +Datum ora_format_error_backtrace(PG_FUNCTION_ARGS) { + ErrorData *edata = current_error_data; // From core + // Format Oracle-style backtrace + return backtrace_string; +} +``` + +**Pros:** +- ✅ No dependency on PL/iSQL +- ✅ Works with ANY procedural language (PL/Python, PL/Perl, etc.) +- ✅ Most general solution + +**Cons:** +- ❌ PostgreSQL's error context format differs from Oracle's +- ❌ May not have PL/iSQL-specific details (procedure names, line numbers) +- ❌ Requires translation layer + +## Recommendation + +**Use Solution 1 for now, plan for Solution 2 long-term:** + +1. **Short-term (current implementation):** + - Move `dbms_utility.c` to `src/pl/plisql/src/pl_dbms_utility.c` + - Keep SQL wrapper in `contrib/ivorysql_ora/` + - Clean dependency: no cross-module includes + +2. **Long-term (if more extensions need error context):** + - Extract public API: `src/pl/plisql/src/pl_public_api.c` + - Export stable functions for exception context + - Move `pl_dbms_utility.c` back to `contrib` using public API + +3. **Future consideration:** + - If many DBMS packages need PL/iSQL internals, they should ALL be in `src/pl/plisql/src/` + - If only DBMS_UTILITY needs it, keep it there as a special case + +## Comparison with Other Systems + +### Oracle + +Oracle's DBMS packages are **part of the database core**, not extensions: +- Built into the database binary +- No separation between "language" and "packages" +- Tightly integrated with PL/SQL runtime + +### EDB Postgres Advanced Server (EPAS) + +EPAS implements Oracle compatibility as **built-in features**: +- Not separate extensions +- DBMS packages compiled into server +- Similar to Solution 1 (part of core) + +### Orafce (PostgreSQL Extension) + +Orafce implements Oracle compatibility as a **pure extension**: +- Does NOT access PL/pgSQL internals +- Reimplements functionality using PostgreSQL public APIs +- Similar to Solution 3 (use core APIs only) + +## Decision Criteria + +**Choose Solution 1 if:** +- Tight coupling to PL/iSQL is acceptable +- We want native Oracle behavior (exact error formats) +- We're okay with DBMS packages as "part of the language" + +**Choose Solution 2 if:** +- We want clean separation of concerns +- We expect many extensions to need error context +- We value modularity and independent versioning + +**Choose Solution 3 if:** +- We want maximum portability +- We're okay with "Oracle-like" instead of "exact Oracle" +- We want DBMS packages to work with any procedural language + +--- + +**Document Status:** Analysis complete, decision pending +**Last Updated:** 2025-11-30 +**Authors:** Rophy Tsai, Claude diff --git a/design/dbms_utility/EXISTING_PACKAGES.md b/design/dbms_utility/EXISTING_PACKAGES.md new file mode 100644 index 00000000000..37e4aefe1cb --- /dev/null +++ b/design/dbms_utility/EXISTING_PACKAGES.md @@ -0,0 +1,110 @@ +# IvorySQL Built-in Oracle Packages - Current State + +## Summary + +As of November 2025, IvorySQL upstream **does NOT have any built-in Oracle DBMS packages**. DBMS_UTILITY is the **first** Oracle-compatible DBMS package being implemented. + +## Research Findings + +### Web Search Results + +**IvorySQL Documentation:** +- Official docs at ivorysql.org mention **package syntax support** (CREATE PACKAGE, package spec/body) +- Documentation shows **how to create custom packages** but does NOT list built-in DBMS packages +- Blog post "Introduction to IvorySQL Packages" demonstrates user-defined packages only +- No mention of DBMS_UTILITY, DBMS_RANDOM, or other Oracle built-in packages + +**Orafce Extension:** +- IvorySQL imports and enhances the **Orafce extension** for Oracle compatibility +- Orafce provides: Oracle-compatible datatypes, functions, and conversion utilities +- However, **current IvorySQL upstream does NOT include any DBMS packages from Orafce** + +### Git History Analysis + +**Upstream Master Branch:** +```bash +$ git ls-tree --name-only upstream/master contrib/ivorysql_ora/src/builtin_functions/ +builtin_functions--1.0.sql +character_datatype_functions.c +datetime_datatype_functions.c +misc_functions.c +numeric_datatype_functions.c +``` + +**No DBMS packages in upstream.** + +## Current Development Work + +### DBMS_UTILITY Package + +**Status:** In development (not merged upstream) + +**Location (current, to be refactored):** +``` +contrib/ivorysql_ora/src/builtin_functions/ +├── dbms_utility.c (C implementation) +└── dbms_utility--1.0.sql (SQL package wrapper) + +contrib/ivorysql_ora/sql/ +└── dbms_utility.sql (regression tests) +``` + +**Functions Implemented:** +1. `FORMAT_ERROR_BACKTRACE() RETURN TEXT` ✅ + +**Functions Planned:** +- `FORMAT_ERROR_STACK() RETURN TEXT` +- `FORMAT_CALL_STACK() RETURN TEXT` +- (More Oracle DBMS_UTILITY functions TBD) + +**Key Implementation Detail:** +- Requires access to PL/iSQL exception context (`PLiSQL_execstate`) +- Current implementation includes `plisql.h` from `src/pl/plisql/src/` +- Creates **cross-module dependency** (to be resolved - see ARCHITECTURE.md) + +## Comparison with Oracle + +### Oracle Database 23c Built-in Packages + +Oracle provides **hundreds** of built-in PL/SQL packages, including: + +**Most Common DBMS Packages:** +- DBMS_OUTPUT (messaging/debugging) +- DBMS_RANDOM (random number generation) +- DBMS_UTILITY (utility functions) +- DBMS_SQL (dynamic SQL) +- DBMS_LOB (large objects) +- DBMS_SCHEDULER (job scheduling) +- DBMS_METADATA (metadata extraction) +- DBMS_CRYPTO (encryption/hashing) +- And 100+ more... + +**IvorySQL Status:** +- 🚧 DBMS_UTILITY: 1/30+ functions implemented (~3%) - first package +- ❌ All other packages: 0% + +## Implications for Architecture + +### DBMS_UTILITY as First Package + +Since DBMS_UTILITY is the **first** built-in DBMS package, the architectural decisions made here will set the pattern for future packages. + +### Design Issue Identified + +DBMS_UTILITY needs **PL/iSQL internals**, which initially created: +- Cross-module dependency (`contrib` → `src/pl/plisql`) +- Layering violation (extension accessing language private headers) + +**Resolution:** Move DBMS_UTILITY to `src/pl/plisql/src/` (see ARCHITECTURE.md for decision details). + +## References + +- **IvorySQL Docs:** https://www.ivorysql.org/docs/compatibillity_features/package/ +- **Oracle DBMS_UTILITY:** https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_UTILITY.html +- **Orafce Project:** https://github.com/orafce/orafce + +--- + +**Document Status:** Research complete, decision made +**Last Updated:** 2025-11-30 +**Authors:** Rophy Tsai, Claude diff --git a/design/dbms_utility/README.md b/design/dbms_utility/README.md new file mode 100644 index 00000000000..49ac7da82d9 --- /dev/null +++ b/design/dbms_utility/README.md @@ -0,0 +1,94 @@ +# DBMS_UTILITY Design Documentation + +This directory contains design documentation and architectural discussions for implementing Oracle-compatible DBMS packages in IvorySQL. + +## Decision Summary + +**Decision:** Put DBMS_UTILITY entirely in `src/pl/plisql/src/` + +``` +src/pl/plisql/src/ +├── pl_dbms_utility.c ← C implementation +├── plisql--1.0.sql ← CREATE FUNCTION + CREATE PACKAGE +└── Makefile ← Add pl_dbms_utility.o to OBJS +``` + +**Rationale:** +- DBMS_UTILITY needs PL/iSQL internals (exception context) +- Upstream IvorySQL has `plisql` and `ivorysql_ora` as independent modules +- Putting it in `plisql` avoids introducing cross-module dependencies +- `plisql--1.0.sql` runs at `CREATE EXTENSION plisql` time, when the language is available +- DBMS_UTILITY doesn't need Oracle types, so no dependency on `ivorysql_ora` + +See [ARCHITECTURE.md](./ARCHITECTURE.md) for full decision details. + +## Documents + +### 1. [ARCHITECTURE.md](./ARCHITECTURE.md) +**Architectural decision and rationale** ✅ DECISION MADE + +Contains: +- Analysis of two architectures (contrib-based vs. PL/iSQL-based) +- Load order analysis (`initdb -m oracle` sequence) +- Final decision: Architecture 2 (PL/iSQL-based) +- Guidelines for future DBMS packages + +### 2. [EXISTING_PACKAGES.md](./EXISTING_PACKAGES.md) +**Status of built-in Oracle packages in IvorySQL** + +Key findings: +- IvorySQL upstream has **ZERO** built-in DBMS packages +- DBMS_UTILITY is the **first** built-in package being implemented +- Web search confirms: No pre-existing DBMS packages in IvorySQL docs + +### 3. [DEPENDENCY_ANALYSIS.md](./DEPENDENCY_ANALYSIS.md) +**Deep dive into the cross-module dependency problem** + +Detailed analysis: +- Why DBMS_UTILITY needs PL/iSQL internals (exception context) +- Why cross-module dependency is problematic (layering violation) +- How PostgreSQL/EPAS/Orafce handle similar issues + +## Guidelines for Future DBMS Packages + +| Package Needs | Location | +|--------------|----------| +| PL/iSQL internals (exception context, call stack, etc.) | `src/pl/plisql/src/` | +| Only Oracle datatypes, no PL/iSQL internals | `contrib/ivorysql_ora/` | +| Both PL/iSQL internals AND Oracle types | Split: C in plisql, SQL wrapper in ivorysql_ora | + +## Current Status + +**DBMS_UTILITY:** +- ✅ FORMAT_ERROR_BACKTRACE implemented (in contrib, needs refactoring) +- ✅ Architecture decision made +- ⏳ Refactor to `src/pl/plisql/src/` +- ⏳ More functions to implement (FORMAT_ERROR_STACK, FORMAT_CALL_STACK, etc.) + +## Next Steps + +1. ✅ Document architecture decision +2. ⏳ Refactor DBMS_UTILITY to `src/pl/plisql/src/` +3. ⏳ Update regression tests +4. ⏳ Update CLAUDE.md with guidelines +5. ⏳ Continue DBMS_UTILITY implementation (more functions) + +## References + +**IvorySQL:** +- Website: https://www.ivorysql.org/ +- Docs: https://www.ivorysql.org/docs/ +- GitHub: https://github.com/IvorySQL/IvorySQL + +**Oracle Documentation:** +- DBMS_UTILITY: https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_UTILITY.html + +**Related Projects:** +- Orafce: https://github.com/orafce/orafce (Oracle compatibility for PostgreSQL) +- EDB EPAS: https://www.enterprisedb.com/ (Commercial Oracle-compatible PostgreSQL) + +--- + +**Last Updated:** 2025-11-30 +**Authors:** Rophy Tsai, Claude +**Status:** Decision made, implementation pending From 6919535cfe97cd8d39f0d29b0c3ea0d6eace48e4 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Sun, 30 Nov 2025 23:12:16 +0000 Subject: [PATCH 18/20] docs: refine DBMS_UTILITY design docs to match implementation - Condense ARCHITECTURE.md from exploratory discussion to final design - Document session-level context storage approach in pl_exec.c - Update DEPENDENCY_ANALYSIS.md with chosen solution details - Streamline EXISTING_PACKAGES.md and README.md --- design/dbms_utility/ARCHITECTURE.md | 351 ++++++--------------- design/dbms_utility/DEPENDENCY_ANALYSIS.md | 330 +++++-------------- design/dbms_utility/EXISTING_PACKAGES.md | 120 ++----- design/dbms_utility/README.md | 104 ++---- 4 files changed, 250 insertions(+), 655 deletions(-) diff --git a/design/dbms_utility/ARCHITECTURE.md b/design/dbms_utility/ARCHITECTURE.md index 3cbaaca173b..7c7cee4c031 100644 --- a/design/dbms_utility/ARCHITECTURE.md +++ b/design/dbms_utility/ARCHITECTURE.md @@ -1,323 +1,174 @@ -# DBMS_UTILITY Architecture Discussion +# DBMS_UTILITY Architecture ## Context -IvorySQL is implementing Oracle-compatible DBMS packages. This document discusses the architectural decisions for where DBMS package implementations should live in the codebase. +IvorySQL is implementing Oracle-compatible DBMS packages. This document describes the architectural decisions for DBMS_UTILITY implementation. -## Current State +## Implementation Summary -### Existing Built-in Packages +**DBMS_UTILITY** is the **first** built-in Oracle DBMS package in IvorySQL. -As of Nov 2025, IvorySQL upstream has **ZERO** built-in DBMS packages. +**Location:** `src/pl/plisql/src/` (part of PL/iSQL extension) -**DBMS_UTILITY** (current work) -- Location: `contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c` (to be refactored) -- Status: In development -- Functions: FORMAT_ERROR_BACKTRACE (and more planned) +**Files:** +- `pl_dbms_utility.c` - C implementation of FORMAT_ERROR_BACKTRACE +- `pl_exec.c` - Session-level exception context storage and retrieval API +- `plisql.h` - Function declaration export +- `plisql--1.0.sql` - Package definition (CREATE PACKAGE) -This is the **first** built-in Oracle DBMS package being implemented for IvorySQL. +**Functions Implemented:** +- `FORMAT_ERROR_BACKTRACE` - Returns Oracle-formatted call stack during exception handling -### IvorySQL Module Structure +## Architecture Decision -IvorySQL has two key modules that compile into separate shared libraries: +**Decision:** DBMS_UTILITY lives entirely in `src/pl/plisql/src/` as part of the PL/iSQL extension. -1. **PL/iSQL Language Runtime** (`src/pl/plisql/src/`) - - Compiles to: `plisql.so` - - Purpose: PL/iSQL procedural language implementation - - Components: Parser, compiler, executor, exception handling - - Exports: Language handler, validator functions - - Key files: `pl_handler.c`, `pl_exec.c`, `pl_comp.c`, `plisql.h` +**Rationale:** +1. FORMAT_ERROR_BACKTRACE needs access to PL/iSQL exception context +2. Keeping it in `plisql` avoids cross-module dependencies +3. `plisql` and `ivorysql_ora` remain independent modules (upstream design) +4. Package is available immediately after `CREATE EXTENSION plisql` -2. **Oracle Compatibility Extension** (`contrib/ivorysql_ora/`) - - Compiles to: `ivorysql_ora.so` - - Purpose: Oracle-compatible datatypes, functions, and packages - - Components: Datatypes (CHAR, VARCHAR2, DATE, etc.), built-in functions, DBMS packages - - Key files: Various C implementations merged into one extension +### Module Structure -## Architectural Question +IvorySQL has two independent modules: -**Where should DBMS package implementations live when they need access to PL/iSQL internals?** +1. **PL/iSQL Language Runtime** (`src/pl/plisql/src/` → `plisql.so`) + - PL/iSQL procedural language implementation + - **Now includes:** DBMS_UTILITY package (for functions needing PL/iSQL internals) -### Two Possible Architectures +2. **Oracle Compatibility Extension** (`contrib/ivorysql_ora/` → `ivorysql_ora.so`) + - Oracle-compatible datatypes and functions + - Independent of PL/iSQL internals -#### Architecture 1: Contrib-based (Current Implementation) +## Implementation Details -``` -contrib/ivorysql_ora/ -├── src/builtin_functions/ -│ ├── dbms_utility.c ← C implementation with PL/iSQL internals access -│ └── dbms_utility--1.0.sql ← SQL package specification and body -└── Makefile - ├── OBJS += dbms_utility.o - └── PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plisql/src ← Cross-dependency! -``` - -**Key characteristic:** Extension code depends on language internals (cross-module dependency). - -#### Architecture 2: PL/iSQL-based (Proposed) - -``` -src/pl/plisql/src/ -├── dbms_utility.c ← C implementation as part of language -├── plisql.h ← Exports function declarations -└── Makefile - └── OBJS += dbms_utility.o - -contrib/ivorysql_ora/ -└── src/builtin_functions/ - └── dbms_utility--1.0.sql ← SQL package wrapper calling plisql functions -``` - -**Key characteristic:** Language provides native functions; extension wraps them in Oracle syntax. - -## Analysis +### Exception Context Storage -### Current Cross-Module Dependency +The key challenge is accessing exception context from a C function that's called from PL/iSQL package body. -The current implementation requires `contrib/ivorysql_ora` to access PL/iSQL internals: +**Solution:** Session-level storage in `pl_exec.c` ```c -// In contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c -#include "plisql.h" // From src/pl/plisql/src/ - -// Accesses: -// - PLiSQL_execstate (exception context) -// - Error stack information -// - PL/iSQL internal data structures -``` - -```makefile -# In contrib/ivorysql_ora/Makefile (line 79) -PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plisql/src -``` - -This creates a **layering violation**: an extension (contrib) depends on a core language module's private headers. - -### Trade-offs Comparison - -| Aspect | Architecture 1 (Contrib) | Architecture 2 (PL/iSQL) | -|--------|-------------------------|--------------------------| -| **Dependency Direction** | Extension → Language (bad) | Language is self-contained (good) | -| **Code Organization** | All Oracle features in one place | Split: internals in language, wrappers in extension | -| **Versioning** | Can version extension independently | DBMS packages tied to language version | -| **Maintainability** | Cross-module changes harder | Changes contained in language module | -| **Encapsulation** | Violates module boundaries | Respects module boundaries | -| **Optionality** | Can load/unload extension | Always available with language | -| **Consistency** | All DBMS packages in same location | Some in language, some in extension | - -### PostgreSQL Precedent - -PostgreSQL's PL/pgSQL follows a similar pattern: +// pl_exec.c - Static session storage +static char *plisql_current_exception_context = NULL; -``` -src/pl/plpgsql/src/ -├── pl_exec.c ← Language implementation -└── (exports functions that extensions can call) - -contrib/extensions/ -└── (use plpgsql functions without accessing internals) -``` - -**Key insight:** Extensions don't include `plpgsql.h` internals. If they need internal features, those features are exported as public functions. - -## Recommendations - -### Recommendation 1: Split by Coupling Level +// When entering exception handler (in exec_stmt_block): +if (edata->context) +{ + plisql_current_exception_context = + MemoryContextStrdup(TopMemoryContext, edata->context); +} -**For DBMS packages that NEED PL/iSQL internals (like DBMS_UTILITY.FORMAT_ERROR_BACKTRACE):** +// When exiting exception handler: +if (plisql_current_exception_context) +{ + pfree(plisql_current_exception_context); + plisql_current_exception_context = NULL; +} -Put C implementation in `src/pl/plisql/src/`: +// Public API for retrieval +const char * +plisql_get_current_exception_context(void) +{ + return plisql_current_exception_context; +} ``` -src/pl/plisql/src/ -├── pl_dbms_utility.c ← Native implementation -├── plisql.h ← Export declarations -└── Makefile (add to OBJS) -contrib/ivorysql_ora/ -└── dbms_utility--1.0.sql ← CREATE PACKAGE wrapper -``` +**Why this approach:** +1. Exception context is stored when entering handler (before user code runs) +2. C function can retrieve it via public API without accessing `PLiSQL_execstate` +3. Context is cleared after exception handling completes +4. Memory allocated in `TopMemoryContext` survives function calls -**For DBMS packages that DON'T need PL/iSQL internals:** +### C Function Implementation -Keep in `contrib/ivorysql_ora/`: -``` -contrib/ivorysql_ora/ -└── src/builtin_functions/ - ├── .c ← Self-contained implementation - └── --1.0.sql ← CREATE PACKAGE wrapper -``` +`pl_dbms_utility.c` transforms PostgreSQL error context to Oracle format: -### Recommendation 2: Clean Layering +```c +// Input (PostgreSQL format): +"PL/iSQL function test_level3() line 3 at RAISE" +"SQL statement \"CALL test_level3()\"" +"PL/iSQL function test_level2() line 3 at CALL" -Follow this dependency hierarchy: -``` -1. PL/iSQL Language (src/pl/plisql/src/) - ↓ provides functions -2. Oracle Extension (contrib/ivorysql_ora/) - ↓ uses language functions, adds Oracle syntax -3. User Code +// Output (Oracle format): +"ORA-06512: at \"PUBLIC.TEST_LEVEL3\", line 3\n" +"ORA-06512: at \"PUBLIC.TEST_LEVEL2\", line 3\n" ``` -**Never:** Extension → Language internals (violates encapsulation) +Key transformations: +- Skip "SQL statement" lines +- Extract function name and line number from "PL/iSQL function" lines +- Convert to uppercase for Oracle compatibility +- Handle anonymous blocks (`inline_code_block` → `at line N`) -### Recommendation 3: File Naming Convention +### Package Definition -If moving DBMS packages to `src/pl/plisql/src/`, use clear naming: -- `pl_dbms_utility.c` (part of language, for DBMS_UTILITY) -- `pl_exec.c` (language execution, existing) +`plisql--1.0.sql`: -This distinguishes "language internals" from "language-provided DBMS packages." - -## Decision: Architecture 2 - PL/iSQL-based - -**Decision Date:** 2025-11-30 - -After detailed analysis, we chose **Architecture 2: PL/iSQL-based** for DBMS_UTILITY. - -### Rationale - -1. **Original Design Intent:** In upstream IvorySQL, `plisql` and `ivorysql_ora` are **independent modules** with no cross-dependencies. Both depend on core headers (`src/include/`), but neither depends on the other. - -2. **Our Change Broke This:** By adding `#include "plisql.h"` in `contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c`, we introduced a cross-module dependency that didn't exist before. - -3. **DBMS_UTILITY Doesn't Need Oracle Types:** The C implementation only uses PostgreSQL native types (`TEXT`). It doesn't require `VARCHAR2` or other Oracle types at compile time or load time. - -4. **Load Order Analysis:** - ``` - initdb -m oracle - │ - ├─→ preload_ora_misc.sql (1st - basic objects, NO plisql yet) - │ - ├─→ CREATE EXTENSION plisql (2nd - loads plisql--1.0.sql) - │ - └─→ CREATE EXTENSION ivorysql_ora (3rd - loads ivorysql_ora--1.0.sql) - ``` - - Since DBMS_UTILITY doesn't need Oracle types, it can be fully defined at step 2 (`plisql--1.0.sql`), before `ivorysql_ora` loads. - -5. **plisql--1.0.sql is Currently Empty:** IvorySQL's `plisql--1.0.sql` is almost empty because the language is pre-defined in system catalogs (`pg_proc.dat`, `pg_language.dat`). This file is the right place to add DBMS package definitions. - -### Final Structure - -``` -src/pl/plisql/src/ -├── pl_dbms_utility.c ← C implementation (needs plisql.h internals) -├── plisql.h ← Already has what we need -├── plisql--1.0.sql ← CREATE FUNCTION + CREATE PACKAGE (all in one file!) -└── Makefile ← Add pl_dbms_utility.o to OBJS - -src/bin/initdb/initdb.c -└── load_plisql() ← Wraps CREATE EXTENSION in Oracle mode -``` - -**Key Insight: SET commands work in extension SQL files!** - -Extension SQL files CAN temporarily switch `compatible_mode` to enable Oracle syntax. -This is much cleaner than hardcoding SQL in initdb.c. - -**plisql--1.0.sql implementation:** ```sql --- C function wrapper (works in PG mode) +-- C function wrapper CREATE FUNCTION sys.ora_format_error_backtrace() RETURNS TEXT AS 'MODULE_PATHNAME', 'ora_format_error_backtrace' LANGUAGE C VOLATILE STRICT; --- Switch to Oracle mode for CREATE PACKAGE syntax -SET ivorysql.compatible_mode TO oracle; - +-- Package specification CREATE OR REPLACE PACKAGE dbms_utility IS FUNCTION FORMAT_ERROR_BACKTRACE RETURN TEXT; END dbms_utility; +-- Package body (calls C function) CREATE OR REPLACE PACKAGE BODY dbms_utility IS FUNCTION FORMAT_ERROR_BACKTRACE RETURN TEXT IS BEGIN RETURN sys.ora_format_error_backtrace(); END; END dbms_utility; - --- Switch back to PG mode -SET ivorysql.compatible_mode TO pg; ``` -**initdb.c load_plisql() implementation:** -```c -static void -load_plisql(FILE *cmdfd) -{ - /* Switch to oracle mode to allow CREATE PACKAGE in extension SQL */ - PG_CMD_PUTS("set ivorysql.compatible_mode to oracle;\n\n"); - PG_CMD_PUTS("CREATE EXTENSION plisql;\n\n"); - PG_CMD_PUTS("set ivorysql.compatible_mode to pg;\n\n"); -} -``` - -**Why this approach is better:** -- ✅ Package definition in SQL file (proper extension versioning) -- ✅ No hardcoded SQL in C code -- ✅ Syntax highlighting and easier maintenance -- ✅ Self-contained extension -- ✅ Works with CREATE/DROP EXTENSION after initdb +**Note:** The extension SQL uses Oracle package syntax directly. The `CREATE EXTENSION plisql` runs in Oracle mode context (set by initdb.c's `load_plisql()`). -### Benefits +### initdb.c Integration -1. **No Cross-Module Dependency:** Everything DBMS_UTILITY needs is within `src/pl/plisql/src/` -2. **Clean Architecture:** Respects original module boundaries -3. **Built-in at initdb:** Available immediately after `CREATE EXTENSION plisql` -4. **Single Compilation Unit:** C code compiles into `plisql.so` - -### Trade-offs Accepted +```c +// In load_plisql(): +PG_CMD_PUTS("SET ivorysql.identifier_case_from_pg_dump TO true;\n"); +PG_CMD_PUTS("CREATE EXTENSION plisql;\n"); +``` -1. **Split Location:** DBMS packages that need PL/iSQL internals live in `plisql`, others may stay in `ivorysql_ora` -2. **TEXT vs VARCHAR2:** Package returns `TEXT` instead of `VARCHAR2` (compatible in Oracle mode) +The extension loads in Oracle mode context during `initdb -m oracle`. ## Guidelines for Future DBMS Packages -Based on this decision: - | Package Needs | Location | |--------------|----------| | PL/iSQL internals (exception context, call stack, etc.) | `src/pl/plisql/src/` | | Only Oracle datatypes, no PL/iSQL internals | `contrib/ivorysql_ora/` | | Both PL/iSQL internals AND Oracle types | Split: C in plisql, SQL wrapper in ivorysql_ora | -## Answered Questions +## Testing -1. **Extension Loading:** DBMS_UTILITY will be created when `CREATE EXTENSION plisql` runs (during `initdb -m oracle`). No separate extension needed. +Regression tests in `src/pl/plisql/src/sql/dbms_utility.sql` cover: +- Basic exception handling +- Nested procedure calls (3+ levels deep) +- Function calls +- Anonymous blocks +- No exception context (returns NULL) +- Re-raised exceptions +- Package procedures +- Schema-qualified calls -2. **Future Packages:** Follow the guidelines table above based on dependencies. +Run tests: `cd src/pl/plisql/src && make oracle-check` ## Implementation Status -1. ✅ Document architecture decision (this document) -2. ✅ Implement C function in `src/pl/plisql/src/pl_dbms_utility.c` -3. ✅ Register C function in `src/pl/plisql/src/plisql--1.0.sql` -4. ✅ Create DBMS_UTILITY package in `plisql--1.0.sql` (using SET compatible_mode) -5. ✅ Update initdb.c to wrap extension load in Oracle mode -6. ✅ Add regression tests in `src/pl/plisql/src/sql/dbms_utility.sql` -7. ✅ All 17 plisql oracle-check tests passing - -## Key Discovery (2025-11-30) - -**Original concern:** Package creation was hardcoded in `initdb.c` instead of in extension SQL files. - -**Investigation:** We tested whether `SET ivorysql.compatible_mode` works within extension SQL files. - -**Result:** ✅ **It works!** Extension SQL files CAN temporarily switch compatible_mode to enable Oracle syntax. - -**Solution:** -1. Move CREATE PACKAGE statements from `initdb.c` to `plisql--1.0.sql` -2. Wrap them with `SET ivorysql.compatible_mode TO oracle/pg` -3. Update `load_plisql()` in initdb.c to ensure extension loads in Oracle mode context - -**Benefits:** -- Cleaner code (SQL in .sql files, not C strings) -- Proper extension versioning support -- Easier maintenance and testing -- Self-contained extension +- ✅ Architecture decision documented +- ✅ C function in `pl_dbms_utility.c` +- ✅ Session storage API in `pl_exec.c` +- ✅ Package definition in `plisql--1.0.sql` +- ✅ Regression tests (9 test cases) +- ✅ All plisql oracle-check tests passing --- -**Document Status:** Implementation complete (refactored to use extension SQL approach) **Last Updated:** 2025-11-30 -**Authors:** Rophy Tsai diff --git a/design/dbms_utility/DEPENDENCY_ANALYSIS.md b/design/dbms_utility/DEPENDENCY_ANALYSIS.md index f5f20564a2f..d84246e10c2 100644 --- a/design/dbms_utility/DEPENDENCY_ANALYSIS.md +++ b/design/dbms_utility/DEPENDENCY_ANALYSIS.md @@ -2,309 +2,143 @@ ## The Problem -DBMS_UTILITY's `FORMAT_ERROR_BACKTRACE` function needs to access PL/iSQL's exception context, creating a cross-module dependency. +DBMS_UTILITY's `FORMAT_ERROR_BACKTRACE` needs PL/iSQL exception context. This document analyzes the dependency problem and the chosen solution. -## Current Implementation +## Module Structure -### Module Structure - -IvorySQL has two separate compilation units: +IvorySQL has two independent modules: ``` src/pl/plisql/src/ → plisql.so (language runtime) -contrib/ivorysql_ora/ → ivorysql_ora.so (Oracle compatibility extension) +contrib/ivorysql_ora/ → ivorysql_ora.so (Oracle compatibility) ``` -These compile to **separate shared libraries** that are loaded independently. +**Upstream design:** These modules have NO cross-dependencies. -### Dependency Chain +## The Challenge ``` User SQL: DBMS_UTILITY.FORMAT_ERROR_BACKTRACE -↓ Calls (via package body) - -C Function (in ivorysql_ora.so): - sys.ora_format_error_backtrace() - ↓ Needs access to -PL/iSQL Internals (in plisql.so): - PLiSQL_execstate->err_text - Error stack information - Exception context -``` - -### Current Implementation Files - -**contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c:** -```c -#include "postgres.h" -#include "plisql.h" // ← From src/pl/plisql/src/plisql.h - -PG_FUNCTION_INFO_V1(ora_format_error_backtrace); - -Datum -ora_format_error_backtrace(PG_FUNCTION_ARGS) -{ - // Needs access to: - PLiSQL_execstate *estate = ...; // PL/iSQL execution state - char *err_text = estate->err_text; // Exception context - // ... -} +PL/iSQL Exception Context: + - Error message and context string + - Call stack information + - Only available inside exception handler ``` -**contrib/ivorysql_ora/Makefile (line 79):** -```makefile -# Include path for PL/iSQL headers (needed for DBMS_UTILITY.FORMAT_ERROR_BACKTRACE) -PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plisql/src -``` +## Why Cross-Module Dependency Is Bad -## Why This Is Problematic +If `ivorysql_ora.so` included `plisql.h`: -### 1. Layering Violation +1. **Layering Violation:** Extension depends on language internals +2. **Encapsulation Break:** Internal structures exposed +3. **Maintenance Burden:** Changes to PL/iSQL break extension +4. **Binary Compatibility:** Version coupling between shared libraries -``` -┌─────────────────────────────────┐ -│ User Code │ -└─────────────────┬───────────────┘ - │ -┌─────────────────▼───────────────┐ -│ contrib/ivorysql_ora │ ← Extension layer -│ (Oracle compatibility) │ -└─────────────────┬───────────────┘ - │ - │ ❌ SHOULD NOT ACCESS INTERNALS - │ -┌─────────────────▼───────────────┐ -│ src/pl/plisql/src │ ← Language layer -│ (PL/iSQL runtime internals) │ -└─────────────────────────────────┘ -``` +## Solution: Keep It In PL/iSQL -**Principle:** Higher-level modules (extensions) should NOT depend on lower-level module internals. +**Decision:** Implement DBMS_UTILITY entirely in `src/pl/plisql/src/` -### 2. Encapsulation Break +### Implementation Approach -`plisql.h` contains **private implementation details**: -- `PLiSQL_execstate` structure layout -- Internal error handling mechanisms -- Memory management details +Instead of accessing `PLiSQL_execstate` directly from a C function, use session-level storage: -These are **not intended as a public API**. Changes to PL/iSQL internals will break `ivorysql_ora`. - -### 3. Maintenance Burden - -**Scenario:** PL/iSQL team refactors exception handling ```c -// Before (in plisql.h) -typedef struct PLiSQL_execstate { - char *err_text; -} PLiSQL_execstate; - -// After (refactored) -typedef struct PLiSQL_execstate { - ErrorData *error_data; // Changed! -} PLiSQL_execstate; -``` - -**Result:** `dbms_utility.c` breaks because it depends on internal structure. +// pl_exec.c - Session storage +static char *plisql_current_exception_context = NULL; -### 4. Binary Compatibility - -Two separate shared libraries (`plisql.so` and `ivorysql_ora.so`) must maintain **ABI compatibility**: -- If `plisql.so` is upgraded, `ivorysql_ora.so` may break -- Versioning becomes complex -- Testing matrix increases (all combinations of versions) - -## Dependency Patterns in PostgreSQL - -### How PL/pgSQL Handles This - -PostgreSQL's PL/pgSQL **does NOT** expose internals to extensions. Instead: - -**Option 1: Public API Functions** -```c -// In src/pl/plpgsql/src/pl_funcs.c (exported) -Datum plpgsql_get_error_info(PG_FUNCTION_ARGS) { - // Access internals safely - return internal_data; +// Store context when entering exception handler +// (in exec_stmt_block, within PG_CATCH block) +if (edata->context) +{ + plisql_current_exception_context = + MemoryContextStrdup(TopMemoryContext, edata->context); } -``` - -Extensions call public functions, not access internals directly. -**Option 2: Callback Mechanism** -```c -// Language registers callbacks that extensions can use -void register_error_callback(ErrorCallbackFunc func); -``` - -**Option 3: Shared State in Core** -```c -// In src/backend/utils/error/elog.c (core PostgreSQL) -ErrorData *current_error_data; // Accessible to all +// Public API for retrieval +const char * +plisql_get_current_exception_context(void) +{ + return plisql_current_exception_context; +} ``` -All modules access shared state in core, not each other's internals. +### Why This Works -### What IvorySQL Should Do - -**Current (Bad):** -``` -ivorysql_ora.so → #include "plisql.h" → Access internals directly -``` +1. **Exception handler stores context:** When PL/iSQL catches an exception, it saves the context string in session storage before user code runs. -**Better (Good):** -``` -ivorysql_ora.so → Call plisql_get_exception_context() → plisql.so handles internals -``` +2. **C function retrieves via API:** The `ora_format_error_backtrace()` function calls `plisql_get_current_exception_context()` - a simple public API. -## Proposed Solutions +3. **No direct struct access:** The C function doesn't need to know about `PLiSQL_execstate` internals. -### Solution 1: Move DBMS_UTILITY to PL/iSQL +4. **Clean memory management:** Context stored in `TopMemoryContext`, cleared when exiting handler. -**Rationale:** If it needs PL/iSQL internals, it IS part of PL/iSQL. +### Data Flow ``` -src/pl/plisql/src/ -├── pl_dbms_utility.c ← Native implementation (can access internals) -├── plisql--1.0.sql ← Export as sys.ora_format_error_backtrace() -└── plisql.h ← No need to expose, internal use only - -contrib/ivorysql_ora/ -└── dbms_utility--1.0.sql ← CREATE PACKAGE wrapper (calls plisql functions) +1. Exception occurs in PL/iSQL code + ↓ +2. PL/iSQL catches exception (PG_CATCH in exec_stmt_block) + ↓ +3. Context stored: plisql_current_exception_context = edata->context + ↓ +4. User's EXCEPTION block runs + ↓ +5. User calls DBMS_UTILITY.FORMAT_ERROR_BACKTRACE + ↓ +6. Package body calls sys.ora_format_error_backtrace() + ↓ +7. C function calls plisql_get_current_exception_context() + ↓ +8. Returns stored context string + ↓ +9. C function transforms to Oracle format + ↓ +10. Exception handler exits, context cleared ``` -**Pros:** -- ✅ No cross-module dependency -- ✅ Clean encapsulation -- ✅ Natural architecture: "internal to language" - -**Cons:** -- ❌ Splits DBMS packages across two locations -- ❌ Makes DBMS_UTILITY part of core (harder to make optional) +## Alternatives Considered -### Solution 2: Create Public API in PL/iSQL +### Alternative: Public API in PL/iSQL (Not Chosen) -**Rationale:** PL/iSQL exports exception info through a stable API. +Export exception context via SQL-callable function: ```c -// In src/pl/plisql/src/pl_public_api.c (NEW FILE) +// plisql.so exports: PG_FUNCTION_INFO_V1(plisql_get_exception_context); -Datum plisql_get_exception_context(PG_FUNCTION_ARGS) { - // Access internal PLiSQL_execstate - // Return formatted error info - return error_context_string; -} -``` - -```c -// In contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c -// NO #include "plisql.h" needed! - -Datum ora_format_error_backtrace(PG_FUNCTION_ARGS) { - // Call public API - return DirectFunctionCall0(plisql_get_exception_context); -} +// ivorysql_ora.so calls: +DirectFunctionCall0(plisql_get_exception_context); ``` -**Pros:** -- ✅ Keeps all DBMS packages in one location (contrib) -- ✅ Stable API (internals can change without breaking extension) -- ✅ Follows PostgreSQL patterns +**Why not chosen:** More complex, still requires coordination between modules. Simpler to keep everything in one place. -**Cons:** -- ❌ More boilerplate (need public API layer) -- ❌ PL/iSQL must maintain backward compatibility for API +### Alternative: Use Core PostgreSQL Error System (Not Chosen) -### Solution 3: Use Core PostgreSQL Error System +Access `ErrorData` from PostgreSQL's elog.c instead of PL/iSQL. -**Rationale:** PostgreSQL core already tracks errors; use that instead. +**Why not chosen:** The error context string with PL/iSQL procedure names and line numbers is only available through PL/iSQL's exception handling. -```c -// In contrib/ivorysql_ora/src/builtin_functions/dbms_utility.c -#include "utils/elog.h" // Core PostgreSQL, not plisql.h +## Final Architecture -Datum ora_format_error_backtrace(PG_FUNCTION_ARGS) { - ErrorData *edata = current_error_data; // From core - // Format Oracle-style backtrace - return backtrace_string; -} +``` +src/pl/plisql/src/ +├── pl_exec.c ← Exception context storage + API +├── pl_dbms_utility.c ← Format transformation +├── plisql.h ← API declaration +└── plisql--1.0.sql ← Package definition ``` -**Pros:** -- ✅ No dependency on PL/iSQL -- ✅ Works with ANY procedural language (PL/Python, PL/Perl, etc.) -- ✅ Most general solution - -**Cons:** -- ❌ PostgreSQL's error context format differs from Oracle's -- ❌ May not have PL/iSQL-specific details (procedure names, line numbers) -- ❌ Requires translation layer - -## Recommendation - -**Use Solution 1 for now, plan for Solution 2 long-term:** - -1. **Short-term (current implementation):** - - Move `dbms_utility.c` to `src/pl/plisql/src/pl_dbms_utility.c` - - Keep SQL wrapper in `contrib/ivorysql_ora/` - - Clean dependency: no cross-module includes - -2. **Long-term (if more extensions need error context):** - - Extract public API: `src/pl/plisql/src/pl_public_api.c` - - Export stable functions for exception context - - Move `pl_dbms_utility.c` back to `contrib` using public API - -3. **Future consideration:** - - If many DBMS packages need PL/iSQL internals, they should ALL be in `src/pl/plisql/src/` - - If only DBMS_UTILITY needs it, keep it there as a special case - -## Comparison with Other Systems - -### Oracle - -Oracle's DBMS packages are **part of the database core**, not extensions: -- Built into the database binary -- No separation between "language" and "packages" -- Tightly integrated with PL/SQL runtime - -### EDB Postgres Advanced Server (EPAS) - -EPAS implements Oracle compatibility as **built-in features**: -- Not separate extensions -- DBMS packages compiled into server -- Similar to Solution 1 (part of core) - -### Orafce (PostgreSQL Extension) - -Orafce implements Oracle compatibility as a **pure extension**: -- Does NOT access PL/pgSQL internals -- Reimplements functionality using PostgreSQL public APIs -- Similar to Solution 3 (use core APIs only) - -## Decision Criteria - -**Choose Solution 1 if:** -- Tight coupling to PL/iSQL is acceptable -- We want native Oracle behavior (exact error formats) -- We're okay with DBMS packages as "part of the language" - -**Choose Solution 2 if:** -- We want clean separation of concerns -- We expect many extensions to need error context -- We value modularity and independent versioning - -**Choose Solution 3 if:** -- We want maximum portability -- We're okay with "Oracle-like" instead of "exact Oracle" -- We want DBMS packages to work with any procedural language +**Benefits:** +- ✅ No cross-module dependency +- ✅ Self-contained in PL/iSQL +- ✅ Clean public API (`plisql_get_current_exception_context`) +- ✅ Respects upstream module boundaries --- -**Document Status:** Analysis complete, decision pending +**Status:** Implemented **Last Updated:** 2025-11-30 -**Authors:** Rophy Tsai, Claude diff --git a/design/dbms_utility/EXISTING_PACKAGES.md b/design/dbms_utility/EXISTING_PACKAGES.md index 37e4aefe1cb..b7f1057e8da 100644 --- a/design/dbms_utility/EXISTING_PACKAGES.md +++ b/design/dbms_utility/EXISTING_PACKAGES.md @@ -1,110 +1,56 @@ -# IvorySQL Built-in Oracle Packages - Current State +# IvorySQL Built-in Oracle Packages ## Summary -As of November 2025, IvorySQL upstream **does NOT have any built-in Oracle DBMS packages**. DBMS_UTILITY is the **first** Oracle-compatible DBMS package being implemented. +As of November 2025, IvorySQL upstream has **ZERO** built-in Oracle DBMS packages. -## Research Findings +**DBMS_UTILITY** is the **first** Oracle-compatible DBMS package implemented for IvorySQL. -### Web Search Results +## Current State -**IvorySQL Documentation:** -- Official docs at ivorysql.org mention **package syntax support** (CREATE PACKAGE, package spec/body) -- Documentation shows **how to create custom packages** but does NOT list built-in DBMS packages -- Blog post "Introduction to IvorySQL Packages" demonstrates user-defined packages only -- No mention of DBMS_UTILITY, DBMS_RANDOM, or other Oracle built-in packages +### Upstream IvorySQL -**Orafce Extension:** -- IvorySQL imports and enhances the **Orafce extension** for Oracle compatibility -- Orafce provides: Oracle-compatible datatypes, functions, and conversion utilities -- However, **current IvorySQL upstream does NOT include any DBMS packages from Orafce** +IvorySQL provides: +- ✅ Oracle package syntax (CREATE PACKAGE, package spec/body) +- ✅ Oracle-compatible datatypes (VARCHAR2, NUMBER, DATE, etc.) +- ✅ Oracle-compatible functions (NVL, DECODE, TO_CHAR, etc.) +- ❌ No built-in DBMS packages -### Git History Analysis +### This Implementation -**Upstream Master Branch:** -```bash -$ git ls-tree --name-only upstream/master contrib/ivorysql_ora/src/builtin_functions/ -builtin_functions--1.0.sql -character_datatype_functions.c -datetime_datatype_functions.c -misc_functions.c -numeric_datatype_functions.c -``` - -**No DBMS packages in upstream.** - -## Current Development Work - -### DBMS_UTILITY Package - -**Status:** In development (not merged upstream) - -**Location (current, to be refactored):** -``` -contrib/ivorysql_ora/src/builtin_functions/ -├── dbms_utility.c (C implementation) -└── dbms_utility--1.0.sql (SQL package wrapper) - -contrib/ivorysql_ora/sql/ -└── dbms_utility.sql (regression tests) -``` - -**Functions Implemented:** -1. `FORMAT_ERROR_BACKTRACE() RETURN TEXT` ✅ - -**Functions Planned:** -- `FORMAT_ERROR_STACK() RETURN TEXT` -- `FORMAT_CALL_STACK() RETURN TEXT` -- (More Oracle DBMS_UTILITY functions TBD) - -**Key Implementation Detail:** -- Requires access to PL/iSQL exception context (`PLiSQL_execstate`) -- Current implementation includes `plisql.h` from `src/pl/plisql/src/` -- Creates **cross-module dependency** (to be resolved - see ARCHITECTURE.md) +**DBMS_UTILITY** (first package): +- Location: `src/pl/plisql/src/` (part of PL/iSQL extension) +- Functions: FORMAT_ERROR_BACKTRACE ✅ +- Status: Implemented and tested ## Comparison with Oracle -### Oracle Database 23c Built-in Packages - -Oracle provides **hundreds** of built-in PL/SQL packages, including: - -**Most Common DBMS Packages:** -- DBMS_OUTPUT (messaging/debugging) -- DBMS_RANDOM (random number generation) -- DBMS_UTILITY (utility functions) -- DBMS_SQL (dynamic SQL) -- DBMS_LOB (large objects) -- DBMS_SCHEDULER (job scheduling) -- DBMS_METADATA (metadata extraction) -- DBMS_CRYPTO (encryption/hashing) -- And 100+ more... - -**IvorySQL Status:** -- 🚧 DBMS_UTILITY: 1/30+ functions implemented (~3%) - first package -- ❌ All other packages: 0% - -## Implications for Architecture - -### DBMS_UTILITY as First Package +Oracle Database provides 100+ built-in DBMS packages. Common ones: -Since DBMS_UTILITY is the **first** built-in DBMS package, the architectural decisions made here will set the pattern for future packages. +| Package | Oracle | IvorySQL | +|---------|--------|----------| +| DBMS_OUTPUT | ✅ | ✅ (via plisql) | +| DBMS_UTILITY | ✅ | 🚧 1 function | +| DBMS_RANDOM | ✅ | ❌ | +| DBMS_SQL | ✅ | ❌ | +| DBMS_LOB | ✅ | ❌ | +| DBMS_SCHEDULER | ✅ | ❌ | -### Design Issue Identified +## Architecture Pattern -DBMS_UTILITY needs **PL/iSQL internals**, which initially created: -- Cross-module dependency (`contrib` → `src/pl/plisql`) -- Layering violation (extension accessing language private headers) +DBMS_UTILITY establishes the pattern for future packages: -**Resolution:** Move DBMS_UTILITY to `src/pl/plisql/src/` (see ARCHITECTURE.md for decision details). +| Package Needs | Location | +|--------------|----------| +| PL/iSQL internals | `src/pl/plisql/src/` | +| Oracle datatypes only | `contrib/ivorysql_ora/` | +| Both | Split implementation | ## References -- **IvorySQL Docs:** https://www.ivorysql.org/docs/compatibillity_features/package/ -- **Oracle DBMS_UTILITY:** https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_UTILITY.html -- **Orafce Project:** https://github.com/orafce/orafce +- [Oracle DBMS_UTILITY](https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_UTILITY.html) +- [IvorySQL Packages](https://www.ivorysql.org/docs/compatibillity_features/package/) --- -**Document Status:** Research complete, decision made **Last Updated:** 2025-11-30 -**Authors:** Rophy Tsai, Claude diff --git a/design/dbms_utility/README.md b/design/dbms_utility/README.md index 49ac7da82d9..91d5ceb4eed 100644 --- a/design/dbms_utility/README.md +++ b/design/dbms_utility/README.md @@ -1,94 +1,58 @@ # DBMS_UTILITY Design Documentation -This directory contains design documentation and architectural discussions for implementing Oracle-compatible DBMS packages in IvorySQL. +Design documentation for IvorySQL's first Oracle-compatible DBMS package. -## Decision Summary +## Implementation Summary -**Decision:** Put DBMS_UTILITY entirely in `src/pl/plisql/src/` +**DBMS_UTILITY** is implemented in `src/pl/plisql/src/` as part of the PL/iSQL extension. ``` src/pl/plisql/src/ -├── pl_dbms_utility.c ← C implementation -├── plisql--1.0.sql ← CREATE FUNCTION + CREATE PACKAGE -└── Makefile ← Add pl_dbms_utility.o to OBJS +├── pl_dbms_utility.c ← C implementation (format transformation) +├── pl_exec.c ← Session-level exception context storage +├── plisql.h ← API export declaration +├── plisql--1.0.sql ← Package definition +├── sql/dbms_utility.sql ← Regression tests +└── expected/dbms_utility.out ← Expected test output ``` -**Rationale:** -- DBMS_UTILITY needs PL/iSQL internals (exception context) -- Upstream IvorySQL has `plisql` and `ivorysql_ora` as independent modules -- Putting it in `plisql` avoids introducing cross-module dependencies -- `plisql--1.0.sql` runs at `CREATE EXTENSION plisql` time, when the language is available -- DBMS_UTILITY doesn't need Oracle types, so no dependency on `ivorysql_ora` +**Current Functions:** +- `FORMAT_ERROR_BACKTRACE` - Returns Oracle-formatted call stack in exception handlers -See [ARCHITECTURE.md](./ARCHITECTURE.md) for full decision details. +## Key Design Decisions -## Documents - -### 1. [ARCHITECTURE.md](./ARCHITECTURE.md) -**Architectural decision and rationale** ✅ DECISION MADE - -Contains: -- Analysis of two architectures (contrib-based vs. PL/iSQL-based) -- Load order analysis (`initdb -m oracle` sequence) -- Final decision: Architecture 2 (PL/iSQL-based) -- Guidelines for future DBMS packages - -### 2. [EXISTING_PACKAGES.md](./EXISTING_PACKAGES.md) -**Status of built-in Oracle packages in IvorySQL** - -Key findings: -- IvorySQL upstream has **ZERO** built-in DBMS packages -- DBMS_UTILITY is the **first** built-in package being implemented -- Web search confirms: No pre-existing DBMS packages in IvorySQL docs +1. **Location:** `src/pl/plisql/src/` (not `contrib/ivorysql_ora/`) + - Avoids cross-module dependencies + - Has direct access to PL/iSQL exception handling -### 3. [DEPENDENCY_ANALYSIS.md](./DEPENDENCY_ANALYSIS.md) -**Deep dive into the cross-module dependency problem** +2. **Exception Context Storage:** Session-level static variable in `pl_exec.c` + - Stored when entering exception handler + - Retrieved via `plisql_get_current_exception_context()` API + - Cleared when exiting exception handler -Detailed analysis: -- Why DBMS_UTILITY needs PL/iSQL internals (exception context) -- Why cross-module dependency is problematic (layering violation) -- How PostgreSQL/EPAS/Orafce handle similar issues +3. **Output Format:** Transforms PostgreSQL error context to Oracle format + - `ORA-06512: at "SCHEMA.FUNCTION", line N` + - `ORA-06512: at line N` (for anonymous blocks) -## Guidelines for Future DBMS Packages - -| Package Needs | Location | -|--------------|----------| -| PL/iSQL internals (exception context, call stack, etc.) | `src/pl/plisql/src/` | -| Only Oracle datatypes, no PL/iSQL internals | `contrib/ivorysql_ora/` | -| Both PL/iSQL internals AND Oracle types | Split: C in plisql, SQL wrapper in ivorysql_ora | - -## Current Status +## Documents -**DBMS_UTILITY:** -- ✅ FORMAT_ERROR_BACKTRACE implemented (in contrib, needs refactoring) -- ✅ Architecture decision made -- ⏳ Refactor to `src/pl/plisql/src/` -- ⏳ More functions to implement (FORMAT_ERROR_STACK, FORMAT_CALL_STACK, etc.) +| Document | Description | +|----------|-------------| +| [ARCHITECTURE.md](./ARCHITECTURE.md) | Implementation details and rationale | +| [DEPENDENCY_ANALYSIS.md](./DEPENDENCY_ANALYSIS.md) | Analysis of cross-module dependency problem | +| [EXISTING_PACKAGES.md](./EXISTING_PACKAGES.md) | Survey of Oracle packages in IvorySQL | -## Next Steps +## Implementation Status -1. ✅ Document architecture decision -2. ⏳ Refactor DBMS_UTILITY to `src/pl/plisql/src/` -3. ⏳ Update regression tests -4. ⏳ Update CLAUDE.md with guidelines -5. ⏳ Continue DBMS_UTILITY implementation (more functions) +- ✅ FORMAT_ERROR_BACKTRACE implemented +- ✅ Regression tests passing +- ⏳ Future: FORMAT_ERROR_STACK, FORMAT_CALL_STACK ## References -**IvorySQL:** -- Website: https://www.ivorysql.org/ -- Docs: https://www.ivorysql.org/docs/ -- GitHub: https://github.com/IvorySQL/IvorySQL - -**Oracle Documentation:** -- DBMS_UTILITY: https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_UTILITY.html - -**Related Projects:** -- Orafce: https://github.com/orafce/orafce (Oracle compatibility for PostgreSQL) -- EDB EPAS: https://www.enterprisedb.com/ (Commercial Oracle-compatible PostgreSQL) +- [Oracle DBMS_UTILITY Documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_UTILITY.html) +- [IvorySQL Documentation](https://www.ivorysql.org/docs/) --- **Last Updated:** 2025-11-30 -**Authors:** Rophy Tsai, Claude -**Status:** Decision made, implementation pending From 05cd10b3416696318e8faaf3ea79e1a528026420 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Mon, 1 Dec 2025 01:04:11 +0000 Subject: [PATCH 19/20] fix: prevent memory leak and nested handler context corruption - Add pfree() for func_name on early return paths in pl_dbms_utility.c - Replace global plisql_current_exception_context with per-estate storage - Add exception_handling_estate pointer to track active handler context - This fixes issues where nested exception handlers could overwrite the outer handler's backtrace context --- src/pl/plisql/src/pl_dbms_utility.c | 6 ++ src/pl/plisql/src/pl_exec.c | 101 +++++++++++++++++++++++----- src/pl/plisql/src/plisql--1.0.sql | 4 ++ src/pl/plisql/src/plisql.h | 6 ++ 4 files changed, 101 insertions(+), 16 deletions(-) diff --git a/src/pl/plisql/src/pl_dbms_utility.c b/src/pl/plisql/src/pl_dbms_utility.c index 52d9733d3ac..2571255ae33 100644 --- a/src/pl/plisql/src/pl_dbms_utility.c +++ b/src/pl/plisql/src/pl_dbms_utility.c @@ -106,7 +106,10 @@ transform_and_append_line(StringInfo result, const char *line) /* Find " line " */ line_marker = strstr(func_end, " line "); if (!line_marker) + { + pfree(func_name); return false; + } line_num_start = line_marker + 6; /* Skip " line " */ line_num = atoi(line_num_start); @@ -120,7 +123,10 @@ transform_and_append_line(StringInfo result, const char *line) /* Find " line " */ line_marker = strstr(func_end, " line "); if (!line_marker) + { + pfree(func_name); return false; + } line_num_start = line_marker + 6; /* Skip " line " */ line_num = atoi(line_num_start); diff --git a/src/pl/plisql/src/pl_exec.c b/src/pl/plisql/src/pl_exec.c index c0a4c7d9662..418bcd11b33 100644 --- a/src/pl/plisql/src/pl_exec.c +++ b/src/pl/plisql/src/pl_exec.c @@ -115,12 +115,18 @@ static SimpleEcontextStackEntry *simple_econtext_stack = NULL; static ResourceOwner shared_simple_eval_resowner = NULL; /* - * Session-level storage for the current exception context string. - * This is set when entering an exception handler and can be retrieved - * by DBMS_UTILITY.FORMAT_ERROR_BACKTRACE and similar functions. - * The memory is allocated in TopMemoryContext to survive across function calls. + * Stack of currently active execution states. The topmost entry is the + * currently executing function. */ -static char *plisql_current_exception_context = NULL; +static PLiSQL_execstate *active_estate = NULL; + +/* + * Pointer to the estate currently handling an exception. This is separate + * from active_estate because when we call functions (like DBMS_UTILITY + * package functions) from within an exception handler, active_estate + * changes but we still need access to the original handler's context. + */ +static PLiSQL_execstate *exception_handling_estate = NULL; /* * Memory management within a plisql function generally works with three @@ -525,10 +531,17 @@ plisql_exec_function(PLiSQL_function * func, FunctionCallInfo fcinfo, ErrorContextCallback plerrcontext; int i; int rc; + PLiSQL_execstate *save_active_estate; char function_from = plisql_function_from(fcinfo); bool anonymous_have_outparam = false; + /* + * Save the previous active estate so we can restore it on exit. + * Must save this BEFORE plisql_estate_setup() which will change it. + */ + save_active_estate = active_estate; + /* * Setup the execution state */ @@ -939,6 +952,11 @@ plisql_exec_function(PLiSQL_function * func, FunctionCallInfo fcinfo, } } + /* + * Restore the previous active estate + */ + active_estate = save_active_estate; + /* * Return the function's result */ @@ -1081,6 +1099,13 @@ plisql_exec_trigger(PLiSQL_function * func, PLiSQL_rec *rec_new, *rec_old; HeapTuple rettup; + PLiSQL_execstate *save_active_estate; + + /* + * Save the previous active estate so we can restore it on exit. + * Must save this BEFORE plisql_estate_setup() which will change it. + */ + save_active_estate = active_estate; /* * Setup the execution state @@ -1299,6 +1324,11 @@ plisql_exec_trigger(PLiSQL_function * func, */ error_context_stack = plerrcontext.previous; + /* + * Restore the previous active estate + */ + active_estate = save_active_estate; + /* * Return the trigger's result */ @@ -1316,6 +1346,13 @@ plisql_exec_event_trigger(PLiSQL_function * func, EventTriggerData *trigdata) PLiSQL_execstate estate; ErrorContextCallback plerrcontext; int rc; + PLiSQL_execstate *save_active_estate; + + /* + * Save the previous active estate so we can restore it on exit. + * Must save this BEFORE plisql_estate_setup() which will change it. + */ + save_active_estate = active_estate; /* * Setup the execution state @@ -1373,6 +1410,11 @@ plisql_exec_event_trigger(PLiSQL_function * func, EventTriggerData *trigdata) * Pop the error context stack */ error_context_stack = plerrcontext.previous; + + /* + * Restore the previous active estate + */ + active_estate = save_active_estate; } /* @@ -1946,6 +1988,7 @@ exec_stmt_block(PLiSQL_execstate * estate, PLiSQL_stmt_block * block) ResourceOwner oldowner = CurrentResourceOwner; ExprContext *old_eval_econtext = estate->eval_econtext; ErrorData *save_cur_error = estate->cur_error; + PLiSQL_execstate *save_exception_handling_estate = exception_handling_estate; MemoryContext stmt_mcontext; estate->err_text = gettext_noop("during statement block entry"); @@ -2096,25 +2139,39 @@ exec_stmt_block(PLiSQL_execstate * estate, PLiSQL_stmt_block * block) estate->cur_error = edata; /* - * Store the exception context string in session-level storage + * Store the exception context string in estate storage * so that DBMS_UTILITY.FORMAT_ERROR_BACKTRACE and similar * functions can access it. This provides Oracle compatibility. + * We use the estate's datum_context (SPI Proc context) for storage + * so it's cleaned up automatically when the function completes. */ - if (plisql_current_exception_context) + if (estate->current_exception_context) { - pfree(plisql_current_exception_context); - plisql_current_exception_context = NULL; + pfree(estate->current_exception_context); + estate->current_exception_context = NULL; } if (edata->context) { - plisql_current_exception_context = - MemoryContextStrdup(TopMemoryContext, edata->context); + estate->current_exception_context = + MemoryContextStrdup(estate->datum_context, edata->context); } estate->err_text = NULL; + /* + * Set exception_handling_estate so that functions called + * from within the exception handler (like DBMS_UTILITY + * package functions) can access the exception context. + */ + exception_handling_estate = estate; + rc = exec_stmts(estate, exception->action); + /* + * Restore exception_handling_estate after handler execution. + */ + exception_handling_estate = save_exception_handling_estate; + break; } } @@ -2127,13 +2184,13 @@ exec_stmt_block(PLiSQL_execstate * estate, PLiSQL_stmt_block * block) estate->cur_error = save_cur_error; /* - * Clear the session-level exception context now that we've finished + * Clear the exception context now that we've finished * handling the exception. */ - if (plisql_current_exception_context) + if (estate->current_exception_context) { - pfree(plisql_current_exception_context); - plisql_current_exception_context = NULL; + pfree(estate->current_exception_context); + estate->current_exception_context = NULL; } /* If no match found, re-throw the error */ @@ -4361,6 +4418,10 @@ plisql_estate_setup(PLiSQL_execstate * estate, estate->exitlabel = NULL; estate->cur_error = NULL; + estate->current_exception_context = NULL; + + /* Track this as the active estate for exception context access */ + active_estate = estate; estate->tuple_store = NULL; estate->tuple_store_desc = NULL; @@ -10048,5 +10109,13 @@ plisql_anonymous_return_out_parameter(PLiSQL_execstate * estate, PLiSQL_function const char * plisql_get_current_exception_context(void) { - return plisql_current_exception_context; + /* + * Return the exception context from the estate currently handling + * an exception. This is separate from active_estate because when + * we call functions from within an exception handler, active_estate + * changes but we still need access to the original handler's context. + */ + if (exception_handling_estate != NULL) + return exception_handling_estate->current_exception_context; + return NULL; } diff --git a/src/pl/plisql/src/plisql--1.0.sql b/src/pl/plisql/src/plisql--1.0.sql index 63a09b10937..9ba42996ca5 100755 --- a/src/pl/plisql/src/plisql--1.0.sql +++ b/src/pl/plisql/src/plisql--1.0.sql @@ -39,6 +39,10 @@ COMMENT ON FUNCTION sys.ora_format_error_backtrace() IS 'Internal function for D -- -- DBMS_UTILITY Package Definition -- +-- Note: CREATE PACKAGE syntax requires Oracle compatibility mode. +-- In single-user mode (initdb), compatible_mode is automatically set to 'oracle' +-- when database_mode is 'oracle', so no manual mode switching is needed. +-- CREATE OR REPLACE PACKAGE dbms_utility IS FUNCTION FORMAT_ERROR_BACKTRACE RETURN TEXT; diff --git a/src/pl/plisql/src/plisql.h b/src/pl/plisql/src/plisql.h index 15bff544b0c..44833e2a1e8 100755 --- a/src/pl/plisql/src/plisql.h +++ b/src/pl/plisql/src/plisql.h @@ -1159,6 +1159,12 @@ typedef struct PLiSQL_execstate PLiSQL_variable *err_var; /* current variable, if in a DECLARE section */ const char *err_text; /* additional state info */ + /* + * Exception context for this execution, used by DBMS_UTILITY.FORMAT_ERROR_BACKTRACE. + * Stored per-estate to handle nested exception handlers correctly. + */ + char *current_exception_context; + void *plugin_info; /* reserved for use by optional plugin */ } PLiSQL_execstate; From 84f4128005f02aca7d9ac62e9a724d9bb21686c4 Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Mon, 1 Dec 2025 01:05:54 +0000 Subject: [PATCH 20/20] test: add nested exception handler test for FORMAT_ERROR_BACKTRACE Verifies that calling a procedure with its own exception handler from within an outer exception handler preserves the outer context. --- src/pl/plisql/src/expected/dbms_utility.out | 39 +++++++++++++++++++++ src/pl/plisql/src/sql/dbms_utility.sql | 37 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/pl/plisql/src/expected/dbms_utility.out b/src/pl/plisql/src/expected/dbms_utility.out index 583cf7a1646..e2fbf34d598 100644 --- a/src/pl/plisql/src/expected/dbms_utility.out +++ b/src/pl/plisql/src/expected/dbms_utility.out @@ -225,3 +225,42 @@ DROP SCHEMA test_schema CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to function test_schema.schema_error() drop cascades to function test_schema.schema_caller() +-- Test 10: Nested exception handlers - outer context preserved after inner handler +-- This tests that when an exception handler calls a procedure that has its own +-- exception handler, the outer handler's backtrace is preserved. +CREATE OR REPLACE PROCEDURE test_nested_inner AS +BEGIN + RAISE EXCEPTION 'Inner error'; +EXCEPTION + WHEN OTHERS THEN + RAISE INFO 'Inner handler caught error'; +END; +/ +CREATE OR REPLACE PROCEDURE test_nested_outer AS + v_bt_before VARCHAR2(4000); + v_bt_after VARCHAR2(4000); +BEGIN + RAISE EXCEPTION 'Outer error'; +EXCEPTION + WHEN OTHERS THEN + v_bt_before := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Outer backtrace before: %', v_bt_before; + test_nested_inner(); + v_bt_after := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Outer backtrace after: %', v_bt_after; + IF v_bt_before = v_bt_after THEN + RAISE INFO 'SUCCESS: Outer backtrace preserved'; + ELSE + RAISE INFO 'FAILURE: Outer backtrace changed'; + END IF; +END; +/ +CALL test_nested_outer(); +INFO: Outer backtrace before: ORA-06512: at "PUBLIC.TEST_NESTED_OUTER", line 4 + +INFO: Inner handler caught error +INFO: Outer backtrace after: ORA-06512: at "PUBLIC.TEST_NESTED_OUTER", line 4 + +INFO: SUCCESS: Outer backtrace preserved +DROP PROCEDURE test_nested_outer; +DROP PROCEDURE test_nested_inner; diff --git a/src/pl/plisql/src/sql/dbms_utility.sql b/src/pl/plisql/src/sql/dbms_utility.sql index c9225baabdb..f51cb7ef514 100644 --- a/src/pl/plisql/src/sql/dbms_utility.sql +++ b/src/pl/plisql/src/sql/dbms_utility.sql @@ -231,3 +231,40 @@ END; CALL test_schema.schema_caller(); DROP SCHEMA test_schema CASCADE; + +-- Test 10: Nested exception handlers - outer context preserved after inner handler +-- This tests that when an exception handler calls a procedure that has its own +-- exception handler, the outer handler's backtrace is preserved. +CREATE OR REPLACE PROCEDURE test_nested_inner AS +BEGIN + RAISE EXCEPTION 'Inner error'; +EXCEPTION + WHEN OTHERS THEN + RAISE INFO 'Inner handler caught error'; +END; +/ + +CREATE OR REPLACE PROCEDURE test_nested_outer AS + v_bt_before VARCHAR2(4000); + v_bt_after VARCHAR2(4000); +BEGIN + RAISE EXCEPTION 'Outer error'; +EXCEPTION + WHEN OTHERS THEN + v_bt_before := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Outer backtrace before: %', v_bt_before; + test_nested_inner(); + v_bt_after := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; + RAISE INFO 'Outer backtrace after: %', v_bt_after; + IF v_bt_before = v_bt_after THEN + RAISE INFO 'SUCCESS: Outer backtrace preserved'; + ELSE + RAISE INFO 'FAILURE: Outer backtrace changed'; + END IF; +END; +/ + +CALL test_nested_outer(); + +DROP PROCEDURE test_nested_outer; +DROP PROCEDURE test_nested_inner;