oracle_enhanced 1.2.5
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/.gitignore +10 -0
- data/History.txt +182 -0
- data/License.txt +20 -0
- data/Manifest.txt +32 -0
- data/README.rdoc +77 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1661 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -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 +370 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +203 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +114 -0
- metadata +140 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
# interface independent methods
|
4
|
+
class OracleEnhancedConnection #:nodoc:
|
5
|
+
|
6
|
+
def self.create(config)
|
7
|
+
case ORACLE_ENHANCED_CONNECTION
|
8
|
+
when :oci
|
9
|
+
OracleEnhancedOCIConnection.new(config)
|
10
|
+
when :jdbc
|
11
|
+
OracleEnhancedJDBCConnection.new(config)
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :raw_connection
|
18
|
+
|
19
|
+
# Oracle column names by default are case-insensitive, but treated as upcase;
|
20
|
+
# for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
|
21
|
+
# their column names when creating Oracle tables, which makes then case-sensitive.
|
22
|
+
# I don't know anybody who does this, but we'll handle the theoretical case of a
|
23
|
+
# camelCase column name. I imagine other dbs handle this different, since there's a
|
24
|
+
# unit test that's currently failing test_oci.
|
25
|
+
def oracle_downcase(column_name)
|
26
|
+
return nil if column_name.nil?
|
27
|
+
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
# Used always by JDBC connection as well by OCI connection when describing tables over database link
|
31
|
+
def describe(name)
|
32
|
+
name = name.to_s
|
33
|
+
if name.include?('@')
|
34
|
+
name, db_link = name.split('@')
|
35
|
+
default_owner = select_value("SELECT username FROM all_db_links WHERE db_link = '#{db_link.upcase}'")
|
36
|
+
db_link = "@#{db_link}"
|
37
|
+
else
|
38
|
+
db_link = nil
|
39
|
+
default_owner = @owner
|
40
|
+
end
|
41
|
+
real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.upcase : name
|
42
|
+
if real_name.include?('.')
|
43
|
+
table_owner, table_name = real_name.split('.')
|
44
|
+
else
|
45
|
+
table_owner, table_name = default_owner, real_name
|
46
|
+
end
|
47
|
+
sql = <<-SQL
|
48
|
+
SELECT owner, table_name, 'TABLE' name_type
|
49
|
+
FROM all_tables#{db_link}
|
50
|
+
WHERE owner = '#{table_owner}'
|
51
|
+
AND table_name = '#{table_name}'
|
52
|
+
UNION ALL
|
53
|
+
SELECT owner, view_name table_name, 'VIEW' name_type
|
54
|
+
FROM all_views#{db_link}
|
55
|
+
WHERE owner = '#{table_owner}'
|
56
|
+
AND view_name = '#{table_name}'
|
57
|
+
UNION ALL
|
58
|
+
SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
|
59
|
+
FROM all_synonyms#{db_link}
|
60
|
+
WHERE owner = '#{table_owner}'
|
61
|
+
AND synonym_name = '#{table_name}'
|
62
|
+
UNION ALL
|
63
|
+
SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
|
64
|
+
FROM all_synonyms#{db_link}
|
65
|
+
WHERE owner = 'PUBLIC'
|
66
|
+
AND synonym_name = '#{real_name}'
|
67
|
+
SQL
|
68
|
+
if result = select_one(sql)
|
69
|
+
case result['name_type']
|
70
|
+
when 'SYNONYM'
|
71
|
+
describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}#{db_link}")
|
72
|
+
else
|
73
|
+
db_link ? [result['owner'], result['table_name'], db_link] : [result['owner'], result['table_name']]
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Returns a record hash with the column names as keys and column values
|
83
|
+
# as values.
|
84
|
+
def select_one(sql)
|
85
|
+
result = select(sql)
|
86
|
+
result.first if result
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a single value from a record
|
90
|
+
def select_value(sql)
|
91
|
+
if result = select_one(sql)
|
92
|
+
result.values.first
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns an array of the values of the first column in a select:
|
97
|
+
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
98
|
+
def select_values(sql)
|
99
|
+
result = select(sql)
|
100
|
+
result.map { |r| r.values.first }
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
class OracleEnhancedConnectionException < StandardError #:nodoc:
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# if MRI or YARV
|
112
|
+
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
|
113
|
+
ORACLE_ENHANCED_CONNECTION = :oci
|
114
|
+
require 'active_record/connection_adapters/oracle_enhanced_oci_connection'
|
115
|
+
# if JRuby
|
116
|
+
elsif RUBY_ENGINE == 'jruby'
|
117
|
+
ORACLE_ENHANCED_CONNECTION = :jdbc
|
118
|
+
require 'active_record/connection_adapters/oracle_enhanced_jdbc_connection'
|
119
|
+
else
|
120
|
+
raise "Unsupported Ruby engine #{RUBY_ENGINE}"
|
121
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "bigdecimal"
|
2
|
+
unless BigDecimal.instance_methods.include?("to_d")
|
3
|
+
BigDecimal.class_eval do
|
4
|
+
def to_d #:nodoc:
|
5
|
+
self
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
unless Bignum.instance_methods.include?("to_d")
|
11
|
+
Bignum.class_eval do
|
12
|
+
def to_d #:nodoc:
|
13
|
+
BigDecimal.new(self.to_s)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
unless Fixnum.instance_methods.include?("to_d")
|
19
|
+
Fixnum.class_eval do
|
20
|
+
def to_d #:nodoc:
|
21
|
+
BigDecimal.new(self.to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Add Unicode aware String#upcase and String#downcase methods when mb_chars method is called
|
27
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '1.9'
|
28
|
+
begin
|
29
|
+
gem "unicode_utils", ">=1.0.0"
|
30
|
+
require "unicode_utils/upcase"
|
31
|
+
require "unicode_utils/downcase"
|
32
|
+
|
33
|
+
module ActiveRecord #:nodoc:
|
34
|
+
module ConnectionAdapters #:nodoc:
|
35
|
+
module OracleEnhancedUnicodeString #:nodoc:
|
36
|
+
def upcase #:nodoc:
|
37
|
+
UnicodeUtils.upcase(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def downcase #:nodoc:
|
41
|
+
UnicodeUtils.downcase(self)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class String #:nodoc:
|
48
|
+
def mb_chars #:nodoc:
|
49
|
+
self.extend(ActiveRecord::ConnectionAdapters::OracleEnhancedUnicodeString)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
rescue LoadError
|
55
|
+
warning_message = "WARNING: Please install unicode_utils gem to support Unicode aware upcase and downcase for String#mb_chars"
|
56
|
+
if defined?(RAILS_DEFAULT_LOGGER)
|
57
|
+
RAILS_DEFAULT_LOGGER.warn warning_message
|
58
|
+
else
|
59
|
+
STDERR.puts warning_message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
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,39 @@
|
|
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 '' or empty text (CLOB) as NULL
|
18
|
+
# therefore need to convert empty string value to nil if old value is nil
|
19
|
+
elsif (column.type == :string || column.type == :text) && column.null && old.nil?
|
20
|
+
value = nil if value == ''
|
21
|
+
else
|
22
|
+
value = column.type_cast(value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
old != value
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if ActiveRecord::Base.instance_methods.include?('changed?')
|
36
|
+
ActiveRecord::Base.class_eval do
|
37
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedDirty::InstanceMethods
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
begin
|
2
|
+
require "java"
|
3
|
+
require "jruby"
|
4
|
+
|
5
|
+
# ojdbc14.jar file should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path
|
6
|
+
|
7
|
+
ojdbc_jar = "ojdbc14.jar"
|
8
|
+
|
9
|
+
unless ENV_JAVA['java.class.path'] =~ Regexp.new(ojdbc_jar)
|
10
|
+
# On Unix environment variable should be PATH, on Windows it is sometimes Path
|
11
|
+
env_path = ENV["PATH"] || ENV["Path"] || ''
|
12
|
+
if ojdbc_jar_path = env_path.split(/[:;]/).concat($LOAD_PATH).find{|d| File.exists?(File.join(d,ojdbc_jar))}
|
13
|
+
require File.join(ojdbc_jar_path,ojdbc_jar)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
java.sql.DriverManager.registerDriver Java::oracle.jdbc.driver.OracleDriver.new
|
18
|
+
|
19
|
+
# set tns_admin property from TNS_ADMIN environment variable
|
20
|
+
if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
|
21
|
+
java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"])
|
22
|
+
end
|
23
|
+
|
24
|
+
rescue LoadError, NameError
|
25
|
+
# JDBC driver is unavailable.
|
26
|
+
raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. Please install ojdbc14.jar library."
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
module ActiveRecord
|
31
|
+
module ConnectionAdapters
|
32
|
+
|
33
|
+
# JDBC database interface for JRuby
|
34
|
+
class OracleEnhancedJDBCConnection < OracleEnhancedConnection #:nodoc:
|
35
|
+
|
36
|
+
attr_accessor :active
|
37
|
+
alias :active? :active
|
38
|
+
|
39
|
+
attr_accessor :auto_retry
|
40
|
+
alias :auto_retry? :auto_retry
|
41
|
+
@auto_retry = false
|
42
|
+
|
43
|
+
def initialize(config)
|
44
|
+
@active = true
|
45
|
+
@config = config
|
46
|
+
new_connection(@config)
|
47
|
+
end
|
48
|
+
|
49
|
+
# modified method to support JNDI connections
|
50
|
+
def new_connection(config)
|
51
|
+
username = nil
|
52
|
+
|
53
|
+
if config[:jndi]
|
54
|
+
jndi = config[:jndi].to_s
|
55
|
+
ctx = javax.naming.InitialContext.new
|
56
|
+
ds = nil
|
57
|
+
|
58
|
+
# tomcat needs first lookup method, oc4j (and maybe other application servers) need second method
|
59
|
+
begin
|
60
|
+
env = ctx.lookup('java:/comp/env')
|
61
|
+
ds = env.lookup(jndi)
|
62
|
+
rescue
|
63
|
+
ds = ctx.lookup(jndi)
|
64
|
+
end
|
65
|
+
|
66
|
+
# check if datasource supports pooled connections, otherwise use default
|
67
|
+
if ds.respond_to?(:pooled_connection)
|
68
|
+
@raw_connection = ds.pooled_connection
|
69
|
+
else
|
70
|
+
@raw_connection = ds.connection
|
71
|
+
end
|
72
|
+
|
73
|
+
config[:driver] ||= @raw_connection.meta_data.connection.java_class.name
|
74
|
+
username = @raw_connection.meta_data.user_name
|
75
|
+
else
|
76
|
+
username = config[:username].to_s
|
77
|
+
password, database = config[:password].to_s, config[:database].to_s
|
78
|
+
privilege = config[:privilege] && config[:privilege].to_s
|
79
|
+
host, port = config[:host], config[:port]
|
80
|
+
|
81
|
+
# connection using TNS alias
|
82
|
+
if database && !host && !config[:url] && ENV['TNS_ADMIN']
|
83
|
+
url = "jdbc:oracle:thin:@#{database || 'XE'}"
|
84
|
+
else
|
85
|
+
url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}:#{database || 'XE'}"
|
86
|
+
end
|
87
|
+
|
88
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
89
|
+
# get session time_zone from configuration or from TZ environment variable
|
90
|
+
time_zone = config[:time_zone] || ENV['TZ'] || java.util.TimeZone.default.getID
|
91
|
+
|
92
|
+
properties = java.util.Properties.new
|
93
|
+
properties.put("user", username)
|
94
|
+
properties.put("password", password)
|
95
|
+
properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
|
96
|
+
properties.put("internal_logon", privilege) if privilege
|
97
|
+
|
98
|
+
@raw_connection = java.sql.DriverManager.getConnection(url, properties)
|
99
|
+
|
100
|
+
# Set session time zone to current time zone
|
101
|
+
@raw_connection.setSessionTimeZone(time_zone)
|
102
|
+
|
103
|
+
# Set default number of rows to prefetch
|
104
|
+
# @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
|
105
|
+
end
|
106
|
+
|
107
|
+
# by default VARCHAR2 column size will be interpreted as max number of characters (and not bytes)
|
108
|
+
nls_length_semantics = config[:nls_length_semantics] || 'CHAR'
|
109
|
+
cursor_sharing = config[:cursor_sharing] || 'force'
|
110
|
+
|
111
|
+
# from here it remaings common for both connections types
|
112
|
+
exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
113
|
+
exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS:FF6'}
|
114
|
+
exec "alter session set cursor_sharing = #{cursor_sharing}"
|
115
|
+
exec "alter session set nls_length_semantics = '#{nls_length_semantics}'"
|
116
|
+
self.autocommit = true
|
117
|
+
|
118
|
+
# default schema owner
|
119
|
+
@owner = username.upcase unless username.nil?
|
120
|
+
|
121
|
+
@raw_connection
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def logoff
|
126
|
+
@active = false
|
127
|
+
@raw_connection.close
|
128
|
+
true
|
129
|
+
rescue
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
def commit
|
134
|
+
@raw_connection.commit
|
135
|
+
end
|
136
|
+
|
137
|
+
def rollback
|
138
|
+
@raw_connection.rollback
|
139
|
+
end
|
140
|
+
|
141
|
+
def autocommit?
|
142
|
+
@raw_connection.getAutoCommit
|
143
|
+
end
|
144
|
+
|
145
|
+
def autocommit=(value)
|
146
|
+
@raw_connection.setAutoCommit(value)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Checks connection, returns true if active. Note that ping actively
|
150
|
+
# checks the connection, while #active? simply returns the last
|
151
|
+
# known state.
|
152
|
+
def ping
|
153
|
+
exec_no_retry("select 1 from dual")
|
154
|
+
@active = true
|
155
|
+
rescue NativeException => e
|
156
|
+
@active = false
|
157
|
+
if e.message =~ /^java\.sql\.SQLException/
|
158
|
+
raise OracleEnhancedConnectionException, e.message
|
159
|
+
else
|
160
|
+
raise
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Resets connection, by logging off and creating a new connection.
|
165
|
+
def reset!
|
166
|
+
logoff rescue nil
|
167
|
+
begin
|
168
|
+
new_connection(@config)
|
169
|
+
@active = true
|
170
|
+
rescue NativeException => e
|
171
|
+
@active = false
|
172
|
+
if e.message =~ /^java\.sql\.SQLException/
|
173
|
+
raise OracleEnhancedConnectionException, e.message
|
174
|
+
else
|
175
|
+
raise
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# mark connection as dead if connection lost
|
181
|
+
def with_retry(&block)
|
182
|
+
should_retry = auto_retry? && autocommit?
|
183
|
+
begin
|
184
|
+
yield if block_given?
|
185
|
+
rescue NativeException => e
|
186
|
+
raise unless e.message =~ /^java\.sql\.SQLException: (Closed Connection|Io exception:|No more data to read from socket)/
|
187
|
+
@active = false
|
188
|
+
raise unless should_retry
|
189
|
+
should_retry = false
|
190
|
+
reset! rescue nil
|
191
|
+
retry
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def exec(sql)
|
196
|
+
with_retry do
|
197
|
+
exec_no_retry(sql)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def exec_no_retry(sql)
|
202
|
+
case sql
|
203
|
+
when /\A\s*(UPDATE|INSERT|DELETE)/i
|
204
|
+
s = @raw_connection.prepareStatement(sql)
|
205
|
+
s.executeUpdate
|
206
|
+
# it is safer for CREATE and DROP statements not to use PreparedStatement
|
207
|
+
# as it does not allow creation of triggers with :NEW in their definition
|
208
|
+
when /\A\s*(CREATE|DROP)/i
|
209
|
+
s = @raw_connection.createStatement()
|
210
|
+
s.execute(sql)
|
211
|
+
true
|
212
|
+
else
|
213
|
+
s = @raw_connection.prepareStatement(sql)
|
214
|
+
s.execute
|
215
|
+
true
|
216
|
+
end
|
217
|
+
ensure
|
218
|
+
s.close rescue nil
|
219
|
+
end
|
220
|
+
|
221
|
+
def returning_clause(quoted_pk)
|
222
|
+
" RETURNING #{quoted_pk} INTO ?"
|
223
|
+
end
|
224
|
+
|
225
|
+
# execute sql with RETURNING ... INTO :insert_id
|
226
|
+
# and return :insert_id value
|
227
|
+
def exec_with_returning(sql)
|
228
|
+
with_retry do
|
229
|
+
begin
|
230
|
+
# it will always be INSERT statement
|
231
|
+
|
232
|
+
# TODO: need to investigate why PreparedStatement is giving strange exception "Protocol violation"
|
233
|
+
# s = @raw_connection.prepareStatement(sql)
|
234
|
+
# s.registerReturnParameter(1, ::Java::oracle.jdbc.OracleTypes::NUMBER)
|
235
|
+
# count = s.executeUpdate
|
236
|
+
# if count > 0
|
237
|
+
# rs = s.getReturnResultSet
|
238
|
+
# if rs.next
|
239
|
+
# # Assuming that primary key will not be larger as long max value
|
240
|
+
# insert_id = rs.getLong(1)
|
241
|
+
# rs.wasNull ? nil : insert_id
|
242
|
+
# else
|
243
|
+
# nil
|
244
|
+
# end
|
245
|
+
# else
|
246
|
+
# nil
|
247
|
+
# end
|
248
|
+
|
249
|
+
# Workaround with CallableStatement
|
250
|
+
s = @raw_connection.prepareCall("BEGIN #{sql}; END;")
|
251
|
+
s.registerOutParameter(1, java.sql.Types::BIGINT)
|
252
|
+
s.execute
|
253
|
+
insert_id = s.getLong(1)
|
254
|
+
s.wasNull ? nil : insert_id
|
255
|
+
ensure
|
256
|
+
# rs.close rescue nil
|
257
|
+
s.close rescue nil
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def select(sql, name = nil, return_column_names = false)
|
263
|
+
with_retry do
|
264
|
+
select_no_retry(sql, name, return_column_names)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def select_no_retry(sql, name = nil, return_column_names = false)
|
269
|
+
stmt = @raw_connection.prepareStatement(sql)
|
270
|
+
rset = stmt.executeQuery
|
271
|
+
|
272
|
+
# Reuse the same hash for all rows
|
273
|
+
column_hash = {}
|
274
|
+
|
275
|
+
metadata = rset.getMetaData
|
276
|
+
column_count = metadata.getColumnCount
|
277
|
+
|
278
|
+
cols_types_index = (1..column_count).map do |i|
|
279
|
+
col_name = oracle_downcase(metadata.getColumnName(i))
|
280
|
+
next if col_name == 'raw_rnum_'
|
281
|
+
column_hash[col_name] = nil
|
282
|
+
[col_name, metadata.getColumnTypeName(i).to_sym, i]
|
283
|
+
end
|
284
|
+
cols_types_index.delete(nil)
|
285
|
+
|
286
|
+
rows = []
|
287
|
+
get_lob_value = !(name == 'Writable Large Object')
|
288
|
+
|
289
|
+
while rset.next
|
290
|
+
hash = column_hash.dup
|
291
|
+
cols_types_index.each do |col, column_type, i|
|
292
|
+
hash[col] = get_ruby_value_from_result_set(rset, i, column_type, get_lob_value)
|
293
|
+
end
|
294
|
+
rows << hash
|
295
|
+
end
|
296
|
+
|
297
|
+
return_column_names ? [rows, cols_types_index.map(&:first)] : rows
|
298
|
+
ensure
|
299
|
+
rset.close rescue nil
|
300
|
+
stmt.close rescue nil
|
301
|
+
end
|
302
|
+
|
303
|
+
def write_lob(lob, value, is_binary = false)
|
304
|
+
if is_binary
|
305
|
+
lob.setBytes(1, value.to_java_bytes)
|
306
|
+
else
|
307
|
+
lob.setString(1,value)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Return NativeException / java.sql.SQLException error code
|
312
|
+
def error_code(exception)
|
313
|
+
exception.cause.getErrorCode
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
# def prepare_statement(sql)
|
319
|
+
# @raw_connection.prepareStatement(sql)
|
320
|
+
# end
|
321
|
+
|
322
|
+
# def prepare_call(sql, *bindvars)
|
323
|
+
# @raw_connection.prepareCall(sql)
|
324
|
+
# end
|
325
|
+
|
326
|
+
def get_ruby_value_from_result_set(rset, i, type_name, get_lob_value = true)
|
327
|
+
case type_name
|
328
|
+
when :NUMBER
|
329
|
+
# d = rset.getBigDecimal(i)
|
330
|
+
# if d.nil?
|
331
|
+
# nil
|
332
|
+
# elsif d.scale == 0
|
333
|
+
# d.toBigInteger+0
|
334
|
+
# else
|
335
|
+
# # Is there better way how to convert Java BigDecimal to Ruby BigDecimal?
|
336
|
+
# d.toString.to_d
|
337
|
+
# end
|
338
|
+
d = rset.getNUMBER(i)
|
339
|
+
if d.nil?
|
340
|
+
nil
|
341
|
+
elsif d.isInt
|
342
|
+
Integer(d.stringValue)
|
343
|
+
else
|
344
|
+
BigDecimal.new(d.stringValue)
|
345
|
+
end
|
346
|
+
when :VARCHAR2, :CHAR, :LONG
|
347
|
+
rset.getString(i)
|
348
|
+
when :DATE
|
349
|
+
if dt = rset.getDATE(i)
|
350
|
+
d = dt.dateValue
|
351
|
+
t = dt.timeValue
|
352
|
+
if OracleEnhancedAdapter.emulate_dates && t.hours == 0 && t.minutes == 0 && t.seconds == 0
|
353
|
+
Date.new(d.year + 1900, d.month + 1, d.date)
|
354
|
+
else
|
355
|
+
Time.send(Base.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
|
356
|
+
end
|
357
|
+
else
|
358
|
+
nil
|
359
|
+
end
|
360
|
+
when :TIMESTAMP, :TIMESTAMPTZ, :TIMESTAMPLTZ
|
361
|
+
ts = rset.getTimestamp(i)
|
362
|
+
ts && Time.send(Base.default_timezone, ts.year + 1900, ts.month + 1, ts.date, ts.hours, ts.minutes, ts.seconds,
|
363
|
+
ts.nanos / 1000)
|
364
|
+
when :CLOB
|
365
|
+
get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
|
366
|
+
when :BLOB
|
367
|
+
get_lob_value ? lob_to_ruby_value(rset.getBlob(i)) : rset.getBlob(i)
|
368
|
+
else
|
369
|
+
nil
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def lob_to_ruby_value(val)
|
374
|
+
case val
|
375
|
+
when ::Java::OracleSql::CLOB
|
376
|
+
if val.isEmptyLob
|
377
|
+
nil
|
378
|
+
else
|
379
|
+
val.getSubString(1, val.length)
|
380
|
+
end
|
381
|
+
when ::Java::OracleSql::BLOB
|
382
|
+
if val.isEmptyLob
|
383
|
+
nil
|
384
|
+
else
|
385
|
+
String.from_java_bytes(val.getBytes(1, val.length))
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
end
|
391
|
+
|
392
|
+
end
|
393
|
+
end
|