orafce

repository·master·Indexed 19 days ago

https://github.com/orafce/orafce

A PostgreSQL extension that provides functions, operators, and packages to emulate Oracle RDBMS compatibility. It facilitates the migration of Oracle applications to PostgreSQL by providing compatible data types (VARCHAR2, NVARCHAR2, DATE), SQL functions for math, string, and date/time handling, and Oracle-style packages. It includes a virtual DUAL table and specific configuration guidance for the search_path to ensure orafce functions take precedence over standard PostgreSQL functions.

Tokens
52K
Snippets
179
Records
199
Agent score
68%

What's inside orafce

  1. Use Orafce for Oracle compatibility in PostgreSQL

    master

    Orafce provides functions and operators that emulate a subset of Oracle RDBMS functions and packages, facilitating the porting of Oracle applications to PostgreSQL. It is supported in AWS Aurora with PostgreSQL Compatibility and Azure Database for PostgreSQL.

    To ensure proper function resolution (especially when Orafce functions share names with PostgreSQL built-ins), it is highly recommended to include the oracle schema in your search_path.

    SET search_path TO oracle, "$user", public, pg_catalog;
  2. How to use Orafce packages in PL/pgSQL

    master

    In Orafce, a "package" is a group of features organized by schemas that provide specific functionalities. To use these features within PL/pgSQL, you must qualify the function name with the package name. You can invoke these functions using either the PERFORM statement (for functions that return VOID) or a SELECT statement (for functions that return values).

    Supported packages include:

    • DBMS_ALERT
    • DBMS_ASSERT
    • DBMS_OUTPUT
    • DBMS_PIPE
    • DBMS_RANDOM
    • DBMS_UTILITY
    • UTL_FILE
  3. Understand DBMS_ALERT execution and message behavior differences

    master

    When migrating DBMS_ALERT from Oracle to orafce, be aware of these behavioral differences:

    1. Signal Serialization

    • Oracle: DBMS_ALERT.SIGNAL is serialized. If multiple sessions signal the same alert, each signal waits until the preceding one is committed.
    • PostgreSQL: DBMS_ALERT.SIGNAL is not serialized. A signal can be sent even if the preceding signal is not yet committed; the first one committed is reported.

    2. Message Retention (Multiple Signals)

    • Oracle: If multiple signals occur between a REGISTER and a WAIT call, only the last message is received. Earlier messages are discarded.
    • PostgreSQL: If multiple signals occur between a REGISTER and a WAIT call, the first message is received. Subsequent messages are retained and not discarded.

    Recommendation: If your application logic depends on receiving every single alert, ensure you use DBMS_ALERT.REMOVE or DBMS_ALERT.REMOVEALL to clean up alerts when they are no longer needed to prevent session buildup.

  4. Use VARCHAR2 and NVARCHAR2 types

    master

    Orafce provides VARCHAR2 and NVARCHAR2 to emulate Oracle string types.

    VARCHAR2

    Implements BYTE semantics by default.

    • Supports the bytes unit of type modifier.
    • Unlike standard PostgreSQL varchar, implicit casts to VARCHAR2 do not truncate whitespace over the declared maximum length.
    • Note: It does not impose the Oracle 4000-byte limit; it behaves like PostgreSQL varchar (up to ~10MB).

    NVARCHAR2

    Implements CHARACTER semantics.

    • Uses the database's character set/encoding.
    • Use this type if character-based semantics are preferred over byte-based semantics.
    • Also does not impose the 4000-character limit.

    Null-Safe Concatenation

    -- Enable null-safe concatenation (similar to Oracle behavior)
    SET orafce.varchar2_null_safe_concat TO true;
    
    -- With concatenation enabled, NULLs in the chain are treated as empty strings
    SELECT NULL || 'hello'::varchar2 || NULL;
  5. Understand differences in DML with Subquery behavior

    master

    There are behavioral differences when using subqueries within DML (Data Manipulation Language) statements, particularly regarding how concurrent sessions interact with data that is being modified by a subquery.

    • Oracle: May detect that the result of a subquery has changed due to concurrent transactions and may rollback or rerun the statement to maintain consistency.
    • PostgreSQL: May not detect the change in the subquery's result set in the same way, leading to different row counts being affected by concurrent deletes or updates.

    Best Practice: To ensure consistent behavior across both Oracle and PostgreSQL, avoid using subqueries in DML statements and in SELECT ... FOR UPDATE queries.

    -- Example of DML with Subquery difference
    create table dml(a int, b int);
    insert into dml values(1, 1), (2,2);
    
    -- session 1: 
    begin; 
    delete from dml where a in (select min(a) from dml); 
    
    -- session 2:  
    delete from dml where a in (select min(a) from dml); 
    
    -- session 1:  
    commit;
    
    -- Oracle: 0 rows in dml at last.
    -- PostgreSQL: 1 row in dml at last.
  6. Understand behavior differences between PostgreSQL and orafce

    master

    When using orafce, some functions and data types behave differently than standard PostgreSQL to maintain Oracle compatibility. If public or oracle are in your search_path, the following differences apply:

    Data Types

    • DATE: In standard PostgreSQL, this stores date only. In orafce, it stores both date and time.

    Functions

    • LENGTH: For CHAR types, orafce includes trailing spaces in the length, whereas standard PostgreSQL does not.
    • SUBSTR:
      • If 0 is specified as the start position, extraction starts from the beginning of the string.
      • If a negative value is specified, extraction starts from the position counted from the end of the string.
    • LPAD / RPAD:
      • For CHAR types, orafce pads the value without removing trailing spaces.
      • The result length is based on string width: fullwidth characters count as 2, and halfwidth characters count as 1.
    • LTRIM / RTRIM / BTRIM: For CHAR types, orafce removes the value without removing trailing spaces.
    • TO_DATE: Returns a TIMESTAMP in orafce, whereas standard PostgreSQL returns a DATE.

    Features requiring oracle in search_path

    The following features cannot be used with the default PostgreSQL configuration and require oracle to be added to the search_path before pg_catalog:

    • Functions: SYSDATE, DBTIMEZONE, SESSIONTIMEZONE, TO_CHAR (for date/time values).
    • Operators: Datetime operators.
  7. Understand differences in Handled Statement Failure behavior

    master

    When running a transaction containing multiple statements, Oracle and PostgreSQL handle statement failures differently during a COMMIT:

    • Oracle: If a statement within a transaction fails (e.g., a primary key violation), the COMMIT can still succeed for the preceding successful statements. The table will contain the rows inserted before the failure.
    • PostgreSQL: If any statement within a transaction block fails, the entire transaction is marked as aborted. The COMMIT will fail, and no changes from that transaction will be applied (the table will remain as it was before the BEGIN).
    create table t (a int primary key, b int);
    begin;
    insert into t values(1,1);
    insert into t values(1, 1); -- This fails due to PK violation
    commit; 
    -- Oracle: commit succeeds, t has 1 row.
    -- PostgreSQL: commit fails, t has 0 rows.
  8. Communicate between sessions using DBMS_PIPE

    master

    The DBMS_PIPE package enables inter-session communication in PL/pgSQL. It uses a local buffer (approx. 8 KB) to pack and send messages.

    Pipe Types

    TypeCharacteristics
    ExplicitCreated via CREATE_PIPE. Can be Public (accessible by all) or Private (only accessible by the creator). Must be removed via REMOVE_PIPE.
    ImplicitCreated automatically when SEND_MESSAGE or RECEIVE_MESSAGE is used. These are always Public and are removed automatically when empty.

    Core Workflow

    1. Create a pipe: PERFORM DBMS_PIPE.CREATE_PIPE('pipe_name', max_messages, private_boolean);.
    2. Pack messages: Use PACK_MESSAGE(data) to add items to the local buffer. Supported types include Character, Integer (converted to NUMERIC), NUMERIC, DATE, TIMESTAMP (converted to TIMESTAMP WITH TIME ZONE), BYTEA, and RECORD.
    3. Send/Receive: Use SEND_MESSAGE to push the buffer to a pipe, or RECEIVE_MESSAGE to pull from one.
    4. Unpack: Use UNPACK_MESSAGE_TEXT, UNPACK_MESSAGE_NUMBER, etc., to extract data from the local buffer after receiving.
    5. Cleanup: Use PURGE('pipe_name') to empty a pipe or REMOVE_PIPE('pipe_name') to delete an explicit pipe.

    Important Constraints

    • A single instance can use up to 50 pipes concurrently.
    • Memory Warning: Repeatedly creating and removing Private pipes can lead to memory exhaustion because creator information persists even after removal. Use Public pipes for frequent creation/removal cycles.
    • If a timeout occurs during RECEIVE_MESSAGE on an implicit pipe, the pipe is not removed.
    PERFORM DBMS_PIPE.CREATE_PIPE('P01', 100, FALSE);
    PERFORM DBMS_PIPE.PACK_MESSAGE('Message Test001');
    PERFORM DBMS_PIPE.SEND_MESSAGE('P01');
  9. Understand UTL_FILE functional differences in PostgreSQL

    master

    When using UTL_FILE in orafce, be aware of these functional differences compared to Oracle:

    1. Open Modes: The rb (read byte), wb (write byte), and ab (append byte) modes cannot be specified for OPEN_MODE.
    2. IS_OPEN State: After calling UTL_FILE.FCLOSE_ALL, UTL_FILE.IS_OPEN will return FALSE in PostgreSQL, whereas it returns TRUE in Oracle.
    3. FFLUSH Timing: In Oracle, UTL_FILE.FFLUSH writes buffered data up to the newline character. In PostgreSQL, UTL_FILE.FFLUSH writes all buffered data.
  10. Migrate %FOUND to FOUND

    master

    In Oracle, %FOUND is used to check if an SQL statement affected rows. In PostgreSQL, use the FOUND keyword.

    Migration Steps:

    • Implicit Cursors: Replace cursorName%FOUND or SQL%FOUND with FOUND.
    • Explicit Cursors: Since PostgreSQL's FOUND refers to the most recent command, you cannot call it directly on a cursor name. Instead:
      1. Declare a BOOLEAN variable for each explicit cursor.
      2. Immediately after each FETCH statement, assign the value: variable := FOUND;.
      3. Replace the original %FOUND check with your new boolean variable.

    Note: If an SQL statement has not been executed, FOUND is FALSE (matching Oracle's behavior).

    -- Oracle
    IF SQL%FOUND THEN ...
    
    -- PostgreSQL
    IF FOUND THEN ...