Verdict
The lowering story is viable in spirit, with two material factual errors that
would have caused embarrassment at first PL/SQL emission (the cursor FOR loop
sketch’s wrong %ROWTYPE, and RESULT_CACHE RELIES_ON being treated as a
live clause when it has been deprecated/ignored since 11.2). The error model
is conceptually sound — the §6.5 surface API for distinguishing absence vs
invariant is the strongest contribution of the doc — but the §6.6 strategy
comparison is overoptimistic about (A): the -20000..-20999 band is 1000
codes shared schema-wide and RAISE_APPLICATION_ERROR is capped at 2048
bytes, both of which (A) bumps into in any non-trivial codebase. §6.3
finally lowering needs to address early return, nested raises, and
package-init blocks. Pragma compatibility claims in §9.2 are mostly correct
for 23ai but PRAGMA INLINE targets a call-site statement, not a function
definition, so the annotation surface needs to reflect that. Net: small set
of corrections, not a redesign.
Strong points
- §6.5 / §6.5.1 distinction between
Option/Result/expectis genuinely better than PL/SQL’s overloadedNO_DATA_FOUNDand worth shipping. - §4.5.1 commitment to never emit
OPEN/FETCH/CLOSEand to keepSELECT INTOout of the lowering avoids two well-known PL/SQL footguns. - §9 uniform
@nameannotation surface for the pragma grab-bag is the right call; PL/SQL’s mix of signature clauses vs body pragmas is a consistent source of bugs. - §5.1.4 acknowledging that
map<K,V>cannot be passed to SQL is honest and correct. - §3 table is a good elevator pitch.
Concerns
- §4.5.1 the cursor FOR loop sketch declared
l_result employees%rowtypewhile the cursor projects onlyid, name— that won’t compile. The holder record must match the projection, not the table. Also worth adding the optimizer-plan note forFETCH FIRST nand elision when the source is already PK-bounded. - §4.5 / §5.1 the claim that
list<T>works inTABLE(:xs)“for all element types” is too broad. SQL-visible nested tables must be schema- level (CREATE TYPE … AS TABLE OF …), not package-local PL/SQL collections, and the element type must itself be SQL-visible. Records of records, assoc arrays as elements, and certain interval-precision combinations break this. - §6.3 the finally lowering sketch doesn’t address: early
returnfrom inside the body, a raise inside the finally body itself masking the original exception, package-initializerfinally, or how@autonomousinteracts with the wrapper. - §6.5.1 “uncatchable” overstates the property —
RAISE_APPLICATION_ERRORis a normal exception that arbitrary hand-written PL/SQL can catch withWHEN OTHERSor viaPRAGMA EXCEPTION_INIT. The property is “the pell compiler refuses to emit a handler”, not a database-enforced guarantee. - §6.6(A) the
-20000..-20999band is 1000 codes total, shared schema-wide with every other tool/ORM/hand-package. For a real codebase this needs management; the cons bullet undersells it. Also the 2048-byte message cap onRAISE_APPLICATION_ERRORis not mentioned. - §6.6(B) “thread-local payload” is the wrong PL/SQL primitive — there are no threads; package state is session-scoped. The mechanism still works but the description should say session-scoped.
- §9.2
@result_cache(relies_on = …)theRELIES_ONclause was deprecated in 11.2 and is parsed-but-ignored in modern Oracle including- Treating it as a live dependency hint is factually wrong.
- §9.2
@inlinelowers toPRAGMA INLINE(name, 'YES'), which is a statement-level pragma at the call site, not a function-level decoration. The “target: fn or call site” entry is incorrect for fn. - §9.2
@udf+@autonomousthe note says “mutually exclusive with some@autonomoussettings” — make this a hard rule, since UDF semantics assume participation in the caller’s SQL transaction. - §9.2
@serially_reusablemust be emitted in both package spec and body; doc doesn’t say so, and missing one is a common bug. Also worth noting that serially-reusable packages can’t be referenced from triggers.
Proposed edits (made in this worktree)
- §4.5.1 fixed the
%ROWTYPEmismatch in the.first()sketch by declaring a holder record matching the projection, added anexit;for symmetry, and added an optimizer-plan note plus an elision rule for PK-bounded source queries. - §4.5 / §5.1 (table row) / §5.1.4 corrected the nested-table claim:
list<T>is onlyTABLE(:xs)-usable when the element type is SQL-visible and the nested-table type lives at schema scope, not package scope. Enumerated the disallowed element shapes (record-of- record, assoc-array element, etc.) and called out interval-precision. - §6.3 rewrote the lowering sketch to drop the redundant
l_failedflag (it wasn’t used) and added an explicit list of edge cases the compiler must handle: earlyreturn, secondary raises from inside the finally body, package-init blocks, and@autonomous-wrapped fns. - §6.5.1 softened “uncatchable” to “uncatchable in pell source”,
explained that the ORA-20001 emission is catchable in hand-written
PL/SQL, and called out the 2048-byte
RAISE_APPLICATION_ERRORmessage cap plus the truncation strategy. - §6.6 expanded both (A) and (B) with concrete sizing — 1000-code band
and 2KB message cap for (A); reframed (B)’s “thread-local” as
“session-scoped payload register” and noted the residual-state-cleanup
obligation at outermost entry points and the
@autonomousnested-frame requirement. Bias still (A) but flagged migration to (B) as expected. - §9.2 corrected the pragma table: dropped
RELIES_ONas deprecated; flagged@inlineas call-site-only and made fn-level a compile error; hardened@udf+@autonomousmutual exclusion; noted@serially_reusablemust be emitted in both spec and body. Updated the worked example to droprelies_onand added a paragraph confirmingDETERMINISTIC+RESULT_CACHE+PRAGMA UDFall coexist legally in 23ai with the compiler-enforced incompatibilities listed. - §9.3 expanded the
@error_codenote to acknowledge the 1000-code band reality, the deterministic-assignment collision risk, and the registry-file collision check.
Open questions for Shaun
- For strategy (A) error lowering, do you want the compiler to maintain a project-local code registry (one band per project, allocated up front) or a schema-global one (shared across pell projects in the same DB)? The latter is more conservative but requires a shared deployment step.
- Source maps for the
RAISE_APPLICATION_ERRORmessage under (A) — do we prefix the message with a correlation id (so the bulk of the payload goes to a side table) when it would otherwise exceed 2KB, or hard-fail the compile? I’d lean correlation-id-with-side-table; it’s the only thing that scales. @result_cachewithoutRELIES_ON: the doc acceptedrelies_onas a “hint”. Now that the clause is dead, do you want the annotation to accept an “intended dependencies” list anyway forpell docpurposes even though it doesn’t lower to anything?- For
list<T>schema-level types: are you OK with the compiler emittingCREATE TYPEDDL as part ofdeploy.sql, given §11 question 4 leans “no DDL in v1”? Either we relax that for collection types, or we declare them as a build-time prerequisite the user runs by hand. I’d document them as compiler-generated DDL that’s still considered “infrastructure types”, not “user DDL”. - §6.5.1
.expectpanic: do you wantfinallyblocks above the panic site to be able to log the panic message (read-only access to it) before it propagates? Otherwise operators only see theRAISE_APPLICATION_ERRORtext. Cheap to add via a runtime helper.