plxplsql: the Oracle PL/SQL dialect¶
plxplsql lets you write PostgreSQL functions with Oracle PL/SQL syntax. At
CREATE FUNCTION time plx transpiles the body to plpgsql and stores the plpgsql
in pg_proc.prosrc. The function runs on the standard plpgsql interpreter.
PL/SQL and plpgsql are both descended from Ada, so most of the language is the
same in both: DECLARE/BEGIN/EXCEPTION/END, IF/ELSIF/ELSE,
LOOP/WHILE/FOR, CASE, assignment with :=, || concatenation, cursors,
%TYPE, and %ROWTYPE all pass through unchanged. plxplsql is a
layout-preserving rewriter: it keeps your formatting and comments and only
translates the Oracle-specific spellings. This makes it a practical path for
moving Oracle PL/SQL into PostgreSQL.
Setup¶
Function basics¶
The body is a PL/SQL block: optional declarations, then BEGIN ... END, with an
optional EXCEPTION section. A DECLARE keyword is added automatically when the
body opens with declarations.
CREATE FUNCTION grade(score numeric) RETURNS text LANGUAGE plxplsql AS $$
result VARCHAR2(10);
BEGIN
IF score >= 90 THEN
result := 'A';
ELSIF score >= 80 THEN
result := 'B';
ELSE
result := 'F';
END IF;
RETURN result;
END;
$$;
Function signatures use PostgreSQL types¶
The body is PL/SQL, but the function signature (the parameter and return
types in CREATE FUNCTION) is parsed by PostgreSQL before plx sees the body, so
it must use PostgreSQL type names: write RETURNS numeric, not RETURN NUMBER.
Inside the body, Oracle type names are translated (see below).
Oracle to PostgreSQL translations¶
Everything not listed here is emitted verbatim (it is already valid plpgsql).
Types (in the body)¶
| Oracle | PostgreSQL |
|---|---|
NUMBER, NUMBER(p,s) |
numeric, numeric(p,s) |
VARCHAR2(n), NVARCHAR2(n) |
varchar(n) |
PLS_INTEGER, BINARY_INTEGER, SIMPLE_INTEGER |
integer |
BINARY_FLOAT / BINARY_DOUBLE |
real / double precision |
CLOB, NCLOB, LONG |
text |
BLOB, RAW |
bytea |
Statements and functions¶
| Oracle | PostgreSQL |
|---|---|
DBMS_OUTPUT.PUT_LINE(x) |
RAISE NOTICE '%', (x) |
RAISE_APPLICATION_ERROR(num, msg) |
RAISE EXCEPTION '%', (msg) |
EXECUTE IMMEDIATE sql [INTO v] [USING a] |
EXECUTE sql [INTO v] [USING a] |
... FROM DUAL |
... (removed) |
NVL(a, b) |
coalesce(a, b) |
seq.NEXTVAL / seq.CURRVAL |
nextval('seq') / currval('seq') |
SYSDATE |
LOCALTIMESTAMP |
SYSTIMESTAMP |
clock_timestamp() |
CURSOR c IS query |
c CURSOR FOR query |
Cursors¶
Explicit cursors work as in PL/SQL:
CURSOR c IS SELECT v FROM t ORDER BY v;
...
OPEN c;
LOOP
FETCH c INTO r;
EXIT WHEN NOT FOUND; -- or c%NOTFOUND, see notes
...
END LOOP;
CLOSE c;
Errors¶
Handle exceptions with the PL/SQL (and plpgsql) EXCEPTION section:
Condition names such as NO_DATA_FOUND, TOO_MANY_ROWS, DUP_VAL_ON_INDEX,
and ZERO_DIVIDE are recognized by plpgsql as well; where a name differs, use
the SQLSTATE condition name.
Dynamic SQL¶
EXECUTE IMMEDIATE 'UPDATE t SET n = n + 1 WHERE id = $1' USING v_id;
EXECUTE IMMEDIATE 'SELECT count(*) FROM t WHERE g = $1' INTO v_count USING v_g;
Semantic differences¶
These are intentional. plx pins semantics to SQL and plpgsql.
- The signature uses PostgreSQL types (see above); the body uses PL/SQL.
- Oracle type names are translated only in the declaration section (before the
first
BEGIN), so a body reference to a column or alias named like an Oracle type (e.g. a columnnumber) is left untouched. A cast to an Oracle type in the body should use the PostgreSQL type (x::numeric, notx::NUMBER). NUMBERmaps tonumeric(arbitrary precision), not Oracle'sNUMBERinternal representation; arithmetic is PostgreSQLnumeric.- Empty string and NULL are distinct in PostgreSQL, unlike Oracle where
''is NULL. Code that relies on Oracle's empty-string-is-NULL behavior needs review. - Date and time follow PostgreSQL types and functions.
SYSDATEmaps toLOCALTIMESTAMP.
Not supported¶
Rejected or unchanged (and therefore likely to error) at CREATE FUNCTION time:
- Packages (
CREATE PACKAGE), package-qualified calls other than theDBMS_OUTPUTforms above, and the widerDBMS_*/UTL_*library. - PL/SQL collections and associative arrays, records beyond
%ROWTYPE,BULK COLLECT,FORALL, and pipelined functions. - Autonomous transactions (
PRAGMA AUTONOMOUS_TRANSACTION) and other pragmas. - Oracle-only SQL (outer-join
(+),CONNECT BY,DECODE,MERGEspecifics, Oracle hint comments) inside statements. Use the PostgreSQL equivalent. - Oracle spellings inside a
DBMS_OUTPUT.PUT_LINE(...)orRAISE_APPLICATION_ERROR(...)argument are not translated (the argument is copied verbatim), so avoidNVL/SYSDATE/seq.NEXTVALthere; assign to a variable first, or use the PostgreSQL form. - Schema-qualified sequence pseudocolumns (
schema.seq.NEXTVAL): writenextval('schema.seq')instead. - A type name (
NUMBER,VARCHAR2, ...) is translated only in the outermost declaration section; a nestedDECLAREblock's declarations use PostgreSQL type names. - Parameterized cursors (
CURSOR c(p NUMBER) IS ...) and single-argumentRAISE_APPLICATION_ERROR.
See PARITY.md for the plpgsql construct matrix and ARCHITECTURE.md for how plx maps to the plpgsql engine.