activerecord-oracle_enhanced-adapter 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. data/.gitignore +3 -1
  2. data/Gemfile +37 -0
  3. data/History.txt +17 -0
  4. data/README.rdoc +9 -4
  5. data/RUNNING_TESTS.rdoc +28 -0
  6. data/VERSION +1 -1
  7. data/activerecord-oracle_enhanced-adapter.gemspec +13 -6
  8. data/lib/active_record/connection_adapters/oracle_enhanced.rake +32 -24
  9. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +19 -793
  10. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +79 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +106 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +32 -2
  13. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +3 -44
  14. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +2 -2
  15. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +4 -2
  16. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +19 -3
  17. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +14 -6
  18. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +331 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +46 -14
  20. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +290 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +15 -10
  22. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  23. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +51 -19
  24. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +1 -1
  25. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +31 -2
  26. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +152 -11
  27. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +27 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +19 -1
  29. data/spec/active_record/connection_adapters/{oracle_enhanced_schema_spec.rb → oracle_enhanced_schema_statements_spec.rb} +44 -1
  30. data/spec/active_record/connection_adapters/{oracle_enhanced_adapter_structure_dumper_spec.rb → oracle_enhanced_structure_dump_spec.rb} +49 -1
  31. data/spec/spec_helper.rb +60 -53
  32. metadata +15 -8
@@ -52,31 +52,51 @@ module ActiveRecord
52
52
  # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id)
53
53
  # ON DELETE SET NULL
54
54
  #
55
+ # ==== Creating a composite foreign key
56
+ # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk')
57
+ # generates
58
+ # ALTER TABLE comments ADD CONSTRAINT
59
+ # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id)
60
+ #
55
61
  # === Supported options
56
62
  # [:column]
57
63
  # Specify the column name on the from_table that references the to_table. By default this is guessed
58
64
  # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
59
65
  # as the default <tt>:column</tt>.
66
+ # [:columns]
67
+ # An array of column names when defining composite foreign keys. An alias of <tt>:column</tt> provided for improved readability.
60
68
  # [:primary_key]
61
69
  # Specify the column name on the to_table that is referenced by this foreign key. By default this is
62
- # assumed to be "id".
70
+ # assumed to be "id". Ignored when defining composite foreign keys.
63
71
  # [:name]
64
72
  # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
65
73
  # [:dependent]
66
74
  # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
67
75
  # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
68
76
  def add_foreign_key(from_table, to_table, options = {})
69
- column = options[:column] || "#{to_table.to_s.singularize}_id"
70
- constraint_name = foreign_key_constraint_name(from_table, column, options)
77
+ columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id"
78
+ constraint_name = foreign_key_constraint_name(from_table, columns, options)
71
79
  sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
72
80
  sql << foreign_key_definition(to_table, options)
73
81
  execute sql
74
82
  end
75
83
 
76
84
  def foreign_key_definition(to_table, options = {}) #:nodoc:
77
- column = options[:column] || "#{to_table.to_s.singularize}_id"
78
- primary_key = options[:primary_key] || "id"
79
- sql = "FOREIGN KEY (#{quote_column_name(column)}) REFERENCES #{quote_table_name(to_table)}(#{primary_key})"
85
+ columns = Array(options[:column] || options[:columns])
86
+
87
+ if columns.size > 1
88
+ # composite foreign key
89
+ columns_sql = columns.map {|c| quote_column_name(c)}.join(',')
90
+ references = options[:references] || columns
91
+ references_sql = references.map {|c| quote_column_name(c)}.join(',')
92
+ else
93
+ columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id")
94
+ references = options[:references] ? options[:references].first : nil
95
+ references_sql = quote_column_name(options[:primary_key] || references || "id")
96
+ end
97
+
98
+ sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(to_table)}(#{references_sql})"
99
+
80
100
  case options[:dependent]
81
101
  when :nullify
82
102
  sql << " ON DELETE SET NULL"
@@ -106,9 +126,12 @@ module ActiveRecord
106
126
 
107
127
  private
108
128
 
109
- def foreign_key_constraint_name(table_name, column, options = {})
110
- constraint_name = original_name = options[:name] || "#{table_name}_#{column}_fk"
129
+ def foreign_key_constraint_name(table_name, columns, options = {})
130
+ columns = Array(columns)
131
+ constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk"
132
+
111
133
  return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
134
+
112
135
  # leave just first three letters from each word
113
136
  constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_')
114
137
  # generate unique name using hash function
@@ -128,7 +151,7 @@ module ActiveRecord
128
151
 
129
152
  fk_info = select_all(<<-SQL, 'Foreign Keys')
130
153
  SELECT r.table_name to_table
131
- ,rc.column_name primary_key
154
+ ,rc.column_name references_column
132
155
  ,cc.column_name
133
156
  ,c.constraint_name name
134
157
  ,c.delete_rule
@@ -144,18 +167,27 @@ module ActiveRecord
144
167
  AND rc.owner = r.owner
145
168
  AND rc.constraint_name = r.constraint_name
146
169
  AND rc.position = cc.position
170
+ ORDER BY name, to_table, column_name, references_column
147
171
  SQL
148
172
 
173
+ fks = {}
174
+
149
175
  fk_info.map do |row|
150
- options = {:column => oracle_downcase(row['column_name']), :name => oracle_downcase(row['name']),
151
- :primary_key => oracle_downcase(row['primary_key'])}
176
+ name = oracle_downcase(row['name'])
177
+ fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] }
178
+ fks[name][:columns] << oracle_downcase(row['column_name'])
179
+ fks[name][:references] << oracle_downcase(row['references_column'])
152
180
  case row['delete_rule']
153
181
  when 'CASCADE'
154
- options[:dependent] = :delete
182
+ fks[name][:dependent] = :delete
155
183
  when 'SET NULL'
156
- options[:dependent] = :nullify
184
+ fks[name][:dependent] = :nullify
157
185
  end
158
- OracleEnhancedForeignKeyDefinition.new(table_name, oracle_downcase(row['to_table']), options)
186
+ end
187
+
188
+ fks.map do |k, v|
189
+ options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]}
190
+ OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options)
159
191
  end
160
192
  end
161
193
 
@@ -0,0 +1,290 @@
1
+ module ActiveRecord #:nodoc:
2
+ module ConnectionAdapters #:nodoc:
3
+ module OracleEnhancedStructureDump #:nodoc:
4
+
5
+ # Statements separator used in structure dump to allow loading of structure dump also with SQL*Plus
6
+ STATEMENT_TOKEN = "\n\n/\n\n"
7
+
8
+ def structure_dump #:nodoc:
9
+ structure = select_values("select sequence_name from user_sequences order by 1").map do |seq|
10
+ "CREATE SEQUENCE \"#{seq}\""
11
+ end
12
+ select_values("select table_name from all_tables t
13
+ where owner = sys_context('userenv','session_user') and secondary='N'
14
+ and not exists (select mv.mview_name from all_mviews mv where mv.owner = t.owner and mv.mview_name = t.table_name)
15
+ and not exists (select mvl.log_table from all_mview_logs mvl where mvl.log_owner = t.owner and mvl.log_table = t.table_name)
16
+ order by 1").each do |table_name|
17
+ virtual_columns = virtual_columns_for(table_name)
18
+ ddl = "CREATE#{ ' GLOBAL TEMPORARY' if temporary_table?(table_name)} TABLE \"#{table_name}\" (\n"
19
+ cols = select_all(%Q{
20
+ select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
21
+ from user_tab_columns
22
+ where table_name = '#{table_name}'
23
+ order by column_id
24
+ }).map do |row|
25
+ if(v = virtual_columns.find {|col| col['column_name'] == row['column_name']})
26
+ structure_dump_virtual_column(row, v['data_default'])
27
+ else
28
+ structure_dump_column(row)
29
+ end
30
+ end
31
+ ddl << cols.join(",\n ")
32
+ ddl << structure_dump_primary_key(table_name)
33
+ ddl << "\n)"
34
+ structure << ddl
35
+ structure << structure_dump_indexes(table_name)
36
+ structure << structure_dump_unique_keys(table_name)
37
+ end
38
+
39
+ join_with_statement_token(structure) << structure_dump_fk_constraints
40
+ end
41
+
42
+ def structure_dump_column(column) #:nodoc:
43
+ col = "\"#{column['column_name']}\" #{column['data_type']}"
44
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
45
+ col << "(#{column['data_precision'].to_i}"
46
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
47
+ col << ')'
48
+ elsif column['data_type'].include?('CHAR')
49
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
50
+ col << "(#{length})"
51
+ end
52
+ col << " DEFAULT #{column['data_default']}" if !column['data_default'].nil?
53
+ col << ' NOT NULL' if column['nullable'] == 'N'
54
+ col
55
+ end
56
+
57
+ def structure_dump_virtual_column(column, data_default) #:nodoc:
58
+ data_default = data_default.gsub(/"/, '')
59
+ col = "\"#{column['column_name']}\" #{column['data_type']}"
60
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
61
+ col << "(#{column['data_precision'].to_i}"
62
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
63
+ col << ')'
64
+ elsif column['data_type'].include?('CHAR')
65
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
66
+ col << "(#{length})"
67
+ end
68
+ col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
69
+ end
70
+
71
+ def structure_dump_primary_key(table) #:nodoc:
72
+ opts = {:name => '', :cols => []}
73
+ pks = select_all(<<-SQL, "Primary Keys")
74
+ select a.constraint_name, a.column_name, a.position
75
+ from user_cons_columns a
76
+ join user_constraints c
77
+ on a.constraint_name = c.constraint_name
78
+ where c.table_name = '#{table.upcase}'
79
+ and c.constraint_type = 'P'
80
+ and c.owner = sys_context('userenv', 'session_user')
81
+ SQL
82
+ pks.each do |row|
83
+ opts[:name] = row['constraint_name']
84
+ opts[:cols][row['position']-1] = row['column_name']
85
+ end
86
+ opts[:cols].length > 0 ? ",\n CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : ''
87
+ end
88
+
89
+ def structure_dump_unique_keys(table) #:nodoc:
90
+ keys = {}
91
+ uks = select_all(<<-SQL, "Primary Keys")
92
+ select a.constraint_name, a.column_name, a.position
93
+ from user_cons_columns a
94
+ join user_constraints c
95
+ on a.constraint_name = c.constraint_name
96
+ where c.table_name = '#{table.upcase}'
97
+ and c.constraint_type = 'U'
98
+ and c.owner = sys_context('userenv', 'session_user')
99
+ SQL
100
+ uks.each do |uk|
101
+ keys[uk['constraint_name']] ||= []
102
+ keys[uk['constraint_name']][uk['position']-1] = uk['column_name']
103
+ end
104
+ keys.map do |k,v|
105
+ "ALTER TABLE #{table.upcase} ADD CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
106
+ end
107
+ end
108
+
109
+ def structure_dump_indexes(table_name) #:nodoc:
110
+ indexes(table_name).map do |options|
111
+ column_names = options[:columns]
112
+ options = {:name => options[:name], :unique => options[:unique]}
113
+ index_name = index_name(table_name, :column => column_names)
114
+ if Hash === options # legacy support, since this param was a string
115
+ index_type = options[:unique] ? "UNIQUE" : ""
116
+ index_name = options[:name] || index_name
117
+ else
118
+ index_type = options
119
+ end
120
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
121
+ "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
122
+ end
123
+ end
124
+
125
+ def structure_dump_fk_constraints #:nodoc:
126
+ fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
127
+ if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
128
+ foreign_keys.map do |fk|
129
+ sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(fk.options[:name])} "
130
+ sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
131
+ end
132
+ end
133
+ end.flatten.compact
134
+ join_with_statement_token(fks)
135
+ end
136
+
137
+ def dump_schema_information #:nodoc:
138
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
139
+ migrated = select_values("SELECT version FROM #{sm_table}")
140
+ join_with_statement_token(migrated.map{|v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" })
141
+ end
142
+
143
+ # Extract all stored procedures, packages, synonyms and views.
144
+ def structure_dump_db_stored_code #:nodoc:
145
+ structure = []
146
+ select_all("select distinct name, type
147
+ from all_source
148
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
149
+ and name not like 'BIN$%'
150
+ and owner = sys_context('userenv','session_user') order by type").each do |source|
151
+ ddl = "CREATE OR REPLACE \n"
152
+ lines = select_all(%Q{
153
+ select text
154
+ from all_source
155
+ where name = '#{source['name']}'
156
+ and type = '#{source['type']}'
157
+ and owner = sys_context('userenv','session_user')
158
+ order by line
159
+ }).map do |row|
160
+ ddl << row['text']
161
+ end
162
+ ddl << ";" unless ddl.strip[-1,1] == ";"
163
+ structure << ddl
164
+ end
165
+
166
+ # export views
167
+ select_all("select view_name, text from user_views").each do |view|
168
+ structure << "CREATE OR REPLACE VIEW #{view['view_name']} AS\n #{view['text']}"
169
+ end
170
+
171
+ # export synonyms
172
+ select_all("select owner, synonym_name, table_name, table_owner
173
+ from all_synonyms
174
+ where owner = sys_context('userenv','session_user') ").each do |synonym|
175
+ structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']}"
176
+ structure << " FOR #{synonym['table_owner']}.#{synonym['table_name']}"
177
+ end
178
+
179
+ join_with_statement_token(structure)
180
+ end
181
+
182
+ def structure_drop #:nodoc:
183
+ statements = select_values("select sequence_name from user_sequences order by 1").map do |seq|
184
+ "DROP SEQUENCE \"#{seq}\""
185
+ end
186
+ select_values("select table_name from all_tables t
187
+ where owner = sys_context('userenv','session_user') and secondary='N'
188
+ and not exists (select mv.mview_name from all_mviews mv where mv.owner = t.owner and mv.mview_name = t.table_name)
189
+ and not exists (select mvl.log_table from all_mview_logs mvl where mvl.log_owner = t.owner and mvl.log_table = t.table_name)
190
+ order by 1").each do |table|
191
+ statements << "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
192
+ end
193
+ join_with_statement_token(statements)
194
+ end
195
+
196
+ def temp_table_drop #:nodoc:
197
+ join_with_statement_token(select_values(
198
+ "select table_name from all_tables
199
+ where owner = sys_context('userenv','session_user') and secondary='N' and temporary = 'Y' order by 1").map do |table|
200
+ "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
201
+ end)
202
+ end
203
+
204
+ def full_drop(preserve_tables=false) #:nodoc:
205
+ s = preserve_tables ? [] : [structure_drop]
206
+ s << temp_table_drop if preserve_tables
207
+ s << drop_sql_for_feature("view")
208
+ s << drop_sql_for_feature("materialized view")
209
+ s << drop_sql_for_feature("synonym")
210
+ s << drop_sql_for_feature("type")
211
+ s << drop_sql_for_object("package")
212
+ s << drop_sql_for_object("function")
213
+ s << drop_sql_for_object("procedure")
214
+ s.join
215
+ end
216
+
217
+ def add_column_options!(sql, options) #:nodoc:
218
+ type = options[:type] || ((column = options[:column]) && column.type)
219
+ type = type && type.to_sym
220
+ # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
221
+ if options_include_default?(options)
222
+ if type == :text
223
+ sql << " DEFAULT #{quote(options[:default])}"
224
+ else
225
+ # from abstract adapter
226
+ sql << " DEFAULT #{quote(options[:default], options[:column])}"
227
+ end
228
+ end
229
+ # must explicitly add NULL or NOT NULL to allow change_column to work on migrations
230
+ if options[:null] == false
231
+ sql << " NOT NULL"
232
+ elsif options[:null] == true
233
+ sql << " NULL" unless type == :primary_key
234
+ end
235
+ end
236
+
237
+ def execute_structure_dump(string)
238
+ string.split(STATEMENT_TOKEN).each do |ddl|
239
+ ddl.chop! if ddl.last == ";"
240
+ execute(ddl) unless ddl.blank?
241
+ end
242
+ end
243
+
244
+ private
245
+
246
+ # virtual columns are an 11g feature. This returns [] if feature is not
247
+ # present or none are found.
248
+ # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
249
+ def virtual_columns_for(table)
250
+ begin
251
+ select_all <<-SQL
252
+ select column_name, data_default
253
+ from user_tab_cols
254
+ where virtual_column='YES'
255
+ and table_name='#{table.upcase}'
256
+ SQL
257
+ # feature not supported previous to 11g
258
+ rescue ActiveRecord::StatementInvalid => e
259
+ []
260
+ end
261
+ end
262
+
263
+ def drop_sql_for_feature(type)
264
+ short_type = type == 'materialized view' ? 'mview' : type
265
+ join_with_statement_token(
266
+ select_values("select #{short_type}_name from user_#{short_type.tableize}").map do |name|
267
+ "DROP #{type.upcase} \"#{name}\""
268
+ end)
269
+ end
270
+
271
+ def drop_sql_for_object(type)
272
+ join_with_statement_token(
273
+ select_values("select object_name from user_objects where object_type = '#{type.upcase}'").map do |name|
274
+ "DROP #{type.upcase} \"#{name}\""
275
+ end)
276
+ end
277
+
278
+ def join_with_statement_token(array)
279
+ string = array.join(STATEMENT_TOKEN)
280
+ string << STATEMENT_TOKEN unless string.blank?
281
+ string
282
+ end
283
+
284
+ end
285
+ end
286
+ end
287
+
288
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
289
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedStructureDump
290
+ end
@@ -1,12 +1,17 @@
1
- # implementation idea taken from JDBC adapter
2
- if defined?(Rake.application) && Rake.application &&
3
- ActiveRecord::Base.configurations[defined?(Rails.env) ? Rails.env : RAILS_ENV]['adapter'] == 'oracle_enhanced'
4
- oracle_enhanced_rakefile = File.dirname(__FILE__) + "/oracle_enhanced.rake"
5
- if Rake.application.lookup("environment")
6
- # rails tasks already defined; load the override tasks now
7
- load oracle_enhanced_rakefile
8
- else
9
- # rails tasks not loaded yet; load as an import
10
- Rake.application.add_import(oracle_enhanced_rakefile)
1
+ # Used just for Rails 2.x
2
+ # In Rails 3.x rake tasks are loaded using railtie
3
+ if ActiveRecord::VERSION::MAJOR == 2
4
+
5
+ if defined?(Rake.application) && Rake.application &&
6
+ ActiveRecord::Base.configurations[defined?(Rails.env) ? Rails.env : RAILS_ENV]['adapter'] == 'oracle_enhanced'
7
+ oracle_enhanced_rakefile = File.dirname(__FILE__) + "/oracle_enhanced.rake"
8
+ if Rake.application.lookup("environment")
9
+ # rails tasks already defined; load the override tasks now
10
+ load oracle_enhanced_rakefile
11
+ else
12
+ # rails tasks not loaded yet; load as an import
13
+ Rake.application.add_import(oracle_enhanced_rakefile)
14
+ end
11
15
  end
16
+
12
17
  end
@@ -0,0 +1,25 @@
1
+ # define railtie which will be executed in Rails 3
2
+ if defined?(::Rails::Railtie)
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ class OracleEnhancedRailtie < ::Rails::Railtie
7
+ rake_tasks do
8
+ load 'active_record/connection_adapters/oracle_enhanced.rake'
9
+ end
10
+
11
+ ActiveSupport.on_load(:active_record) do
12
+ require 'active_record/connection_adapters/oracle_enhanced_adapter'
13
+
14
+ # Cache column descriptions between requests in test and production environments
15
+ if Rails.env.test? || Rails.env.production?
16
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+ end