flash-gordons-ruby-plsql 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +14 -0
- data/History.txt +172 -0
- data/License.txt +20 -0
- data/README.md +182 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/plsql/connection.rb +233 -0
- data/lib/plsql/helpers.rb +9 -0
- data/lib/plsql/jdbc_connection.rb +542 -0
- data/lib/plsql/oci8_patches.rb +25 -0
- data/lib/plsql/oci_connection.rb +339 -0
- data/lib/plsql/package.rb +80 -0
- data/lib/plsql/procedure.rb +269 -0
- data/lib/plsql/procedure_call.rb +124 -0
- data/lib/plsql/schema.rb +309 -0
- data/lib/plsql/sequence.rb +49 -0
- data/lib/plsql/sql_statements.rb +87 -0
- data/lib/plsql/table.rb +348 -0
- data/lib/plsql/type.rb +275 -0
- data/lib/plsql/variable.rb +146 -0
- data/lib/plsql/version.rb +3 -0
- data/lib/plsql/view.rb +41 -0
- data/lib/ruby-plsql.rb +1 -0
- data/lib/ruby_plsql.rb +18 -0
- data/ruby-plsql.gemspec +87 -0
- data/spec/plsql/connection_spec.rb +495 -0
- data/spec/plsql/package_spec.rb +149 -0
- data/spec/plsql/procedure_spec.rb +2048 -0
- data/spec/plsql/schema_spec.rb +331 -0
- data/spec/plsql/sequence_spec.rb +67 -0
- data/spec/plsql/sql_statements_spec.rb +91 -0
- data/spec/plsql/table_spec.rb +371 -0
- data/spec/plsql/type_spec.rb +304 -0
- data/spec/plsql/variable_spec.rb +505 -0
- data/spec/plsql/version_spec.rb +8 -0
- data/spec/plsql/view_spec.rb +264 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +79 -0
- metadata +159 -0
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.0
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module PLSQL
|
2
|
+
class Connection
|
3
|
+
attr_reader :raw_driver
|
4
|
+
attr_reader :activerecord_class
|
5
|
+
|
6
|
+
def initialize(raw_conn, ar_class = nil) #:nodoc:
|
7
|
+
@raw_driver = self.class.driver_type
|
8
|
+
@raw_connection = raw_conn
|
9
|
+
@activerecord_class = ar_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.create(raw_conn, ar_class = nil) #:nodoc:
|
13
|
+
if ar_class && !(defined?(::ActiveRecord) && ar_class.ancestors.include?(::ActiveRecord::Base))
|
14
|
+
raise ArgumentError, "Wrong ActiveRecord class"
|
15
|
+
end
|
16
|
+
case driver_type
|
17
|
+
when :oci
|
18
|
+
OCIConnection.new(raw_conn, ar_class)
|
19
|
+
when :jdbc
|
20
|
+
JDBCConnection.new(raw_conn, ar_class)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Unknown raw driver"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create_new(params) #:nodoc:
|
27
|
+
conn = case driver_type
|
28
|
+
when :oci
|
29
|
+
OCIConnection.create_raw(params)
|
30
|
+
when :jdbc
|
31
|
+
JDBCConnection.create_raw(params)
|
32
|
+
else
|
33
|
+
raise ArgumentError, "Unknown raw driver"
|
34
|
+
end
|
35
|
+
conn.set_time_zone(params[:time_zone])
|
36
|
+
conn
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.driver_type #:nodoc:
|
40
|
+
# MRI 1.8.6 or YARV 1.9.1
|
41
|
+
@driver_type ||= if (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") && defined?(OCI8)
|
42
|
+
:oci
|
43
|
+
# JRuby
|
44
|
+
elsif (defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby")
|
45
|
+
:jdbc
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns OCI8 or JDBC connection
|
52
|
+
def raw_connection
|
53
|
+
if @activerecord_class
|
54
|
+
@activerecord_class.connection.raw_connection
|
55
|
+
else
|
56
|
+
@raw_connection
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Is it OCI8 connection
|
61
|
+
def oci?
|
62
|
+
@raw_driver == :oci
|
63
|
+
end
|
64
|
+
|
65
|
+
# Is it JDBC connection
|
66
|
+
def jdbc?
|
67
|
+
@raw_driver == :jdbc
|
68
|
+
end
|
69
|
+
|
70
|
+
def logoff #:nodoc:
|
71
|
+
# Rollback any uncommited transactions
|
72
|
+
rollback
|
73
|
+
# Common cleanup activities before logoff, should be called from particular driver method
|
74
|
+
drop_session_ruby_temporary_tables
|
75
|
+
end
|
76
|
+
|
77
|
+
def commit #:nodoc:
|
78
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
79
|
+
end
|
80
|
+
|
81
|
+
def rollback #:nodoc:
|
82
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Current autocommit mode (true or false)
|
86
|
+
def autocommit?
|
87
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set autocommit mode (true or false)
|
91
|
+
def autocommit=(value)
|
92
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Set number of rows to be prefetched. This can reduce the number of network round trips when fetching many rows.
|
96
|
+
# The default value is one. (If ActiveRecord oracle_enhanced connection is used then default is 100)
|
97
|
+
def prefetch_rows=(value)
|
98
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
99
|
+
end
|
100
|
+
|
101
|
+
def select_first(sql, *bindvars) #:nodoc:
|
102
|
+
cursor = cursor_from_query(sql, bindvars, :prefetch_rows => 1)
|
103
|
+
cursor.fetch
|
104
|
+
ensure
|
105
|
+
cursor.close rescue nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def select_hash_first(sql, *bindvars) #:nodoc:
|
109
|
+
cursor = cursor_from_query(sql, bindvars, :prefetch_rows => 1)
|
110
|
+
cursor.fetch_hash
|
111
|
+
ensure
|
112
|
+
cursor.close rescue nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def select_all(sql, *bindvars, &block) #:nodoc:
|
116
|
+
cursor = cursor_from_query(sql, bindvars)
|
117
|
+
results = []
|
118
|
+
row_count = 0
|
119
|
+
while row = cursor.fetch
|
120
|
+
if block_given?
|
121
|
+
yield(row)
|
122
|
+
row_count += 1
|
123
|
+
else
|
124
|
+
results << row
|
125
|
+
end
|
126
|
+
end
|
127
|
+
block_given? ? row_count : results
|
128
|
+
ensure
|
129
|
+
cursor.close rescue nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def select_hash_all(sql, *bindvars, &block) #:nodoc:
|
133
|
+
cursor = cursor_from_query(sql, bindvars)
|
134
|
+
results = []
|
135
|
+
row_count = 0
|
136
|
+
while row = cursor.fetch_hash
|
137
|
+
if block_given?
|
138
|
+
yield(row)
|
139
|
+
row_count += 1
|
140
|
+
else
|
141
|
+
results << row
|
142
|
+
end
|
143
|
+
end
|
144
|
+
block_given? ? row_count : results
|
145
|
+
ensure
|
146
|
+
cursor.close rescue nil
|
147
|
+
end
|
148
|
+
|
149
|
+
def exec(sql, *bindvars) #:nodoc:
|
150
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
151
|
+
end
|
152
|
+
|
153
|
+
def parse(sql) #:nodoc:
|
154
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
155
|
+
end
|
156
|
+
|
157
|
+
module CursorCommon
|
158
|
+
# Fetch all rows from cursor, each row as array of values
|
159
|
+
def fetch_all
|
160
|
+
rows = []
|
161
|
+
while (row = fetch)
|
162
|
+
rows << row
|
163
|
+
end
|
164
|
+
rows
|
165
|
+
end
|
166
|
+
|
167
|
+
# Fetch all rows from cursor, each row as hash {:column => value, ...}
|
168
|
+
def fetch_hash_all
|
169
|
+
rows = []
|
170
|
+
while (row = fetch_hash)
|
171
|
+
rows << row
|
172
|
+
end
|
173
|
+
rows
|
174
|
+
end
|
175
|
+
|
176
|
+
# Fetch row from cursor as hash {:column => value, ...}
|
177
|
+
def fetch_hash
|
178
|
+
(row = fetch) && ArrayHelpers::to_hash(fields, row)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# all_synonyms view is quite slow therefore
|
183
|
+
# this implementation is overriden in OCI connection with faster native OCI method
|
184
|
+
def describe_synonym(schema_name, synonym_name) #:nodoc:
|
185
|
+
select_first(
|
186
|
+
"SELECT table_owner, table_name FROM all_synonyms WHERE owner = :owner AND synonym_name = :synonym_name",
|
187
|
+
schema_name.to_s.upcase, synonym_name.to_s.upcase)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns array with major and minor version of database (e.g. [10, 2])
|
191
|
+
def database_version
|
192
|
+
raise NoMethodError, "Not implemented for this raw driver"
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns session ID
|
196
|
+
def session_id
|
197
|
+
@session_id ||= select_first("SELECT TO_NUMBER(USERENV('SESSIONID')) FROM dual")[0]
|
198
|
+
end
|
199
|
+
|
200
|
+
# Set time zone (default taken from TZ environment variable)
|
201
|
+
def set_time_zone(time_zone=nil)
|
202
|
+
time_zone ||= ENV['TZ']
|
203
|
+
exec("alter session set time_zone = '#{time_zone}'") if time_zone
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns session time zone
|
207
|
+
def time_zone
|
208
|
+
select_first("SELECT SESSIONTIMEZONE FROM dual")[0]
|
209
|
+
end
|
210
|
+
|
211
|
+
RUBY_TEMP_TABLE_PREFIX = 'ruby_'
|
212
|
+
|
213
|
+
# Drop all ruby temporary tables that are used for calling packages with table parameter types defined in packages
|
214
|
+
def drop_all_ruby_temporary_tables
|
215
|
+
select_all("SELECT table_name FROM user_tables WHERE temporary='Y' AND table_name LIKE :table_name",
|
216
|
+
RUBY_TEMP_TABLE_PREFIX.upcase+'%').each do |row|
|
217
|
+
exec "TRUNCATE TABLE #{row[0]}"
|
218
|
+
exec "DROP TABLE #{row[0]}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Drop ruby temporary tables created in current session that are used for calling packages with table parameter types defined in packages
|
223
|
+
def drop_session_ruby_temporary_tables
|
224
|
+
select_all("SELECT table_name FROM user_tables WHERE temporary='Y' AND table_name LIKE :table_name",
|
225
|
+
RUBY_TEMP_TABLE_PREFIX.upcase+"#{session_id}_%").each do |row|
|
226
|
+
exec "TRUNCATE TABLE #{row[0]}"
|
227
|
+
exec "DROP TABLE #{row[0]}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
@@ -0,0 +1,542 @@
|
|
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 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(/[:;]/)
|
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
|
+
java.sql.DriverManager.registerDriver Java::oracle.jdbc.OracleDriver.new
|
27
|
+
|
28
|
+
# set tns_admin property from TNS_ADMIN environment variable
|
29
|
+
if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
|
30
|
+
java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"])
|
31
|
+
end
|
32
|
+
|
33
|
+
rescue LoadError, NameError
|
34
|
+
# JDBC driver is unavailable.
|
35
|
+
raise LoadError, "ERROR: ruby-plsql could not load Oracle JDBC driver. Please install #{ojdbc_jar || "Oracle JDBC"} library."
|
36
|
+
end
|
37
|
+
|
38
|
+
module PLSQL
|
39
|
+
class JDBCConnection < Connection #:nodoc:
|
40
|
+
|
41
|
+
def self.create_raw(params)
|
42
|
+
url = if ENV['TNS_ADMIN'] && params[:database] && !params[:host] && !params[:url]
|
43
|
+
"jdbc:oracle:thin:@#{params[:database]}"
|
44
|
+
else
|
45
|
+
params[:url] || "jdbc:oracle:thin:@#{params[:host] || 'localhost'}:#{params[:port] || 1521}:#{params[:database]}"
|
46
|
+
end
|
47
|
+
new(java.sql.DriverManager.getConnection(url, params[:username], params[:password]))
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_time_zone(time_zone=nil)
|
51
|
+
time_zone ||= ENV['TZ']
|
52
|
+
raw_connection.setSessionTimeZone(time_zone)
|
53
|
+
end
|
54
|
+
|
55
|
+
def logoff
|
56
|
+
super
|
57
|
+
raw_connection.close
|
58
|
+
true
|
59
|
+
rescue
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def commit
|
64
|
+
raw_connection.commit
|
65
|
+
end
|
66
|
+
|
67
|
+
def rollback
|
68
|
+
raw_connection.rollback
|
69
|
+
end
|
70
|
+
|
71
|
+
def autocommit?
|
72
|
+
raw_connection.getAutoCommit
|
73
|
+
end
|
74
|
+
|
75
|
+
def autocommit=(value)
|
76
|
+
raw_connection.setAutoCommit(value)
|
77
|
+
end
|
78
|
+
|
79
|
+
def prefetch_rows=(value)
|
80
|
+
raw_connection.setDefaultRowPrefetch(value)
|
81
|
+
end
|
82
|
+
|
83
|
+
def exec(sql, *bindvars)
|
84
|
+
cs = prepare_call(sql, *bindvars)
|
85
|
+
cs.execute
|
86
|
+
true
|
87
|
+
ensure
|
88
|
+
cs.close rescue nil
|
89
|
+
end
|
90
|
+
|
91
|
+
class CallableStatement #:nodoc:
|
92
|
+
|
93
|
+
def initialize(conn, sql)
|
94
|
+
@sql = sql
|
95
|
+
@connection = conn
|
96
|
+
@params = sql.scan(/\:\w+/)
|
97
|
+
@out_types = {}
|
98
|
+
@out_index = {}
|
99
|
+
@statement = @connection.prepare_call(sql)
|
100
|
+
end
|
101
|
+
|
102
|
+
def bind_param(arg, value, metadata)
|
103
|
+
type, length = @connection.plsql_to_ruby_data_type(metadata)
|
104
|
+
ora_value = @connection.ruby_value_to_ora_value(value, type, metadata)
|
105
|
+
@connection.set_bind_variable(@statement, arg, ora_value, type, length, metadata)
|
106
|
+
if metadata[:in_out] =~ /OUT/
|
107
|
+
@out_types[arg] = type || ora_value.class
|
108
|
+
@out_index[arg] = bind_param_index(arg)
|
109
|
+
if ['TABLE','VARRAY','OBJECT'].include?(metadata[:data_type])
|
110
|
+
@statement.registerOutParameter(@out_index[arg], @connection.get_java_sql_type(ora_value,type),
|
111
|
+
metadata[:sql_type_name])
|
112
|
+
else
|
113
|
+
@statement.registerOutParameter(@out_index[arg],@connection.get_java_sql_type(ora_value,type))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def exec
|
119
|
+
@statement.execute
|
120
|
+
end
|
121
|
+
|
122
|
+
def [](key)
|
123
|
+
@connection.ora_value_to_ruby_value(@connection.get_bind_variable(@statement, @out_index[key], @out_types[key]))
|
124
|
+
end
|
125
|
+
|
126
|
+
def close
|
127
|
+
@statement.close
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def bind_param_index(key)
|
133
|
+
return key if key.kind_of? Integer
|
134
|
+
key = ":#{key.to_s}" unless key.to_s =~ /^:/
|
135
|
+
@params.index(key)+1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Cursor #:nodoc:
|
140
|
+
include Connection::CursorCommon
|
141
|
+
|
142
|
+
attr_reader :result_set
|
143
|
+
attr_accessor :statement
|
144
|
+
|
145
|
+
def initialize(conn, result_set)
|
146
|
+
@connection = conn
|
147
|
+
@result_set = result_set
|
148
|
+
@metadata = @result_set.getMetaData
|
149
|
+
@column_count = @metadata.getColumnCount
|
150
|
+
@column_type_names = [nil] # column numbering starts at 1
|
151
|
+
(1..@column_count).each do |i|
|
152
|
+
@column_type_names << {:type_name => @metadata.getColumnTypeName(i), :sql_type => @metadata.getColumnType(i)}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.new_from_query(conn, sql, bindvars=[], options={})
|
157
|
+
stmt = conn.prepare_statement(sql, *bindvars)
|
158
|
+
if prefetch_rows = options[:prefetch_rows]
|
159
|
+
stmt.setRowPrefetch(prefetch_rows)
|
160
|
+
end
|
161
|
+
cursor = Cursor.new(conn, stmt.executeQuery)
|
162
|
+
cursor.statement = stmt
|
163
|
+
cursor
|
164
|
+
rescue
|
165
|
+
# in case of any error close statement
|
166
|
+
stmt.close rescue nil
|
167
|
+
raise
|
168
|
+
end
|
169
|
+
|
170
|
+
def fetch
|
171
|
+
if @result_set.next
|
172
|
+
(1..@column_count).map do |i|
|
173
|
+
@connection.get_ruby_value_from_result_set(@result_set, i, @column_type_names[i])
|
174
|
+
end
|
175
|
+
else
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def fields
|
181
|
+
@fields ||= (1..@column_count).map do |i|
|
182
|
+
@metadata.getColumnName(i).downcase.to_sym
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def close
|
187
|
+
@result_set.close
|
188
|
+
@statement.close if @statement
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def parse(sql)
|
193
|
+
CallableStatement.new(self, sql)
|
194
|
+
end
|
195
|
+
|
196
|
+
def cursor_from_query(sql, bindvars=[], options={})
|
197
|
+
Cursor.new_from_query(self, sql, bindvars, options)
|
198
|
+
end
|
199
|
+
|
200
|
+
def prepare_statement(sql, *bindvars)
|
201
|
+
stmt = raw_connection.prepareStatement(sql)
|
202
|
+
bindvars.each_with_index do |bv, i|
|
203
|
+
set_bind_variable(stmt, i+1, ruby_value_to_ora_value(bv))
|
204
|
+
end
|
205
|
+
stmt
|
206
|
+
end
|
207
|
+
|
208
|
+
def prepare_call(sql, *bindvars)
|
209
|
+
stmt = raw_connection.prepareCall(sql)
|
210
|
+
bindvars.each_with_index do |bv, i|
|
211
|
+
set_bind_variable(stmt, i+1, bv)
|
212
|
+
end
|
213
|
+
stmt
|
214
|
+
end
|
215
|
+
|
216
|
+
RUBY_CLASS_TO_SQL_TYPE = {
|
217
|
+
Fixnum => java.sql.Types::INTEGER,
|
218
|
+
Bignum => java.sql.Types::INTEGER,
|
219
|
+
Integer => java.sql.Types::INTEGER,
|
220
|
+
Float => java.sql.Types::FLOAT,
|
221
|
+
BigDecimal => java.sql.Types::NUMERIC,
|
222
|
+
String => java.sql.Types::VARCHAR,
|
223
|
+
Java::OracleSql::CLOB => Java::oracle.jdbc.OracleTypes::CLOB,
|
224
|
+
Java::OracleSql::BLOB => Java::oracle.jdbc.OracleTypes::BLOB,
|
225
|
+
Date => java.sql.Types::DATE,
|
226
|
+
Time => java.sql.Types::TIMESTAMP,
|
227
|
+
DateTime => java.sql.Types::DATE,
|
228
|
+
Java::OracleSql::ARRAY => Java::oracle.jdbc.OracleTypes::ARRAY,
|
229
|
+
Array => Java::oracle.jdbc.OracleTypes::ARRAY,
|
230
|
+
Java::OracleSql::STRUCT => Java::oracle.jdbc.OracleTypes::STRUCT,
|
231
|
+
Hash => Java::oracle.jdbc.OracleTypes::STRUCT,
|
232
|
+
java.sql.ResultSet => Java::oracle.jdbc.OracleTypes::CURSOR,
|
233
|
+
}
|
234
|
+
|
235
|
+
SQL_TYPE_TO_RUBY_CLASS = {
|
236
|
+
java.sql.Types::CHAR => String,
|
237
|
+
java.sql.Types::NCHAR => String,
|
238
|
+
java.sql.Types::VARCHAR => String,
|
239
|
+
java.sql.Types::NVARCHAR => String,
|
240
|
+
java.sql.Types::LONGVARCHAR => String,
|
241
|
+
java.sql.Types::NUMERIC => BigDecimal,
|
242
|
+
java.sql.Types::INTEGER => Fixnum,
|
243
|
+
java.sql.Types::DATE => Time,
|
244
|
+
java.sql.Types::TIMESTAMP => Time,
|
245
|
+
Java::oracle.jdbc.OracleTypes::TIMESTAMPTZ => Time,
|
246
|
+
Java::oracle.jdbc.OracleTypes::TIMESTAMPLTZ => Time,
|
247
|
+
java.sql.Types::BLOB => String,
|
248
|
+
java.sql.Types::CLOB => String,
|
249
|
+
java.sql.Types::ARRAY => Java::OracleSql::ARRAY,
|
250
|
+
java.sql.Types::STRUCT => Java::OracleSql::STRUCT,
|
251
|
+
Java::oracle.jdbc.OracleTypes::CURSOR => java.sql.ResultSet
|
252
|
+
}
|
253
|
+
|
254
|
+
def get_java_sql_type(value, type)
|
255
|
+
RUBY_CLASS_TO_SQL_TYPE[type || value.class] || java.sql.Types::VARCHAR
|
256
|
+
end
|
257
|
+
|
258
|
+
def set_bind_variable(stmt, i, value, type=nil, length=nil, metadata={})
|
259
|
+
key = i.kind_of?(Integer) ? nil : i.to_s.gsub(':','')
|
260
|
+
type_symbol = (!value.nil? && type ? type : value.class).to_s.to_sym
|
261
|
+
case type_symbol
|
262
|
+
when :Fixnum, :Bignum, :Integer
|
263
|
+
stmt.send("setInt#{key && "AtName"}", key || i, value)
|
264
|
+
when :Float
|
265
|
+
stmt.send("setFloat#{key && "AtName"}", key || i, value)
|
266
|
+
when :BigDecimal, :'Java::JavaMath::BigDecimal'
|
267
|
+
stmt.send("setBigDecimal#{key && "AtName"}", key || i, value)
|
268
|
+
when :String
|
269
|
+
stmt.send("setString#{key && "AtName"}", key || i, value)
|
270
|
+
when :'Java::OracleSql::CLOB'
|
271
|
+
stmt.send("setClob#{key && "AtName"}", key || i, value)
|
272
|
+
when :'Java::OracleSql::BLOB'
|
273
|
+
stmt.send("setBlob#{key && "AtName"}", key || i, value)
|
274
|
+
when :Date, :DateTime, :'Java::OracleSql::DATE'
|
275
|
+
stmt.send("setDATE#{key && "AtName"}", key || i, value)
|
276
|
+
when :Time, :'Java::JavaSql::Timestamp'
|
277
|
+
stmt.send("setTimestamp#{key && "AtName"}", key || i, value)
|
278
|
+
when :NilClass
|
279
|
+
if ['TABLE', 'VARRAY', 'OBJECT'].include?(metadata[:data_type])
|
280
|
+
stmt.send("setNull#{key && "AtName"}", key || i, get_java_sql_type(value, type),
|
281
|
+
metadata[:sql_type_name])
|
282
|
+
elsif metadata[:data_type] == 'REF CURSOR'
|
283
|
+
# TODO: cannot bind NULL value to cursor parameter, getting error
|
284
|
+
# java.sql.SQLException: Unsupported feature: sqlType=-10
|
285
|
+
# Currently do nothing and assume that NULL values will not be passed to IN parameters
|
286
|
+
# If cursor is IN/OUT or OUT parameter then it should work
|
287
|
+
else
|
288
|
+
stmt.send("setNull#{key && "AtName"}", key || i, get_java_sql_type(value, type))
|
289
|
+
end
|
290
|
+
when :'Java::OracleSql::ARRAY'
|
291
|
+
stmt.send("setARRAY#{key && "AtName"}", key || i, value)
|
292
|
+
when :'Java::OracleSql::STRUCT'
|
293
|
+
stmt.send("setSTRUCT#{key && "AtName"}", key || i, value)
|
294
|
+
when :'Java::JavaSql::ResultSet'
|
295
|
+
# TODO: cannot find how to pass cursor parameter from JDBC
|
296
|
+
# setCursor is giving exception java.sql.SQLException: Unsupported feature
|
297
|
+
stmt.send("setCursor#{key && "AtName"}", key || i, value)
|
298
|
+
else
|
299
|
+
raise ArgumentError, "Don't know how to bind variable with type #{type_symbol}"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def get_bind_variable(stmt, i, type)
|
304
|
+
case type.to_s.to_sym
|
305
|
+
when :Fixnum, :Bignum, :Integer
|
306
|
+
stmt.getInt(i)
|
307
|
+
when :Float
|
308
|
+
stmt.getFloat(i)
|
309
|
+
when :BigDecimal
|
310
|
+
bd = stmt.getBigDecimal(i)
|
311
|
+
bd && BigDecimal.new(bd.to_s)
|
312
|
+
when :String
|
313
|
+
stmt.getString(i)
|
314
|
+
when :'Java::OracleSql::CLOB'
|
315
|
+
stmt.getClob(i)
|
316
|
+
when :'Java::OracleSql::BLOB'
|
317
|
+
stmt.getBlob(i)
|
318
|
+
when :Date, :DateTime
|
319
|
+
stmt.getDATE(i)
|
320
|
+
when :Time
|
321
|
+
stmt.getTimestamp(i)
|
322
|
+
when :'Java::OracleSql::ARRAY'
|
323
|
+
stmt.getArray(i)
|
324
|
+
when :'Java::OracleSql::STRUCT'
|
325
|
+
stmt.getSTRUCT(i)
|
326
|
+
when :'Java::JavaSql::ResultSet'
|
327
|
+
stmt.getCursor(i)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def get_ruby_value_from_result_set(rset, i, metadata)
|
332
|
+
ruby_type = SQL_TYPE_TO_RUBY_CLASS[metadata[:sql_type]]
|
333
|
+
ora_value = get_bind_variable(rset, i, ruby_type)
|
334
|
+
result_new = ora_value_to_ruby_value(ora_value)
|
335
|
+
end
|
336
|
+
|
337
|
+
def result_set_to_ruby_data_type(column_type, column_type_name)
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
def plsql_to_ruby_data_type(metadata)
|
342
|
+
data_type, data_length = metadata[:data_type], metadata[:data_length]
|
343
|
+
case data_type
|
344
|
+
when "VARCHAR2", "CHAR", "NVARCHAR2", "NCHAR"
|
345
|
+
[String, data_length || 32767]
|
346
|
+
when "CLOB", "NCLOB"
|
347
|
+
[Java::OracleSql::CLOB, nil]
|
348
|
+
when "BLOB"
|
349
|
+
[Java::OracleSql::BLOB, nil]
|
350
|
+
when "NUMBER"
|
351
|
+
[BigDecimal, nil]
|
352
|
+
when "PLS_INTEGER", "BINARY_INTEGER"
|
353
|
+
[Fixnum, nil]
|
354
|
+
when "DATE"
|
355
|
+
[DateTime, nil]
|
356
|
+
when "TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH LOCAL TIME ZONE"
|
357
|
+
[Time, nil]
|
358
|
+
when "TABLE", "VARRAY"
|
359
|
+
[Java::OracleSql::ARRAY, nil]
|
360
|
+
when "OBJECT"
|
361
|
+
[Java::OracleSql::STRUCT, nil]
|
362
|
+
when "REF CURSOR"
|
363
|
+
[java.sql.ResultSet, nil]
|
364
|
+
else
|
365
|
+
[String, 32767]
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def ruby_value_to_ora_value(value, type=nil, metadata={})
|
370
|
+
type ||= value.class
|
371
|
+
case type.to_s.to_sym
|
372
|
+
when :Fixnum, :String
|
373
|
+
value
|
374
|
+
when :BigDecimal
|
375
|
+
case value
|
376
|
+
when TrueClass
|
377
|
+
java_bigdecimal(1)
|
378
|
+
when FalseClass
|
379
|
+
java_bigdecimal(0)
|
380
|
+
else
|
381
|
+
java_bigdecimal(value)
|
382
|
+
end
|
383
|
+
when :Date, :DateTime
|
384
|
+
case value
|
385
|
+
when DateTime
|
386
|
+
java_date(Time.send(plsql.default_timezone, value.year, value.month, value.day, value.hour, value.min, value.sec))
|
387
|
+
when Date
|
388
|
+
java_date(Time.send(plsql.default_timezone, value.year, value.month, value.day, 0, 0, 0))
|
389
|
+
else
|
390
|
+
java_date(value)
|
391
|
+
end
|
392
|
+
when :Time
|
393
|
+
java_timestamp(value)
|
394
|
+
when :'Java::OracleSql::CLOB'
|
395
|
+
if value
|
396
|
+
clob = Java::OracleSql::CLOB.createTemporary(raw_connection, false, Java::OracleSql::CLOB::DURATION_SESSION)
|
397
|
+
clob.setString(1, value)
|
398
|
+
clob
|
399
|
+
else
|
400
|
+
nil
|
401
|
+
end
|
402
|
+
when :'Java::OracleSql::BLOB'
|
403
|
+
if value
|
404
|
+
blob = Java::OracleSql::BLOB.createTemporary(raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION)
|
405
|
+
blob.setBytes(1, value.to_java_bytes)
|
406
|
+
blob
|
407
|
+
else
|
408
|
+
nil
|
409
|
+
end
|
410
|
+
when :'Java::OracleSql::ARRAY'
|
411
|
+
if value
|
412
|
+
raise ArgumentError, "You should pass Array value for collection type parameter" unless value.is_a?(Array)
|
413
|
+
descriptor = Java::OracleSql::ArrayDescriptor.createDescriptor(metadata[:sql_type_name], raw_connection)
|
414
|
+
elem_type = descriptor.getBaseType
|
415
|
+
elem_type_name = descriptor.getBaseName
|
416
|
+
elem_list = value.map do |elem|
|
417
|
+
case elem_type
|
418
|
+
when Java::oracle.jdbc.OracleTypes::ARRAY
|
419
|
+
ruby_value_to_ora_value(elem, Java::OracleSql::ARRAY, :sql_type_name => elem_type_name)
|
420
|
+
when Java::oracle.jdbc.OracleTypes::STRUCT
|
421
|
+
ruby_value_to_ora_value(elem, Java::OracleSql::STRUCT, :sql_type_name => elem_type_name)
|
422
|
+
else
|
423
|
+
ruby_value_to_ora_value(elem)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
Java::OracleSql::ARRAY.new(descriptor, raw_connection, elem_list.to_java)
|
427
|
+
end
|
428
|
+
when :'Java::OracleSql::STRUCT'
|
429
|
+
if value
|
430
|
+
raise ArgumentError, "You should pass Hash value for object type parameter" unless value.is_a?(Hash)
|
431
|
+
descriptor = Java::OracleSql::StructDescriptor.createDescriptor(metadata[:sql_type_name], raw_connection)
|
432
|
+
struct_metadata = descriptor.getMetaData
|
433
|
+
struct_fields = (1..descriptor.getLength).inject({}) do |hash, i|
|
434
|
+
hash[struct_metadata.getColumnName(i).downcase.to_sym] =
|
435
|
+
{:type => struct_metadata.getColumnType(i), :type_name => struct_metadata.getColumnTypeName(i)}
|
436
|
+
hash
|
437
|
+
end
|
438
|
+
object_attrs = java.util.HashMap.new
|
439
|
+
value.each do |key, attr_value|
|
440
|
+
raise ArgumentError, "Wrong object type field passed to PL/SQL procedure" unless (field = struct_fields[key])
|
441
|
+
case field[:type]
|
442
|
+
when Java::oracle.jdbc.OracleTypes::ARRAY
|
443
|
+
# nested collection
|
444
|
+
object_attrs.put(key.to_s.upcase, ruby_value_to_ora_value(attr_value, Java::OracleSql::ARRAY, :sql_type_name => field[:type_name]))
|
445
|
+
when Java::oracle.jdbc.OracleTypes::STRUCT
|
446
|
+
# nested object type
|
447
|
+
object_attrs.put(key.to_s.upcase, ruby_value_to_ora_value(attr_value, Java::OracleSql::STRUCT, :sql_type_name => field[:type_name]))
|
448
|
+
else
|
449
|
+
object_attrs.put(key.to_s.upcase, ruby_value_to_ora_value(attr_value))
|
450
|
+
end
|
451
|
+
end
|
452
|
+
Java::OracleSql::STRUCT.new(descriptor, raw_connection, object_attrs)
|
453
|
+
end
|
454
|
+
when :'Java::JavaSql::ResultSet'
|
455
|
+
if value
|
456
|
+
value.result_set
|
457
|
+
end
|
458
|
+
else
|
459
|
+
value
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def ora_value_to_ruby_value(value)
|
464
|
+
case value
|
465
|
+
when Float, BigDecimal
|
466
|
+
ora_number_to_ruby_number(value)
|
467
|
+
when Java::JavaMath::BigDecimal
|
468
|
+
value && ora_number_to_ruby_number(BigDecimal.new(value.to_s))
|
469
|
+
when Java::OracleSql::DATE
|
470
|
+
if value
|
471
|
+
d = value.dateValue
|
472
|
+
t = value.timeValue
|
473
|
+
Time.send(plsql.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
|
474
|
+
end
|
475
|
+
when Java::JavaSql::Timestamp
|
476
|
+
if value
|
477
|
+
Time.send(plsql.default_timezone, value.year + 1900, value.month + 1, value.date, value.hours, value.minutes, value.seconds,
|
478
|
+
value.nanos / 1000)
|
479
|
+
end
|
480
|
+
when Java::OracleSql::CLOB
|
481
|
+
if value.isEmptyLob
|
482
|
+
nil
|
483
|
+
else
|
484
|
+
value.getSubString(1, value.length)
|
485
|
+
end
|
486
|
+
when Java::OracleSql::BLOB
|
487
|
+
if value.isEmptyLob
|
488
|
+
nil
|
489
|
+
else
|
490
|
+
String.from_java_bytes(value.getBytes(1, value.length))
|
491
|
+
end
|
492
|
+
when Java::OracleSql::ARRAY
|
493
|
+
value.getArray.map{|e| ora_value_to_ruby_value(e)}
|
494
|
+
when Java::OracleSql::STRUCT
|
495
|
+
descriptor = value.getDescriptor
|
496
|
+
struct_metadata = descriptor.getMetaData
|
497
|
+
field_names = (1..descriptor.getLength).map {|i| struct_metadata.getColumnName(i).downcase.to_sym}
|
498
|
+
field_values = value.getAttributes.map{|e| ora_value_to_ruby_value(e)}
|
499
|
+
ArrayHelpers::to_hash(field_names, field_values)
|
500
|
+
when Java::java.sql.ResultSet
|
501
|
+
Cursor.new(self, value)
|
502
|
+
else
|
503
|
+
value
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def database_version
|
508
|
+
@database_version ||= if md = raw_connection.getMetaData
|
509
|
+
major = md.getDatabaseMajorVersion
|
510
|
+
minor = md.getDatabaseMinorVersion
|
511
|
+
if md.getDatabaseProductVersion =~ /#{major}\.#{minor}\.(\d+)\.(\d+)/
|
512
|
+
update = $1.to_i
|
513
|
+
patch = $2.to_i
|
514
|
+
else
|
515
|
+
update = patch = 0
|
516
|
+
end
|
517
|
+
[major, minor, update, patch]
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
private
|
522
|
+
|
523
|
+
def java_date(value)
|
524
|
+
value && Java::oracle.sql.DATE.new(value.strftime("%Y-%m-%d %H:%M:%S"))
|
525
|
+
end
|
526
|
+
|
527
|
+
def java_timestamp(value)
|
528
|
+
value && Java::java.sql.Timestamp.new(value.year-1900, value.month-1, value.day, value.hour, value.min, value.sec, value.usec * 1000)
|
529
|
+
end
|
530
|
+
|
531
|
+
def java_bigdecimal(value)
|
532
|
+
value && java.math.BigDecimal.new(value.to_s)
|
533
|
+
end
|
534
|
+
|
535
|
+
def ora_number_to_ruby_number(num)
|
536
|
+
# return BigDecimal instead of Float to avoid rounding errors
|
537
|
+
num == (num_to_i = num.to_i) ? num_to_i : (num.is_a?(BigDecimal) ? num : BigDecimal.new(num.to_s))
|
538
|
+
end
|
539
|
+
|
540
|
+
end
|
541
|
+
|
542
|
+
end
|