Skip to content

Commit 696a2ac

Browse files
committed
efficient piecewise LOB fetching
1 parent 5b4d2c8 commit 696a2ac

10 files changed

Lines changed: 227 additions & 313 deletions

lib/oci8/bindtype.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,18 @@ def self.create(con, val, param, max_array_size)
245245
# datatype type size prec scale
246246
# -------------------------------------------------
247247
# CLOB SQLT_CLOB 4000 0 0
248-
OCI8::BindType::Mapping[:clob] = OCI8::BindType::CLOB
249-
OCI8::BindType::Mapping[:nclob] = OCI8::BindType::NCLOB
248+
# NCLOB SQLT_CLOB 4000 0 0
249+
# Default: Fetch as String using SQLT_CHR (fast, max 2GB)
250+
# See: OCI8::lob_fetch_mode
251+
OCI8::BindType::Mapping[:clob] = OCI8::BindType::Long
252+
OCI8::BindType::Mapping[:nclob] = OCI8::BindType::Long
250253

251254
# datatype type size prec scale
252255
# -------------------------------------------------
253256
# BLOB SQLT_BLOB 4000 0 0
254-
OCI8::BindType::Mapping[:blob] = OCI8::BindType::BLOB
257+
# Default: Fetch as binary String using SQLT_CHR (fast, max 2GB)
258+
# See: OCI8::lob_fetch_mode
259+
OCI8::BindType::Mapping[:blob] = OCI8::BindType::LongRaw
255260

256261
# datatype type size prec scale
257262
# -------------------------------------------------

lib/oci8/oci8.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def initialize(*args)
152152
end
153153

154154
@prefetch_rows = 100
155+
@lob_prefetch_size = 0 # 0 means use Oracle default (disabled)
155156
@username = nil
156157
end
157158

@@ -333,6 +334,27 @@ def prefetch_rows=(num)
333334
@prefetch_rows = num
334335
end
335336

337+
# Sets the LOB prefetch size in bytes. Only used when lob_fetch_mode is :locator.
338+
# i.e. sets OCI_ATTR_DEFAULT_LOBPREFETCH_SIZE on the session handle.
339+
# The default value is 0 (disabled).
340+
#
341+
# When set to a non-zero value (e.g., 65536 for 64KB), Oracle prefetches
342+
# LOB data along with the row if the LOB size is <= this value.
343+
# This reduces network round trips when fetching small to medium LOBs.
344+
#
345+
# This is a session-wide setting that applies to all LOB columns fetched
346+
# from this connection.
347+
#
348+
# It has no effect when lob_fetch_mode is :long_as_string (the default).
349+
#
350+
# @param [Integer] size prefetch size in bytes (0 to disable)
351+
# @see OCI8::lob_fetch_mode=
352+
# @note Requires Oracle 11g or later
353+
def lob_prefetch_size=(size)
354+
@lob_prefetch_size = size
355+
@session_handle.send(:attr_set_ub4, 438, size)
356+
end
357+
336358
# @private
337359
def inspect
338360
"#<OCI8:#{username}>"
@@ -370,6 +392,48 @@ def self.client_charset_name
370392
@@client_charset_name
371393
end
372394

395+
# Returns the current LOB fetch mode.
396+
#
397+
# @return [Symbol] :long_as_string or :locator
398+
# @see lob_fetch_mode=
399+
def self.lob_fetch_mode
400+
@@lob_fetch_mode ||= :long_as_string
401+
end
402+
403+
# Sets the LOB fetch mode.
404+
#
405+
# - +:long_as_string+ (default): Fetch LOBs as Strings using
406+
# Runtime Data Allocation and Piecewise Operations in OCI.
407+
# Fastest and most efficient fetch but limited to 2GB LOBs.
408+
#
409+
# - +:locator+: Fetch LOB locators (OCI8::CLOB/BLOB objects).
410+
# Calls OCILobRead2() on the lob fields individually.
411+
# Required for LOBs > 2GB, random access, or read/write operations.
412+
# @conn.lob_prefetch_size may reduce network roundtrips but my
413+
# unscientific testing only showed performance degradation.
414+
#
415+
# @param [Symbol] mode :long_as_string or :locator
416+
# @return [Symbol] the mode that was set
417+
# @see https://github.com/oracle/odpi/issues/163
418+
def self.lob_fetch_mode=(mode)
419+
unless [:long_as_string, :locator].include?(mode)
420+
raise ArgumentError, "lob_fetch_mode must be :long_as_string or :locator"
421+
end
422+
423+
case mode
424+
when :long_as_string
425+
OCI8::BindType::Mapping[:clob] = OCI8::BindType::Long
426+
OCI8::BindType::Mapping[:nclob] = OCI8::BindType::Long
427+
OCI8::BindType::Mapping[:blob] = OCI8::BindType::LongRaw
428+
when :locator
429+
OCI8::BindType::Mapping[:clob] = OCI8::BindType::CLOB
430+
OCI8::BindType::Mapping[:nclob] = OCI8::BindType::NCLOB
431+
OCI8::BindType::Mapping[:blob] = OCI8::BindType::BLOB
432+
end
433+
434+
@@lob_fetch_mode = mode
435+
end
436+
373437
if OCI8.oracle_client_version >= OCI8::ORAVER_11_1
374438
# Returns send timeout in seconds.
375439
# Zero means no timeout.

test/config.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# GRANT EXECUTE ON dbms_lock TO ruby;
44
$dbuser = "ruby"
55
$dbpass = "oci8"
6-
$dbname = "127.0.0.1:1521/systempdb"
6+
$dbname = nil
77

88
# for test_bind_string_as_nchar in test_encoding.rb
99
ENV['ORA_NCHAR_LITERAL_REPLACE'] = 'TRUE' if OCI8.client_charset_name.include? 'UTF8'

test/test_clob.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ class TestCLob < Minitest::Test
66

77
def setup
88
@conn = get_oci8_connection
9+
# This test needs LOB locators for read/write operations
10+
OCI8.lob_fetch_mode = :locator
911
drop_table('test_table')
1012
@conn.exec('CREATE TABLE test_table (filename VARCHAR2(40), content CLOB)')
1113
end
1214

15+
def teardown
16+
# Restore default mode
17+
OCI8.lob_fetch_mode = :long_as_string if @conn
18+
end
19+
1320
def test_insert
1421
filename = File.basename($lobfile)
1522
@conn.exec("DELETE FROM test_table WHERE filename = :1", filename)

test/test_dbi_clob.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ class TestDbiCLob < Minitest::Test
66

77
def setup
88
@dbh = get_dbi_connection()
9+
# This test needs LOB locators for read/write operations
10+
OCI8.lob_fetch_mode = :locator
911
drop_table('test_table')
1012
@dbh.execute('CREATE TABLE test_table (filename VARCHAR2(40), content CLOB)')
1113
end
1214

15+
def teardown
16+
# Restore default mode
17+
OCI8.lob_fetch_mode = :long_as_string if @dbh
18+
end
19+
1320
def test_insert
1421
filename = File.basename($lobfile)
1522

0 commit comments

Comments
 (0)