ruby-oci8 2.2.3 → 2.2.12

Sign up to get free protection for your applications and to get access to all the features.
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