Skip to content

Commit 5fad8c8

Browse files
committed
feat: implement dynamic ring buffer for DBMS_OUTPUT
- Ring buffer with length-prefixed strings (2 bytes per line overhead) - Dynamic growth: expands by min(buffer_size, max(DEFAULT_BUFFER_SIZE, capacity * GROWTH_FACTOR)) - ENABLE(NULL) creates unlimited buffer starting at DEFAULT_BUFFER_SIZE - Values < MIN_BUFFER_SIZE silently clamped to minimum (Oracle behavior) - Buffer space recycled when lines are read via GET_LINE/GET_LINES - Per-line overhead not counted toward user-specified limit (Oracle behavior)
1 parent 42afdcc commit 5fad8c8

3 files changed

Lines changed: 463 additions & 112 deletions

File tree

contrib/ivorysql_ora/expected/ora_dbms_output.out

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,8 @@ NOTICE: Test 3.4 - Only visible after enable: [Visible], Status: 0
208208
-- =============================================================================
209209
-- Section 4: Buffer size limits
210210
-- =============================================================================
211-
-- Test 4.1: Buffer size below minimum (should fail)
211+
-- Test 4.1: Buffer size below minimum (silently clamped to 2000, Oracle behavior)
212212
CALL dbms_output.enable(1000);
213-
ERROR: buffer size must be between 2000 and 1000000
214-
CONTEXT: SQL statement "SELECT sys.ora_dbms_output_enable(buffer_size)"
215-
PL/iSQL function enable line 3 at PERFORM
216213
-- Test 4.2: Buffer size at minimum (should succeed)
217214
DECLARE
218215
line TEXT;
@@ -237,12 +234,9 @@ BEGIN
237234
END;
238235
/
239236
NOTICE: Test 4.3 - Max buffer: [Max buffer works]
240-
-- Test 4.4: Buffer size above maximum (should fail)
237+
-- Test 4.4: Buffer size above 1000000 (now allowed, no max limit)
241238
CALL dbms_output.enable(1000001);
242-
ERROR: buffer size must be between 2000 and 1000000
243-
CONTEXT: SQL statement "SELECT sys.ora_dbms_output_enable(buffer_size)"
244-
PL/iSQL function enable line 3 at PERFORM
245-
-- Test 4.5: NULL buffer size uses maximum (1000000)
239+
-- Test 4.5: NULL buffer size uses unlimited (starts at 20000, grows dynamically)
246240
DECLARE
247241
line TEXT;
248242
status INTEGER;
@@ -279,6 +273,34 @@ BEGIN
279273
END;
280274
/
281275
NOTICE: Test 5.1 - Overflow at line 47: ORU-10027: buffer overflow, limit of 2000 bytes
276+
-- Test 5.2: User limit honored even after internal buffer expansion
277+
-- Small lines cause internal buffer to expand (2-byte overhead per line)
278+
-- but user-perceived content limit should still be enforced
279+
DECLARE
280+
v_count INTEGER := 0;
281+
BEGIN
282+
dbms_output.enable(2000); -- 2000 byte content limit
283+
-- Write 1-byte lines until overflow
284+
-- Internal: 1-byte lines need 3 bytes each (2 prefix + 1 data)
285+
-- Buffer will expand from 2000 to accommodate overhead
286+
-- But content limit (2000 bytes) should still be enforced
287+
FOR i IN 1..3000 LOOP
288+
BEGIN
289+
dbms_output.put_line('X');
290+
v_count := i;
291+
EXCEPTION WHEN OTHERS THEN
292+
EXIT;
293+
END;
294+
END LOOP;
295+
-- Should write exactly 2000 lines (2000 bytes content)
296+
IF v_count = 2000 THEN
297+
RAISE NOTICE 'Test 5.2 - User limit honored after expansion: PASSED (% lines)', v_count;
298+
ELSE
299+
RAISE NOTICE 'Test 5.2 - FAILED: expected 2000 lines, got %', v_count;
300+
END IF;
301+
END;
302+
/
303+
NOTICE: Test 5.2 - User limit honored after expansion: PASSED (2000 lines)
282304
-- =============================================================================
283305
-- Section 6: GET_LINE and GET_LINES behavior
284306
-- =============================================================================
@@ -666,6 +688,43 @@ END;
666688
$$;
667689
NOTICE: Test 11.2 - VARCHAR2[] GET_LINES: 2 lines
668690
-- =============================================================================
691+
-- Section 12: Buffer Space Recycling (Oracle recycles space after GET_LINE)
692+
-- =============================================================================
693+
-- Test 12.1: Buffer space should be recycled after GET_LINE
694+
-- Oracle frees buffer space when lines are read, allowing reuse
695+
DO $$
696+
DECLARE
697+
v_line TEXT;
698+
v_status INTEGER;
699+
v_chunk TEXT := RPAD('X', 50, 'X'); -- 50 byte line
700+
BEGIN
701+
dbms_output.enable(2000); -- 2000 byte buffer
702+
703+
-- Phase 1: Write 30 lines (approx 1500 bytes, within limit)
704+
FOR i IN 1..30 LOOP
705+
dbms_output.put_line(v_chunk);
706+
END LOOP;
707+
708+
-- Phase 2: Read 20 lines (should free ~1000 bytes)
709+
FOR i IN 1..20 LOOP
710+
dbms_output.get_line(v_line, v_status);
711+
END LOOP;
712+
713+
-- Phase 3: Write 20 more lines (~1000 bytes)
714+
-- With recycling: ~500 bytes unread + 1000 new = 1500 bytes (OK)
715+
-- Without recycling: 1500 + 1000 = 2500 bytes (overflow)
716+
BEGIN
717+
FOR i IN 1..20 LOOP
718+
dbms_output.put_line(v_chunk);
719+
END LOOP;
720+
RAISE NOTICE 'Test 12.1 - Buffer recycling: PASSED';
721+
EXCEPTION WHEN OTHERS THEN
722+
RAISE NOTICE 'Test 12.1 - Buffer recycling FAILED: %', SQLERRM;
723+
END;
724+
END;
725+
$$;
726+
NOTICE: Test 12.1 - Buffer recycling: PASSED
727+
-- =============================================================================
669728
-- Cleanup
670729
-- =============================================================================
671730
DROP PROCEDURE test_output_proc;

contrib/ivorysql_ora/sql/ora_dbms_output.sql

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ END;
208208
-- Section 4: Buffer size limits
209209
-- =============================================================================
210210

211-
-- Test 4.1: Buffer size below minimum (should fail)
211+
-- Test 4.1: Buffer size below minimum (silently clamped to 2000, Oracle behavior)
212212
CALL dbms_output.enable(1000);
213213

214214
-- Test 4.2: Buffer size at minimum (should succeed)
@@ -235,10 +235,10 @@ BEGIN
235235
END;
236236
/
237237

238-
-- Test 4.4: Buffer size above maximum (should fail)
238+
-- Test 4.4: Buffer size above 1000000 (now allowed, no max limit)
239239
CALL dbms_output.enable(1000001);
240240

241-
-- Test 4.5: NULL buffer size uses maximum (1000000)
241+
-- Test 4.5: NULL buffer size uses unlimited (starts at 20000, grows dynamically)
242242
DECLARE
243243
line TEXT;
244244
status INTEGER;
@@ -276,6 +276,34 @@ BEGIN
276276
END;
277277
/
278278

279+
-- Test 5.2: User limit honored even after internal buffer expansion
280+
-- Small lines cause internal buffer to expand (2-byte overhead per line)
281+
-- but user-perceived content limit should still be enforced
282+
DECLARE
283+
v_count INTEGER := 0;
284+
BEGIN
285+
dbms_output.enable(2000); -- 2000 byte content limit
286+
-- Write 1-byte lines until overflow
287+
-- Internal: 1-byte lines need 3 bytes each (2 prefix + 1 data)
288+
-- Buffer will expand from 2000 to accommodate overhead
289+
-- But content limit (2000 bytes) should still be enforced
290+
FOR i IN 1..3000 LOOP
291+
BEGIN
292+
dbms_output.put_line('X');
293+
v_count := i;
294+
EXCEPTION WHEN OTHERS THEN
295+
EXIT;
296+
END;
297+
END LOOP;
298+
-- Should write exactly 2000 lines (2000 bytes content)
299+
IF v_count = 2000 THEN
300+
RAISE NOTICE 'Test 5.2 - User limit honored after expansion: PASSED (% lines)', v_count;
301+
ELSE
302+
RAISE NOTICE 'Test 5.2 - FAILED: expected 2000 lines, got %', v_count;
303+
END IF;
304+
END;
305+
/
306+
279307
-- =============================================================================
280308
-- Section 6: GET_LINE and GET_LINES behavior
281309
-- =============================================================================
@@ -646,6 +674,44 @@ BEGIN
646674
END;
647675
$$;
648676

677+
-- =============================================================================
678+
-- Section 12: Buffer Space Recycling (Oracle recycles space after GET_LINE)
679+
-- =============================================================================
680+
681+
-- Test 12.1: Buffer space should be recycled after GET_LINE
682+
-- Oracle frees buffer space when lines are read, allowing reuse
683+
DO $$
684+
DECLARE
685+
v_line TEXT;
686+
v_status INTEGER;
687+
v_chunk TEXT := RPAD('X', 50, 'X'); -- 50 byte line
688+
BEGIN
689+
dbms_output.enable(2000); -- 2000 byte buffer
690+
691+
-- Phase 1: Write 30 lines (approx 1500 bytes, within limit)
692+
FOR i IN 1..30 LOOP
693+
dbms_output.put_line(v_chunk);
694+
END LOOP;
695+
696+
-- Phase 2: Read 20 lines (should free ~1000 bytes)
697+
FOR i IN 1..20 LOOP
698+
dbms_output.get_line(v_line, v_status);
699+
END LOOP;
700+
701+
-- Phase 3: Write 20 more lines (~1000 bytes)
702+
-- With recycling: ~500 bytes unread + 1000 new = 1500 bytes (OK)
703+
-- Without recycling: 1500 + 1000 = 2500 bytes (overflow)
704+
BEGIN
705+
FOR i IN 1..20 LOOP
706+
dbms_output.put_line(v_chunk);
707+
END LOOP;
708+
RAISE NOTICE 'Test 12.1 - Buffer recycling: PASSED';
709+
EXCEPTION WHEN OTHERS THEN
710+
RAISE NOTICE 'Test 12.1 - Buffer recycling FAILED: %', SQLERRM;
711+
END;
712+
END;
713+
$$;
714+
649715
-- =============================================================================
650716
-- Cleanup
651717
-- =============================================================================

0 commit comments

Comments
 (0)