Skip to content

Conversation

@rophy
Copy link
Contributor

@rophy rophy commented Dec 3, 2025

Overview

DBMS_OUTPUT is an Oracle-compatible package that provides a simple interface for displaying output from PL/SQL (PL/iSQL) blocks, stored procedures, functions, and triggers. It buffers text output during execution and allows retrieval via GET_LINE/GET_LINES procedures.

Architecture

Module Location

contrib/ivorysql_ora/
├── src/builtin_packages/dbms_output/
│   └── dbms_output.c           # C implementation
├── sql/ora_dbms_output.sql     # Test SQL
├── expected/ora_dbms_output.out # Expected test output
├── ivorysql_ora_merge_sqls     # SQL merge configuration
└── Makefile                    # Build configuration

Design Decision: DBMS_OUTPUT is implemented within the ivorysql_ora extension because:

  1. Extension ordering: plisql loads before ivorysql_ora during database initialization (see initdb.c). This allows ivorysql_ora to use PACKAGE syntax which requires PL/iSQL.

  2. Oracle package grouping: DBMS_OUTPUT belongs with other Oracle-compatible built-in packages in ivorysql_ora.

  3. Type compatibility: Uses PostgreSQL native TEXT type instead of VARCHAR2 to avoid circular dependencies. Implicit casts between TEXT and VARCHAR2 ensure transparent compatibility.

Component Diagram

┌─────────────────────────────────────────────────────────────┐
│                      User Session                            │
├─────────────────────────────────────────────────────────────┤
│  PL/iSQL Block                                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  dbms_output.put_line('Hello');                     │    │
│  │  dbms_output.get_line(line, status);                │    │
│  └─────────────────────────────────────────────────────┘    │
│                           │                                  │
│                           ▼                                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  PACKAGE dbms_output (ivorysql_ora--1.0.sql)        │    │
│  │  - Wrapper procedures with Oracle-compatible API    │    │
│  └─────────────────────────────────────────────────────┘    │
│                           │                                  │
│                           ▼                                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  C Functions (dbms_output.c)                        │    │
│  │  - ora_dbms_output_enable()                         │    │
│  │  - ora_dbms_output_put_line()                       │    │
│  │  - ora_dbms_output_get_line()                       │    │
│  │  - etc.                                             │    │
│  └─────────────────────────────────────────────────────┘    │
│                           │                                  │
│                           ▼                                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Session-level Buffer (TopMemoryContext)            │    │
│  │  - StringInfo for line buffer                       │    │
│  │  - List of completed lines                          │    │
│  │  - Buffer size tracking                             │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

Memory Management

  • Buffer Storage: Uses TopMemoryContext for session-level persistence
  • Transaction Callbacks: Registered via RegisterXactCallback to clear buffer on transaction end
  • Line Storage: Completed lines stored in a List structure
  • Partial Line: Current incomplete line stored in StringInfo

API Reference

ENABLE

PROCEDURE enable(buffer_size INTEGER DEFAULT 20000);

Enables output buffering with specified buffer size.

Parameter Type Default Description
buffer_size INTEGER 20000 Buffer size in bytes (2000-1000000)

Notes:

  • NULL buffer_size uses default (20000 bytes)
  • Re-calling ENABLE clears existing buffer
  • Buffer size below 2000 or above 1000000 raises error

DISABLE

PROCEDURE disable;

Disables output buffering and clears the buffer.

PUT

PROCEDURE put(a TEXT);

Appends text to current line without newline.

Parameter Type Description
a TEXT Text to append (NULL is ignored)

Notes:

  • Accumulated line length must not exceed 32767 bytes
  • Exceeding line length raises ORU-10028 error

PUT_LINE

PROCEDURE put_line(a TEXT);

Appends text and completes the line.

Parameter Type Description
a TEXT Text to output (NULL/empty string stored as NULL)

Notes:

  • Maximum line length is 32767 bytes (Oracle compatible)
  • Exceeding line length raises ORU-10028 error

NEW_LINE

PROCEDURE new_line;

Completes current line (adds newline to buffer).

GET_LINE

PROCEDURE get_line(line OUT TEXT, status OUT INTEGER);

Retrieves one line from the buffer.

Parameter Direction Type Description
line OUT TEXT Retrieved line (NULL if none)
status OUT INTEGER 0=success, 1=no more lines

GET_LINES

PROCEDURE get_lines(lines OUT TEXT[], numlines IN OUT INTEGER);

Retrieves multiple lines from the buffer.

Parameter Direction Type Description
lines OUT TEXT[] Array of retrieved lines
numlines IN OUT INTEGER Requested/actual count

Implementation Details

Buffer Structure

typedef struct {
    bool        enabled;
    int         buffer_size;
    int         current_size;
    StringInfo  current_line;    /* Partial line being built */
    List       *lines;           /* Completed lines */
} DbmsOutputBuffer;

Error Handling

Error Code Message Condition
ORU-10027 buffer overflow, limit of N bytes Total buffer size exceeded
ORU-10028 line length overflow, limit of 32767 bytes per line Single line exceeds 32767 bytes
ERROR buffer size must be between 2000 and 1000000 Invalid buffer_size parameter

Transaction Behavior

  • Buffer persists across statements within a transaction
  • Buffer is cleared on transaction commit/abort
  • DISABLE clears buffer immediately

Test Coverage

Tests located in contrib/ivorysql_ora/sql/ora_dbms_output.sql

Section Tests Coverage
1. Basic PUT_LINE/GET_LINE 5 Content verification, empty/NULL handling, empty buffer status
2. PUT and NEW_LINE 4 Multi-PUT, NULL handling, line creation
3. ENABLE/DISABLE 4 Disable prevents buffering, clears buffer, re-enable behavior
4. Buffer size limits 5 Min/max bounds, error cases, NULL default
5. Buffer overflow 1 ORU-10027 error generation
6. GET behavior 3 FIFO order, partial retrieval, numlines adjustment
7. Procedures/Functions 2 Output preserved across proc/func calls
8. Special cases 6 Special chars, numerics, long lines, exceptions, nesting
9. Line length limits 3 32767 byte max, ORU-10028 error on overflow

Total: 33 test cases

Oracle Compatibility

Comparison Summary

Feature IvorySQL Oracle Compatible
PUT_LINE basic Yes
PUT + NEW_LINE Yes
GET_LINE/GET_LINES Yes
DISABLE behavior Yes
Buffer overflow error ORU-10027 ORU-10027 Yes
Line length limit ORU-10028 at 32767 ORU-10028 at 32767 Yes
Proc/Func output Yes
Exception handling Yes
NULL/empty in PUT_LINE Stored as NULL Stored as NULL Yes
Re-ENABLE behavior Clears buffer Preserves No
Buffer size range 2000-1000000 2000-unlimited No

Compatibility Rate: 91% (30/33 tests pass)

Detailed Differences

1. Re-ENABLE Behavior

IvorySQL:

dbms_output.enable();
dbms_output.put_line('First');
dbms_output.enable();  -- Clears buffer
dbms_output.get_line(line, status);
-- status = 1 (no lines)

Oracle:

DBMS_OUTPUT.ENABLE();
DBMS_OUTPUT.PUT_LINE('First');
DBMS_OUTPUT.ENABLE();  -- Preserves buffer
DBMS_OUTPUT.GET_LINE(line, status);
-- line = 'First', status = 0

Impact: Medium. Applications that call ENABLE() multiple times may see different behavior.

2. Buffer Size Limits (#22)

IvorySQL: Enforces strict range 2000-1000000 bytes, rejects values outside.

Oracle: Minimum 2000 bytes (values below silently adjusted up), maximum unlimited. Per Oracle docs.

Impact: Low. IvorySQL is stricter but catches invalid values early.

Status: Open issue - should silently adjust values below 2000 instead of rejecting.

Compatibility Recommendations

  1. For maximum compatibility:

    • Always call ENABLE() once at the start
    • Use buffer sizes within 2000-1000000
    • Keep individual lines under 32767 bytes
  2. Migration considerations:

    • Audit code for multiple ENABLE() calls

Files Modified

File Changes
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c C implementation
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output--1.0.sql Package SQL definition
contrib/ivorysql_ora/Makefile Added dbms_output.o to OBJS
contrib/ivorysql_ora/meson.build Added dbms_output.c to sources
contrib/ivorysql_ora/ivorysql_ora_merge_sqls Added dbms_output SQL merge entry

Future Enhancements

  1. SERVEROUTPUT setting: Add psql-like automatic output display
  2. Re-ENABLE behavior: Consider matching Oracle's buffer-preserving behavior
  3. Buffer size flexibility: Consider removing upper limit like Oracle

Summary by CodeRabbit

  • New Features

    • Added Oracle-compatible DBMS_OUTPUT package with backend-backed buffered output and transaction-scoped lifecycle
    • ENABLE/DISABLE buffering control with configurable (including unlimited) sizes
    • PUT, PUT_LINE, NEW_LINE to emit output; GET_LINE and GET_LINES to retrieve it
    • Preserves Oracle semantics for NULL handling and enforces 32,767-byte line limits
  • Tests

    • Added comprehensive regression tests covering buffering, line assembly, limits, overflow and edge cases

✏️ Tip: You can customize this high-level summary in your review settings.

rophy added 3 commits December 3, 2025 04:32
Implement Oracle-compatible DBMS_OUTPUT package providing:
- PUT_LINE, PUT, NEW_LINE for buffered output
- GET_LINE, GET_LINES for retrieving output
- ENABLE/DISABLE for buffer control
- Buffer overflow detection (ORU-10027)

Located in contrib/ivorysql_ora/src/builtin_packages/dbms_output/
following IvorySQL maintainer guidance (discussion IvorySQL#988).

Includes design documentation and Oracle compatibility comparison
with 82% test compatibility rate (27/33 tests pass).

Known differences from Oracle:
- NULL stored as empty string vs NULL
- Re-ENABLE clears buffer vs preserves
- Buffer size range 2000-1000000 (Oracle: 2000-unlimited)
- No 32767 byte line length limit
Oracle treats empty strings as NULL. Both PUT_LINE('') and
PUT_LINE(NULL) should store actual NULL values, which GET_LINE
returns as SQL NULL.

Verified against Oracle 23.26 Free.

Fixes #25
Oracle enforces a maximum line length of 32767 bytes per line.
Exceeding this limit raises ORU-10028 error.

- Check line length in PUT_LINE before adding to buffer
- Check accumulated line length in PUT before appending
- Add test cases for line length boundary conditions

Verified against Oracle 23.26 Free.

Fixes #21
@rophy
Copy link
Contributor Author

rophy commented Dec 3, 2025

DBMS_OUTPUT Oracle Compatibility Test Results

Test Date: 2025-12-03
Oracle Version: 23.26.0.0 Free
IvorySQL Branch: feat/dbms_output

Test Methodology

Equivalent test cases were executed on both IvorySQL and Oracle Database to verify behavioral compatibility. Tests use PUT followed by GET to verify actual buffer content rather than just syntax validation.

Section 1: Basic PUT_LINE and GET_LINE

Test 1.1: Simple PUT_LINE verified by GET_LINE

IvorySQL:

NOTICE:  Test 1.1 - Line: [Hello, World\!], Status: 0

Oracle:

Test 1.1 - Line: [Hello, World\!], Status: 0

Result: ✅ MATCH


Test 1.2: Multiple PUT_LINE calls verified by GET_LINES

IvorySQL:

NOTICE:  Test 1.2 - Retrieved 3 lines
NOTICE:    Line 1: [First line]
NOTICE:    Line 2: [Second line]
NOTICE:    Line 3: [Third line]

Oracle:

Test 1.2 - Retrieved 3 lines
  Line 1: [First line]
  Line 2: [Second line]
  Line 3: [Third line]

Result: ✅ MATCH


Test 1.3: Empty string handling

IvorySQL:

NOTICE:  Test 1.3 - Empty string: [<NULL>], Status: 0

Oracle:

Test 1.3 - Empty string: [<NULL>], Status: 0

Result: ✅ MATCH

Analysis: Oracle treats empty strings as NULL. Both IvorySQL and Oracle store empty string as NULL and return it via GET_LINE.


Test 1.4: NULL handling

IvorySQL:

NOTICE:  Test 1.4 - NULL input: [<NULL>], Status: 0

Oracle:

Test 1.4 - NULL input: [<NULL>], Status: 0

Result: ✅ MATCH

Analysis: Both IvorySQL and Oracle preserve NULL values. Fixed in commit c9aa53f (Fixes #25).


Test 1.5: GET_LINE when buffer is empty

IvorySQL:

NOTICE:  Test 1.5 - Empty buffer: Line=[<NULL>], Status=1

Oracle:

Test 1.5 - Empty buffer: Line=[<NULL>], Status=1

Result: ✅ MATCH


Section 2: PUT and NEW_LINE

Test 2.1: PUT followed by NEW_LINE

IvorySQL:

NOTICE:  Test 2.1 - Combined: [First part second part], Status: 0

Oracle:

Test 2.1 - Combined: [First part second part], Status: 0

Result: ✅ MATCH


Test 2.2: PUT with NULL

IvorySQL:

NOTICE:  Test 2.2 - PUT with NULL: [BeforeAfter], Status: 0

Oracle:

Test 2.2 - PUT with NULL: [BeforeAfter], Status: 0

Result: ✅ MATCH


Test 2.3: Multiple PUT calls building one line

IvorySQL:

NOTICE:  Test 2.3 - Multiple PUTs: [ABCD], Status: 0

Oracle:

Test 2.3 - Multiple PUTs: [ABCD], Status: 0

Result: ✅ MATCH


Test 2.4: PUT + NEW_LINE + PUT_LINE creates two lines

IvorySQL:

NOTICE:  Test 2.4 - Retrieved 2 lines
NOTICE:    Line 1: [Partial]
NOTICE:    Line 2: [Complete]

Oracle:

Test 2.4 - Retrieved 2 lines
  Line 1: [Partial]
  Line 2: [Complete]

Result: ✅ MATCH


Section 3: ENABLE and DISABLE behavior

Test 3.1: DISABLE prevents output from being buffered

IvorySQL:

NOTICE:  Test 3.1 - After disable/enable cycle: [After re-enable], Status: 0

Oracle:

Test 3.1 - After disable/enable cycle: [After re-enable], Status: 0

Result: ✅ MATCH


Test 3.2: DISABLE clears existing buffer

IvorySQL:

NOTICE:  Test 3.2 - Buffer after disable: [<NULL>], Status: 1

Oracle:

Test 3.2 - Buffer after disable: [<NULL>], Status: 1

Result: ✅ MATCH


Test 3.3: Re-ENABLE clears buffer

IvorySQL:

NOTICE:  Test 3.3 - After re-enable: [<NULL>], Status: 1

Oracle:

Test 3.3 - After re-enable: [First enable content], Status: 0

Result: ❌ DIFFERENCE

Analysis: This is a significant behavioral difference:

  • IvorySQL: Calling ENABLE() when already enabled clears the buffer
  • Oracle: Calling ENABLE() when already enabled preserves existing buffer content

This affects applications that call ENABLE() multiple times during execution. See Issue #26 for tracking.


Test 3.4: Output while disabled is silently ignored

IvorySQL:

NOTICE:  Test 3.4 - Only visible after enable: [Visible], Status: 0

Oracle:

Test 3.4 - Only visible after enable: [Visible], Status: 0

Result: ✅ MATCH


Section 4: Buffer size limits

Test 4.1: Buffer size below minimum (1000)

IvorySQL:

ERROR:  buffer size must be between 2000 and 1000000

Oracle:

Test 4.1 - 1000 buffer: succeeded

Result: ❌ DIFFERENCE

Analysis: IvorySQL rejects values below 2000 with an error. Oracle silently adjusts values below 2000 up to 2000 (the minimum). See Issue #22 for tracking.


Test 4.2: Buffer size at minimum (2000)

IvorySQL:

NOTICE:  Test 4.2 - Min buffer: [Min buffer works]

Oracle:

Test 4.2 - Min buffer: [Min buffer works]

Result: ✅ MATCH


Test 4.3: Buffer size at maximum (1000000)

IvorySQL:

NOTICE:  Test 4.3 - Max buffer: [Max buffer works]

Oracle:

Test 4.3 - Max buffer: [Max buffer works]

Result: ✅ MATCH


Test 4.4: Buffer size above maximum (1000001)

IvorySQL:

ERROR:  buffer size must be between 2000 and 1000000

Oracle:

Test 4.4 - 1000001 buffer: succeeded

Result: ❌ DIFFERENCE

Analysis: IvorySQL enforces maximum buffer size of 1000000, Oracle accepts larger values. This is documented as an intentional IvorySQL limitation.


Test 4.5: NULL buffer size uses default

IvorySQL:

NOTICE:  Test 4.5 - NULL buffer: [NULL buffer uses default]

Oracle:

Test 4.5 - NULL buffer: [NULL buffer uses default]

Result: ✅ MATCH


Section 5: Buffer overflow

Test 5.1: Buffer overflow produces error

IvorySQL:

NOTICE:  Test 5.1 - Overflow at line 47: ORU-10027: buffer overflow, limit of 2000 bytes

Oracle:

ORA-20000: ORU-10027: buffer overflow, limit of 2000 bytes
(Overflow at line 47)

Result: ✅ MATCH

Analysis: Both produce the same Oracle-compatible error code (ORU-10027) and overflow occurs at approximately the same line count.


Section 6: GET_LINE and GET_LINES behavior

Test 6.1: GET_LINE returns lines in order

IvorySQL:

NOTICE:  Test 6.1a - First: [Line A]
NOTICE:  Test 6.1b - Second: [Line B]
NOTICE:  Test 6.1c - Third: [Line C]
NOTICE:  Test 6.1d - Fourth (empty): [<NULL>], Status: 1

Oracle:

Test 6.1a - First: [Line A]
Test 6.1b - Second: [Line B]
Test 6.1c - Third: [Line C]
Test 6.1d - Fourth (empty): [<NULL>], Status: 1

Result: ✅ MATCH


Test 6.2: GET_LINES with numlines larger than available

IvorySQL:

NOTICE:  Test 6.2 - Requested 100, got 3
NOTICE:    Line 1: [Only]
NOTICE:    Line 2: [Three]
NOTICE:    Line 3: [Lines]

Oracle:

Test 6.2 - Requested 100, got 3
  Line 1: [Only]
  Line 2: [Three]
  Line 3: [Lines]

Result: ✅ MATCH


Test 6.3: GET_LINES with numlines smaller than available

IvorySQL:

NOTICE:  Test 6.3a - Got 2 lines with GET_LINES
NOTICE:    Line 1: [One]
NOTICE:    Line 2: [Two]
NOTICE:  Test 6.3b - Remaining: [Three], Status: 0
NOTICE:  Test 6.3c - Remaining: [Four], Status: 0

Oracle:

Test 6.3a - Got 2 lines with GET_LINES
  Line 1: [One]
  Line 2: [Two]
Test 6.3b - Remaining: [Three], Status: 0
Test 6.3c - Remaining: [Four], Status: 0

Result: ✅ MATCH


Section 7: Usage in procedures and functions

Test 7.1: Output from procedure

IvorySQL:

NOTICE:  Test 7.1 - From procedure: [Proc says: Hello from procedure]

Oracle:

Test 7.1 - From procedure: [Proc says: Hello from procedure]

Result: ✅ MATCH


Test 7.2: Output from function

IvorySQL:

NOTICE:  Test 7.2 - Function returned: 10
NOTICE:    Output 1: [Func input: 5]
NOTICE:    Output 2: [Func output: 10]

Oracle:

Test 7.2 - Function returned: 10
  Output 1: [Func input: 5]
  Output 2: [Func output: 10]

Result: ✅ MATCH


Section 8: Special cases

Test 8.1: Special characters

IvorySQL:

NOTICE:  Test 8.1 - Special chars: 3 lines
NOTICE:    [Tab:	here]
NOTICE:    [Quote: 'single' "double"]
NOTICE:    [Backslash: \ forward: /]

Oracle:

Test 8.1 - Special chars: 3 lines
  [Tab:	here]
  [Quote: 'single' "double"]
  [Backslash: \ forward: /]

Result: ✅ MATCH


Test 8.2: Numeric values via concatenation

IvorySQL:

NOTICE:  Test 8.2 - Numeric: [Number: 42]

Oracle:

Test 8.2 - Numeric: [Number: 42]

Result: ✅ MATCH


Test 8.3: Very long line

IvorySQL:

NOTICE:  Test 8.3 - Long line length: 1000

Oracle:

Test 8.3 - Long line length: 1000

Result: ✅ MATCH


Test 8.4: Exception handling preserves buffer

IvorySQL:

NOTICE:  Test 8.4a - [Before exception]
NOTICE:  Test 8.4b - [Caught: Test error]
NOTICE:  Test 8.4c - [After exception]

Oracle:

Test 8.4a - [Before exception]
Test 8.4b - [Caught: ORA-20001: Test error]
Test 8.4c - [After exception]

Result: ✅ MATCH (error message format differs but behavior matches)


Test 8.5: Nested blocks

IvorySQL:

NOTICE:  Test 8.5 - Nested blocks: 4 lines
NOTICE:    [Outer]
NOTICE:    [Inner 1]
NOTICE:    [Inner 2]
NOTICE:    [Back to outer]

Oracle:

Test 8.5 - Nested blocks: 4 lines
  [Outer]
  [Inner 1]
  [Inner 2]
  [Back to outer]

Result: ✅ MATCH


Test 8.6: Loop output

IvorySQL:

NOTICE:  Test 8.6 - Loop: 3 lines
NOTICE:    [Iteration 1]
NOTICE:    [Iteration 2]
NOTICE:    [Iteration 3]

Oracle:

Test 8.6 - Loop: 3 lines
  [Iteration 1]
  [Iteration 2]
  [Iteration 3]

Result: ✅ MATCH


Section 9: Line Length Limits

Test 9.1: Maximum line length (32767 bytes)

IvorySQL:

NOTICE:  Test 9.1 - Max line (32767 bytes): length=32767, Status=0

Oracle:

Test 9.1 (32767): OK, length=32767, Status=0

Result: ✅ MATCH


Test 9.2: Exceeding max line length (32768 bytes via PUT_LINE)

IvorySQL:

NOTICE:  Test 9.2 - Line overflow error: ORU-10028: line length overflow, limit of 32767 bytes per line

Oracle:

Test 9.2 (32768 via PUT): ORA-20000: ORU-10028: line length overflow, limit of 32767 bytes per line

Result: ✅ MATCH

Analysis: Both IvorySQL and Oracle enforce the 32767 byte line length limit with ORU-10028 error. Fixed in commit 80355d1 (Fixes #21).


Test 9.3: PUT accumulating to exceed 32767 bytes

IvorySQL:

NOTICE:  Test 9.3 - PUT overflow error: ORU-10028: line length overflow, limit of 32767 bytes per line

Oracle:

Test 9.3 (50000 via PUT): ORA-20000: ORU-10028: line length overflow, limit of 32767 bytes per line

Result: ✅ MATCH

Analysis: Both IvorySQL and Oracle detect line length overflow during PUT accumulation. Fixed in commit 80355d1 (Fixes #21).


Summary

Category Tests Passed Failed
Section 1: Basic PUT_LINE/GET_LINE 5 5 0
Section 2: PUT and NEW_LINE 4 4 0
Section 3: ENABLE/DISABLE 4 3 1
Section 4: Buffer size limits 5 3 2
Section 5: Buffer overflow 1 1 0
Section 6: GET behavior 3 3 0
Section 7: Procedures/Functions 2 2 0
Section 8: Special cases 6 6 0
Section 9: Line length limits 3 3 0
Total 33 30 3

Compatibility Rate: 91% (30/33 tests)

Differences Summary

Test Issue Severity Tracking
3.3 Re-ENABLE clears buffer vs preserves Medium Issue #26
4.1 Should silently adjust to 2000, not reject Low Issue #22
4.4 Maximum buffer 1000000 vs unlimited Low Documented as intentional limitation

Fixed Issues

Test Issue Fix
1.3, 1.4 NULL/empty string handling Fixed in commit c9aa53f (#25)
9.2, 9.3 32767 byte line length limit Fixed in commit 80355d1 (#21)

Recommendations

  1. Medium Priority: Consider matching Oracle's re-ENABLE behavior (preserving buffer) as this could affect migrated applications. See Issue #26.

  2. Low Priority: The buffer size differences are edge cases unlikely to affect most applications. See Issue #22.

  3. Documentation: All remaining differences should be documented in user-facing documentation for migration guidance.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

Adds an Oracle-compatible DBMS_OUTPUT feature to contrib/ivorysql_ora: new C implementation, SQL package/spec, regression tests, and build/manifest entries to compile and register the module.

Changes

Cohort / File(s) Summary
Build & manifest
contrib/ivorysql_ora/Makefile, contrib/ivorysql_ora/ivorysql_ora_merge_sqls
Added src/builtin_packages/dbms_output/dbms_output.o to OBJS, added module path src/builtin_packages/dbms_output/dbms_output to merge list, and added ora_dbms_output to ORA_REGRESS.
SQL tests
contrib/ivorysql_ora/sql/ora_dbms_output.sql
Added comprehensive regression tests for PUT/PUT_LINE/NEW_LINE/GET_LINE/GET_LINES, ENABLE/DISABLE, buffer sizing, overflow, edge cases, and cleanup.
SQL package spec
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output--1.0.sql
Added package spec and body, composite types (dbms_output_line, dbms_output_lines), and SQL-accessible wrappers for C functions (enable, disable, put_line, put, new_line, get_line, get_lines).
C implementation
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c
New native implementation: per-backend transaction-scoped buffer, memory-context management, transaction callback cleanup, line accumulation, buffer/line-size enforcement, and SQL-callable functions for enable/disable/put/put_line/new_line/get_line/get_lines.

Sequence Diagram

sequenceDiagram
    autonumber
    participant Client
    participant SQL_Pkg as "SQL Package\n(dbms_output)"
    participant C_Backend as "C Backend\n(ora_dbms_output_*)"
    participant Buffer as "DbmsOutputBuffer"
    participant Txn as "Txn Callback"

    Client->>SQL_Pkg: ENABLE(size?)
    SQL_Pkg->>C_Backend: ora_dbms_output_enable()
    C_Backend->>Buffer: init_output_buffer()
    Buffer->>Txn: register callback

    Client->>SQL_Pkg: PUT(text) / PUT_LINE(text)
    SQL_Pkg->>C_Backend: ora_dbms_output_put/_put_line
    C_Backend->>Buffer: accumulate/append (enforce limits)

    Client->>SQL_Pkg: NEW_LINE()
    SQL_Pkg->>C_Backend: ora_dbms_output_new_line()
    C_Backend->>Buffer: flush current PUT into lines array

    Client->>SQL_Pkg: GET_LINE() / GET_LINES(n)
    SQL_Pkg->>C_Backend: ora_dbms_output_get_line/get_lines
    C_Backend->>Buffer: read from buffer (advance position)
    Buffer-->>C_Backend: line(s) + status/count
    C_Backend-->>SQL_Pkg: composite result
    SQL_Pkg-->>Client: return values

    Client->>SQL_Pkg: DISABLE()
    SQL_Pkg->>C_Backend: ora_dbms_output_disable()

    Txn->>Buffer: on commit/abort -> cleanup_output_buffer()
    Buffer->>Txn: unregister callback / free memory
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay special attention to:
    • contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c: memory-context usage, transaction callback lifecycle, buffer growth and byte accounting, per-line length handling, NULL semantics, and error reporting.
    • contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output--1.0.sql: SQL/C declarations and type mappings.
    • contrib/ivorysql_ora/sql/ora_dbms_output.sql: test correctness for edge cases and overflow behavior.
    • contrib/ivorysql_ora/Makefile and merge manifest: build/test integration.

Suggested reviewers

  • jiaoshuntian

Poem

I nibble at bytes and stitch each line,
ENABLE my burrow, PUT and then shine.
NEW_LINE hops in, GET_LINES brings cheer,
I tidy buffers until commit is near. 🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The pull request title directly and clearly summarizes the main change: adding an Oracle-compatible DBMS_OUTPUT package implementation to IvorySQL.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
contrib/ivorysql_ora/sql/ora_dbms_output.sql (1)

147-189: Strong DBMS_OUTPUT coverage; consider clarifying DISABLE vs re‑ENABLE comments

The tests around DISABLE/re‑ENABLE (3.1–3.3) correctly verify that:

  • Output is not buffered while disabled.
  • A subsequent ENABLE call effectively resets the buffer.

However, comments like “DISABLE clears existing buffer” in Test 3.2 can be misread as “DISABLE alone clears,” whereas the behavior is really “re‑ENABLE starts with a fresh buffer.” Consider tweaking those comments to avoid confusion for future readers; the test logic itself is fine.

contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c (1)

82-146: Minor behavioral/doc consistency around ENABLE/DISABLE and callback registration

Two small nits:

  • The comment above init_output_buffer says “Oracle behavior – ENABLE always clears existing buffer,” while your own tests intentionally validate a non‑Oracle behavior (re‑ENABLE clears, whereas Oracle typically preserves). It would be clearer to phrase this as “Our behavior: ENABLE always clears existing buffer (differs from Oracle)” to avoid confusion for future maintainers.

  • Tracking callback_registered in the buffer struct and immediately unregistering inside cleanup_output_buffer works, but given that the callback itself simply calls cleanup_output_buffer(), you could simplify by:

    • Keeping the callback permanently registered for the backend, and
    • Letting it no‑op when output_buffer == NULL.

Not required for correctness, but it would reduce state bookkeeping and the chance of subtle registration/unregistration bugs later.

Also applies to: 296-309

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7261f1c and ce711ad.

⛔ Files ignored due to path filters (2)
  • contrib/ivorysql_ora/expected/ora_dbms_output.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_nested_subproc2.out is excluded by !**/*.out
📒 Files selected for processing (5)
  • contrib/ivorysql_ora/Makefile (2 hunks)
  • contrib/ivorysql_ora/ivorysql_ora_merge_sqls (1 hunks)
  • contrib/ivorysql_ora/sql/ora_dbms_output.sql (1 hunks)
  • contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output--1.0.sql (1 hunks)
  • contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T10:43:25.257Z
Learnt from: syasirs
Repo: IvorySQL/IvorySQL PR: 858
File: src/Makefile.global.in:513-514
Timestamp: 2025-09-02T10:43:25.257Z
Learning: In IvorySQL Makefile.global.in, $(wildcard t/oracle/*.pl) is used instead of t/oracle/*.pl in Oracle TAP test discovery because the t/oracle directory is optional and may not exist in most builds. The wildcard function safely returns empty string when the directory is missing, preventing shell expansion errors.

Applied to files:

  • contrib/ivorysql_ora/Makefile
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: meson_build (ubuntu-latest)
  • GitHub Check: contrib_regression
  • GitHub Check: oracle_regression (ubuntu-latest)
  • GitHub Check: pg_regression (ubuntu-latest)
  • GitHub Check: build (ubuntu-latest)
🔇 Additional comments (3)
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output--1.0.sql (1)

26-127: DBMS_OUTPUT package spec/body and C wrappers look consistent and well‑aligned

  • C functions, composite types, and package procedures line up correctly with the C signatures and intended OUT/IN OUT semantics.
  • Using VOLATILE for these session/transaction‑scoped buffers is appropriate.
    No issues from a correctness or integration standpoint.
contrib/ivorysql_ora/ivorysql_ora_merge_sqls (1)

3-3: New dbms_output merge entry is consistent with file layout

The added path src/builtin_packages/dbms_output/dbms_output matches the new package’s location and naming convention; no further changes needed here.

contrib/ivorysql_ora/Makefile (1)

28-29: Build and regression wiring for DBMS_OUTPUT looks correct

  • Object file path src/builtin_packages/dbms_output/dbms_output.o matches the new C source.
  • ora_dbms_output entry in ORA_REGRESS corresponds to the new SQL test file and is placed consistently with existing tests.
    No further Makefile changes appear necessary.

Also applies to: 73-75

- Fix potential integer overflow in buffer check by using subtraction
  instead of addition (line_bytes > buffer_size - buffer_used)
- Normalize negative numlines parameter to 0 in GET_LINES
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c (2)

175-200: Consider guarding lines_allocated growth against pathological overflow

ensure_lines_capacity grows lines_allocated by 50% (with a +100 floor), which is a good amortized strategy. In the “unlimited” case, it’s theoretically possible to push a huge number of zero-byte lines (e.g., repeated NEW_LINE calls with no text), causing lines_allocated to grow very large. At extreme scales, new_capacity (an int) could overflow before repalloc is called.

This is extremely pathological, but you could defensively cap new_capacity (e.g., check for new_capacity <= output_buffer->lines_allocated and throw an error) to avoid ever wrapping the int.


241-262: Optional: handle parallel xact events if IvorySQL’s base Postgres exposes them

dbms_output_xact_callback currently cleans up only on XACT_EVENT_ABORT, XACT_EVENT_COMMIT, and XACT_EVENT_PREPARE, and ignores all other events. On newer Postgres versions there are also XACT_EVENT_PARALLEL_* variants commonly handled alongside the base events.

If IvorySQL’s underlying Postgres has those events, you may want to route XACT_EVENT_PARALLEL_COMMIT / PARALLEL_ABORT / PARALLEL_PREPARE through the same cleanup path to avoid leaking buffers in parallel contexts; if not, this can be left as-is.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce711ad and 74dd6d6.

📒 Files selected for processing (1)
  • contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c (7)
src/backend/utils/mmgr/mcxt.c (6)
  • palloc0 (1350-1370)
  • MemoryContextDelete (453-488)
  • pfree (1527-1541)
  • repalloc (1547-1581)
  • pstrdup (1702-1706)
  • palloc (1320-1348)
src/common/stringinfo.c (2)
  • makeStringInfo (71-75)
  • resetStringInfo (125-134)
src/backend/utils/fmgr/funcapi.c (1)
  • get_call_result_type (283-293)
src/backend/executor/execTuples.c (1)
  • BlessTupleDesc (2314-2322)
src/include/postgres.h (2)
  • Int32GetDatum (216-220)
  • PointerGetDatum (326-330)
src/include/funcapi.h (1)
  • HeapTupleGetDatum (336-340)
src/backend/utils/adt/arrayfuncs.c (2)
  • construct_empty_array (3599-3610)
  • construct_md_array (3513-3594)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: oracle_regression (ubuntu-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: oracle_pg_regression (ubuntu-latest)
  • GitHub Check: meson_build (ubuntu-latest)
  • GitHub Check: pg_regression (ubuntu-latest)
  • GitHub Check: contrib_regression
🔇 Additional comments (5)
contrib/ivorysql_ora/src/builtin_packages/dbms_output/dbms_output.c (5)

39-57: Buffer struct design and memory-context split look solid

The DbmsOutputBuffer layout (separate buffer_mcxt, byte-based buffer_used, read_position, and preallocated lines array) is clear and aligns well with Postgres memory management patterns. No issues here.


148-173: Cleanup and xact-callback unregistration pattern looks correct

cleanup_output_buffer correctly:

  • No-ops when output_buffer == NULL,
  • Unregisters the xact callback only when it was registered,
  • Deletes buffer_mcxt (freeing all lines and current_line), and
  • Frees the DbmsOutputBuffer struct and resets the global pointer.

This matches typical Postgres extension patterns; no changes needed.


202-238: Overflow check and NULL-line handling in add_line_to_buffer look good

The revised overflow check using subtraction (line_bytes > buffer_size - buffer_used) avoids signed-addition overflow, and treating NULL lines as zero bytes with a NULL pointer in lines[] matches the intended semantics. Allocating copies in buffer_mcxt is also correct.

No further issues here.


297-444: Core PUT/PUT_LINE/NEW_LINE semantics and limits look correct

The ora_dbms_output_disable, ora_dbms_output_put_line, ora_dbms_output_put, and ora_dbms_output_new_line paths:

  • Correctly ignore output when the buffer is disabled or not yet enabled.
  • Enforce the 32767-byte per-line limit on both pure PUT_LINE calls and accumulated PUT text, raising ORU-10028 consistently.
  • Treat PUT(NULL) as a no-op, and PUT_LINE(NULL)/NULL lines as actual NULL entries in the buffer.
  • Flush pending PUT content before PUT_LINE, and make NEW_LINE create an empty line when there’s no pending text.

Given the regression tests you described, these match the intended behavior; I don’t see any functional issues here.


446-590: GET_LINE/GET_LINES tuple and array construction looks correct; negative numlines handling is clean

For ora_dbms_output_get_line and ora_dbms_output_get_lines:

  • The get_call_result_type + BlessTupleDesc + heap_form_tuple pattern is standard and correct.
  • NULL lines are preserved (NULL in buffer → NULL element / field in result).
  • Status codes and counts are consistent (status = 1 when no more lines, empty TEXT[] + count 0 when requested_lines ≤ 0 or buffer is empty).
  • Normalizing negative requested_lines to 0 avoids surprising negative counts and aligns with the prior review feedback.

No further changes needed here.

rophy added 3 commits December 3, 2025 06:57
Address CodeRabbit review feedback:
- Change "full Oracle compatibility" to "high Oracle compatibility"
- Clarify that re-ENABLE clearing buffer is IvorySQL behavior (not Oracle)
- Clarify that buffer size range is IvorySQL-enforced (stricter than Oracle)
- Reference GitHub issues #22 and #26 for tracking differences
Add dbms_output_line and dbms_output_lines composite types to the
expected output for the collation sanity check query.
@rophy rophy changed the title DBMS_OUTPUT Package Design Add Oracle-compatible DBMS_OUTPUT package implementation Dec 3, 2025
@rophy
Copy link
Contributor Author

rophy commented Dec 3, 2025

@bigplaice following up #971, this one is real implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant