Skip to content

Commit a157391

Browse files
committed
docs: add DBMS_OUTPUT design and Oracle comparison
- Architecture and API documentation - Oracle compatibility test results (87% compatible) - Document 4 behavioral differences
1 parent b3c25bd commit a157391

File tree

2 files changed

+899
-0
lines changed

2 files changed

+899
-0
lines changed

design/dbms_output/README.md

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
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

Comments
 (0)