activerecord-oracle_enhanced-adapter-with-schema 0.0.1
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.
- data/.rspec +2 -0
- data/Gemfile +52 -0
- data/History.md +301 -0
- data/License.txt +20 -0
- data/README.md +123 -0
- data/RUNNING_TESTS.md +45 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1399 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +46 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
- data/lib/activerecord-oracle_enhanced-adapter-with-schema.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1388 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +141 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +378 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +440 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
- data/spec/spec_helper.rb +189 -0
- metadata +260 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require "bigdecimal"
|
2
|
+
|
3
|
+
unless BigDecimal.method_defined?(:to_d)
|
4
|
+
BigDecimal.class_eval do
|
5
|
+
def to_d #:nodoc:
|
6
|
+
self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
unless Bignum.method_defined?(:to_d)
|
12
|
+
Bignum.class_eval do
|
13
|
+
def to_d #:nodoc:
|
14
|
+
BigDecimal.new(self.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
unless Fixnum.method_defined?(:to_d)
|
20
|
+
Fixnum.class_eval do
|
21
|
+
def to_d #:nodoc:
|
22
|
+
BigDecimal.new(self.to_s)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveRecord #:nodoc:
|
2
|
+
module ConnectionAdapters #:nodoc:
|
3
|
+
module OracleEnhancedCpk #:nodoc:
|
4
|
+
|
5
|
+
# This mightn't be in Core, but count(distinct x,y) doesn't work for me.
|
6
|
+
# Return that not supported if composite_primary_keys gem is required.
|
7
|
+
def supports_count_distinct? #:nodoc:
|
8
|
+
@supports_count_distinct ||= ! defined?(CompositePrimaryKeys)
|
9
|
+
end
|
10
|
+
|
11
|
+
def concat(*columns) #:nodoc:
|
12
|
+
"(#{columns.join('||')})"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
|
20
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedCpk
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveRecord #:nodoc:
|
2
|
+
module ConnectionAdapters #:nodoc:
|
3
|
+
module OracleEnhancedDirty #:nodoc:
|
4
|
+
|
5
|
+
module InstanceMethods #:nodoc:
|
6
|
+
private
|
7
|
+
|
8
|
+
def _field_changed?(attr, old, value)
|
9
|
+
if column = column_for_attribute(attr)
|
10
|
+
# Added also :decimal type
|
11
|
+
if (column.type == :integer || column.type == :decimal) && column.null && (old.nil? || old == 0) && value.blank?
|
12
|
+
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
|
13
|
+
# Hence we don't record it as a change if the value changes from nil to ''.
|
14
|
+
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
15
|
+
# be typecast back to 0 (''.to_i => 0)
|
16
|
+
value = nil
|
17
|
+
# Oracle stores empty string '' as NULL
|
18
|
+
# therefore need to convert empty string value to nil if old value is nil
|
19
|
+
elsif column.type == :string && column.null && old.nil?
|
20
|
+
value = nil if value == ''
|
21
|
+
elsif old == 0 && value.is_a?(String) && value.present? && value != '0'
|
22
|
+
value = nil
|
23
|
+
else
|
24
|
+
value = column.type_cast(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
old != value
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if ActiveRecord::Base.method_defined?(:changed?)
|
38
|
+
ActiveRecord::Base.class_eval do
|
39
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedDirty::InstanceMethods
|
40
|
+
# Starting with rails 3.2.9 the method #field_changed?
|
41
|
+
# was renamed to #_field_changed?
|
42
|
+
if private_method_defined?(:field_changed?)
|
43
|
+
alias_method :field_changed?, :_field_changed?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,565 @@
|
|
1
|
+
begin
|
2
|
+
require "java"
|
3
|
+
require "jruby"
|
4
|
+
|
5
|
+
# ojdbc6.jar or ojdbc5.jar file should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path
|
6
|
+
|
7
|
+
java_version = java.lang.System.getProperty("java.version")
|
8
|
+
ojdbc_jar = if java_version =~ /^1.5/
|
9
|
+
"ojdbc5.jar"
|
10
|
+
elsif java_version >= '1.6'
|
11
|
+
"ojdbc6.jar"
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
unless ojdbc_jar.nil? || ENV_JAVA['java.class.path'] =~ Regexp.new(ojdbc_jar)
|
17
|
+
# On Unix environment variable should be PATH, on Windows it is sometimes Path
|
18
|
+
env_path = (ENV["PATH"] || ENV["Path"] || '').split(File::PATH_SEPARATOR)
|
19
|
+
# Look for JDBC driver at first in lib subdirectory (application specific JDBC file version)
|
20
|
+
# then in Ruby load path and finally in environment PATH
|
21
|
+
if ojdbc_jar_path = ['./lib'].concat($LOAD_PATH).concat(env_path).find{|d| File.exists?(File.join(d,ojdbc_jar))}
|
22
|
+
require File.join(ojdbc_jar_path,ojdbc_jar)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
ORACLE_DRIVER = Java::oracle.jdbc.OracleDriver.new
|
27
|
+
java.sql.DriverManager.registerDriver ORACLE_DRIVER
|
28
|
+
|
29
|
+
# set tns_admin property from TNS_ADMIN environment variable
|
30
|
+
if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
|
31
|
+
java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"])
|
32
|
+
end
|
33
|
+
|
34
|
+
rescue LoadError, NameError
|
35
|
+
# JDBC driver is unavailable.
|
36
|
+
raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. Please install #{ojdbc_jar || "Oracle JDBC"} library."
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
module ActiveRecord
|
41
|
+
module ConnectionAdapters
|
42
|
+
|
43
|
+
# JDBC database interface for JRuby
|
44
|
+
class OracleEnhancedJDBCConnection < OracleEnhancedConnection #:nodoc:
|
45
|
+
|
46
|
+
attr_accessor :active
|
47
|
+
alias :active? :active
|
48
|
+
|
49
|
+
attr_accessor :auto_retry
|
50
|
+
alias :auto_retry? :auto_retry
|
51
|
+
@auto_retry = false
|
52
|
+
|
53
|
+
def initialize(config)
|
54
|
+
@active = true
|
55
|
+
@config = config
|
56
|
+
new_connection(@config)
|
57
|
+
end
|
58
|
+
|
59
|
+
# modified method to support JNDI connections
|
60
|
+
def new_connection(config)
|
61
|
+
username = nil
|
62
|
+
|
63
|
+
if config[:jndi]
|
64
|
+
jndi = config[:jndi].to_s
|
65
|
+
ctx = javax.naming.InitialContext.new
|
66
|
+
ds = nil
|
67
|
+
|
68
|
+
# tomcat needs first lookup method, oc4j (and maybe other application servers) need second method
|
69
|
+
begin
|
70
|
+
env = ctx.lookup('java:/comp/env')
|
71
|
+
ds = env.lookup(jndi)
|
72
|
+
rescue
|
73
|
+
ds = ctx.lookup(jndi)
|
74
|
+
end
|
75
|
+
|
76
|
+
# check if datasource supports pooled connections, otherwise use default
|
77
|
+
if ds.respond_to?(:pooled_connection)
|
78
|
+
@raw_connection = ds.pooled_connection
|
79
|
+
else
|
80
|
+
@raw_connection = ds.connection
|
81
|
+
end
|
82
|
+
|
83
|
+
# get Oracle JDBC connection when using DBCP in Tomcat or jBoss
|
84
|
+
if @raw_connection.respond_to?(:getInnermostDelegate)
|
85
|
+
@pooled_connection = @raw_connection
|
86
|
+
@raw_connection = @raw_connection.innermost_delegate
|
87
|
+
elsif @raw_connection.respond_to?(:getUnderlyingConnection)
|
88
|
+
@pooled_connection = @raw_connection
|
89
|
+
@raw_connection = @raw_connection.underlying_connection
|
90
|
+
end
|
91
|
+
|
92
|
+
config[:driver] ||= @raw_connection.meta_data.connection.java_class.name
|
93
|
+
username = @raw_connection.meta_data.user_name
|
94
|
+
else
|
95
|
+
# to_s needed if username, password or database is specified as number in database.yml file
|
96
|
+
username = config[:username] && config[:username].to_s
|
97
|
+
password = config[:password] && config[:password].to_s
|
98
|
+
database = config[:database] && config[:database].to_s || 'XE'
|
99
|
+
host, port = config[:host], config[:port]
|
100
|
+
privilege = config[:privilege] && config[:privilege].to_s
|
101
|
+
|
102
|
+
# connection using TNS alias
|
103
|
+
if database && !host && !config[:url] && ENV['TNS_ADMIN']
|
104
|
+
url = "jdbc:oracle:thin:@#{database}"
|
105
|
+
else
|
106
|
+
unless database.match(/^(\:|\/)/)
|
107
|
+
# assume database is a SID if no colon or slash are supplied (backward-compatibility)
|
108
|
+
database = ":#{database}"
|
109
|
+
end
|
110
|
+
url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}#{database}"
|
111
|
+
end
|
112
|
+
|
113
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
114
|
+
# get session time_zone from configuration or from TZ environment variable
|
115
|
+
time_zone = config[:time_zone] || ENV['TZ'] || java.util.TimeZone.default.getID
|
116
|
+
|
117
|
+
properties = java.util.Properties.new
|
118
|
+
properties.put("user", username)
|
119
|
+
properties.put("password", password)
|
120
|
+
properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
|
121
|
+
properties.put("internal_logon", privilege) if privilege
|
122
|
+
|
123
|
+
begin
|
124
|
+
@raw_connection = java.sql.DriverManager.getConnection(url, properties)
|
125
|
+
rescue
|
126
|
+
# bypass DriverManager to work in cases where ojdbc*.jar
|
127
|
+
# is added to the load path at runtime and not on the
|
128
|
+
# system classpath
|
129
|
+
@raw_connection = ORACLE_DRIVER.connect(url, properties)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Set session time zone to current time zone
|
133
|
+
@raw_connection.setSessionTimeZone(time_zone)
|
134
|
+
|
135
|
+
# Set default number of rows to prefetch
|
136
|
+
# @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
|
137
|
+
end
|
138
|
+
|
139
|
+
cursor_sharing = config[:cursor_sharing] || 'force'
|
140
|
+
exec "alter session set cursor_sharing = #{cursor_sharing}"
|
141
|
+
|
142
|
+
# Initialize NLS parameters
|
143
|
+
OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS.each do |key, default_value|
|
144
|
+
value = config[key] || ENV[key.to_s.upcase] || default_value
|
145
|
+
if value
|
146
|
+
exec "alter session set #{key} = '#{value}'"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
self.autocommit = true
|
151
|
+
|
152
|
+
# default schema owner
|
153
|
+
@owner = username.upcase unless username.nil?
|
154
|
+
|
155
|
+
@raw_connection
|
156
|
+
end
|
157
|
+
|
158
|
+
def logoff
|
159
|
+
@active = false
|
160
|
+
if defined?(@pooled_connection)
|
161
|
+
@pooled_connection.close
|
162
|
+
else
|
163
|
+
@raw_connection.close
|
164
|
+
end
|
165
|
+
true
|
166
|
+
rescue
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
def commit
|
171
|
+
@raw_connection.commit
|
172
|
+
end
|
173
|
+
|
174
|
+
def rollback
|
175
|
+
@raw_connection.rollback
|
176
|
+
end
|
177
|
+
|
178
|
+
def autocommit?
|
179
|
+
@raw_connection.getAutoCommit
|
180
|
+
end
|
181
|
+
|
182
|
+
def autocommit=(value)
|
183
|
+
@raw_connection.setAutoCommit(value)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Checks connection, returns true if active. Note that ping actively
|
187
|
+
# checks the connection, while #active? simply returns the last
|
188
|
+
# known state.
|
189
|
+
def ping
|
190
|
+
exec_no_retry("select 1 from dual")
|
191
|
+
@active = true
|
192
|
+
rescue NativeException => e
|
193
|
+
@active = false
|
194
|
+
if e.message =~ /^java\.sql\.SQL(Recoverable)?Exception/
|
195
|
+
raise OracleEnhancedConnectionException, e.message
|
196
|
+
else
|
197
|
+
raise
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Resets connection, by logging off and creating a new connection.
|
202
|
+
def reset!
|
203
|
+
logoff rescue nil
|
204
|
+
begin
|
205
|
+
new_connection(@config)
|
206
|
+
@active = true
|
207
|
+
rescue NativeException => e
|
208
|
+
@active = false
|
209
|
+
if e.message =~ /^java\.sql\.SQL(Recoverable)?Exception/
|
210
|
+
raise OracleEnhancedConnectionException, e.message
|
211
|
+
else
|
212
|
+
raise
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# mark connection as dead if connection lost
|
218
|
+
def with_retry(&block)
|
219
|
+
should_retry = auto_retry? && autocommit?
|
220
|
+
begin
|
221
|
+
yield if block_given?
|
222
|
+
rescue NativeException => e
|
223
|
+
raise unless e.message =~ /^java\.sql\.SQL(Recoverable)?Exception: (Closed Connection|Io exception:|No more data to read from socket|IO Error:)/
|
224
|
+
@active = false
|
225
|
+
raise unless should_retry
|
226
|
+
should_retry = false
|
227
|
+
reset! rescue nil
|
228
|
+
retry
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def exec(sql)
|
233
|
+
with_retry do
|
234
|
+
exec_no_retry(sql)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def exec_no_retry(sql)
|
239
|
+
case sql
|
240
|
+
when /\A\s*(UPDATE|INSERT|DELETE)/i
|
241
|
+
s = @raw_connection.prepareStatement(sql)
|
242
|
+
s.executeUpdate
|
243
|
+
# it is safer for CREATE and DROP statements not to use PreparedStatement
|
244
|
+
# as it does not allow creation of triggers with :NEW in their definition
|
245
|
+
when /\A\s*(CREATE|DROP)/i
|
246
|
+
s = @raw_connection.createStatement()
|
247
|
+
# this disables SQL92 syntax processing of {...} which can result in statement execution errors
|
248
|
+
# if sql contains {...} in strings or comments
|
249
|
+
s.setEscapeProcessing(false)
|
250
|
+
s.execute(sql)
|
251
|
+
true
|
252
|
+
else
|
253
|
+
s = @raw_connection.prepareStatement(sql)
|
254
|
+
s.execute
|
255
|
+
true
|
256
|
+
end
|
257
|
+
ensure
|
258
|
+
s.close rescue nil
|
259
|
+
end
|
260
|
+
|
261
|
+
def returning_clause(quoted_pk)
|
262
|
+
" RETURNING #{quoted_pk} INTO ?"
|
263
|
+
end
|
264
|
+
|
265
|
+
# execute sql with RETURNING ... INTO :insert_id
|
266
|
+
# and return :insert_id value
|
267
|
+
def exec_with_returning(sql)
|
268
|
+
with_retry do
|
269
|
+
begin
|
270
|
+
# it will always be INSERT statement
|
271
|
+
|
272
|
+
# TODO: need to investigate why PreparedStatement is giving strange exception "Protocol violation"
|
273
|
+
# s = @raw_connection.prepareStatement(sql)
|
274
|
+
# s.registerReturnParameter(1, ::Java::oracle.jdbc.OracleTypes::NUMBER)
|
275
|
+
# count = s.executeUpdate
|
276
|
+
# if count > 0
|
277
|
+
# rs = s.getReturnResultSet
|
278
|
+
# if rs.next
|
279
|
+
# # Assuming that primary key will not be larger as long max value
|
280
|
+
# insert_id = rs.getLong(1)
|
281
|
+
# rs.wasNull ? nil : insert_id
|
282
|
+
# else
|
283
|
+
# nil
|
284
|
+
# end
|
285
|
+
# else
|
286
|
+
# nil
|
287
|
+
# end
|
288
|
+
|
289
|
+
# Workaround with CallableStatement
|
290
|
+
s = @raw_connection.prepareCall("BEGIN #{sql}; END;")
|
291
|
+
s.registerOutParameter(1, java.sql.Types::BIGINT)
|
292
|
+
s.execute
|
293
|
+
insert_id = s.getLong(1)
|
294
|
+
s.wasNull ? nil : insert_id
|
295
|
+
ensure
|
296
|
+
# rs.close rescue nil
|
297
|
+
s.close rescue nil
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def prepare(sql)
|
303
|
+
Cursor.new(self, @raw_connection.prepareStatement(sql))
|
304
|
+
end
|
305
|
+
|
306
|
+
class Cursor
|
307
|
+
def initialize(connection, raw_statement)
|
308
|
+
@connection = connection
|
309
|
+
@raw_statement = raw_statement
|
310
|
+
end
|
311
|
+
|
312
|
+
def bind_param(position, value, col_type = nil)
|
313
|
+
java_value = ruby_to_java_value(value, col_type)
|
314
|
+
case value
|
315
|
+
when Integer
|
316
|
+
@raw_statement.setLong(position, java_value)
|
317
|
+
when Float
|
318
|
+
@raw_statement.setFloat(position, java_value)
|
319
|
+
when BigDecimal
|
320
|
+
@raw_statement.setBigDecimal(position, java_value)
|
321
|
+
when String
|
322
|
+
case col_type
|
323
|
+
when :text
|
324
|
+
@raw_statement.setClob(position, java_value)
|
325
|
+
when :binary
|
326
|
+
@raw_statement.setBlob(position, java_value)
|
327
|
+
when :raw
|
328
|
+
@raw_statement.setString(position, OracleEnhancedAdapter.encode_raw(java_value))
|
329
|
+
when :decimal
|
330
|
+
@raw_statement.setBigDecimal(position, java_value)
|
331
|
+
else
|
332
|
+
@raw_statement.setString(position, java_value)
|
333
|
+
end
|
334
|
+
when Date, DateTime
|
335
|
+
@raw_statement.setDATE(position, java_value)
|
336
|
+
when Time
|
337
|
+
@raw_statement.setTimestamp(position, java_value)
|
338
|
+
when NilClass
|
339
|
+
# TODO: currently nil is always bound as NULL with VARCHAR type.
|
340
|
+
# When nils will actually be used by ActiveRecord as bound parameters
|
341
|
+
# then need to pass actual column type.
|
342
|
+
@raw_statement.setNull(position, java.sql.Types::VARCHAR)
|
343
|
+
else
|
344
|
+
raise ArgumentError, "Don't know how to bind variable with type #{value.class}"
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def bind_returning_param(position, bind_type)
|
349
|
+
@returning_positions ||= []
|
350
|
+
@returning_positions << position
|
351
|
+
if bind_type == Integer
|
352
|
+
@raw_statement.registerReturnParameter(position, java.sql.Types::BIGINT)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def exec
|
357
|
+
@raw_result_set = @raw_statement.executeQuery
|
358
|
+
true
|
359
|
+
end
|
360
|
+
|
361
|
+
def exec_update
|
362
|
+
@raw_statement.executeUpdate
|
363
|
+
end
|
364
|
+
|
365
|
+
def metadata
|
366
|
+
@metadata ||= @raw_result_set.getMetaData
|
367
|
+
end
|
368
|
+
|
369
|
+
def column_types
|
370
|
+
@column_types ||= (1..metadata.getColumnCount).map{|i| metadata.getColumnTypeName(i).to_sym}
|
371
|
+
end
|
372
|
+
|
373
|
+
def column_names
|
374
|
+
@column_names ||= (1..metadata.getColumnCount).map{|i| metadata.getColumnName(i)}
|
375
|
+
end
|
376
|
+
alias :get_col_names :column_names
|
377
|
+
|
378
|
+
def fetch(options={})
|
379
|
+
if @raw_result_set.next
|
380
|
+
get_lob_value = options[:get_lob_value]
|
381
|
+
row_values = []
|
382
|
+
column_types.each_with_index do |column_type, i|
|
383
|
+
row_values <<
|
384
|
+
@connection.get_ruby_value_from_result_set(@raw_result_set, i+1, column_type, get_lob_value)
|
385
|
+
end
|
386
|
+
row_values
|
387
|
+
else
|
388
|
+
@raw_result_set.close
|
389
|
+
nil
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def get_returning_param(position, type)
|
394
|
+
rs_position = @returning_positions.index(position) + 1
|
395
|
+
rs = @raw_statement.getReturnResultSet
|
396
|
+
if rs.next
|
397
|
+
# Assuming that primary key will not be larger as long max value
|
398
|
+
returning_id = rs.getLong(rs_position)
|
399
|
+
rs.wasNull ? nil : returning_id
|
400
|
+
else
|
401
|
+
nil
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def close
|
406
|
+
@raw_statement.close
|
407
|
+
end
|
408
|
+
|
409
|
+
private
|
410
|
+
|
411
|
+
def ruby_to_java_value(value, col_type = nil)
|
412
|
+
case value
|
413
|
+
when Fixnum, Float
|
414
|
+
value
|
415
|
+
when String
|
416
|
+
case col_type
|
417
|
+
when :text
|
418
|
+
clob = Java::OracleSql::CLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::CLOB::DURATION_SESSION)
|
419
|
+
clob.setString(1, value)
|
420
|
+
clob
|
421
|
+
when :binary
|
422
|
+
blob = Java::OracleSql::BLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION)
|
423
|
+
blob.setBytes(1, value.to_java_bytes)
|
424
|
+
blob
|
425
|
+
when :decimal
|
426
|
+
java.math.BigDecimal.new(value.to_s)
|
427
|
+
else
|
428
|
+
value
|
429
|
+
end
|
430
|
+
when BigDecimal
|
431
|
+
java.math.BigDecimal.new(value.to_s)
|
432
|
+
when Date, DateTime
|
433
|
+
Java::oracle.sql.DATE.new(value.strftime("%Y-%m-%d %H:%M:%S"))
|
434
|
+
when Time
|
435
|
+
Java::java.sql.Timestamp.new(value.year-1900, value.month-1, value.day, value.hour, value.min, value.sec, value.usec * 1000)
|
436
|
+
else
|
437
|
+
value
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
|
443
|
+
def select(sql, name = nil, return_column_names = false)
|
444
|
+
with_retry do
|
445
|
+
select_no_retry(sql, name, return_column_names)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def select_no_retry(sql, name = nil, return_column_names = false)
|
450
|
+
stmt = @raw_connection.prepareStatement(sql)
|
451
|
+
rset = stmt.executeQuery
|
452
|
+
|
453
|
+
# Reuse the same hash for all rows
|
454
|
+
column_hash = {}
|
455
|
+
|
456
|
+
metadata = rset.getMetaData
|
457
|
+
column_count = metadata.getColumnCount
|
458
|
+
|
459
|
+
cols_types_index = (1..column_count).map do |i|
|
460
|
+
col_name = oracle_downcase(metadata.getColumnName(i))
|
461
|
+
next if col_name == 'raw_rnum_'
|
462
|
+
column_hash[col_name] = nil
|
463
|
+
[col_name, metadata.getColumnTypeName(i).to_sym, i]
|
464
|
+
end
|
465
|
+
cols_types_index.delete(nil)
|
466
|
+
|
467
|
+
rows = []
|
468
|
+
get_lob_value = !(name == 'Writable Large Object')
|
469
|
+
|
470
|
+
while rset.next
|
471
|
+
hash = column_hash.dup
|
472
|
+
cols_types_index.each do |col, column_type, i|
|
473
|
+
hash[col] = get_ruby_value_from_result_set(rset, i, column_type, get_lob_value)
|
474
|
+
end
|
475
|
+
rows << hash
|
476
|
+
end
|
477
|
+
|
478
|
+
return_column_names ? [rows, cols_types_index.map(&:first)] : rows
|
479
|
+
ensure
|
480
|
+
rset.close rescue nil
|
481
|
+
stmt.close rescue nil
|
482
|
+
end
|
483
|
+
|
484
|
+
def write_lob(lob, value, is_binary = false)
|
485
|
+
if is_binary
|
486
|
+
lob.setBytes(1, value.to_java_bytes)
|
487
|
+
else
|
488
|
+
lob.setString(1,value)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
# Return NativeException / java.sql.SQLException error code
|
493
|
+
def error_code(exception)
|
494
|
+
case exception
|
495
|
+
when NativeException
|
496
|
+
exception.cause.getErrorCode
|
497
|
+
else
|
498
|
+
nil
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def get_ruby_value_from_result_set(rset, i, type_name, get_lob_value = true)
|
503
|
+
case type_name
|
504
|
+
when :NUMBER
|
505
|
+
d = rset.getNUMBER(i)
|
506
|
+
if d.nil?
|
507
|
+
nil
|
508
|
+
elsif d.isInt
|
509
|
+
Integer(d.stringValue)
|
510
|
+
else
|
511
|
+
BigDecimal.new(d.stringValue)
|
512
|
+
end
|
513
|
+
when :VARCHAR2, :CHAR, :LONG, :NVARCHAR2, :NCHAR
|
514
|
+
rset.getString(i)
|
515
|
+
when :DATE
|
516
|
+
if dt = rset.getDATE(i)
|
517
|
+
d = dt.dateValue
|
518
|
+
t = dt.timeValue
|
519
|
+
if OracleEnhancedAdapter.emulate_dates && t.hours == 0 && t.minutes == 0 && t.seconds == 0
|
520
|
+
Date.new(d.year + 1900, d.month + 1, d.date)
|
521
|
+
else
|
522
|
+
Time.send(Base.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
|
523
|
+
end
|
524
|
+
else
|
525
|
+
nil
|
526
|
+
end
|
527
|
+
when :TIMESTAMP, :TIMESTAMPTZ, :TIMESTAMPLTZ, :"TIMESTAMP WITH TIME ZONE", :"TIMESTAMP WITH LOCAL TIME ZONE"
|
528
|
+
ts = rset.getTimestamp(i)
|
529
|
+
ts && Time.send(Base.default_timezone, ts.year + 1900, ts.month + 1, ts.date, ts.hours, ts.minutes, ts.seconds,
|
530
|
+
ts.nanos / 1000)
|
531
|
+
when :CLOB
|
532
|
+
get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
|
533
|
+
when :BLOB
|
534
|
+
get_lob_value ? lob_to_ruby_value(rset.getBlob(i)) : rset.getBlob(i)
|
535
|
+
when :RAW
|
536
|
+
raw_value = rset.getRAW(i)
|
537
|
+
raw_value && raw_value.getBytes.to_a.pack('C*')
|
538
|
+
else
|
539
|
+
nil
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
private
|
544
|
+
|
545
|
+
def lob_to_ruby_value(val)
|
546
|
+
case val
|
547
|
+
when ::Java::OracleSql::CLOB
|
548
|
+
if val.isEmptyLob
|
549
|
+
nil
|
550
|
+
else
|
551
|
+
val.getSubString(1, val.length)
|
552
|
+
end
|
553
|
+
when ::Java::OracleSql::BLOB
|
554
|
+
if val.isEmptyLob
|
555
|
+
nil
|
556
|
+
else
|
557
|
+
String.from_java_bytes(val.getBytes(1, val.length))
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
end
|
563
|
+
|
564
|
+
end
|
565
|
+
end
|