|
| 1 | +# DBMS_OUTPUT Package Design |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +## Architecture |
| 8 | + |
| 9 | +### Module Location |
| 10 | + |
| 11 | +``` |
| 12 | +src/pl/plisql/src/ |
| 13 | +├── pl_dbms_output.c # C implementation |
| 14 | +├── plisql--1.0.sql # Package definition (SQL) |
| 15 | +└── Makefile # Build configuration |
| 16 | +``` |
| 17 | + |
| 18 | +**Design Decision**: DBMS_OUTPUT is implemented within the `plisql` extension rather than `ivorysql_ora` because: |
| 19 | + |
| 20 | +1. **Dependency ordering**: `ivorysql_ora` loads before `plisql` during database initialization. Placing DBMS_OUTPUT in `ivorysql_ora` would create a backwards dependency since DBMS_OUTPUT uses `PACKAGE` syntax which requires PL/iSQL. |
| 21 | + |
| 22 | +2. **Type compatibility**: Uses PostgreSQL native `TEXT` type instead of `VARCHAR2` to avoid dependency on `ivorysql_ora` types. Implicit casts between TEXT and VARCHAR2 ensure transparent compatibility. |
| 23 | + |
| 24 | +### Component Diagram |
| 25 | + |
| 26 | +``` |
| 27 | +┌─────────────────────────────────────────────────────────────┐ |
| 28 | +│ User Session │ |
| 29 | +├─────────────────────────────────────────────────────────────┤ |
| 30 | +│ PL/iSQL Block │ |
| 31 | +│ ┌─────────────────────────────────────────────────────┐ │ |
| 32 | +│ │ dbms_output.put_line('Hello'); │ │ |
| 33 | +│ │ dbms_output.get_line(line, status); │ │ |
| 34 | +│ └─────────────────────────────────────────────────────┘ │ |
| 35 | +│ │ │ |
| 36 | +│ ▼ │ |
| 37 | +│ ┌─────────────────────────────────────────────────────┐ │ |
| 38 | +│ │ PACKAGE dbms_output (plisql--1.0.sql) │ │ |
| 39 | +│ │ - Wrapper procedures with Oracle-compatible API │ │ |
| 40 | +│ └─────────────────────────────────────────────────────┘ │ |
| 41 | +│ │ │ |
| 42 | +│ ▼ │ |
| 43 | +│ ┌─────────────────────────────────────────────────────┐ │ |
| 44 | +│ │ C Functions (pl_dbms_output.c) │ │ |
| 45 | +│ │ - ora_dbms_output_enable() │ │ |
| 46 | +│ │ - ora_dbms_output_put_line() │ │ |
| 47 | +│ │ - ora_dbms_output_get_line() │ │ |
| 48 | +│ │ - etc. │ │ |
| 49 | +│ └─────────────────────────────────────────────────────┘ │ |
| 50 | +│ │ │ |
| 51 | +│ ▼ │ |
| 52 | +│ ┌─────────────────────────────────────────────────────┐ │ |
| 53 | +│ │ Session-level Buffer (TopMemoryContext) │ │ |
| 54 | +│ │ - StringInfo for line buffer │ │ |
| 55 | +│ │ - List of completed lines │ │ |
| 56 | +│ │ - Buffer size tracking │ │ |
| 57 | +│ └─────────────────────────────────────────────────────┘ │ |
| 58 | +└─────────────────────────────────────────────────────────────┘ |
| 59 | +``` |
| 60 | + |
| 61 | +### Memory Management |
| 62 | + |
| 63 | +- **Buffer Storage**: Uses `TopMemoryContext` for session-level persistence |
| 64 | +- **Transaction Callbacks**: Registered via `RegisterXactCallback` to clear buffer on transaction end |
| 65 | +- **Line Storage**: Completed lines stored in a `List` structure |
| 66 | +- **Partial Line**: Current incomplete line stored in `StringInfo` |
| 67 | + |
| 68 | +## API Reference |
| 69 | + |
| 70 | +### ENABLE |
| 71 | + |
| 72 | +```sql |
| 73 | +PROCEDURE enable(buffer_size INTEGER DEFAULT 20000); |
| 74 | +``` |
| 75 | + |
| 76 | +Enables output buffering with specified buffer size. |
| 77 | + |
| 78 | +| Parameter | Type | Default | Description | |
| 79 | +|-----------|------|---------|-------------| |
| 80 | +| buffer_size | INTEGER | 20000 | Buffer size in bytes (2000-1000000) | |
| 81 | + |
| 82 | +**Notes**: |
| 83 | +- NULL buffer_size uses default (20000 bytes) |
| 84 | +- Re-calling ENABLE clears existing buffer |
| 85 | +- Buffer size below 2000 or above 1000000 raises error |
| 86 | + |
| 87 | +### DISABLE |
| 88 | + |
| 89 | +```sql |
| 90 | +PROCEDURE disable; |
| 91 | +``` |
| 92 | + |
| 93 | +Disables output buffering and clears the buffer. |
| 94 | + |
| 95 | +### PUT |
| 96 | + |
| 97 | +```sql |
| 98 | +PROCEDURE put(a TEXT); |
| 99 | +``` |
| 100 | + |
| 101 | +Appends text to current line without newline. |
| 102 | + |
| 103 | +| Parameter | Type | Description | |
| 104 | +|-----------|------|-------------| |
| 105 | +| a | TEXT | Text to append (NULL is ignored) | |
| 106 | + |
| 107 | +### PUT_LINE |
| 108 | + |
| 109 | +```sql |
| 110 | +PROCEDURE put_line(a TEXT); |
| 111 | +``` |
| 112 | + |
| 113 | +Appends text and completes the line. |
| 114 | + |
| 115 | +| Parameter | Type | Description | |
| 116 | +|-----------|------|-------------| |
| 117 | +| a | TEXT | Text to output (NULL outputs empty line) | |
| 118 | + |
| 119 | +### NEW_LINE |
| 120 | + |
| 121 | +```sql |
| 122 | +PROCEDURE new_line; |
| 123 | +``` |
| 124 | + |
| 125 | +Completes current line (adds newline to buffer). |
| 126 | + |
| 127 | +### GET_LINE |
| 128 | + |
| 129 | +```sql |
| 130 | +PROCEDURE get_line(line OUT TEXT, status OUT INTEGER); |
| 131 | +``` |
| 132 | + |
| 133 | +Retrieves one line from the buffer. |
| 134 | + |
| 135 | +| Parameter | Direction | Type | Description | |
| 136 | +|-----------|-----------|------|-------------| |
| 137 | +| line | OUT | TEXT | Retrieved line (NULL if none) | |
| 138 | +| status | OUT | INTEGER | 0=success, 1=no more lines | |
| 139 | + |
| 140 | +### GET_LINES |
| 141 | + |
| 142 | +```sql |
| 143 | +PROCEDURE get_lines(lines OUT TEXT[], numlines IN OUT INTEGER); |
| 144 | +``` |
| 145 | + |
| 146 | +Retrieves multiple lines from the buffer. |
| 147 | + |
| 148 | +| Parameter | Direction | Type | Description | |
| 149 | +|-----------|-----------|------|-------------| |
| 150 | +| lines | OUT | TEXT[] | Array of retrieved lines | |
| 151 | +| numlines | IN OUT | INTEGER | Requested/actual count | |
| 152 | + |
| 153 | +## Implementation Details |
| 154 | + |
| 155 | +### Buffer Structure |
| 156 | + |
| 157 | +```c |
| 158 | +typedef struct { |
| 159 | + bool enabled; |
| 160 | + int buffer_size; |
| 161 | + int current_size; |
| 162 | + StringInfo current_line; /* Partial line being built */ |
| 163 | + List *lines; /* Completed lines */ |
| 164 | +} DbmsOutputBuffer; |
| 165 | +``` |
| 166 | + |
| 167 | +### Error Handling |
| 168 | + |
| 169 | +| Error Code | Message | Condition | |
| 170 | +|------------|---------|-----------| |
| 171 | +| ORU-10027 | buffer overflow, limit of N bytes | Buffer size exceeded | |
| 172 | +| ERROR | buffer size must be between 2000 and 1000000 | Invalid buffer_size parameter | |
| 173 | + |
| 174 | +### Transaction Behavior |
| 175 | + |
| 176 | +- Buffer persists across statements within a transaction |
| 177 | +- Buffer is cleared on transaction commit/abort |
| 178 | +- DISABLE clears buffer immediately |
| 179 | + |
| 180 | +## Test Coverage |
| 181 | + |
| 182 | +Tests located in `src/pl/plisql/src/sql/plisql_dbms_output.sql` |
| 183 | + |
| 184 | +| Section | Tests | Coverage | |
| 185 | +|---------|-------|----------| |
| 186 | +| 1. Basic PUT_LINE/GET_LINE | 5 | Content verification, empty/NULL handling, empty buffer status | |
| 187 | +| 2. PUT and NEW_LINE | 4 | Multi-PUT, NULL handling, line creation | |
| 188 | +| 3. ENABLE/DISABLE | 4 | Disable prevents buffering, clears buffer, re-enable behavior | |
| 189 | +| 4. Buffer size limits | 5 | Min/max bounds, error cases, NULL default | |
| 190 | +| 5. Buffer overflow | 1 | ORU-10027 error generation | |
| 191 | +| 6. GET behavior | 3 | FIFO order, partial retrieval, numlines adjustment | |
| 192 | +| 7. Procedures/Functions | 2 | Output preserved across proc/func calls | |
| 193 | +| 8. Special cases | 6 | Special chars, numerics, long lines, exceptions, nesting | |
| 194 | + |
| 195 | +**Total: 30 test cases** |
| 196 | + |
| 197 | +## Oracle Compatibility |
| 198 | + |
| 199 | +### Comparison Summary |
| 200 | + |
| 201 | +| Feature | IvorySQL | Oracle | Compatible | |
| 202 | +|---------|----------|--------|------------| |
| 203 | +| PUT_LINE basic | ✓ | ✓ | Yes | |
| 204 | +| PUT + NEW_LINE | ✓ | ✓ | Yes | |
| 205 | +| GET_LINE/GET_LINES | ✓ | ✓ | Yes | |
| 206 | +| DISABLE behavior | ✓ | ✓ | Yes | |
| 207 | +| Buffer overflow error | ORU-10027 | ORU-10027 | Yes | |
| 208 | +| Proc/Func output | ✓ | ✓ | Yes | |
| 209 | +| Exception handling | ✓ | ✓ | Yes | |
| 210 | +| NULL in PUT_LINE | Empty string | NULL | **No** | |
| 211 | +| Re-ENABLE behavior | Clears buffer | Preserves | **No** | |
| 212 | +| Buffer size range | 2000-1000000 | More permissive | **No** | |
| 213 | + |
| 214 | +### Detailed Differences |
| 215 | + |
| 216 | +#### 1. NULL Handling in PUT_LINE |
| 217 | + |
| 218 | +**IvorySQL**: |
| 219 | +```sql |
| 220 | +dbms_output.put_line(NULL); |
| 221 | +dbms_output.get_line(line, status); |
| 222 | +-- line = '' (empty string), status = 0 |
| 223 | +``` |
| 224 | + |
| 225 | +**Oracle**: |
| 226 | +```sql |
| 227 | +DBMS_OUTPUT.PUT_LINE(NULL); |
| 228 | +DBMS_OUTPUT.GET_LINE(line, status); |
| 229 | +-- line = NULL, status = 0 |
| 230 | +``` |
| 231 | + |
| 232 | +**Impact**: Low. Most applications check for empty output rather than distinguishing NULL from empty string. |
| 233 | + |
| 234 | +#### 2. Re-ENABLE Behavior |
| 235 | + |
| 236 | +**IvorySQL**: |
| 237 | +```sql |
| 238 | +dbms_output.enable(); |
| 239 | +dbms_output.put_line('First'); |
| 240 | +dbms_output.enable(); -- Clears buffer |
| 241 | +dbms_output.get_line(line, status); |
| 242 | +-- status = 1 (no lines) |
| 243 | +``` |
| 244 | + |
| 245 | +**Oracle**: |
| 246 | +```sql |
| 247 | +DBMS_OUTPUT.ENABLE(); |
| 248 | +DBMS_OUTPUT.PUT_LINE('First'); |
| 249 | +DBMS_OUTPUT.ENABLE(); -- Preserves buffer |
| 250 | +DBMS_OUTPUT.GET_LINE(line, status); |
| 251 | +-- line = 'First', status = 0 |
| 252 | +``` |
| 253 | + |
| 254 | +**Impact**: Medium. Applications that call ENABLE() multiple times may see different behavior. |
| 255 | + |
| 256 | +#### 3. Buffer Size Limits |
| 257 | + |
| 258 | +**IvorySQL**: Enforces strict range 2000-1000000 bytes |
| 259 | + |
| 260 | +**Oracle**: Accepts values outside this range (more permissive) |
| 261 | + |
| 262 | +**Impact**: Low. Standard Oracle documentation recommends values within this range. |
| 263 | + |
| 264 | +### Compatibility Recommendations |
| 265 | + |
| 266 | +1. **For maximum compatibility**: |
| 267 | + - Always call ENABLE() once at the start |
| 268 | + - Avoid relying on NULL vs empty string distinction |
| 269 | + - Use buffer sizes within 2000-1000000 |
| 270 | + |
| 271 | +2. **Migration considerations**: |
| 272 | + - Audit code for multiple ENABLE() calls |
| 273 | + - Test NULL handling in PUT_LINE if application depends on it |
| 274 | + |
| 275 | +## Files Modified |
| 276 | + |
| 277 | +| File | Changes | |
| 278 | +|------|---------| |
| 279 | +| `src/pl/plisql/src/pl_dbms_output.c` | New file - C implementation | |
| 280 | +| `src/pl/plisql/src/plisql--1.0.sql` | Added DBMS_OUTPUT package definition | |
| 281 | +| `src/pl/plisql/src/Makefile` | Added pl_dbms_output.o to OBJS | |
| 282 | +| `src/bin/initdb/initdb.c` | Enable Oracle mode for plisql extension | |
| 283 | + |
| 284 | +## Future Enhancements |
| 285 | + |
| 286 | +1. **SERVEROUTPUT setting**: Add psql-like automatic output display |
| 287 | +2. **Strict Oracle mode**: Option to match Oracle NULL behavior exactly |
| 288 | +3. **Buffer size flexibility**: Consider removing upper limit like Oracle |
0 commit comments