c3-activerecord-oracle_enhanced-adapter 1.2.4
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/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/activerecord-oracle_enhanced-adapter.gemspec +79 -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 +1664 -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 +218 -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 +137 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# RSI: implementation idea taken from JDBC adapter
|
2
|
+
def redefine_task(*args, &block)
|
3
|
+
task_name = Hash === args.first ? args.first.keys[0] : args.first
|
4
|
+
existing_task = Rake.application.lookup task_name
|
5
|
+
if existing_task
|
6
|
+
class << existing_task; public :instance_variable_set; end
|
7
|
+
existing_task.instance_variable_set "@prerequisites", FileList[]
|
8
|
+
existing_task.instance_variable_set "@actions", []
|
9
|
+
end
|
10
|
+
task(*args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :db do
|
14
|
+
|
15
|
+
namespace :structure do
|
16
|
+
redefine_task :dump => :environment do
|
17
|
+
abcs = ActiveRecord::Base.configurations
|
18
|
+
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
|
19
|
+
File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
|
20
|
+
File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_fk_constraints }
|
21
|
+
if ActiveRecord::Base.connection.supports_migrations?
|
22
|
+
File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
|
23
|
+
end
|
24
|
+
if abcs[RAILS_ENV]['structure_dump'] == "db_stored_code"
|
25
|
+
File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
namespace :test do
|
32
|
+
redefine_task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
|
33
|
+
abcs = ActiveRecord::Base.configurations
|
34
|
+
ActiveRecord::Base.establish_connection(:test)
|
35
|
+
IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
|
36
|
+
ddl.chop! if ddl.last == ";"
|
37
|
+
ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
redefine_task :purge => :environment do
|
42
|
+
abcs = ActiveRecord::Base.configurations
|
43
|
+
ActiveRecord::Base.establish_connection(:test)
|
44
|
+
ActiveRecord::Base.connection.full_drop.split("\n\n").each do |ddl|
|
45
|
+
ddl.chop! if ddl.last == ";"
|
46
|
+
ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,1664 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
|
3
|
+
#
|
4
|
+
# Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
|
5
|
+
#
|
6
|
+
# Current maintainer: Raimonds Simanovskis (http://blog.rayapps.com)
|
7
|
+
#
|
8
|
+
#########################################################################
|
9
|
+
#
|
10
|
+
# See History.txt for changes added to original oracle_adapter.rb
|
11
|
+
#
|
12
|
+
#########################################################################
|
13
|
+
#
|
14
|
+
# From original oracle_adapter.rb:
|
15
|
+
#
|
16
|
+
# Implementation notes:
|
17
|
+
# 1. Redefines (safely) a method in ActiveRecord to make it possible to
|
18
|
+
# implement an autonumbering solution for Oracle.
|
19
|
+
# 2. The OCI8 driver is patched to properly handle values for LONG and
|
20
|
+
# TIMESTAMP columns. The driver-author has indicated that a future
|
21
|
+
# release of the driver will obviate this patch.
|
22
|
+
# 3. LOB support is implemented through an after_save callback.
|
23
|
+
# 4. Oracle does not offer native LIMIT and OFFSET options; this
|
24
|
+
# functionality is mimiced through the use of nested selects.
|
25
|
+
# See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
|
26
|
+
#
|
27
|
+
# Do what you want with this code, at your own peril, but if any
|
28
|
+
# significant portion of my code remains then please acknowledge my
|
29
|
+
# contribution.
|
30
|
+
# portions Copyright 2005 Graham Jenkins
|
31
|
+
|
32
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
33
|
+
|
34
|
+
require 'active_record/connection_adapters/oracle_enhanced_connection'
|
35
|
+
|
36
|
+
require 'digest/sha1'
|
37
|
+
|
38
|
+
module ActiveRecord
|
39
|
+
class Base
|
40
|
+
# Establishes a connection to the database that's used by all Active Record objects.
|
41
|
+
def self.oracle_enhanced_connection(config) #:nodoc:
|
42
|
+
if config[:emulate_oracle_adapter] == true
|
43
|
+
# allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
|
44
|
+
# conditionals in the rails activerecord test suite
|
45
|
+
require 'active_record/connection_adapters/emulation/oracle_adapter'
|
46
|
+
ConnectionAdapters::OracleAdapter.new(
|
47
|
+
ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
|
48
|
+
else
|
49
|
+
ConnectionAdapters::OracleEnhancedAdapter.new(
|
50
|
+
ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Specify table columns which should be ignored by ActiveRecord, e.g.:
|
55
|
+
#
|
56
|
+
# ignore_table_columns :attribute1, :attribute2
|
57
|
+
def self.ignore_table_columns(*args)
|
58
|
+
connection.ignore_table_columns(table_name,*args)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Specify which table columns should be typecasted to Date (without time), e.g.:
|
62
|
+
#
|
63
|
+
# set_date_columns :created_on, :updated_on
|
64
|
+
def self.set_date_columns(*args)
|
65
|
+
connection.set_type_for_columns(table_name,:date,*args)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Specify which table columns should be typecasted to Time (or DateTime), e.g.:
|
69
|
+
#
|
70
|
+
# set_datetime_columns :created_date, :updated_date
|
71
|
+
def self.set_datetime_columns(*args)
|
72
|
+
connection.set_type_for_columns(table_name,:datetime,*args)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Specify which table columns should be typecasted to boolean values +true+ or +false+, e.g.:
|
76
|
+
#
|
77
|
+
# set_boolean_columns :is_valid, :is_completed
|
78
|
+
def self.set_boolean_columns(*args)
|
79
|
+
connection.set_type_for_columns(table_name,:boolean,*args)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Specify which table columns should be typecasted to integer values.
|
83
|
+
# Might be useful to force NUMBER(1) column to be integer and not boolean, or force NUMBER column without
|
84
|
+
# scale to be retrieved as integer and not decimal. Example:
|
85
|
+
#
|
86
|
+
# set_integer_columns :version_number, :object_identifier
|
87
|
+
def self.set_integer_columns(*args)
|
88
|
+
connection.set_type_for_columns(table_name,:integer,*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Specify which table columns should be typecasted to string values.
|
92
|
+
# Might be useful to specify that columns should be string even if its name matches boolean column criteria.
|
93
|
+
#
|
94
|
+
# set_integer_columns :active_flag
|
95
|
+
def self.set_string_columns(*args)
|
96
|
+
connection.set_type_for_columns(table_name,:string,*args)
|
97
|
+
end
|
98
|
+
|
99
|
+
# After setting large objects to empty, select the OCI8::LOB
|
100
|
+
# and write back the data.
|
101
|
+
after_save :enhanced_write_lobs
|
102
|
+
def enhanced_write_lobs #:nodoc:
|
103
|
+
if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
|
104
|
+
!(self.class.custom_create_method || self.class.custom_update_method)
|
105
|
+
connection.write_lobs(self.class.table_name, self.class, attributes)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
private :enhanced_write_lobs
|
109
|
+
|
110
|
+
class << self
|
111
|
+
# patch ORDER BY to work with LOBs
|
112
|
+
def add_order_with_lobs!(sql, order, scope = :auto)
|
113
|
+
if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
|
114
|
+
order = connection.lob_order_by_expression(self, order) if order
|
115
|
+
|
116
|
+
orig_scope = scope
|
117
|
+
scope = scope(:find) if :auto == scope
|
118
|
+
if scope
|
119
|
+
new_scope_order = connection.lob_order_by_expression(self, scope[:order])
|
120
|
+
if new_scope_order != scope[:order]
|
121
|
+
scope = scope.merge(:order => new_scope_order)
|
122
|
+
else
|
123
|
+
scope = orig_scope
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
add_order_without_lobs!(sql, order, scope = :auto)
|
128
|
+
end
|
129
|
+
private :add_order_with_lobs!
|
130
|
+
#:stopdoc:
|
131
|
+
alias_method :add_order_without_lobs!, :add_order!
|
132
|
+
alias_method :add_order!, :add_order_with_lobs!
|
133
|
+
#:startdoc:
|
134
|
+
end
|
135
|
+
|
136
|
+
# Get table comment from schema definition.
|
137
|
+
def self.table_comment
|
138
|
+
connection.table_comment(self.table_name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
module ConnectionAdapters #:nodoc:
|
144
|
+
class OracleEnhancedColumn < Column
|
145
|
+
|
146
|
+
attr_reader :table_name, :forced_column_type #:nodoc:
|
147
|
+
|
148
|
+
def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil) #:nodoc:
|
149
|
+
@table_name = table_name
|
150
|
+
@forced_column_type = forced_column_type
|
151
|
+
super(name, default, sql_type, null)
|
152
|
+
end
|
153
|
+
|
154
|
+
def type_cast(value) #:nodoc:
|
155
|
+
return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
# convert something to a boolean
|
160
|
+
# added y as boolean value
|
161
|
+
def self.value_to_boolean(value) #:nodoc:
|
162
|
+
if value == true || value == false
|
163
|
+
value
|
164
|
+
elsif value.is_a?(String) && value.blank?
|
165
|
+
nil
|
166
|
+
else
|
167
|
+
%w(true t 1 y +).include?(value.to_s.downcase)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# convert Time or DateTime value to Date for :date columns
|
172
|
+
def self.string_to_date(string) #:nodoc:
|
173
|
+
return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
|
174
|
+
super
|
175
|
+
end
|
176
|
+
|
177
|
+
# convert Date value to Time for :datetime columns
|
178
|
+
def self.string_to_time(string) #:nodoc:
|
179
|
+
return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
|
180
|
+
super
|
181
|
+
end
|
182
|
+
|
183
|
+
# Get column comment from schema definition.
|
184
|
+
# Will work only if using default ActiveRecord connection.
|
185
|
+
def comment
|
186
|
+
ActiveRecord::Base.connection.column_comment(@table_name, name)
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
def simplified_type(field_type)
|
191
|
+
forced_column_type ||
|
192
|
+
case field_type
|
193
|
+
when /decimal|numeric|number/i
|
194
|
+
return :boolean if OracleEnhancedAdapter.emulate_booleans && field_type == 'NUMBER(1)'
|
195
|
+
return :integer if extract_scale(field_type) == 0
|
196
|
+
# if column name is ID or ends with _ID
|
197
|
+
return :integer if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
|
198
|
+
:decimal
|
199
|
+
when /char/i
|
200
|
+
return :boolean if OracleEnhancedAdapter.emulate_booleans_from_strings &&
|
201
|
+
OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name)
|
202
|
+
:string
|
203
|
+
when /date/i
|
204
|
+
forced_column_type ||
|
205
|
+
(:date if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name)) ||
|
206
|
+
:datetime
|
207
|
+
when /timestamp/i then :timestamp
|
208
|
+
when /time/i then :datetime
|
209
|
+
else super
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def guess_date_or_time(value)
|
214
|
+
value.respond_to?(:hour) && (value.hour == 0 and value.min == 0 and value.sec == 0) ?
|
215
|
+
Date.new(value.year, value.month, value.day) : value
|
216
|
+
end
|
217
|
+
|
218
|
+
class << self
|
219
|
+
protected
|
220
|
+
|
221
|
+
def fallback_string_to_date(string) #:nodoc:
|
222
|
+
if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
|
223
|
+
return (string_to_date_or_time_using_format(string).to_date rescue super)
|
224
|
+
end
|
225
|
+
super
|
226
|
+
end
|
227
|
+
|
228
|
+
def fallback_string_to_time(string) #:nodoc:
|
229
|
+
if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
|
230
|
+
return (string_to_date_or_time_using_format(string).to_time rescue super)
|
231
|
+
end
|
232
|
+
super
|
233
|
+
end
|
234
|
+
|
235
|
+
def string_to_date_or_time_using_format(string) #:nodoc:
|
236
|
+
if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
|
237
|
+
return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
|
238
|
+
end
|
239
|
+
DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
# Oracle enhanced adapter will work with both
|
247
|
+
# Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
|
248
|
+
# or with JRuby and Oracle JDBC driver.
|
249
|
+
#
|
250
|
+
# It should work with Oracle 9i, 10g and 11g databases.
|
251
|
+
# Limited set of functionality should work on Oracle 8i as well but several features
|
252
|
+
# rely on newer functionality in Oracle database.
|
253
|
+
#
|
254
|
+
# Usage notes:
|
255
|
+
# * Key generation assumes a "${table_name}_seq" sequence is available
|
256
|
+
# for all tables; the sequence name can be changed using
|
257
|
+
# ActiveRecord::Base.set_sequence_name. When using Migrations, these
|
258
|
+
# sequences are created automatically.
|
259
|
+
# Use set_sequence_name :autogenerated with legacy tables that have
|
260
|
+
# triggers that populate primary keys automatically.
|
261
|
+
# * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
|
262
|
+
# Consequently some hacks are employed to map data back to Date or Time
|
263
|
+
# in Ruby. Timezones and sub-second precision on timestamps are
|
264
|
+
# not supported.
|
265
|
+
# * Default values that are functions (such as "SYSDATE") are not
|
266
|
+
# supported. This is a restriction of the way ActiveRecord supports
|
267
|
+
# default values.
|
268
|
+
#
|
269
|
+
# Required parameters:
|
270
|
+
#
|
271
|
+
# * <tt>:username</tt>
|
272
|
+
# * <tt>:password</tt>
|
273
|
+
# * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
|
274
|
+
#
|
275
|
+
# Optional parameters:
|
276
|
+
#
|
277
|
+
# * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
|
278
|
+
# * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
|
279
|
+
# * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
|
280
|
+
# * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
|
281
|
+
# * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
|
282
|
+
# * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "force"
|
283
|
+
# * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to "CHAR"
|
284
|
+
# (meaning that size specifies number of characters and not bytes)
|
285
|
+
# * <tt>:time_zone</tt> - database session time zone
|
286
|
+
# (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone)
|
287
|
+
class OracleEnhancedAdapter < AbstractAdapter
|
288
|
+
|
289
|
+
##
|
290
|
+
# :singleton-method:
|
291
|
+
# By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
|
292
|
+
# as boolean. If you wish to disable this emulation you can add the following line
|
293
|
+
# to your initializer file:
|
294
|
+
#
|
295
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
|
296
|
+
cattr_accessor :emulate_booleans
|
297
|
+
self.emulate_booleans = true
|
298
|
+
|
299
|
+
##
|
300
|
+
# :singleton-method:
|
301
|
+
# By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
|
302
|
+
# to Time or DateTime (if value is out of Time value range) value.
|
303
|
+
# If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
|
304
|
+
# to Date then you can add the following line to your initializer file:
|
305
|
+
#
|
306
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
|
307
|
+
#
|
308
|
+
# As this option can have side effects when unnecessary typecasting is done it is recommended
|
309
|
+
# that Date columns are explicily defined with +set_date_columns+ method.
|
310
|
+
cattr_accessor :emulate_dates
|
311
|
+
self.emulate_dates = false
|
312
|
+
|
313
|
+
##
|
314
|
+
# :singleton-method:
|
315
|
+
# By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
|
316
|
+
# to Time or DateTime (if value is out of Time value range) value.
|
317
|
+
# If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
|
318
|
+
# to Date then you can add the following line to your initializer file:
|
319
|
+
#
|
320
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
|
321
|
+
#
|
322
|
+
# As this option can have side effects when unnecessary typecasting is done it is recommended
|
323
|
+
# that Date columns are explicily defined with +set_date_columns+ method.
|
324
|
+
cattr_accessor :emulate_dates_by_column_name
|
325
|
+
self.emulate_dates_by_column_name = false
|
326
|
+
|
327
|
+
# Check column name to identify if it is Date (and not Time) column.
|
328
|
+
# Is used if +emulate_dates_by_column_name+ option is set to +true+.
|
329
|
+
# Override this method definition in initializer file if different Date column recognition is needed.
|
330
|
+
def self.is_date_column?(name, table_name = nil)
|
331
|
+
name =~ /(^|_)date(_|$)/i
|
332
|
+
end
|
333
|
+
|
334
|
+
# instance method uses at first check if column type defined at class level
|
335
|
+
def is_date_column?(name, table_name = nil) #:nodoc:
|
336
|
+
case get_type_for_column(table_name, name)
|
337
|
+
when nil
|
338
|
+
self.class.is_date_column?(name, table_name)
|
339
|
+
when :date
|
340
|
+
true
|
341
|
+
else
|
342
|
+
false
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
##
|
347
|
+
# :singleton-method:
|
348
|
+
# By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
|
349
|
+
# (without precision or scale) to Float or BigDecimal value.
|
350
|
+
# If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
|
351
|
+
# to Integer then you can add the following line to your initializer file:
|
352
|
+
#
|
353
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
|
354
|
+
cattr_accessor :emulate_integers_by_column_name
|
355
|
+
self.emulate_integers_by_column_name = false
|
356
|
+
|
357
|
+
# Check column name to identify if it is Integer (and not Float or BigDecimal) column.
|
358
|
+
# Is used if +emulate_integers_by_column_name+ option is set to +true+.
|
359
|
+
# Override this method definition in initializer file if different Integer column recognition is needed.
|
360
|
+
def self.is_integer_column?(name, table_name = nil)
|
361
|
+
name =~ /(^|_)id$/i
|
362
|
+
end
|
363
|
+
|
364
|
+
##
|
365
|
+
# :singleton-method:
|
366
|
+
# If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
|
367
|
+
# are typecasted to booleans then you can add the following line to your initializer file:
|
368
|
+
#
|
369
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
|
370
|
+
cattr_accessor :emulate_booleans_from_strings
|
371
|
+
self.emulate_booleans_from_strings = false
|
372
|
+
|
373
|
+
# Check column name to identify if it is boolean (and not String) column.
|
374
|
+
# Is used if +emulate_booleans_from_strings+ option is set to +true+.
|
375
|
+
# Override this method definition in initializer file if different boolean column recognition is needed.
|
376
|
+
def self.is_boolean_column?(name, field_type, table_name = nil)
|
377
|
+
return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
|
378
|
+
field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
|
379
|
+
end
|
380
|
+
|
381
|
+
# How boolean value should be quoted to String.
|
382
|
+
# Used if +emulate_booleans_from_strings+ option is set to +true+.
|
383
|
+
def self.boolean_to_string(bool)
|
384
|
+
bool ? "Y" : "N"
|
385
|
+
end
|
386
|
+
|
387
|
+
##
|
388
|
+
# :singleton-method:
|
389
|
+
# Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
|
390
|
+
#
|
391
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
|
392
|
+
cattr_accessor :string_to_date_format
|
393
|
+
self.string_to_date_format = nil
|
394
|
+
|
395
|
+
##
|
396
|
+
# :singleton-method:
|
397
|
+
# Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
|
398
|
+
#
|
399
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
|
400
|
+
cattr_accessor :string_to_time_format
|
401
|
+
self.string_to_time_format = nil
|
402
|
+
|
403
|
+
def initialize(connection, logger = nil) #:nodoc:
|
404
|
+
super
|
405
|
+
@quoted_column_names, @quoted_table_names = {}, {}
|
406
|
+
end
|
407
|
+
|
408
|
+
ADAPTER_NAME = 'OracleEnhanced'.freeze
|
409
|
+
|
410
|
+
def adapter_name #:nodoc:
|
411
|
+
ADAPTER_NAME
|
412
|
+
end
|
413
|
+
|
414
|
+
def supports_migrations? #:nodoc:
|
415
|
+
true
|
416
|
+
end
|
417
|
+
|
418
|
+
def supports_savepoints? #:nodoc:
|
419
|
+
true
|
420
|
+
end
|
421
|
+
|
422
|
+
#:stopdoc:
|
423
|
+
NATIVE_DATABASE_TYPES = {
|
424
|
+
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
|
425
|
+
:string => { :name => "VARCHAR2", :limit => 255 },
|
426
|
+
:text => { :name => "CLOB" },
|
427
|
+
:integer => { :name => "NUMBER", :limit => 38 },
|
428
|
+
:float => { :name => "NUMBER" },
|
429
|
+
:decimal => { :name => "DECIMAL" },
|
430
|
+
:datetime => { :name => "DATE" },
|
431
|
+
# changed to native TIMESTAMP type
|
432
|
+
# :timestamp => { :name => "DATE" },
|
433
|
+
:timestamp => { :name => "TIMESTAMP" },
|
434
|
+
:time => { :name => "DATE" },
|
435
|
+
:date => { :name => "DATE" },
|
436
|
+
:binary => { :name => "BLOB" },
|
437
|
+
:boolean => { :name => "NUMBER", :limit => 1 }
|
438
|
+
}
|
439
|
+
# if emulate_booleans_from_strings then store booleans in VARCHAR2
|
440
|
+
NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
|
441
|
+
:boolean => { :name => "VARCHAR2", :limit => 1 }
|
442
|
+
)
|
443
|
+
#:startdoc:
|
444
|
+
|
445
|
+
def native_database_types #:nodoc:
|
446
|
+
emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
|
447
|
+
end
|
448
|
+
|
449
|
+
# maximum length of Oracle identifiers
|
450
|
+
IDENTIFIER_MAX_LENGTH = 30
|
451
|
+
|
452
|
+
def table_alias_length #:nodoc:
|
453
|
+
IDENTIFIER_MAX_LENGTH
|
454
|
+
end
|
455
|
+
|
456
|
+
# QUOTING ==================================================
|
457
|
+
#
|
458
|
+
# see: abstract/quoting.rb
|
459
|
+
|
460
|
+
def quote_column_name(name) #:nodoc:
|
461
|
+
# camelCase column names need to be quoted; not that anyone using Oracle
|
462
|
+
# would really do this, but handling this case means we pass the test...
|
463
|
+
@quoted_column_names[name] = name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
|
464
|
+
end
|
465
|
+
|
466
|
+
# unescaped table name should start with letter and
|
467
|
+
# contain letters, digits, _, $ or #
|
468
|
+
# can be prefixed with schema name
|
469
|
+
# CamelCase table names should be quoted
|
470
|
+
def self.valid_table_name?(name) #:nodoc:
|
471
|
+
name = name.to_s
|
472
|
+
name =~ /\A([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ||
|
473
|
+
name =~ /\A([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ? true : false
|
474
|
+
end
|
475
|
+
|
476
|
+
def quote_table_name(name) #:nodoc:
|
477
|
+
# abstract_adapter calls quote_column_name from quote_table_name, so prevent that
|
478
|
+
@quoted_table_names[name] ||= if self.class.valid_table_name?(name)
|
479
|
+
name
|
480
|
+
else
|
481
|
+
"\"#{name}\""
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
def quote_string(s) #:nodoc:
|
486
|
+
s.gsub(/'/, "''")
|
487
|
+
end
|
488
|
+
|
489
|
+
def quote(value, column = nil) #:nodoc:
|
490
|
+
if value && column
|
491
|
+
case column.type
|
492
|
+
when :text, :binary
|
493
|
+
%Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
|
494
|
+
# NLS_DATE_FORMAT independent TIMESTAMP support
|
495
|
+
when :timestamp
|
496
|
+
quote_timestamp_with_to_timestamp(value)
|
497
|
+
# NLS_DATE_FORMAT independent DATE support
|
498
|
+
when :date, :time, :datetime
|
499
|
+
quote_date_with_to_date(value)
|
500
|
+
else
|
501
|
+
super
|
502
|
+
end
|
503
|
+
elsif value.acts_like?(:date)
|
504
|
+
quote_date_with_to_date(value)
|
505
|
+
elsif value.acts_like?(:time)
|
506
|
+
value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
|
507
|
+
else
|
508
|
+
super
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def quoted_true #:nodoc:
|
513
|
+
return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
|
514
|
+
"1"
|
515
|
+
end
|
516
|
+
|
517
|
+
def quoted_false #:nodoc:
|
518
|
+
return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
|
519
|
+
"0"
|
520
|
+
end
|
521
|
+
|
522
|
+
def quote_date_with_to_date(value) #:nodoc:
|
523
|
+
# should support that composite_primary_keys gem will pass date as string
|
524
|
+
value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
|
525
|
+
"TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
|
526
|
+
end
|
527
|
+
|
528
|
+
def quote_timestamp_with_to_timestamp(value) #:nodoc:
|
529
|
+
# add up to 9 digits of fractional seconds to inserted time
|
530
|
+
value = "#{quoted_date(value)}:#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
|
531
|
+
"TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS:FF6')"
|
532
|
+
end
|
533
|
+
|
534
|
+
# CONNECTION MANAGEMENT ====================================
|
535
|
+
#
|
536
|
+
|
537
|
+
# If SQL statement fails due to lost connection then reconnect
|
538
|
+
# and retry SQL statement if autocommit mode is enabled.
|
539
|
+
# By default this functionality is disabled.
|
540
|
+
attr_reader :auto_retry #:nodoc:
|
541
|
+
@auto_retry = false
|
542
|
+
|
543
|
+
def auto_retry=(value) #:nodoc:
|
544
|
+
@auto_retry = value
|
545
|
+
@connection.auto_retry = value if @connection
|
546
|
+
end
|
547
|
+
|
548
|
+
# return raw OCI8 or JDBC connection
|
549
|
+
def raw_connection
|
550
|
+
@connection.raw_connection
|
551
|
+
end
|
552
|
+
|
553
|
+
# Returns true if the connection is active.
|
554
|
+
def active? #:nodoc:
|
555
|
+
# Pings the connection to check if it's still good. Note that an
|
556
|
+
# #active? method is also available, but that simply returns the
|
557
|
+
# last known state, which isn't good enough if the connection has
|
558
|
+
# gone stale since the last use.
|
559
|
+
@connection.ping
|
560
|
+
rescue OracleEnhancedConnectionException
|
561
|
+
false
|
562
|
+
end
|
563
|
+
|
564
|
+
# Reconnects to the database.
|
565
|
+
def reconnect! #:nodoc:
|
566
|
+
@connection.reset!
|
567
|
+
rescue OracleEnhancedConnectionException => e
|
568
|
+
@logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
|
569
|
+
end
|
570
|
+
|
571
|
+
# Disconnects from the database.
|
572
|
+
def disconnect! #:nodoc:
|
573
|
+
@connection.logoff rescue nil
|
574
|
+
end
|
575
|
+
|
576
|
+
# DATABASE STATEMENTS ======================================
|
577
|
+
#
|
578
|
+
# see: abstract/database_statements.rb
|
579
|
+
|
580
|
+
# Executes a SQL statement
|
581
|
+
def execute(sql, name = nil)
|
582
|
+
# hack to pass additional "with_returning" option without changing argument list
|
583
|
+
log(sql, name) { sql.instance_variable_get(:@with_returning) ? @connection.exec_with_returning(sql) : @connection.exec(sql) }
|
584
|
+
end
|
585
|
+
|
586
|
+
# Returns an array of arrays containing the field values.
|
587
|
+
# Order is the same as that returned by #columns.
|
588
|
+
def select_rows(sql, name = nil)
|
589
|
+
# last parameter indicates to return also column list
|
590
|
+
result, columns = select(sql, name, true)
|
591
|
+
result.map{ |v| columns.map{|c| v[c]} }
|
592
|
+
end
|
593
|
+
|
594
|
+
# Executes an INSERT statement and returns the new record's ID
|
595
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
596
|
+
# if primary key value is already prefetched from sequence
|
597
|
+
# or if there is no primary key
|
598
|
+
if id_value || pk.nil?
|
599
|
+
execute(sql, name)
|
600
|
+
return id_value
|
601
|
+
end
|
602
|
+
|
603
|
+
sql_with_returning = sql.dup << @connection.returning_clause(quote_column_name(pk))
|
604
|
+
# hack to pass additional "with_returning" option without changing argument list
|
605
|
+
sql_with_returning.instance_variable_set(:@with_returning, true)
|
606
|
+
clear_query_cache
|
607
|
+
execute(sql_with_returning, name)
|
608
|
+
end
|
609
|
+
protected :insert_sql
|
610
|
+
|
611
|
+
# use in set_sequence_name to avoid fetching primary key value from sequence
|
612
|
+
AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
|
613
|
+
|
614
|
+
# Returns the next sequence value from a sequence generator. Not generally
|
615
|
+
# called directly; used by ActiveRecord to get the next primary key value
|
616
|
+
# when inserting a new database record (see #prefetch_primary_key?).
|
617
|
+
def next_sequence_value(sequence_name)
|
618
|
+
# if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
|
619
|
+
return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
|
620
|
+
select_one("SELECT #{quote_table_name(sequence_name)}.NEXTVAL id FROM dual")['id']
|
621
|
+
end
|
622
|
+
|
623
|
+
def begin_db_transaction #:nodoc:
|
624
|
+
@connection.autocommit = false
|
625
|
+
end
|
626
|
+
|
627
|
+
def commit_db_transaction #:nodoc:
|
628
|
+
@connection.commit
|
629
|
+
ensure
|
630
|
+
@connection.autocommit = true
|
631
|
+
end
|
632
|
+
|
633
|
+
def rollback_db_transaction #:nodoc:
|
634
|
+
@connection.rollback
|
635
|
+
ensure
|
636
|
+
@connection.autocommit = true
|
637
|
+
end
|
638
|
+
|
639
|
+
def create_savepoint #:nodoc:
|
640
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
641
|
+
end
|
642
|
+
|
643
|
+
def rollback_to_savepoint #:nodoc:
|
644
|
+
execute("ROLLBACK TO #{current_savepoint_name}")
|
645
|
+
end
|
646
|
+
|
647
|
+
def release_savepoint #:nodoc:
|
648
|
+
# there is no RELEASE SAVEPOINT statement in Oracle
|
649
|
+
end
|
650
|
+
|
651
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
652
|
+
# added to_i for limit and offset to protect from SQL injection
|
653
|
+
offset = (options[:offset] || 0).to_i
|
654
|
+
|
655
|
+
if limit = options[:limit]
|
656
|
+
limit = limit.to_i
|
657
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
|
658
|
+
elsif offset > 0
|
659
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
@@do_not_prefetch_primary_key = {}
|
664
|
+
|
665
|
+
# Returns true for Oracle adapter (since Oracle requires primary key
|
666
|
+
# values to be pre-fetched before insert). See also #next_sequence_value.
|
667
|
+
def prefetch_primary_key?(table_name = nil)
|
668
|
+
! @@do_not_prefetch_primary_key[table_name.to_s]
|
669
|
+
end
|
670
|
+
|
671
|
+
# used just in tests to clear prefetch primary key flag for all tables
|
672
|
+
def clear_prefetch_primary_key #:nodoc:
|
673
|
+
@@do_not_prefetch_primary_key = {}
|
674
|
+
end
|
675
|
+
|
676
|
+
# Returns default sequence name for table.
|
677
|
+
# Will take all or first 26 characters of table name and append _seq suffix
|
678
|
+
def default_sequence_name(table_name, primary_key = nil)
|
679
|
+
# TODO: remove schema prefix if present before truncating
|
680
|
+
# truncate table name if necessary to fit in max length of identifier
|
681
|
+
"#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_seq"
|
682
|
+
end
|
683
|
+
|
684
|
+
# Inserts the given fixture into the table. Overridden to properly handle lobs.
|
685
|
+
def insert_fixture(fixture, table_name) #:nodoc:
|
686
|
+
super
|
687
|
+
|
688
|
+
klass = fixture.class_name.constantize rescue nil
|
689
|
+
if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
|
690
|
+
write_lobs(table_name, klass, fixture)
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
# Writes LOB values from attributes, as indicated by the LOB columns of klass.
|
695
|
+
def write_lobs(table_name, klass, attributes) #:nodoc:
|
696
|
+
# is class with composite primary key>
|
697
|
+
is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
|
698
|
+
if is_with_cpk
|
699
|
+
id = klass.primary_key.map {|pk| attributes[pk.to_s] }
|
700
|
+
else
|
701
|
+
id = quote(attributes[klass.primary_key])
|
702
|
+
end
|
703
|
+
klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
|
704
|
+
value = attributes[col.name]
|
705
|
+
# changed sequence of next two lines - should check if value is nil before converting to yaml
|
706
|
+
next if value.nil? || (value == '')
|
707
|
+
value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
|
708
|
+
uncached do
|
709
|
+
if is_with_cpk
|
710
|
+
lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE",
|
711
|
+
'Writable Large Object')[col.name]
|
712
|
+
else
|
713
|
+
lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
|
714
|
+
'Writable Large Object')[col.name]
|
715
|
+
end
|
716
|
+
@connection.write_lob(lob, value.to_s, col.type == :binary)
|
717
|
+
end
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
# change LOB column for ORDER BY clause
|
722
|
+
# just first 100 characters are taken for ordering
|
723
|
+
def lob_order_by_expression(klass, order) #:nodoc:
|
724
|
+
return order if order.nil?
|
725
|
+
changed = false
|
726
|
+
new_order = order.to_s.strip.split(/, */).map do |order_by_col|
|
727
|
+
column_name, asc_desc = order_by_col.split(/ +/)
|
728
|
+
if column = klass.columns.detect { |col| col.name == column_name && col.sql_type =~ /LOB$/i}
|
729
|
+
changed = true
|
730
|
+
"DBMS_LOB.SUBSTR(#{column_name},100,1) #{asc_desc}"
|
731
|
+
else
|
732
|
+
order_by_col
|
733
|
+
end
|
734
|
+
end.join(', ')
|
735
|
+
changed ? new_order : order
|
736
|
+
end
|
737
|
+
|
738
|
+
# SCHEMA STATEMENTS ========================================
|
739
|
+
#
|
740
|
+
# see: abstract/schema_statements.rb
|
741
|
+
|
742
|
+
# Current database name
|
743
|
+
def current_database
|
744
|
+
select_value("select sys_context('userenv','db_name') from dual")
|
745
|
+
end
|
746
|
+
|
747
|
+
# Current database session user
|
748
|
+
def current_user
|
749
|
+
select_value("select sys_context('userenv','session_user') from dual")
|
750
|
+
end
|
751
|
+
|
752
|
+
# Default tablespace name of current user
|
753
|
+
def default_tablespace
|
754
|
+
select_value("select lower(default_tablespace) from user_users where username = sys_context('userenv','session_user')")
|
755
|
+
end
|
756
|
+
|
757
|
+
def tables(name = nil) #:nodoc:
|
758
|
+
# changed select from user_tables to all_tables - much faster in large data dictionaries
|
759
|
+
select_all("select decode(table_name,upper(table_name),lower(table_name),table_name) name from all_tables where owner = sys_context('userenv','session_user')").map {|t| t['name']}
|
760
|
+
end
|
761
|
+
|
762
|
+
cattr_accessor :all_schema_indexes #:nodoc:
|
763
|
+
|
764
|
+
# This method selects all indexes at once, and caches them in a class variable.
|
765
|
+
# Subsequent index calls get them from the variable, without going to the DB.
|
766
|
+
def indexes(table_name, name = nil) #:nodoc:
|
767
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
768
|
+
unless all_schema_indexes
|
769
|
+
default_tablespace_name = default_tablespace
|
770
|
+
result = select_all(<<-SQL)
|
771
|
+
SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(i.tablespace_name) as tablespace_name, lower(c.column_name) as column_name, e.column_expression as column_expression
|
772
|
+
FROM all_indexes#{db_link} i
|
773
|
+
JOIN all_ind_columns#{db_link} c on c.index_name = i.index_name and c.index_owner = i.owner
|
774
|
+
LEFT OUTER JOIN all_ind_expressions#{db_link} e on e.index_name = i.index_name and e.index_owner = i.owner and e.column_position = c.column_position
|
775
|
+
WHERE i.owner = '#{owner}'
|
776
|
+
AND i.table_owner = '#{owner}'
|
777
|
+
AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
|
778
|
+
ORDER BY i.index_name, c.column_position
|
779
|
+
SQL
|
780
|
+
|
781
|
+
current_index = nil
|
782
|
+
self.all_schema_indexes = []
|
783
|
+
|
784
|
+
result.each do |row|
|
785
|
+
# have to keep track of indexes because above query returns dups
|
786
|
+
# there is probably a better query we could figure out
|
787
|
+
if current_index != row['index_name']
|
788
|
+
all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE",
|
789
|
+
row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
|
790
|
+
current_index = row['index_name']
|
791
|
+
end
|
792
|
+
all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] : row['column_expression'].gsub('"','').downcase)
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
796
|
+
# Return the indexes just for the requested table, since AR is structured that way
|
797
|
+
table_name = table_name.downcase
|
798
|
+
all_schema_indexes.select{|i| i.table == table_name}
|
799
|
+
end
|
800
|
+
|
801
|
+
@@ignore_table_columns = nil #:nodoc:
|
802
|
+
|
803
|
+
# set ignored columns for table
|
804
|
+
def ignore_table_columns(table_name, *args) #:nodoc:
|
805
|
+
@@ignore_table_columns ||= {}
|
806
|
+
@@ignore_table_columns[table_name] ||= []
|
807
|
+
@@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
|
808
|
+
@@ignore_table_columns[table_name].uniq!
|
809
|
+
end
|
810
|
+
|
811
|
+
def ignored_table_columns(table_name) #:nodoc:
|
812
|
+
@@ignore_table_columns ||= {}
|
813
|
+
@@ignore_table_columns[table_name]
|
814
|
+
end
|
815
|
+
|
816
|
+
# used just in tests to clear ignored table columns
|
817
|
+
def clear_ignored_table_columns #:nodoc:
|
818
|
+
@@ignore_table_columns = nil
|
819
|
+
end
|
820
|
+
|
821
|
+
@@table_column_type = nil #:nodoc:
|
822
|
+
|
823
|
+
# set explicit type for specified table columns
|
824
|
+
def set_type_for_columns(table_name, column_type, *args) #:nodoc:
|
825
|
+
@@table_column_type ||= {}
|
826
|
+
@@table_column_type[table_name] ||= {}
|
827
|
+
args.each do |col|
|
828
|
+
@@table_column_type[table_name][col.to_s.downcase] = column_type
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
def get_type_for_column(table_name, column_name) #:nodoc:
|
833
|
+
@@table_column_type && @@table_column_type[table_name] && @@table_column_type[table_name][column_name.to_s.downcase]
|
834
|
+
end
|
835
|
+
|
836
|
+
# used just in tests to clear column data type definitions
|
837
|
+
def clear_types_for_columns #:nodoc:
|
838
|
+
@@table_column_type = nil
|
839
|
+
end
|
840
|
+
|
841
|
+
# check if table has primary key trigger with _pkt suffix
|
842
|
+
def has_primary_key_trigger?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
|
843
|
+
(owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
|
844
|
+
|
845
|
+
trigger_name = default_trigger_name(table_name).upcase
|
846
|
+
pkt_sql = <<-SQL
|
847
|
+
SELECT trigger_name
|
848
|
+
FROM all_triggers#{db_link}
|
849
|
+
WHERE owner = '#{owner}'
|
850
|
+
AND trigger_name = '#{trigger_name}'
|
851
|
+
AND table_owner = '#{owner}'
|
852
|
+
AND table_name = '#{desc_table_name}'
|
853
|
+
AND status = 'ENABLED'
|
854
|
+
SQL
|
855
|
+
select_value(pkt_sql) ? true : false
|
856
|
+
end
|
857
|
+
|
858
|
+
##
|
859
|
+
# :singleton-method:
|
860
|
+
# Cache column description between requests.
|
861
|
+
# Could be used in development environment to avoid selecting table columns from data dictionary tables for each request.
|
862
|
+
# This can speed up request processing in development mode if development database is not on local computer.
|
863
|
+
#
|
864
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
|
865
|
+
cattr_accessor :cache_columns
|
866
|
+
self.cache_columns = false
|
867
|
+
|
868
|
+
def columns(table_name, name = nil) #:nodoc:
|
869
|
+
# Don't double cache if config.cache_classes is turned on
|
870
|
+
if @@cache_columns && !(defined?(Rails) && Rails.configuration.cache_classes)
|
871
|
+
@@columns_cache ||= {}
|
872
|
+
@@columns_cache[table_name] ||= columns_without_cache(table_name, name)
|
873
|
+
else
|
874
|
+
columns_without_cache(table_name, name)
|
875
|
+
end
|
876
|
+
end
|
877
|
+
|
878
|
+
def columns_without_cache(table_name, name = nil) #:nodoc:
|
879
|
+
# get ignored_columns by original table name
|
880
|
+
ignored_columns = ignored_table_columns(table_name)
|
881
|
+
|
882
|
+
(owner, desc_table_name, db_link) = @connection.describe(table_name)
|
883
|
+
|
884
|
+
if has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
|
885
|
+
@@do_not_prefetch_primary_key[table_name] = true
|
886
|
+
end
|
887
|
+
|
888
|
+
table_cols = <<-SQL
|
889
|
+
select column_name as name, data_type as sql_type, data_default, nullable,
|
890
|
+
decode(data_type, 'NUMBER', data_precision,
|
891
|
+
'FLOAT', data_precision,
|
892
|
+
'VARCHAR2', decode(char_used, 'C', char_length, data_length),
|
893
|
+
'CHAR', decode(char_used, 'C', char_length, data_length),
|
894
|
+
null) as limit,
|
895
|
+
decode(data_type, 'NUMBER', data_scale, null) as scale
|
896
|
+
from all_tab_columns#{db_link}
|
897
|
+
where owner = '#{owner}'
|
898
|
+
and table_name = '#{desc_table_name}'
|
899
|
+
order by column_id
|
900
|
+
SQL
|
901
|
+
|
902
|
+
# added deletion of ignored columns
|
903
|
+
select_all(table_cols, name).delete_if do |row|
|
904
|
+
ignored_columns && ignored_columns.include?(row['name'].downcase)
|
905
|
+
end.map do |row|
|
906
|
+
limit, scale = row['limit'], row['scale']
|
907
|
+
if limit || scale
|
908
|
+
row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
|
909
|
+
end
|
910
|
+
|
911
|
+
# clean up odd default spacing from Oracle
|
912
|
+
if row['data_default']
|
913
|
+
row['data_default'].sub!(/^(.*?)\s*$/, '\1')
|
914
|
+
|
915
|
+
# If a default contains a newline these cleanup regexes need to
|
916
|
+
# match newlines.
|
917
|
+
row['data_default'].sub!(/^'(.*)'$/m, '\1')
|
918
|
+
row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
|
919
|
+
end
|
920
|
+
|
921
|
+
OracleEnhancedColumn.new(oracle_downcase(row['name']),
|
922
|
+
row['data_default'],
|
923
|
+
row['sql_type'],
|
924
|
+
row['nullable'] == 'Y',
|
925
|
+
# pass table name for table specific column definitions
|
926
|
+
table_name,
|
927
|
+
# pass column type if specified in class definition
|
928
|
+
get_type_for_column(table_name, oracle_downcase(row['name'])))
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
# used just in tests to clear column cache
|
933
|
+
def clear_columns_cache #:nodoc:
|
934
|
+
@@columns_cache = nil
|
935
|
+
end
|
936
|
+
|
937
|
+
# used in migrations to clear column cache for specified table
|
938
|
+
def clear_table_columns_cache(table_name)
|
939
|
+
@@columns_cache[table_name.to_s] = nil if @@cache_columns
|
940
|
+
end
|
941
|
+
|
942
|
+
##
|
943
|
+
# :singleton-method:
|
944
|
+
# Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
|
945
|
+
#
|
946
|
+
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
|
947
|
+
cattr_accessor :default_sequence_start_value
|
948
|
+
self.default_sequence_start_value = 10000
|
949
|
+
|
950
|
+
# Additional options for +create_table+ method in migration files.
|
951
|
+
#
|
952
|
+
# You can specify individual starting value in table creation migration file, e.g.:
|
953
|
+
#
|
954
|
+
# create_table :users, :sequence_start_value => 100 do |t|
|
955
|
+
# # ...
|
956
|
+
# end
|
957
|
+
#
|
958
|
+
# You can also specify other sequence definition additional parameters, e.g.:
|
959
|
+
#
|
960
|
+
# create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
|
961
|
+
# # ...
|
962
|
+
# end
|
963
|
+
#
|
964
|
+
# Create primary key trigger (so that you can skip primary key value in INSERT statement).
|
965
|
+
# By default trigger name will be "table_name_pkt", you can override the name with
|
966
|
+
# :trigger_name option (but it is not recommended to override it as then this trigger will
|
967
|
+
# not be detected by ActiveRecord model and it will still do prefetching of sequence value).
|
968
|
+
# Example:
|
969
|
+
#
|
970
|
+
# create_table :users, :primary_key_trigger => true do |t|
|
971
|
+
# # ...
|
972
|
+
# end
|
973
|
+
#
|
974
|
+
# It is possible to add table and column comments in table creation migration files:
|
975
|
+
#
|
976
|
+
# create_table :employees, :comment => “Employees and contractors” do |t|
|
977
|
+
# t.string :first_name, :comment => “Given name”
|
978
|
+
# t.string :last_name, :comment => “Surname”
|
979
|
+
# end
|
980
|
+
|
981
|
+
def create_table(name, options = {}, &block)
|
982
|
+
create_sequence = options[:id] != false
|
983
|
+
column_comments = {}
|
984
|
+
|
985
|
+
table_definition = TableDefinition.new(self)
|
986
|
+
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false
|
987
|
+
|
988
|
+
# store that primary key was defined in create_table block
|
989
|
+
unless create_sequence
|
990
|
+
class << table_definition
|
991
|
+
attr_accessor :create_sequence
|
992
|
+
def primary_key(*args)
|
993
|
+
self.create_sequence = true
|
994
|
+
super(*args)
|
995
|
+
end
|
996
|
+
end
|
997
|
+
end
|
998
|
+
|
999
|
+
# store column comments
|
1000
|
+
class << table_definition
|
1001
|
+
attr_accessor :column_comments
|
1002
|
+
def column(name, type, options = {})
|
1003
|
+
if options[:comment]
|
1004
|
+
self.column_comments ||= {}
|
1005
|
+
self.column_comments[name] = options[:comment]
|
1006
|
+
end
|
1007
|
+
super(name, type, options)
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
result = block.call(table_definition) if block
|
1012
|
+
create_sequence = create_sequence || table_definition.create_sequence
|
1013
|
+
column_comments = table_definition.column_comments if table_definition.column_comments
|
1014
|
+
|
1015
|
+
|
1016
|
+
if options[:force] && table_exists?(name)
|
1017
|
+
drop_table(name, options)
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
create_sql = "CREATE#{' GLOBAL TEMPORARY' if options[:temporary]} TABLE "
|
1021
|
+
create_sql << "#{quote_table_name(name)} ("
|
1022
|
+
create_sql << table_definition.to_sql
|
1023
|
+
create_sql << ") #{options[:options]}"
|
1024
|
+
execute create_sql
|
1025
|
+
|
1026
|
+
create_sequence_and_trigger(name, options) if create_sequence
|
1027
|
+
|
1028
|
+
add_table_comment name, options[:comment]
|
1029
|
+
column_comments.each do |column_name, comment|
|
1030
|
+
add_comment name, column_name, comment
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def rename_table(name, new_name) #:nodoc:
|
1036
|
+
execute "RENAME #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
|
1037
|
+
execute "RENAME #{quote_table_name("#{name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
def drop_table(name, options = {}) #:nodoc:
|
1041
|
+
super(name)
|
1042
|
+
seq_name = options[:sequence_name] || default_sequence_name(name)
|
1043
|
+
execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
|
1044
|
+
ensure
|
1045
|
+
clear_table_columns_cache(name)
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
# clear cached indexes when adding new index
|
1049
|
+
def add_index(table_name, column_name, options = {}) #:nodoc:
|
1050
|
+
self.all_schema_indexes = nil
|
1051
|
+
column_names = Array(column_name)
|
1052
|
+
index_name = index_name(table_name, :column => column_names)
|
1053
|
+
|
1054
|
+
if Hash === options # legacy support, since this param was a string
|
1055
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
1056
|
+
index_name = options[:name] || index_name
|
1057
|
+
tablespace = if options[:tablespace]
|
1058
|
+
" TABLESPACE #{options[:tablespace]}"
|
1059
|
+
else
|
1060
|
+
""
|
1061
|
+
end
|
1062
|
+
else
|
1063
|
+
index_type = options
|
1064
|
+
end
|
1065
|
+
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
|
1066
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace}"
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
# clear cached indexes when removing index
|
1070
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
1071
|
+
self.all_schema_indexes = nil
|
1072
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
# returned shortened index name if default is too large
|
1076
|
+
def index_name(table_name, options) #:nodoc:
|
1077
|
+
default_name = super(table_name, options)
|
1078
|
+
return default_name if default_name.length <= IDENTIFIER_MAX_LENGTH
|
1079
|
+
|
1080
|
+
# remove 'index', 'on' and 'and' keywords
|
1081
|
+
shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
|
1082
|
+
|
1083
|
+
# leave just first three letters from each word
|
1084
|
+
if shortened_name.length > IDENTIFIER_MAX_LENGTH
|
1085
|
+
shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
|
1086
|
+
end
|
1087
|
+
# generate unique name using hash function
|
1088
|
+
if shortened_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
|
1089
|
+
shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
|
1090
|
+
end
|
1091
|
+
@logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
|
1092
|
+
shortened_name
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
1096
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1097
|
+
options[:type] = type
|
1098
|
+
add_column_options!(add_column_sql, options)
|
1099
|
+
execute(add_column_sql)
|
1100
|
+
ensure
|
1101
|
+
clear_table_columns_cache(table_name)
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
1105
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
|
1106
|
+
ensure
|
1107
|
+
clear_table_columns_cache(table_name)
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
1111
|
+
column = column_for(table_name, column_name)
|
1112
|
+
|
1113
|
+
unless null || default.nil?
|
1114
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
change_column table_name, column_name, column.sql_type, :null => null
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
1121
|
+
column = column_for(table_name, column_name)
|
1122
|
+
|
1123
|
+
# remove :null option if its value is the same as current column definition
|
1124
|
+
# otherwise Oracle will raise error
|
1125
|
+
if options.has_key?(:null) && options[:null] == column.null
|
1126
|
+
options[:null] = nil
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1130
|
+
options[:type] = type
|
1131
|
+
add_column_options!(change_column_sql, options)
|
1132
|
+
execute(change_column_sql)
|
1133
|
+
ensure
|
1134
|
+
clear_table_columns_cache(table_name)
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
1138
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
|
1139
|
+
ensure
|
1140
|
+
clear_table_columns_cache(table_name)
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
def remove_column(table_name, column_name) #:nodoc:
|
1144
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
1145
|
+
ensure
|
1146
|
+
clear_table_columns_cache(table_name)
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def add_comment(table_name, column_name, comment) #:nodoc:
|
1150
|
+
return if comment.blank?
|
1151
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
def add_table_comment(table_name, comment) #:nodoc:
|
1155
|
+
return if comment.blank?
|
1156
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
def table_comment(table_name) #:nodoc:
|
1160
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
1161
|
+
select_value <<-SQL
|
1162
|
+
SELECT comments FROM all_tab_comments#{db_link}
|
1163
|
+
WHERE owner = '#{owner}'
|
1164
|
+
AND table_name = '#{table_name}'
|
1165
|
+
SQL
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
def column_comment(table_name, column_name) #:nodoc:
|
1169
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
1170
|
+
select_value <<-SQL
|
1171
|
+
SELECT comments FROM all_col_comments#{db_link}
|
1172
|
+
WHERE owner = '#{owner}'
|
1173
|
+
AND table_name = '#{table_name}'
|
1174
|
+
AND column_name = '#{column_name.upcase}'
|
1175
|
+
SQL
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
# Maps logical Rails types to Oracle-specific data types.
|
1179
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
1180
|
+
# Ignore options for :text and :binary columns
|
1181
|
+
return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
|
1182
|
+
|
1183
|
+
super
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
# Find a table's primary key and sequence.
|
1187
|
+
# *Note*: Only primary key is implemented - sequence will be nil.
|
1188
|
+
def pk_and_sequence_for(table_name) #:nodoc:
|
1189
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
1190
|
+
|
1191
|
+
# changed select from all_constraints to user_constraints - much faster in large data dictionaries
|
1192
|
+
pks = select_values(<<-SQL, 'Primary Key')
|
1193
|
+
select cc.column_name
|
1194
|
+
from user_constraints#{db_link} c, user_cons_columns#{db_link} cc
|
1195
|
+
where c.owner = '#{owner}'
|
1196
|
+
and c.table_name = '#{table_name}'
|
1197
|
+
and c.constraint_type = 'P'
|
1198
|
+
and cc.owner = c.owner
|
1199
|
+
and cc.constraint_name = c.constraint_name
|
1200
|
+
SQL
|
1201
|
+
|
1202
|
+
# only support single column keys
|
1203
|
+
pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
def structure_dump #:nodoc:
|
1207
|
+
s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
|
1208
|
+
structure << "create sequence #{seq.to_a.first.last}#{STATEMENT_TOKEN}"
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
# changed select from user_tables to all_tables - much faster in large data dictionaries
|
1212
|
+
select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
|
1213
|
+
table_name = table['table_name']
|
1214
|
+
virtual_columns = virtual_columns_for(table_name)
|
1215
|
+
ddl = "create#{ ' global temporary' if temporary_table?(table_name)} table #{table_name} (\n "
|
1216
|
+
cols = select_all(%Q{
|
1217
|
+
select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
|
1218
|
+
from user_tab_columns
|
1219
|
+
where table_name = '#{table_name}'
|
1220
|
+
order by column_id
|
1221
|
+
}).map do |row|
|
1222
|
+
if(v = virtual_columns.find {|col| col['column_name'] == row['column_name']})
|
1223
|
+
structure_dump_virtual_column(row, v['data_default'])
|
1224
|
+
else
|
1225
|
+
structure_dump_column(row)
|
1226
|
+
end
|
1227
|
+
end
|
1228
|
+
ddl << cols.join(",\n ")
|
1229
|
+
ddl << structure_dump_constraints(table_name)
|
1230
|
+
ddl << "\n)#{STATEMENT_TOKEN}"
|
1231
|
+
structure << ddl
|
1232
|
+
structure << structure_dump_indexes(table_name)
|
1233
|
+
end
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
def structure_dump_virtual_column(column, data_default) #:nodoc:
|
1237
|
+
data_default = data_default.gsub(/"/, '')
|
1238
|
+
col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
|
1239
|
+
if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
|
1240
|
+
col << "(#{column['data_precision'].to_i}"
|
1241
|
+
col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
|
1242
|
+
col << ')'
|
1243
|
+
elsif column['data_type'].include?('CHAR')
|
1244
|
+
length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
|
1245
|
+
col << "(#{length})"
|
1246
|
+
end
|
1247
|
+
col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
def structure_dump_column(column) #:nodoc:
|
1251
|
+
col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
|
1252
|
+
if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
|
1253
|
+
col << "(#{column['data_precision'].to_i}"
|
1254
|
+
col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
|
1255
|
+
col << ')'
|
1256
|
+
elsif column['data_type'].include?('CHAR')
|
1257
|
+
length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
|
1258
|
+
col << "(#{length})"
|
1259
|
+
end
|
1260
|
+
col << " default #{column['data_default']}" if !column['data_default'].nil?
|
1261
|
+
col << ' not null' if column['nullable'] == 'N'
|
1262
|
+
col
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
def structure_dump_constraints(table) #:nodoc:
|
1266
|
+
out = [structure_dump_primary_key(table), structure_dump_unique_keys(table)].flatten.compact
|
1267
|
+
out.length > 0 ? ",\n#{out.join(",\n")}" : ''
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
def structure_dump_primary_key(table) #:nodoc:
|
1271
|
+
opts = {:name => '', :cols => []}
|
1272
|
+
pks = select_all(<<-SQL, "Primary Keys")
|
1273
|
+
select a.constraint_name, a.column_name, a.position
|
1274
|
+
from user_cons_columns a
|
1275
|
+
join user_constraints c
|
1276
|
+
on a.constraint_name = c.constraint_name
|
1277
|
+
where c.table_name = '#{table.upcase}'
|
1278
|
+
and c.constraint_type = 'P'
|
1279
|
+
and c.owner = sys_context('userenv', 'session_user')
|
1280
|
+
SQL
|
1281
|
+
pks.each do |row|
|
1282
|
+
opts[:name] = row['constraint_name']
|
1283
|
+
opts[:cols][row['position']-1] = row['column_name']
|
1284
|
+
end
|
1285
|
+
opts[:cols].length > 0 ? " CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : nil
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
def structure_dump_unique_keys(table) #:nodoc:
|
1289
|
+
keys = {}
|
1290
|
+
uks = select_all(<<-SQL, "Primary Keys")
|
1291
|
+
select a.constraint_name, a.column_name, a.position
|
1292
|
+
from user_cons_columns a
|
1293
|
+
join user_constraints c
|
1294
|
+
on a.constraint_name = c.constraint_name
|
1295
|
+
where c.table_name = '#{table.upcase}'
|
1296
|
+
and c.constraint_type = 'U'
|
1297
|
+
and c.owner = sys_context('userenv', 'session_user')
|
1298
|
+
SQL
|
1299
|
+
uks.each do |uk|
|
1300
|
+
keys[uk['constraint_name']] ||= []
|
1301
|
+
keys[uk['constraint_name']][uk['position']-1] = uk['column_name']
|
1302
|
+
end
|
1303
|
+
keys.map do |k,v|
|
1304
|
+
" CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
|
1305
|
+
end
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
def structure_dump_fk_constraints #:nodoc:
|
1309
|
+
fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
|
1310
|
+
if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
|
1311
|
+
foreign_keys.map do |fk|
|
1312
|
+
column = fk.options[:column] || "#{fk.to_table.to_s.singularize}_id"
|
1313
|
+
constraint_name = foreign_key_constraint_name(fk.from_table, column, fk.options)
|
1314
|
+
sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
|
1315
|
+
sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
|
1316
|
+
end
|
1317
|
+
end
|
1318
|
+
end.flatten.compact.join(STATEMENT_TOKEN)
|
1319
|
+
fks.length > 1 ? "#{fks}#{STATEMENT_TOKEN}" : ''
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
# Extract all stored procedures, packages, synonyms and views.
|
1323
|
+
def structure_dump_db_stored_code #:nodoc:
|
1324
|
+
structure = ""
|
1325
|
+
select_all("select distinct name, type
|
1326
|
+
from all_source
|
1327
|
+
where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
|
1328
|
+
and owner = sys_context('userenv','session_user') order by type").each do |source|
|
1329
|
+
ddl = "create or replace \n "
|
1330
|
+
lines = select_all(%Q{
|
1331
|
+
select text
|
1332
|
+
from all_source
|
1333
|
+
where name = '#{source['name']}'
|
1334
|
+
and type = '#{source['type']}'
|
1335
|
+
and owner = sys_context('userenv','session_user')
|
1336
|
+
order by line
|
1337
|
+
}).map do |row|
|
1338
|
+
ddl << row['text'] if row['text'].size > 1
|
1339
|
+
end
|
1340
|
+
ddl << ";" unless ddl.strip.last == ";"
|
1341
|
+
structure << ddl << STATEMENT_TOKEN
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
# export views
|
1345
|
+
select_all("select view_name, text from user_views").each do |view|
|
1346
|
+
ddl = "create or replace view #{view['view_name']} AS\n "
|
1347
|
+
# any views with empty lines will cause OCI to barf when loading. remove blank lines =/
|
1348
|
+
ddl << view['text'].gsub(/^\n/, '')
|
1349
|
+
structure << ddl << STATEMENT_TOKEN
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
# export synonyms
|
1353
|
+
select_all("select owner, synonym_name, table_name, table_owner
|
1354
|
+
from all_synonyms
|
1355
|
+
where owner = sys_context('userenv','session_user') ").each do |synonym|
|
1356
|
+
ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']}"
|
1357
|
+
structure << ddl << STATEMENT_TOKEN
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
structure
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
def structure_dump_indexes(table_name) #:nodoc:
|
1364
|
+
statements = indexes(table_name).map do |options|
|
1365
|
+
#def add_index(table_name, column_name, options = {})
|
1366
|
+
column_names = options[:columns]
|
1367
|
+
options = {:name => options[:name], :unique => options[:unique]}
|
1368
|
+
index_name = index_name(table_name, :column => column_names)
|
1369
|
+
if Hash === options # legacy support, since this param was a string
|
1370
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
1371
|
+
index_name = options[:name] || index_name
|
1372
|
+
else
|
1373
|
+
index_type = options
|
1374
|
+
end
|
1375
|
+
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
|
1376
|
+
"CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
|
1377
|
+
end
|
1378
|
+
statements.length > 0 ? "#{statements.join(STATEMENT_TOKEN)}#{STATEMENT_TOKEN}" : ''
|
1379
|
+
end
|
1380
|
+
|
1381
|
+
def structure_drop #:nodoc:
|
1382
|
+
s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
|
1383
|
+
drop << "drop sequence #{seq.to_a.first.last};\n\n"
|
1384
|
+
end
|
1385
|
+
|
1386
|
+
# changed select from user_tables to all_tables - much faster in large data dictionaries
|
1387
|
+
select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
|
1388
|
+
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
1389
|
+
end
|
1390
|
+
end
|
1391
|
+
|
1392
|
+
def temp_table_drop #:nodoc:
|
1393
|
+
# changed select from user_tables to all_tables - much faster in large data dictionaries
|
1394
|
+
select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') and temporary = 'Y' order by 1").inject('') do |drop, table|
|
1395
|
+
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
1396
|
+
end
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
def full_drop(preserve_tables=false) #:nodoc:
|
1400
|
+
s = preserve_tables ? [] : [structure_drop]
|
1401
|
+
s << temp_table_drop if preserve_tables
|
1402
|
+
s << drop_sql_for_feature("view")
|
1403
|
+
s << drop_sql_for_feature("synonym")
|
1404
|
+
s << drop_sql_for_feature("type")
|
1405
|
+
s << drop_sql_for_object("package")
|
1406
|
+
s << drop_sql_for_object("function")
|
1407
|
+
s << drop_sql_for_object("procedure")
|
1408
|
+
s.join("\n\n")
|
1409
|
+
end
|
1410
|
+
|
1411
|
+
def add_column_options!(sql, options) #:nodoc:
|
1412
|
+
type = options[:type] || ((column = options[:column]) && column.type)
|
1413
|
+
type = type && type.to_sym
|
1414
|
+
# handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
|
1415
|
+
if options_include_default?(options)
|
1416
|
+
if type == :text
|
1417
|
+
sql << " DEFAULT #{quote(options[:default])}"
|
1418
|
+
else
|
1419
|
+
# from abstract adapter
|
1420
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}"
|
1421
|
+
end
|
1422
|
+
end
|
1423
|
+
# must explicitly add NULL or NOT NULL to allow change_column to work on migrations
|
1424
|
+
if options[:null] == false
|
1425
|
+
sql << " NOT NULL"
|
1426
|
+
elsif options[:null] == true
|
1427
|
+
sql << " NULL" unless type == :primary_key
|
1428
|
+
end
|
1429
|
+
end
|
1430
|
+
|
1431
|
+
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
1432
|
+
#
|
1433
|
+
# Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
|
1434
|
+
# queries. However, with those columns included in the SELECT DISTINCT list, you
|
1435
|
+
# won't actually get a distinct list of the column you want (presuming the column
|
1436
|
+
# has duplicates with multiple values for the ordered-by columns. So we use the
|
1437
|
+
# FIRST_VALUE function to get a single (first) value for each column, effectively
|
1438
|
+
# making every row the same.
|
1439
|
+
#
|
1440
|
+
# distinct("posts.id", "posts.created_at desc")
|
1441
|
+
def distinct(columns, order_by) #:nodoc:
|
1442
|
+
return "DISTINCT #{columns}" if order_by.blank?
|
1443
|
+
|
1444
|
+
# construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
|
1445
|
+
# FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
|
1446
|
+
order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
|
1447
|
+
order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
|
1448
|
+
"FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
|
1449
|
+
end
|
1450
|
+
sql = "DISTINCT #{columns}, "
|
1451
|
+
sql << order_columns * ", "
|
1452
|
+
end
|
1453
|
+
|
1454
|
+
def temporary_table?(table_name) #:nodoc:
|
1455
|
+
select_value("select temporary from user_tables where table_name = '#{table_name.upcase}'") == 'Y'
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
# statements separator used in structure dump
|
1459
|
+
STATEMENT_TOKEN = "\n\n--@@@--\n\n"
|
1460
|
+
|
1461
|
+
# ORDER BY clause for the passed order option.
|
1462
|
+
#
|
1463
|
+
# Uses column aliases as defined by #distinct.
|
1464
|
+
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
|
1465
|
+
return sql if options[:order].blank?
|
1466
|
+
|
1467
|
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
1468
|
+
order.map! {|s| $1 if s =~ / (.*)/}
|
1469
|
+
order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
|
1470
|
+
|
1471
|
+
sql << " ORDER BY #{order}"
|
1472
|
+
end
|
1473
|
+
|
1474
|
+
protected
|
1475
|
+
|
1476
|
+
def translate_exception(exception, message) #:nodoc:
|
1477
|
+
case @connection.error_code(exception)
|
1478
|
+
when 1
|
1479
|
+
RecordNotUnique.new(message, exception)
|
1480
|
+
when 2291
|
1481
|
+
InvalidForeignKey.new(message, exception)
|
1482
|
+
else
|
1483
|
+
super
|
1484
|
+
end
|
1485
|
+
end
|
1486
|
+
|
1487
|
+
private
|
1488
|
+
|
1489
|
+
def select(sql, name = nil, return_column_names = false)
|
1490
|
+
log(sql, name) do
|
1491
|
+
@connection.select(sql, name, return_column_names)
|
1492
|
+
end
|
1493
|
+
end
|
1494
|
+
|
1495
|
+
def oracle_downcase(column_name)
|
1496
|
+
@connection.oracle_downcase(column_name)
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
def column_for(table_name, column_name)
|
1500
|
+
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
1501
|
+
raise "No such column: #{table_name}.#{column_name}"
|
1502
|
+
end
|
1503
|
+
column
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
def create_sequence_and_trigger(table_name, options)
|
1507
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
1508
|
+
seq_start_value = options[:sequence_start_value] || default_sequence_start_value
|
1509
|
+
execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
|
1510
|
+
|
1511
|
+
create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
|
1512
|
+
end
|
1513
|
+
|
1514
|
+
def create_primary_key_trigger(table_name, options)
|
1515
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
1516
|
+
trigger_name = options[:trigger_name] || default_trigger_name(table_name)
|
1517
|
+
primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
|
1518
|
+
execute compress_lines(<<-SQL)
|
1519
|
+
CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
|
1520
|
+
BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
|
1521
|
+
BEGIN
|
1522
|
+
IF inserting THEN
|
1523
|
+
IF :new.#{quote_column_name(primary_key)} IS NULL THEN
|
1524
|
+
SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
|
1525
|
+
END IF;
|
1526
|
+
END IF;
|
1527
|
+
END;
|
1528
|
+
SQL
|
1529
|
+
end
|
1530
|
+
|
1531
|
+
def default_trigger_name(table_name)
|
1532
|
+
# truncate table name if necessary to fit in max length of identifier
|
1533
|
+
"#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_pkt"
|
1534
|
+
end
|
1535
|
+
|
1536
|
+
def compress_lines(string, spaced = true)
|
1537
|
+
string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
|
1538
|
+
end
|
1539
|
+
|
1540
|
+
# virtual columns are an 11g feature. This returns [] if feature is not
|
1541
|
+
# present or none are found.
|
1542
|
+
# return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
|
1543
|
+
def virtual_columns_for(table)
|
1544
|
+
begin
|
1545
|
+
select_all <<-SQL
|
1546
|
+
select column_name, data_default
|
1547
|
+
from user_tab_cols
|
1548
|
+
where virtual_column='YES'
|
1549
|
+
and table_name='#{table.upcase}'
|
1550
|
+
SQL
|
1551
|
+
# feature not supported previous to 11g
|
1552
|
+
rescue ActiveRecord::StatementInvalid => e
|
1553
|
+
[]
|
1554
|
+
end
|
1555
|
+
end
|
1556
|
+
|
1557
|
+
def drop_sql_for_feature(type)
|
1558
|
+
select_values("select 'DROP #{type.upcase} \"' || #{type}_name || '\";' from user_#{type.tableize}").join("\n\n")
|
1559
|
+
end
|
1560
|
+
|
1561
|
+
def drop_sql_for_object(type)
|
1562
|
+
select_values("select 'DROP #{type.upcase} ' || object_name || ';' from user_objects where object_type = '#{type.upcase}'").join("\n\n")
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
public
|
1566
|
+
# DBMS_OUTPUT =============================================
|
1567
|
+
#
|
1568
|
+
# PL/SQL in Oracle uses dbms_output for logging print statements
|
1569
|
+
# These methods stick that output into the Rails log so Ruby and PL/SQL
|
1570
|
+
# code can can be debugged together in a single application
|
1571
|
+
|
1572
|
+
# Maximum DBMS_OUTPUT buffer size
|
1573
|
+
DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
|
1574
|
+
|
1575
|
+
# Turn DBMS_Output logging on
|
1576
|
+
def enable_dbms_output
|
1577
|
+
set_dbms_output_plsql_connection
|
1578
|
+
@enable_dbms_output = true
|
1579
|
+
plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
|
1580
|
+
end
|
1581
|
+
# Turn DBMS_Output logging off
|
1582
|
+
def disable_dbms_output
|
1583
|
+
set_dbms_output_plsql_connection
|
1584
|
+
@enable_dbms_output = false
|
1585
|
+
plsql(:dbms_output).sys.dbms_output.disable
|
1586
|
+
end
|
1587
|
+
# Is DBMS_Output logging enabled?
|
1588
|
+
def dbms_output_enabled?
|
1589
|
+
@enable_dbms_output
|
1590
|
+
end
|
1591
|
+
|
1592
|
+
protected
|
1593
|
+
def log(sql, name) #:nodoc:
|
1594
|
+
super sql, name
|
1595
|
+
ensure
|
1596
|
+
log_dbms_output if dbms_output_enabled?
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
private
|
1600
|
+
|
1601
|
+
def set_dbms_output_plsql_connection
|
1602
|
+
raise OracleEnhancedConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
|
1603
|
+
# do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
|
1604
|
+
unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
|
1605
|
+
plsql(:dbms_output).connection = raw_connection
|
1606
|
+
end
|
1607
|
+
end
|
1608
|
+
|
1609
|
+
def log_dbms_output
|
1610
|
+
while true do
|
1611
|
+
result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
|
1612
|
+
break unless result[:status] == 0
|
1613
|
+
@logger.debug "DBMS_OUTPUT: #{result[:line]}"
|
1614
|
+
end
|
1615
|
+
end
|
1616
|
+
|
1617
|
+
end
|
1618
|
+
end
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
# Added LOB writing callback for sessions stored in database
|
1622
|
+
# Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
|
1623
|
+
if defined?(CGI::Session::ActiveRecordStore::Session)
|
1624
|
+
if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
|
1625
|
+
CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
|
1626
|
+
#:stopdoc:
|
1627
|
+
class CGI::Session::ActiveRecordStore::Session
|
1628
|
+
after_save :enhanced_write_lobs
|
1629
|
+
end
|
1630
|
+
#:startdoc:
|
1631
|
+
end
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
# Load custom create, update, delete methods functionality
|
1635
|
+
require 'active_record/connection_adapters/oracle_enhanced_procedures'
|
1636
|
+
|
1637
|
+
# Load additional methods for composite_primary_keys support
|
1638
|
+
require 'active_record/connection_adapters/oracle_enhanced_cpk'
|
1639
|
+
|
1640
|
+
# Load patch for dirty tracking methods
|
1641
|
+
require 'active_record/connection_adapters/oracle_enhanced_dirty'
|
1642
|
+
|
1643
|
+
# Load rake tasks definitions
|
1644
|
+
begin
|
1645
|
+
require 'active_record/connection_adapters/oracle_enhanced_tasks'
|
1646
|
+
rescue LoadError
|
1647
|
+
end if defined?(RAILS_ROOT)
|
1648
|
+
|
1649
|
+
# Handles quoting of oracle reserved words
|
1650
|
+
require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
|
1651
|
+
|
1652
|
+
# Patches and enhancements for schema dumper
|
1653
|
+
require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
|
1654
|
+
|
1655
|
+
# Extensions for schema definition statements
|
1656
|
+
require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext'
|
1657
|
+
|
1658
|
+
# Extensions for schema definition
|
1659
|
+
require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
|
1660
|
+
|
1661
|
+
# Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
|
1662
|
+
require 'active_record/connection_adapters/oracle_enhanced_core_ext'
|
1663
|
+
|
1664
|
+
require 'active_record/connection_adapters/oracle_enhanced_version'
|