WITH FUNCTION/PROCEDURE

1. Purpose

This document explains the WITH FUNCTION and WITH PROCEDURE features in IvorySQL, which implement Oracle-style inline PL/SQL functions and procedures inside SQL statements.

WITH FUNCTION/PROCEDURE is Oracle’s Subquery Factoring with PL/SQL Declarations feature. It allows PL/SQL functions and procedures to be defined directly inside the WITH clause (Common Table Expression, CTE) of a SQL statement.

2. Feature Description

2.1. Basic Syntax

In Oracle-compatible mode (compatible_db = ORA_PARSER), the WITH clause supports the following extended syntax:

WITH
  FUNCTION func_name ( [ param_list ] ) RETURN return_type
  { IS | AS }
  [ declare_section ]
  BEGIN
      statements
  END [ func_name ] ;

  PROCEDURE proc_name ( [ param_list ] )
  { IS | AS }
  [ declare_section ]
  BEGIN
      statements
  END [ proc_name ] ;

  cte_name AS ( SELECT ... )
SELECT ...

2.2. Key Characteristics

  • Scope-limited: Functions/procedures defined in the WITH clause are visible only within the current SQL statement.

  • Mixed usage: Function/procedure definitions can be interleaved with standard CTEs (AS (SELECT …​)).

  • Full PL/SQL syntax: The function body supports the complete PL/SQL syntax (BEGIN…​END).

  • No system catalog writes: Definitions are automatically destroyed after execution; they are not persisted to pg_proc.

  • SELECT context only: Valid in SELECT statements and INSERT…​SELECT statements. Using WITH FUNCTION before UPDATE, DELETE, or MERGE raises an error (consistent with Oracle behavior).

2.3. Parameter Modes

Inline procedures support the standard Oracle parameter modes:

  • IN (default): input parameter

  • OUT: output parameter

  • IN OUT: bidirectional parameter

Inline functions only support IN parameters.

3. Supported Statement Types

WITH inline functions/procedures are only allowed in the following top-level statements:

  • SELECT statements (most common)

  • INSERT …​ SELECT statements (Oracle-compatible form; WITH FUNCTION appears after INSERT INTO)

The following statements are not supported (Oracle does not allow them; ERRCODE_FEATURE_NOT_SUPPORTED is raised):

  • WITH FUNCTION …​ UPDATE …​: WITH FUNCTION cannot precede UPDATE

  • WITH FUNCTION …​ DELETE …​: WITH FUNCTION cannot precede DELETE

  • WITH FUNCTION …​ MERGE …​: WITH FUNCTION cannot precede MERGE

To share reusable logic inside DML statements, define a schema-level function with CREATE FUNCTION instead.

4. Syntax Examples

4.1. Simplest Inline Function

WITH
  FUNCTION double_it(n NUMBER) RETURN NUMBER AS
  BEGIN RETURN n * 2; END;
SELECT double_it(5) FROM dual;
-- Expected output: 10

4.2. Function Mixed with a CTE

WITH
  FUNCTION tax(amt NUMBER) RETURN NUMBER AS
  BEGIN RETURN amt * 0.1; END;
  orders AS (SELECT 100 AS amount)
SELECT amount, tax(amount) FROM orders;
-- Expected output: 100 | 10

4.3. Multiple Inline Functions

WITH
  FUNCTION add1(n NUMBER) RETURN NUMBER AS BEGIN RETURN n+1; END;
  FUNCTION mul2(n NUMBER) RETURN NUMBER AS BEGIN RETURN n*2; END;
SELECT mul2(add1(3)) FROM dual;
-- Expected output: 8

4.4. Recursive Function

WITH
  FUNCTION factorial(n NUMBER) RETURN NUMBER AS
  BEGIN
    IF n <= 1 THEN RETURN 1; END IF;
    RETURN n * factorial(n-1);
  END;
SELECT factorial(5) FROM dual;
-- Expected output: 120

4.5. OUT Parameters (PROCEDURE Only)

WITH FUNCTION does not allow OUT or IN OUT parameters (consistent with Oracle ORA-06572 behavior). Only WITH PROCEDURE may declare OUT / IN OUT parameters.

-- Correct: PROCEDURE may declare OUT parameters
WITH
  PROCEDURE swap(val IN NUMBER, result OUT NUMBER) AS
  BEGIN
    result := val * 10;
  END;
SELECT 1 FROM dual;  -- the procedure is called by another subprogram inside the same
                     -- WITH block, not directly from a SELECT expression

-- Error: FUNCTION does not allow OUT parameters
WITH
  FUNCTION bad_func(val NUMBER, result OUT NUMBER) RETURN NUMBER AS
  BEGIN RETURN val; END;
SELECT bad_func(5) FROM dual;
-- Expected output: ERROR: WITH FUNCTION "bad_func" cannot declare OUT or IN OUT parameters

4.6. Default Parameter Values

WITH
  FUNCTION calc(n NUMBER DEFAULT 10) RETURN NUMBER AS
  BEGIN RETURN n * 2; END;
SELECT calc() FROM dual;
-- Expected output: 20

4.7. Exception Handling

WITH
  FUNCTION safe_div(a NUMBER, b NUMBER) RETURN NUMBER AS
  BEGIN
    RETURN a / b;
  EXCEPTION
    WHEN OTHERS THEN RETURN NULL;
  END;
SELECT safe_div(1, 0) FROM dual;
-- Expected output: NULL

4.8. Integration with DML

-- Allowed: INSERT INTO ... WITH FUNCTION ... SELECT ... (Oracle-compatible form)
WITH
  FUNCTION get_bonus(sal NUMBER) RETURN NUMBER AS
  BEGIN RETURN sal * 1.2; END;
INSERT INTO emp_bonus (empno, bonus)
SELECT empno, get_bonus(sal) FROM emp WHERE deptno = 10;
Oracle does not allow WITH FUNCTION to precede UPDATE, DELETE, or MERGE. IvorySQL enforces the same restriction; such usage raises ERRCODE_FEATURE_NOT_SUPPORTED.

5. Scope and Visibility

5.1. Scope Rules

  • The scope of an inline function/procedure is limited to the SQL statement in which it is defined (including subqueries within that statement).

  • Functions/procedures may reference each other regardless of declaration order (mutual recursion is supported).

  • An inline definition shadows any existing database function with the same name and signature (WITH definition takes precedence).

  • Defining two functions/procedures with the same name and parameter types in a single WITH clause is not allowed.

5.2. Visibility Inside Subqueries

WITH
  FUNCTION add_tax(n NUMBER) RETURN NUMBER AS
  BEGIN RETURN n * 1.1; END;
SELECT * FROM (SELECT add_tax(amount) AS total FROM orders);

6. Relationship with Existing Features

Existing Feature Relationship

PL/iSQL nested subprograms

Directly reused: leverages the existing compile/execute infrastructure.

Standard CTE (WITH…​AS (SELECT …​))

Coexists: can be mixed in the same WITH clause.

RECURSIVE CTE

Coexists: WITH RECURSIVE and inline functions can appear together.

Oracle Package

Similar: package procedures/functions are also registered temporarily at session level.

PL/iSQL CREATE FUNCTION

Different: WITH inline functions are not persisted and are not written to the system catalog.

7. Error Handling

7.1. Duplicate Definition

WITH
  FUNCTION dup(n NUMBER) RETURN NUMBER AS BEGIN RETURN n; END;
  FUNCTION dup(n NUMBER) RETURN NUMBER AS BEGIN RETURN n * 2; END;
SELECT dup(1) FROM dual;
-- Expected output: ERROR: WITH clause function "dup" is defined more than once with the same argument types

7.2. Usage in PG_PARSER Mode

-- Attempting to use WITH FUNCTION syntax in PG_PARSER mode
SET compatible_db = PG_PARSER;
WITH FUNCTION foo(n NUMBER) RETURN NUMBER AS BEGIN RETURN n; END;
SELECT foo(1);
-- Expected output: ERROR: syntax error at or near "FUNCTION"

7.3. Function Body Syntax Error

WITH
  FUNCTION broken(n NUMBER) RETURN NUMBER AS
  BEGIN
    RETRUN n;  -- typo
  END;
SELECT broken(1) FROM dual;
-- Expected output: ERROR: syntax error at or near "RETRUN"

7.4. Rejection of Table Function Usage

WITH
  FUNCTION get_rows(n NUMBER) RETURN NUMBER AS
  BEGIN RETURN n; END;
SELECT * FROM get_rows(5);
-- Expected output: ERROR: WITH clause function cannot be used as a table or set-returning function

7.5. Rejection of Qualified Names

WITH
  FUNCTION public.qual_func(n NUMBER) RETURN NUMBER IS
  BEGIN RETURN n; END;
SELECT qual_func(1) FROM dual;
-- Expected output: ERROR: qualified name is not allowed in WITH FUNCTION declaration

8. EXPLAIN Output

8.1. Basic Output

EXPLAIN WITH
  FUNCTION add_one(n NUMBER) RETURN NUMBER AS BEGIN RETURN n + 1; END;
SELECT add_one(5) FROM dual;
-- Expected output includes: WITH Function: add_one(number) RETURN number

8.2. VERBOSE Mode

EXPLAIN (VERBOSE ON) WITH
  FUNCTION double(n NUMBER) RETURN NUMBER AS
  BEGIN RETURN n * 2; END;
SELECT double(3) FROM dual;
-- Expected output includes: Body: BEGIN RETURN n * 2; END

9. Restrictions and Constraints

  1. Oracle parser mode only: This feature is active only when compatible_db = ORA_PARSER.

  2. No system catalog writes: Inline functions/procedures are registered dynamically during statement execution and deregistered immediately after; nothing is written to pg_proc.

  3. Transaction-safe: Registration and deregistration are fully transparent to transactions; no WAL is generated.

  4. Concurrency-safe: Each concurrent session has its own independent inline function/procedure registration context.

  5. Scope-limited: An inline function cannot be called outside the statement that defines it.

  6. No polymorphic parameters: Declaring parameters with types such as ANYELEMENT will fail at call time.

  7. Not usable as a table function: SELECT * FROM with_func(…​) is rejected.

10. Cleanup

-- The scope of WITH FUNCTION/PROCEDURE ends automatically with the statement.
-- No manual cleanup is required.