Annotations attach behavior to items (fns, errors, types). They use @name or @name(args, kwargs) syntax. This page lists every annotation in alphabetical order.

@autonomous

@autonomous
pub fn write_audit_log(event: text) {
    sql! { insert into audit_log(event, ts) values (:event, sysdate) };
}

Wraps the body in PL/SQL’s PRAGMA AUTONOMOUS_TRANSACTION semantics — the fn runs in its own transaction, isolated from the caller. Useful for audit logging that should commit even if the parent transaction rolls back.

Lowering adds a COMMIT on success and ROLLBACK + RAISE on error (autonomous transactions must close before returning, or Oracle raises ORA-06519).

Incompatible with: @udf, @retry.

@binds(...)

unsafe fn count_active(tname: text, status: text) -> number
    @touches(employees)
    @binds(status)
{
    return exec_dyn("select count(*) from {tname} where status = :status").one();
}

(Inside unsafe fn only.) Declares which pell parameters are referenced as :bind variables inside exec_dyn(...) strings. The compiler validates that every :name in the dynamic SQL is listed in @binds, and emits the USING clause to pass them positionally.

If a bind is declared but not used, you get a warning. If a :name is used but not declared, the compiler errors out.

@deterministic

@deterministic
pub fn double(n: number) -> number { return n * 2; }

Lowers to PL/SQL’s DETERMINISTIC clause. Tells Oracle the function returns the same value for the same inputs, so it can cache calls within a query.

Use for pure functions. Don’t use for functions that read tables, read SYS_CONTEXT, or call SYSDATE/RANDOM — they’re not deterministic in Oracle’s sense.

@panic

@panic
pub error InvariantViolation { msg: text }

(On error declarations.) Marks the error as “never retry.” Assigns a SQLCODE in the panic range (-20300..-20399); pell_re’s @retry loop calls pell_is_panic(SQLCODE) and re-raises immediately rather than retrying when the code matches.

@parallel(...)

Two distinct uses depending on where it appears:

On a @pipelined fn

@pipelined
@parallel(partition = hash(id))
pub fn classify(rows: cursor<Row>) -> stream<Scored> { ... }

Enables parallel execution. Requires a partition strategy:

Pell Oracle PARALLEL_ENABLE clause
partition = any PARTITION p BY ANY
partition = hash(col) PARTITION p BY HASH(col)
partition = range(col) PARTITION p BY RANGE(col)
order = asc(col) ORDER p BY (col)
order = desc(col) ORDER p BY (col) DESC
cluster = col1, col2 CLUSTER p BY (col1, col2)

partition = hash(col) requires a strongly-typed REF CURSOR (cursor<T>) — pell auto-emits the type alias in the package spec.

On an aggregate

@parallel
pub aggregate sum_squares(n: number) -> number { ... }

Marks the aggregate as parallel-safe. Requires a merge(other: t) method on the aggregate so Oracle can combine sub-aggregates from parallel slaves.

@pipelined

@pipelined
pub fn split_letters(s: text) -> stream<Slice> {
    for i in 1 .. length(s) {
        yield Slice { idx: i, value: substr(s, i, 1) };
    }
}

Marks the fn as a PL/SQL PIPELINED function. Body uses yield to emit rows; return type must be stream<Record>.

The lowering generates the supporting OBJECT TYPE and TABLE TYPE at schema level, plus the RETURN <nt_name> PIPELINED signature. Compose with @parallel for parallelism.

Incompatible with: @retry, @autonomous.

@propagate

(Default for error; rarely written explicitly.) Marks the error as “retry the body, but re-raise after the budget is exhausted.” Assigns a SQLCODE in the propagate range (-20200..-20299).

@retry(n=, ...)

@retry(n=5, backoff=100, jitter=50, exponential=true, cap=2000)
pub fn fetch_external(url: text) -> text { ... }

Wraps the body in a SAVEPOINT-rollback retry loop on transient failures.

Parameter Default Meaning
n 3 Max attempts (including the first).
backoff 0 Base wait between attempts, ms.
jitter 0 Random jitter added per attempt, ms.
exponential false If true, backoff doubles each attempt.
cap none Maximum total wait per attempt, ms (with exponential).

Errors in the @panic SQLCODE range bypass retry. Errors in @skip range are noted as “this iteration succeeded” — the loop exits.

Incompatible with: @autonomous, @pipelined, finally.

@schema(...)

(Planned, not in v0.) Will attach a JSON Schema validation constraint to a record or JSON field, lowering to Oracle’s IS JSON VALIDATING constraint at the DDL level.

@skip

@skip
pub error DuplicateSku { sku: text }

(On error declarations.) Marks the error as “this is recoverable within the retry loop” — @retry exits normally on this error without re-raising. Assigns a SQLCODE in the skip range (-20100..-20199).

@touches(...)

unsafe fn count_for(tname: text) -> number
    @touches(employees, departments, schedules.shifts)
{
    return exec_dyn("select count(*) from {tname}").one();
}

(Inside unsafe fn only.) Lists tables/views/packages referenced by exec_dyn(...) strings. The compiler emits “pinning cursors” — never- called CURSOR declarations with WHERE 1=0 — so Oracle’s ALL_DEPENDENCIES sees the references.

Without @touches, dynamic SQL is invisible to Oracle’s dependency graph; dropping a referenced column won’t INVALIDATE your package.

@udf

@udf
pub fn sales_tax(amount: number, rate: number) -> number {
    return amount * rate;
}

Marks the fn as eligible for User-Defined Function execution mode, which is much faster from SQL contexts than a regular PL/SQL function.

Restrictions: no DML, no autonomous transactions, no calls to non-UDF functions, no I/O.

Incompatible with: @autonomous.