pell_runtime is a package that pell generates from the union of
every error declaration across your project. It contains:
- One
EXCEPTION+PRAGMA EXCEPTION_INITper pell error type. - A
set_err / get_err / clear_errtriple for stashing error payloads in SYS_CONTEXT.
You install it once per schema. Module code that raises pell errors
references pell_runtime.<exception_name> and the EXCEPTION_INIT
mapping ties it to a specific SQLCODE.
Generating the package
./pell runtime > pell_runtime.sql
# Then install:
sqlplus user/pass @pell_runtime.sql
The runtime subcommand walks the directory (or specific file) and
aggregates every error it finds. Re-run it whenever you add a new
error type and reinstall.
You can target a single source path:
./pell runtime src/ -o build/pell_runtime.sql
Generated structure
-- Context for stashing error payloads, scoped to pell_runtime
CREATE OR REPLACE CONTEXT pell_err USING pell_runtime;
CREATE OR REPLACE PACKAGE pell_runtime AS
PROCEDURE set_err(p_key IN VARCHAR2, p_payload IN VARCHAR2);
PROCEDURE clear_err(p_key IN VARCHAR2);
FUNCTION get_err(p_key IN VARCHAR2) RETURN VARCHAR2;
-- One per pell error declaration
hr_employees_notfound EXCEPTION;
PRAGMA EXCEPTION_INIT(hr_employees_notfound, -20100);
billing_charges_overdraft EXCEPTION;
PRAGMA EXCEPTION_INIT(billing_charges_overdraft, -20102);
...
END pell_runtime;
/
CREATE OR REPLACE PACKAGE BODY pell_runtime AS
PROCEDURE set_err(p_key IN VARCHAR2, p_payload IN VARCHAR2) IS
BEGIN DBMS_SESSION.SET_CONTEXT('pell_err', p_key, p_payload); END;
PROCEDURE clear_err(p_key IN VARCHAR2) IS
BEGIN DBMS_SESSION.CLEAR_CONTEXT('pell_err', p_key); END;
FUNCTION get_err(p_key IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN RETURN SYS_CONTEXT('pell_err', p_key); END;
END pell_runtime;
/
Exception naming
Pell-generated exception names follow the pattern:
<schema>_<package>_<errortype>
Lowercased, joined by underscores. For an error NotFound declared
in module hr.employees, the exception becomes
hr_employees_notfound.
SQLCODE ranges
| Category | SQLCODE range | Behavior with @retry |
|---|---|---|
@skip |
-20100 to -20199 | exit loop normally |
| (propagate) | -20200 to -20299 | re-raise after budget |
@panic |
-20300 to -20399 | re-raise immediately |
Pell assigns SQLCODES sequentially within each range as it
encounters errors. The assignments are stable: re-running
pell runtime on the same input produces the same numbers.
Raising errors from pell
You don’t write RAISE directly. Use Err(...) in pell:
pub fn lookup(id: number) -> Result<text, NotFound> {
if some_check_fails {
return Err(NotFound { id: id });
}
return Ok(...);
}
The lowering uses pell_runtime.set_err to stash the JSON-serialized
payload, then RAISE pell_runtime.<exception>:
pell_runtime.set_err('hr_employees_notfound', '{"id":' || p_id || '}');
RAISE pell_runtime.hr_employees_notfound;
A caller’s WHEN OTHERS (inside @retry or finally) sees the
exception by name; pell_runtime.get_err(SQLCODE-keyed-thing) can
recover the payload if needed.
Catching errors elsewhere
In SQL*Plus or other client code that calls pell-emitted packages,
the error surfaces as a normal ORA-20100 (or whatever code), and the
payload is in SYS_CONTEXT(‘pell_err’, ‘
BEGIN
hr.employees.lookup(:p_id);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('caught: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('payload: ' ||
SYS_CONTEXT('pell_err', 'hr_employees_notfound'));
END;
For library code (other pell modules), you don’t write WHEN OTHERS
yourself — let the error propagate up, and let @retry or finally
in a higher-level fn handle it. See Errors and @retry.
When to regenerate
Anytime you:
- Add a new
errordeclaration to a module. - Remove an error (regenerate so SYS_CONTEXT keys don’t go stale).
- Change an error’s category (
@skip/@panic/ unmarked) — the SQLCODE moves between ranges.
You can build pell_runtime regeneration into your CI / deploy
script:
pell runtime src/ -o build/pell_runtime.sql
diff -q build/pell_runtime.sql current_pell_runtime.sql && echo "no change" || apply_to_db
A note on global state
pell_runtime is a single package shared across the schema. That
means every pell module’s errors go in the same namespace. The
<schema>_<package>_ prefix prevents name collisions, but be aware
that:
- All modules in a schema share the SQLCODE space.
- Reinstalling
pell_runtimeinvalidates dependent packages until they recompile. This is normal Oracle behavior; usually a no-op if the spec didn’t change.
If you want strict isolation, use separate schemas per module group
and install one pell_runtime per schema.