ruby-oci8 2.2.3 → 2.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/ChangeLog +427 -0
  3. data/NEWS +335 -42
  4. data/README.md +20 -9
  5. data/dist-files +9 -3
  6. data/docs/bind-array-to-in_cond.md +2 -2
  7. data/docs/conflicts-local-connections-and-processes.md +7 -4
  8. data/docs/hanging-after-inactivity.md +63 -0
  9. data/docs/install-binary-package.md +15 -11
  10. data/docs/install-full-client.md +18 -21
  11. data/docs/install-instant-client.md +45 -27
  12. data/docs/install-on-osx.md +31 -120
  13. data/docs/ldap-auth-and-function-interposition.md +123 -0
  14. data/docs/number-type-mapping.md +79 -0
  15. data/docs/platform-specific-issues.md +17 -50
  16. data/docs/report-installation-issue.md +3 -0
  17. data/docs/timeout-parameters.md +3 -0
  18. data/ext/oci8/apiwrap.c.tmpl +2 -5
  19. data/ext/oci8/apiwrap.rb +6 -1
  20. data/ext/oci8/apiwrap.yml +34 -22
  21. data/ext/oci8/attr.c +4 -2
  22. data/ext/oci8/bind.c +366 -6
  23. data/ext/oci8/connection_pool.c +3 -3
  24. data/ext/oci8/encoding.c +5 -5
  25. data/ext/oci8/env.c +8 -2
  26. data/ext/oci8/error.c +24 -16
  27. data/ext/oci8/extconf.rb +8 -4
  28. data/ext/oci8/hook_funcs.c +274 -61
  29. data/ext/oci8/lob.c +31 -75
  30. data/ext/oci8/metadata.c +2 -2
  31. data/ext/oci8/object.c +72 -27
  32. data/ext/oci8/oci8.c +45 -132
  33. data/ext/oci8/oci8.h +32 -88
  34. data/ext/oci8/oci8lib.c +178 -38
  35. data/ext/oci8/ocihandle.c +37 -37
  36. data/ext/oci8/ocinumber.c +23 -18
  37. data/ext/oci8/oraconf.rb +158 -339
  38. data/ext/oci8/oradate.c +19 -19
  39. data/ext/oci8/plthook.h +10 -0
  40. data/ext/oci8/plthook_elf.c +433 -268
  41. data/ext/oci8/plthook_osx.c +40 -9
  42. data/ext/oci8/plthook_win32.c +9 -0
  43. data/ext/oci8/stmt.c +52 -17
  44. data/ext/oci8/win32.c +4 -22
  45. data/lib/oci8/bindtype.rb +1 -15
  46. data/lib/oci8/check_load_error.rb +57 -10
  47. data/lib/oci8/cursor.rb +57 -25
  48. data/lib/oci8/metadata.rb +9 -1
  49. data/lib/oci8/object.rb +10 -0
  50. data/lib/oci8/oci8.rb +33 -28
  51. data/lib/oci8/oracle_version.rb +11 -1
  52. data/lib/oci8/properties.rb +22 -0
  53. data/lib/oci8/version.rb +1 -1
  54. data/lib/oci8.rb +48 -4
  55. data/lib/ruby-oci8.rb +0 -3
  56. data/pre-distclean.rb +1 -3
  57. data/ruby-oci8.gemspec +3 -8
  58. data/setup.rb +11 -2
  59. data/test/README.md +37 -0
  60. data/test/config.rb +1 -1
  61. data/test/setup_test_object.sql +21 -13
  62. data/test/setup_test_package.sql +59 -0
  63. data/test/test_all.rb +2 -0
  64. data/test/test_bind_boolean.rb +99 -0
  65. data/test/test_bind_integer.rb +47 -0
  66. data/test/test_break.rb +11 -9
  67. data/test/test_clob.rb +4 -16
  68. data/test/test_connstr.rb +29 -13
  69. data/test/test_datetime.rb +8 -3
  70. data/test/test_object.rb +27 -9
  71. data/test/test_oci8.rb +170 -46
  72. data/test/test_oranumber.rb +12 -6
  73. data/test/test_package_type.rb +15 -3
  74. data/test/test_properties.rb +17 -0
  75. metadata +40 -54
  76. data/docs/osx-install-dev-tools.png +0 -0
  77. data/test/README +0 -42
@@ -0,0 +1,47 @@
1
+ require 'oci8'
2
+ require File.dirname(__FILE__) + '/config'
3
+
4
+ class TestBindInteger < Minitest::Test
5
+
6
+ def setup
7
+ @conn = get_oci8_connection
8
+ end
9
+
10
+ POSITIVE_INTS = [
11
+ 100,
12
+ 2**30,
13
+ 2**31,
14
+ 2**32,
15
+ 2**33,
16
+ 2**62,
17
+ 2**63,
18
+ 2**64,
19
+ ('9' * 38).to_i
20
+ ]
21
+
22
+ def bind_and_get(input_value, output_type)
23
+ cursor = @conn.parse("BEGIN :out := :in; END;")
24
+ cursor.bind_param(:out, output_type)
25
+ cursor.bind_param(:in, input_value)
26
+ cursor.exec
27
+ result = cursor[:out]
28
+ cursor.close
29
+ result
30
+ end
31
+
32
+ (POSITIVE_INTS + [0] + POSITIVE_INTS.map(&:-@)).each do |num|
33
+ define_method("test_bind_param_with_input_of '#{num}'") do
34
+ assert_equal(OraNumber.new(num.to_s), bind_and_get(num, OraNumber))
35
+ end
36
+
37
+ define_method("test_bind_param_with_output_of '#{num}'") do
38
+ result = bind_and_get(OraNumber.new(num.to_s), Integer)
39
+ assert_equal(num, result)
40
+ assert_kind_of(Integer, result)
41
+ end
42
+ end
43
+
44
+ def teardown
45
+ @conn.logoff
46
+ end
47
+ end
data/test/test_break.rb CHANGED
@@ -7,10 +7,12 @@ class TestBreak < Minitest::Test
7
7
 
8
8
  def setup
9
9
  @conn = get_oci8_connection
10
+ Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception)
10
11
  end
11
12
 
12
13
  def teardown
13
14
  @conn.logoff
15
+ Thread.report_on_exception = @original_report_on_exception if Thread.respond_to?(:report_on_exception)
14
16
  end
15
17
 
16
18
  def report(str)
@@ -20,9 +22,8 @@ class TestBreak < Minitest::Test
20
22
  @@server_is_runing_on_windows = nil
21
23
  def server_is_runing_on_windows?
22
24
  if @@server_is_runing_on_windows.nil?
23
- @@server_is_runing_on_windows = false
24
- @conn.exec('select banner from v$version') do |row|
25
- @@server_is_runing_on_windows = true if row[0].include? 'Windows'
25
+ @conn.exec('select dbms_utility.port_string from dual') do |row|
26
+ @@server_is_runing_on_windows = row[0].include? '/WIN'
26
27
  end
27
28
  end
28
29
  @@server_is_runing_on_windows
@@ -61,7 +62,7 @@ class TestBreak < Minitest::Test
61
62
  expect = []
62
63
  expect[PLSQL_DONE] = TIME_IN_PLSQL
63
64
  expect[OCIBREAK] = "Invalid status"
64
- if defined? Rubinius and Rubinius::VERSION >= "2.0"
65
+ if (defined? Rubinius and Rubinius::VERSION >= "2.0") || (defined? RUBY_ENGINE and RUBY_ENGINE == "truffleruby")
65
66
  # Rubinius 2.0.0.
66
67
  # DBMS_LOCK.SLEEP blocks ruby threads which try to call C-API.
67
68
  expect[SEND_BREAK] = TIME_TO_BREAK
@@ -98,6 +99,11 @@ class TestBreak < Minitest::Test
98
99
  def test_timeout
99
100
  @conn.non_blocking = true
100
101
  start_time = Time.now
102
+ if server_is_runing_on_windows?
103
+ end_time = start_time + 5
104
+ else
105
+ end_time = start_time + 1
106
+ end
101
107
 
102
108
  if defined? Rubinius and Rubinius::VERSION < "2.0"
103
109
  # Rubinius 1.2.4
@@ -111,12 +117,8 @@ class TestBreak < Minitest::Test
111
117
  @conn.exec("BEGIN DBMS_LOCK.SLEEP(5); END;")
112
118
  end
113
119
  end
114
- if server_is_runing_on_windows?
115
- end_time = start_time + 5
116
- else
117
- end_time = start_time + 1
118
- end
119
120
  assert_in_delta(Time.now, end_time, 1)
121
+ sleep(0.01) # for truffleruby. Is truffleruby too fast?
120
122
  @conn.exec("BEGIN NULL; END;")
121
123
  assert_in_delta(Time.now, end_time, 1)
122
124
  end
data/test/test_clob.rb CHANGED
@@ -24,22 +24,6 @@ class TestCLob < Minitest::Test
24
24
  lob.close
25
25
  end
26
26
 
27
- def test_insert_with_flush
28
- filename = File.basename($lobfile)
29
- @conn.exec("DELETE FROM test_table WHERE filename = :1", filename)
30
- @conn.exec("INSERT INTO test_table(filename, content) VALUES (:1, EMPTY_CLOB())", filename)
31
- cursor = @conn.exec("SELECT content FROM test_table WHERE filename = :1 FOR UPDATE", filename)
32
- lob = cursor.fetch[0]
33
- lob.sync = false
34
- open($lobfile) do |f|
35
- while s = f.read(1000)
36
- lob.write(s)
37
- end
38
- end
39
- lob.flush
40
- lob.close
41
- end
42
-
43
27
  def test_insert_symbol
44
28
  filename = 'test_symbol'
45
29
  value = :foo_bar
@@ -76,7 +60,11 @@ class TestCLob < Minitest::Test
76
60
 
77
61
  # https://github.com/kubo/ruby-oci8/issues/20
78
62
  def test_github_issue_20
63
+ # Skip this test if FULLTEST isn't set because it takes 4 minutes in my Linux box.
64
+ return if ENV['FULLTEST']
65
+
79
66
  lob1 = OCI8::CLOB.new(@conn, ' ' * (1024 * 1024))
67
+ lob1.read(1) # to suppress `warning: assigned but unused variable - lob1`
80
68
  begin
81
69
  lob2 = OCI8::CLOB.new(@conn, ' ' * (128 * 1024 * 1024))
82
70
  rescue OCIError
data/test/test_connstr.rb CHANGED
@@ -80,9 +80,9 @@ class TestConnStr < Minitest::Test
80
80
  DESC_TEST_CASE =
81
81
  [
82
82
  # Cannot distinguish net service names from easy connect strings in this case.
83
- ["sales-server", nil, nil, "sales-server"],
83
+ ["sales-server", nil, nil, false, "sales-server"],
84
84
  # Easy Connect string with host.
85
- ["//sales-server", nil, nil, <<EOS],
85
+ ["//sales-server", nil, nil, false, <<EOS],
86
86
  (DESCRIPTION=
87
87
  (CONNECT_DATA=
88
88
  (SERVICE_NAME=))
@@ -92,7 +92,7 @@ class TestConnStr < Minitest::Test
92
92
  (PORT=1521)))
93
93
  EOS
94
94
  # Easy Connect string with host and port.
95
- ["sales-server:3456", nil, nil, <<EOS],
95
+ ["sales-server:3456", nil, nil, false, <<EOS],
96
96
  (DESCRIPTION=
97
97
  (CONNECT_DATA=
98
98
  (SERVICE_NAME=))
@@ -102,7 +102,7 @@ EOS
102
102
  (PORT=3456)))
103
103
  EOS
104
104
  # The host name is sales-server and the service name is sales.
105
- ["sales-server/sales", nil, nil, <<EOS],
105
+ ["sales-server/sales", nil, nil, false, <<EOS],
106
106
  (DESCRIPTION=
107
107
  (CONNECT_DATA=
108
108
  (SERVICE_NAME=sales))
@@ -112,7 +112,7 @@ EOS
112
112
  (PORT=1521)))
113
113
  EOS
114
114
  # Easy Connect string with IPv6 address.
115
- ["[2001:0db8:0:0::200C:417A]:80/sales", nil, nil, <<EOS],
115
+ ["[2001:0db8:0:0::200C:417A]:80/sales", nil, nil, false, <<EOS],
116
116
  (DESCRIPTION=
117
117
  (CONNECT_DATA=
118
118
  (SERVICE_NAME=sales))
@@ -122,7 +122,7 @@ EOS
122
122
  (PORT=80)))
123
123
  EOS
124
124
  # Easy Connect string with IPv6 host address.
125
- ["sales-server:80/sales", nil, nil, <<EOS],
125
+ ["sales-server:80/sales", nil, nil, false, <<EOS],
126
126
  (DESCRIPTION=
127
127
  (CONNECT_DATA=
128
128
  (SERVICE_NAME=sales))
@@ -132,7 +132,7 @@ EOS
132
132
  (PORT=80)))
133
133
  EOS
134
134
  # Easy Connect string with host, service name, and server.
135
- ["sales-server/sales:dedicated/inst1", nil, nil, <<EOS],
135
+ ["sales-server/sales:dedicated/inst1", nil, nil, false, <<EOS],
136
136
  (DESCRIPTION=
137
137
  (CONNECT_DATA=
138
138
  (SERVICE_NAME=sales)
@@ -143,7 +143,7 @@ EOS
143
143
  (HOST=sales-server)
144
144
  (PORT=1521)))
145
145
  EOS
146
- ["sales-server//inst1", nil, nil, <<EOS],
146
+ ["sales-server//inst1", nil, nil, false, <<EOS],
147
147
  (DESCRIPTION=
148
148
  (CONNECT_DATA=
149
149
  (SERVICE_NAME=)
@@ -154,7 +154,7 @@ EOS
154
154
  (PORT=1521)))
155
155
  EOS
156
156
  #
157
- ["sales-server/sales", 20, nil, <<EOS],
157
+ ["sales-server/sales", 20, nil, false, <<EOS],
158
158
  (DESCRIPTION=
159
159
  (CONNECT_DATA=
160
160
  (SERVICE_NAME=sales))
@@ -165,7 +165,7 @@ EOS
165
165
  (TRANSPORT_CONNECT_TIMEOUT=20))
166
166
  EOS
167
167
  #
168
- ["sales-server/sales", nil, 30, <<EOS],
168
+ ["sales-server/sales", nil, 30, false, <<EOS],
169
169
  (DESCRIPTION=
170
170
  (CONNECT_DATA=
171
171
  (SERVICE_NAME=sales))
@@ -176,7 +176,7 @@ EOS
176
176
  (CONNECT_TIMEOUT=30))
177
177
  EOS
178
178
  #
179
- ["sales-server/sales", 20, 30, <<EOS],
179
+ ["sales-server/sales", 20, 30, false, <<EOS],
180
180
  (DESCRIPTION=
181
181
  (CONNECT_DATA=
182
182
  (SERVICE_NAME=sales))
@@ -186,6 +186,21 @@ EOS
186
186
  (PORT=1521))
187
187
  (TRANSPORT_CONNECT_TIMEOUT=20)
188
188
  (CONNECT_TIMEOUT=30))
189
+ EOS
190
+ ["sales-server/sales", 20, 30, true, <<EOS],
191
+ (DESCRIPTION=
192
+ (CONNECT_DATA=
193
+ (SERVICE_NAME=sales)
194
+ )
195
+ (ADDRESS=
196
+ (PROTOCOL=TCP)
197
+ (HOST=sales-server)
198
+ (PORT=1521)
199
+ )
200
+ (TRANSPORT_CONNECT_TIMEOUT=20)
201
+ (CONNECT_TIMEOUT=30)
202
+ (ENABLE=BROKEN)
203
+ )
189
204
  EOS
190
205
  ]
191
206
 
@@ -195,9 +210,10 @@ EOS
195
210
  easy_connect_string = test_case[0]
196
211
  tcp_connnect_timeout= test_case[1]
197
212
  outbound_connnect_timeout = test_case[2]
198
- expected_result = test_case[3].gsub(/\s/, '')
213
+ tcp_keepalive = test_case[3]
214
+ expected_result = test_case[4].gsub(/\s/, '')
199
215
  # use instance_eval to call a private method to_connect_descriptor
200
- result = obj.instance_eval { to_connect_descriptor(easy_connect_string, tcp_connnect_timeout, outbound_connnect_timeout) }
216
+ result = obj.instance_eval { to_connect_descriptor(easy_connect_string, tcp_connnect_timeout, outbound_connnect_timeout, tcp_keepalive) }
201
217
  assert_equal(expected_result, result, easy_connect_string)
202
218
  end
203
219
  end
@@ -1,6 +1,5 @@
1
1
  require 'oci8'
2
2
  require File.dirname(__FILE__) + '/config'
3
- require 'scanf'
4
3
 
5
4
  class TestDateTime < Minitest::Test
6
5
 
@@ -28,6 +27,12 @@ class TestDateTime < Minitest::Test
28
27
  convert_to_datetime($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, subsec, $8)
29
28
  end
30
29
 
30
+ # 'YYYY-MM-DD HH24:MI:SS' or 'YYYY-MM-DD HH24:MI:SS.FF6' to array
31
+ def string_to_array(str)
32
+ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)(?:\.(\d+))?/ =~ str
33
+ [$1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, $7 ? $7.to_i / 1000 : 0]
34
+ end
35
+
31
36
  def setup
32
37
  @conn = get_oci8_connection
33
38
  end
@@ -43,7 +48,7 @@ class TestDateTime < Minitest::Test
43
48
  @conn.exec(<<-EOS) do |row|
44
49
  SELECT TO_DATE('#{date}', 'YYYY-MM-DD HH24:MI:SS') FROM dual
45
50
  EOS
46
- assert_equal(Time.local(*date.scanf("%d-%d-%d %d:%d:%d.%06d")), row[0])
51
+ assert_equal(Time.local(*string_to_array(date)), row[0])
47
52
  end
48
53
  end
49
54
  end
@@ -94,7 +99,7 @@ EOS
94
99
  @conn.exec(<<-EOS) do |row|
95
100
  SELECT TO_TIMESTAMP('#{date}', 'YYYY-MM-DD HH24:MI:SS.FF') FROM dual
96
101
  EOS
97
- assert_equal(Time.local(*date.scanf("%d-%d-%d %d:%d:%d.%06d")), row[0])
102
+ assert_equal(Time.local(*string_to_array(date)), row[0])
98
103
  end
99
104
  end
100
105
  end
data/test/test_object.rb CHANGED
@@ -22,7 +22,7 @@ begin
22
22
 
23
23
  begin
24
24
  version = RbTestObj.test_object_version(conn)
25
- error_message = "Invalid test object version" if version != 3
25
+ error_message = "Invalid test object version" if version != 4
26
26
  rescue NoMethodError
27
27
  raise unless $!.to_s.include?('test_object_version')
28
28
  error_message = "rb_test_obj.test_object_version is not declared."
@@ -100,7 +100,8 @@ class TestObj1 < Minitest::Test
100
100
  attr_reader :obj_array_val
101
101
  attr_reader :obj_ary_of_ary_val
102
102
  attr_reader :date_val
103
- # attr_reader :date_array_val
103
+ attr_reader :timestamp_val
104
+ attr_reader :timestamp_tz_val
104
105
 
105
106
  attr_accessor :assertions
106
107
 
@@ -109,16 +110,28 @@ class TestObj1 < Minitest::Test
109
110
  @assertions = 0
110
111
  end
111
112
 
112
- def to_test_date(n)
113
+ def to_test_datetime(n, type)
113
114
  year = (1990 + n).round
114
115
  month = (n.round * 5) % 12 + 1
115
116
  mday = (n.round * 7) % 27 + 1
116
117
  hour = (n.round * 9) % 24
117
118
  minute = (n.round * 11) % 60
118
119
  sec = (n.round * 13) % 60
119
- convert_to_time(year, month, mday, hour, minute, sec, 0, nil)
120
+ nsec = if type == :date
121
+ 0
122
+ else
123
+ ((n.round * 333_333_333) % 1_000_000_000).to_r / 1_000_000_000
124
+ end
125
+ tz = if type == :timestamp_tz
126
+ tzh = (n.round * 15) % 24 - 11
127
+ tzm = (n.round * 17) % 60
128
+ format('%+03d:%02d', tzh, tzm)
129
+ else
130
+ nil
131
+ end
132
+ convert_to_time(year, month, mday, hour, minute, sec, nsec, tz)
120
133
  end
121
- private :to_test_date
134
+ private :to_test_datetime
122
135
 
123
136
  def next
124
137
  @n += 1.2
@@ -135,7 +148,9 @@ class TestObj1 < Minitest::Test
135
148
  @nclob_val = @str_val
136
149
  @blob_val = @str_val
137
150
  @obj_val = ExpectedValObjElem.new(@int_val, @int_val + 1)
138
- @date_val = to_test_date(@n)
151
+ @date_val = to_test_datetime(@n, :date)
152
+ @timestamp_val = to_test_datetime(@n, :timestamp)
153
+ @timestamp_tz_val = to_test_datetime(@n, :timestamp_tz)
139
154
  if @int_val == 1
140
155
  @int_array_val = nil
141
156
  @flt_array_val = nil
@@ -199,7 +214,8 @@ class TestObj1 < Minitest::Test
199
214
  obj_array_val = val[18]
200
215
  obj_ary_of_ary_val = val[19]
201
216
  date_val = val[20]
202
- # date_array_val = val[18]
217
+ timestamp_val = val[21]
218
+ timestamp_tz_val = val[22]
203
219
  else
204
220
  assert_instance_of(RbTestObj, val)
205
221
  int_val = val.int_val
@@ -223,7 +239,8 @@ class TestObj1 < Minitest::Test
223
239
  obj_array_val = val.obj_array_val
224
240
  obj_ary_of_ary_val = val.obj_ary_of_ary_val
225
241
  date_val = val.date_val
226
- # date_array_val = val.date_array_val
242
+ timestamp_val = val.timestamp_val
243
+ timestamp_tz_val = val.timestamp_tz_val
227
244
  end
228
245
 
229
246
  assert_equal(@int_val, int_val)
@@ -267,7 +284,8 @@ class TestObj1 < Minitest::Test
267
284
  assert_nil(obj_ary_of_ary_val)
268
285
  end
269
286
  assert_equal(@date_val, date_val)
270
- # assert_equal(@date_array_val, date_array_val && date_array_val.to_ary)
287
+ assert_equal(@timestamp_val, timestamp_val)
288
+ assert_equal(@timestamp_tz_val, timestamp_tz_val)
271
289
  end
272
290
 
273
291
  def assert_array_in_delta(exp, val)
data/test/test_oci8.rb CHANGED
@@ -25,54 +25,142 @@ EOS
25
25
  drop_table('test_rename_table')
26
26
  end
27
27
 
28
- # USE_DYNAMIC_FETCH doesn't work well...
29
- # This test is disabled.
30
- def _test_long_type
31
- drop_table('test_table')
32
- @conn.exec('CREATE TABLE test_table (id number(38), lng long)')
33
- test_data1 = 'a' * 70000
34
- test_data2 = 'b' * 3000
35
- test_data3 = nil
36
- test_data4 = 'c' * 70000
37
- @conn.exec('insert into test_table values (:1, :2)', 1, test_data1)
38
- @conn.exec('insert into test_table values (:1, :2)', 2, [test_data2, :long])
39
- @conn.exec('insert into test_table values (:1, :2)', 3, [nil, :long])
40
- @conn.exec('insert into test_table values (:1, :2)', 4, [test_data4, :long])
41
-
42
- [8000, 65535, 65536, 80000].each do |read_len|
43
- @conn.long_read_len = read_len
44
- cursor = @conn.parse('SELECT lng from test_table order by id')
45
- cursor.exec
46
- assert_equal(test_data1, cursor.fetch[0])
47
- assert_equal(test_data2, cursor.fetch[0])
48
- assert_equal(test_data3, cursor.fetch[0])
49
- assert_equal(test_data4, cursor.fetch[0])
50
- cursor.close
28
+ # Set `OCI8::BindType::Base.initial_chunk_size = 5` to
29
+ # use the following test data.
30
+ LONG_TEST_DATA = [
31
+ # initial chunk size: 5 (total buffer size: 5)
32
+ 'a' * 4, 'b' * 5, 'c' * 6, 'd' * 5, 'e' * 4,
33
+ # second chunk size: 10 (total buffer size: 15)
34
+ 'f' * 14, 'g' * 15, 'h' * 16, 'i' * 15, 'j' * 14,
35
+ # third chunk size: 20 (total buffer size: 35)
36
+ 'k' * 34, 'l' * 35, 'm' * 36, 'n' * 35, 'o' * 34,
37
+ # use data around initial chunk size again
38
+ 'p' * 4, 'q' * 5, 'r' * 6, 's' * 5, 't' * 4,
39
+ # special data
40
+ '', nil,
41
+ ]
42
+
43
+ def test_long_type
44
+ clob_bind_type = OCI8::BindType::Mapping[:clob]
45
+ blob_bind_type = OCI8::BindType::Mapping[:blob]
46
+ initial_cunk_size = OCI8::BindType::Base.initial_chunk_size
47
+ begin
48
+ OCI8::BindType::Base.initial_chunk_size = 5
49
+ @conn.prefetch_rows = LONG_TEST_DATA.size / 3
50
+ drop_table('test_table')
51
+ ascii_enc = Encoding.find('US-ASCII')
52
+ 0.upto(1) do |i|
53
+ if i == 0
54
+ @conn.exec("CREATE TABLE test_table (id number(38), long_column long, clob_column clob)")
55
+ cursor = @conn.parse('insert into test_table values (:1, :2, :3)')
56
+ cursor.bind_param(1, nil, Integer)
57
+ cursor.bind_param(2, nil, :long)
58
+ cursor.bind_param(3, nil, :clob)
59
+ lob = OCI8::CLOB.new(@conn, '')
60
+ enc = Encoding.default_internal || OCI8.encoding
61
+ else
62
+ @conn.exec("CREATE TABLE test_table (id number(38), long_raw_column long raw, blob_column blob)")
63
+ cursor = @conn.parse('insert into test_table values (:1, :2, :3)')
64
+ cursor.bind_param(1, nil, Integer)
65
+ cursor.bind_param(2, nil, :long_raw)
66
+ cursor.bind_param(3, nil, :blob)
67
+ lob = OCI8::BLOB.new(@conn, '')
68
+ enc = Encoding.find('ASCII-8BIT')
69
+ end
70
+
71
+ LONG_TEST_DATA.each_with_index do |data, index|
72
+ cursor[1] = index
73
+ cursor[2] = data
74
+ if data.nil?
75
+ cursor[3] = nil
76
+ else
77
+ lob.rewind
78
+ lob.write(data)
79
+ lob.size = data.size
80
+ cursor[3] = lob
81
+ end
82
+ cursor.exec
83
+ end
84
+ cursor.close
85
+
86
+ cursor = @conn.parse('SELECT * from test_table order by id')
87
+ cursor.exec
88
+ LONG_TEST_DATA.each_with_index do |data, index|
89
+ row = cursor.fetch
90
+ assert_equal(index, row[0])
91
+ if data.nil?
92
+ assert_nil(row[1])
93
+ assert_nil(row[2])
94
+ elsif data.empty?
95
+ # '' is inserted to the long or long raw column as null.
96
+ assert_nil(row[1])
97
+ # '' is inserted to the clob or blob column as an empty clob.
98
+ # It is fetched as '' when the data is read using a LOB locator.
99
+ assert_equal(data, clob_data = row[2].read)
100
+ assert_equal(ascii_enc, clob_data.encoding)
101
+ else
102
+ assert_equal(data, row[1])
103
+ assert_equal(data, clob_data = row[2].read)
104
+ assert_equal(enc, row[1].encoding)
105
+ assert_equal(enc, clob_data.encoding)
106
+ end
107
+ end
108
+ assert_nil(cursor.fetch)
109
+ cursor.close
110
+
111
+ begin
112
+ OCI8::BindType::Mapping[:clob] = OCI8::BindType::Long
113
+ OCI8::BindType::Mapping[:blob] = OCI8::BindType::LongRaw
114
+ cursor = @conn.parse('SELECT * from test_table order by id')
115
+ cursor.exec
116
+ LONG_TEST_DATA.each_with_index do |data, index|
117
+ row = cursor.fetch
118
+ assert_equal(index, row[0])
119
+ if data.nil?
120
+ assert_nil(row[1])
121
+ assert_nil(row[2])
122
+ elsif data.empty?
123
+ # '' is inserted to the long or long raw column as null.
124
+ assert_nil(row[1])
125
+ # '' is inserted to the clob or blob column as an empty clob.
126
+ # However it is fetched as nil.
127
+ assert_nil(row[2])
128
+ else
129
+ assert_equal(data, row[1])
130
+ assert_equal(data, row[2])
131
+ assert_equal(enc, row[1].encoding)
132
+ assert_equal(enc, row[2].encoding)
133
+ end
134
+ end
135
+ assert_nil(cursor.fetch)
136
+ cursor.close
137
+ ensure
138
+ OCI8::BindType::Mapping[:clob] = clob_bind_type
139
+ OCI8::BindType::Mapping[:blob] = blob_bind_type
140
+ end
141
+ drop_table('test_table')
142
+ end
143
+ ensure
144
+ OCI8::BindType::Base.initial_chunk_size = initial_cunk_size
51
145
  end
52
146
  drop_table('test_table')
53
147
  end
54
148
 
55
- def test_long_type
56
- @conn.long_read_len = 80000
57
- drop_table('test_table')
58
- @conn.exec('CREATE TABLE test_table (id number(38), lng long)')
59
- test_data1 = 'a' * 70000
60
- test_data2 = 'b' * 3000
61
- test_data3 = nil
62
- test_data4 = 'c' * 70000
63
- @conn.exec('insert into test_table values (:1, :2)', 1, test_data1)
64
- @conn.exec('insert into test_table values (:1, :2)', 2, [test_data2, :long])
65
- @conn.exec('insert into test_table values (:1, :2)', 3, [nil, :long])
66
- @conn.exec('insert into test_table values (:1, :2)', 4, [test_data4, :long])
67
-
68
- cursor = @conn.parse('SELECT lng from test_table order by id')
69
- cursor.exec
70
- assert_equal(test_data1, cursor.fetch[0])
71
- assert_equal(test_data2, cursor.fetch[0])
72
- assert_nil(cursor.fetch[0])
73
- assert_equal(test_data4, cursor.fetch[0])
74
- cursor.close
75
- drop_table('test_table')
149
+ def test_bind_long_data
150
+ initial_cunk_size = OCI8::BindType::Base.initial_chunk_size
151
+ begin
152
+ OCI8::BindType::Base.initial_chunk_size = 5
153
+ cursor = @conn.parse("begin :1 := '<' || :2 || '>'; end;")
154
+ cursor.bind_param(1, nil, :long)
155
+ cursor.bind_param(2, nil, :long)
156
+ (LONG_TEST_DATA + ['z' * 4000]).each do |data|
157
+ cursor[2] = data
158
+ cursor.exec
159
+ assert_equal("<#{data}>", cursor[1])
160
+ end
161
+ ensure
162
+ OCI8::BindType::Base.initial_chunk_size = initial_cunk_size
163
+ end
76
164
  end
77
165
 
78
166
  def test_select
@@ -216,8 +304,8 @@ EOS
216
304
  assert_nil(rv[4])
217
305
  assert_nil(rv[5])
218
306
  else
219
- dttm = DateTime.civil(2000 + i, 8, 3, 23, 59, 59, Time.now.utc_offset.to_r/86400)
220
307
  tm = Time.local(2000 + i, 8, 3, 23, 59, 59)
308
+ dttm = DateTime.civil(2000 + i, 8, 3, 23, 59, 59, tm.utc_offset.to_r/86400)
221
309
  dt = Date.civil(2000 + i, 8, 3)
222
310
  assert_equal(tm, rv[3])
223
311
  assert_equal(dttm, rv[4])
@@ -451,6 +539,7 @@ EOS
451
539
  assert_nil(@conn.last_error)
452
540
  @conn.last_error = 'dummy'
453
541
  cursor = @conn.parse('select col1, max(col2) from (select 1 as col1, null as col2 from dual) group by col1')
542
+ cursor.prefetch_rows = 1
454
543
  assert_nil(@conn.last_error)
455
544
 
456
545
  # When an OCI function returns OCI_SUCCESS_WITH_INFO, OCI8#last_error is set.
@@ -492,9 +581,44 @@ EOS
492
581
  else
493
582
  raise "Unknown column size #{column_size}"
494
583
  end
495
- driver_name = cursor.fetch[0]
584
+ driver_name = cursor.fetch[0].strip
496
585
  cursor.close
497
586
  assert_equal(expected_value, driver_name)
498
587
  end
499
588
  end
589
+
590
+ def test_server_version
591
+ cursor = @conn.exec("select * from product_component_version where product like 'Oracle Database %'")
592
+ row = cursor.fetch_hash
593
+ cursor.close
594
+ ver = if OCI8::oracle_client_version >= OCI8::ORAVER_18
595
+ row['VERSION_FULL'] || row['VERSION']
596
+ else
597
+ # OCI8#oracle_server_version could not get infomation corresponding
598
+ # to VERSION_FULL when the Oracle client version is below 18.1.
599
+ row['VERSION']
600
+ end
601
+ assert_equal(ver, @conn.oracle_server_version.to_s)
602
+ end
603
+
604
+ def test_array_fetch
605
+ drop_table('test_table')
606
+ @conn.exec("CREATE TABLE test_table (id number, val clob)")
607
+ cursor = @conn.parse("INSERT INTO test_table VALUES (:1, :2)")
608
+ 1.upto(10) do |i|
609
+ cursor.exec(i, ('a'.ord + i).chr * i)
610
+ end
611
+ cursor.close
612
+ cursor = @conn.parse("select * from test_table where id <= :1 order by id")
613
+ cursor.prefetch_rows = 4
614
+ [1, 6, 2, 7, 3, 8, 4, 9, 5, 10].each do |i|
615
+ cursor.exec(i)
616
+ 1.upto(i) do |j|
617
+ row = cursor.fetch
618
+ assert_equal(j, row[0])
619
+ assert_equal(('a'.ord + j).chr * j, row[1].read)
620
+ end
621
+ assert_nil(cursor.fetch)
622
+ end
623
+ end
500
624
  end # TestOCI8