pmacs-activerecord-oracle_enhanced-adapter 1.4.2.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +52 -0
  3. data/History.md +284 -0
  4. data/License.txt +20 -0
  5. data/README.md +403 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +59 -0
  8. data/VERSION +1 -0
  9. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1408 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +118 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +141 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +135 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +44 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +491 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +231 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +257 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +397 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +265 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  30. data/lib/pmacs-activerecord-oracle_enhanced-adapter.rb +25 -0
  31. data/pmacs-activerecord-oracle_enhanced-adapter.gemspec +131 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
  36. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
  37. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1376 -0
  38. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +141 -0
  40. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  41. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +378 -0
  42. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +438 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1280 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
  45. data/spec/spec_helper.rb +187 -0
  46. metadata +302 -0
@@ -0,0 +1,265 @@
1
+ require 'digest/sha1'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OracleEnhancedSchemaStatementsExt
6
+ def supports_foreign_keys? #:nodoc:
7
+ true
8
+ end
9
+
10
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
11
+ # By default trigger name will be "table_name_pkt", you can override the name with
12
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
13
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
14
+ #
15
+ # add_primary_key_trigger :users
16
+ #
17
+ # You can also create primary key trigger using +create_table+ with :primary_key_trigger
18
+ # option:
19
+ #
20
+ # create_table :users, :primary_key_trigger => true do |t|
21
+ # # ...
22
+ # end
23
+ #
24
+ def add_primary_key_trigger(table_name, options={})
25
+ # call the same private method that is used for create_table :primary_key_trigger => true
26
+ create_primary_key_trigger(table_name, options)
27
+ end
28
+
29
+ def table_definition_tablespace
30
+ # TODO: Support specifying an :index_tablespace option in create_table?
31
+ tablespace_sql = ''
32
+ if tablespace = default_tablespace_for(:index)
33
+ tablespace_sql << " USING INDEX TABLESPACE #{tablespace}"
34
+ end
35
+ tablespace_sql
36
+ end
37
+
38
+ # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
39
+ # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner)
40
+ #
41
+ # The foreign key will be named after the from and to tables unless you pass
42
+ # <tt>:name</tt> as an option.
43
+ #
44
+ # === Examples
45
+ # ==== Creating a foreign key
46
+ # add_foreign_key(:comments, :posts)
47
+ # generates
48
+ # ALTER TABLE comments ADD CONSTRAINT
49
+ # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id)
50
+ #
51
+ # ==== Creating a named foreign key
52
+ # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
53
+ # generates
54
+ # ALTER TABLE comments ADD CONSTRAINT
55
+ # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id)
56
+ #
57
+ # ==== Creating a cascading foreign_key on a custom column
58
+ # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
59
+ # generates
60
+ # ALTER TABLE people ADD CONSTRAINT
61
+ # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id)
62
+ # ON DELETE SET NULL
63
+ #
64
+ # ==== Creating a composite foreign key
65
+ # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk')
66
+ # generates
67
+ # ALTER TABLE comments ADD CONSTRAINT
68
+ # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id)
69
+ #
70
+ # === Supported options
71
+ # [:column]
72
+ # Specify the column name on the from_table that references the to_table. By default this is guessed
73
+ # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
74
+ # as the default <tt>:column</tt>.
75
+ # [:columns]
76
+ # An array of column names when defining composite foreign keys. An alias of <tt>:column</tt> provided for improved readability.
77
+ # [:primary_key]
78
+ # Specify the column name on the to_table that is referenced by this foreign key. By default this is
79
+ # assumed to be "id". Ignored when defining composite foreign keys.
80
+ # [:name]
81
+ # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
82
+ # [:dependent]
83
+ # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
84
+ # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
85
+ def add_foreign_key(from_table, to_table, options = {})
86
+ columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id"
87
+ constraint_name = foreign_key_constraint_name(from_table, columns, options)
88
+ sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
89
+ sql << foreign_key_definition(to_table, options)
90
+ execute sql
91
+ end
92
+
93
+ def foreign_key_definition(to_table, options = {}) #:nodoc:
94
+ columns = Array(options[:column] || options[:columns])
95
+
96
+ if columns.size > 1
97
+ # composite foreign key
98
+ columns_sql = columns.map {|c| quote_column_name(c)}.join(',')
99
+ references = options[:references] || columns
100
+ references_sql = references.map {|c| quote_column_name(c)}.join(',')
101
+ else
102
+ columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id")
103
+ references = options[:references] ? options[:references].first : nil
104
+ references_sql = quote_column_name(options[:primary_key] || references || "id")
105
+ end
106
+
107
+ sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(to_table)}(#{references_sql})"
108
+
109
+ case options[:dependent]
110
+ when :nullify
111
+ sql << " ON DELETE SET NULL"
112
+ when :delete
113
+ sql << " ON DELETE CASCADE"
114
+ end
115
+ sql
116
+ end
117
+
118
+ # Remove the given foreign key from the table.
119
+ #
120
+ # ===== Examples
121
+ # ====== Remove the suppliers_company_id_fk in the suppliers table.
122
+ # remove_foreign_key :suppliers, :companies
123
+ # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
124
+ # remove_foreign_key :accounts, :column => :branch_id
125
+ # ====== Remove the foreign key named party_foreign_key in the accounts table.
126
+ # remove_foreign_key :accounts, :name => :party_foreign_key
127
+ def remove_foreign_key(from_table, options)
128
+ if Hash === options
129
+ constraint_name = foreign_key_constraint_name(from_table, options[:column], options)
130
+ else
131
+ constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id")
132
+ end
133
+ execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}"
134
+ end
135
+
136
+ private
137
+
138
+ def foreign_key_constraint_name(table_name, columns, options = {})
139
+ columns = Array(columns)
140
+ constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk"
141
+
142
+ return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
143
+
144
+ # leave just first three letters from each word
145
+ constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_')
146
+ # generate unique name using hash function
147
+ if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
148
+ constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
149
+ end
150
+ @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger
151
+ constraint_name
152
+ end
153
+
154
+
155
+ public
156
+
157
+ # get table foreign keys for schema dump
158
+ def foreign_keys(table_name) #:nodoc:
159
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
160
+
161
+ fk_info = select_all(<<-SQL, 'Foreign Keys')
162
+ SELECT r.table_name to_table
163
+ ,rc.column_name references_column
164
+ ,cc.column_name
165
+ ,c.constraint_name name
166
+ ,c.delete_rule
167
+ FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc,
168
+ user_constraints#{db_link} r, user_cons_columns#{db_link} rc
169
+ WHERE c.owner = '#{owner}'
170
+ AND c.table_name = '#{desc_table_name}'
171
+ AND c.constraint_type = 'R'
172
+ AND cc.owner = c.owner
173
+ AND cc.constraint_name = c.constraint_name
174
+ AND r.constraint_name = c.r_constraint_name
175
+ AND r.owner = c.owner
176
+ AND rc.owner = r.owner
177
+ AND rc.constraint_name = r.constraint_name
178
+ AND rc.position = cc.position
179
+ ORDER BY name, to_table, column_name, references_column
180
+ SQL
181
+
182
+ fks = {}
183
+
184
+ fk_info.map do |row|
185
+ name = oracle_downcase(row['name'])
186
+ fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] }
187
+ fks[name][:columns] << oracle_downcase(row['column_name'])
188
+ fks[name][:references] << oracle_downcase(row['references_column'])
189
+ case row['delete_rule']
190
+ when 'CASCADE'
191
+ fks[name][:dependent] = :delete
192
+ when 'SET NULL'
193
+ fks[name][:dependent] = :nullify
194
+ end
195
+ end
196
+
197
+ fks.map do |k, v|
198
+ options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]}
199
+ OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options)
200
+ end
201
+ end
202
+
203
+ # REFERENTIAL INTEGRITY ====================================
204
+
205
+ def disable_referential_integrity(&block) #:nodoc:
206
+ sql_constraints = <<-SQL
207
+ SELECT constraint_name, owner, table_name
208
+ FROM user_constraints
209
+ WHERE constraint_type = 'R'
210
+ AND status = 'ENABLED'
211
+ SQL
212
+ old_constraints = select_all(sql_constraints)
213
+ begin
214
+ old_constraints.each do |constraint|
215
+ execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}"
216
+ end
217
+ yield
218
+ ensure
219
+ old_constraints.each do |constraint|
220
+ execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}"
221
+ end
222
+ end
223
+ end
224
+
225
+ # Add synonym to existing table or view or sequence. Can be used to create local synonym to
226
+ # remote table in other schema or in other database
227
+ # Examples:
228
+ #
229
+ # add_synonym :posts, "blog.posts"
230
+ # add_synonym :posts_seq, "blog.posts_seq"
231
+ # add_synonym :employees, "hr.employees@dblink", :force => true
232
+ #
233
+ def add_synonym(name, table_name, options = {})
234
+ sql = "CREATE"
235
+ if options[:force] == true
236
+ sql << " OR REPLACE"
237
+ end
238
+ sql << " SYNONYM #{quote_table_name(name)} FOR #{quote_table_name(table_name)}"
239
+ execute sql
240
+ end
241
+
242
+ # Remove existing synonym to table or view or sequence
243
+ # Example:
244
+ #
245
+ # remove_synonym :posts, "blog.posts"
246
+ #
247
+ def remove_synonym(name)
248
+ execute "DROP SYNONYM #{quote_table_name(name)}"
249
+ end
250
+
251
+ # get synonyms for schema dump
252
+ def synonyms #:nodoc:
253
+ select_all("SELECT synonym_name, table_owner, table_name, db_link FROM all_synonyms WHERE owner = SYS_CONTEXT('userenv', 'current_schema')").collect do |row|
254
+ OracleEnhancedSynonymDefinition.new(oracle_downcase(row['synonym_name']),
255
+ oracle_downcase(row['table_owner']), oracle_downcase(row['table_name']), oracle_downcase(row['db_link']))
256
+ end
257
+ end
258
+
259
+ end
260
+ end
261
+ end
262
+
263
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
264
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatementsExt
265
+ end
@@ -0,0 +1,294 @@
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', 'current_schema') 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', 'current_schema')
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', 'current_schema')
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_or_expression(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', 'current_schema') 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', 'current_schema') 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', 'current_schema')
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', 'current_schema') ").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', 'current_schema') 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', 'current_schema') 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
+ # add AS expression for virtual columns
236
+ if options[:as].present?
237
+ sql << " AS (#{options[:as]})"
238
+ end
239
+ end
240
+
241
+ def execute_structure_dump(string)
242
+ string.split(STATEMENT_TOKEN).each do |ddl|
243
+ ddl.chop! if ddl.last == ";"
244
+ execute(ddl) unless ddl.blank?
245
+ end
246
+ end
247
+
248
+ private
249
+
250
+ # virtual columns are an 11g feature. This returns [] if feature is not
251
+ # present or none are found.
252
+ # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
253
+ def virtual_columns_for(table)
254
+ begin
255
+ select_all <<-SQL
256
+ SELECT column_name, data_default
257
+ FROM user_tab_cols
258
+ WHERE virtual_column = 'YES'
259
+ AND table_name = '#{table.upcase}'
260
+ SQL
261
+ # feature not supported previous to 11g
262
+ rescue ActiveRecord::StatementInvalid => e
263
+ []
264
+ end
265
+ end
266
+
267
+ def drop_sql_for_feature(type)
268
+ short_type = type == 'materialized view' ? 'mview' : type
269
+ join_with_statement_token(
270
+ select_values("SELECT #{short_type}_name FROM user_#{short_type.tableize}").map do |name|
271
+ "DROP #{type.upcase} \"#{name}\""
272
+ end)
273
+ end
274
+
275
+ def drop_sql_for_object(type)
276
+ join_with_statement_token(
277
+ select_values("SELECT object_name FROM user_objects WHERE object_type = '#{type.upcase}'").map do |name|
278
+ "DROP #{type.upcase} \"#{name}\""
279
+ end)
280
+ end
281
+
282
+ def join_with_statement_token(array)
283
+ string = array.join(STATEMENT_TOKEN)
284
+ string << STATEMENT_TOKEN unless string.blank?
285
+ string
286
+ end
287
+
288
+ end
289
+ end
290
+ end
291
+
292
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
293
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedStructureDump
294
+ end