flash-gordons-ruby-plsql 0.5.0
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.
- 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
|