activerecord-oracle_enhanced-adapter-with-schema 0.0.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 (46) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +52 -0
  3. data/History.md +301 -0
  4. data/License.txt +20 -0
  5. data/README.md +123 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +59 -0
  8. data/VERSION +1 -0
  9. data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -0
  10. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1399 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +46 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  30. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  31. data/lib/activerecord-oracle_enhanced-adapter-with-schema.rb +25 -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 +1388 -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 +440 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
  45. data/spec/spec_helper.rb +189 -0
  46. metadata +260 -0
@@ -0,0 +1,258 @@
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
+ # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
30
+ # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner)
31
+ #
32
+ # The foreign key will be named after the from and to tables unless you pass
33
+ # <tt>:name</tt> as an option.
34
+ #
35
+ # === Examples
36
+ # ==== Creating a foreign key
37
+ # add_foreign_key(:comments, :posts)
38
+ # generates
39
+ # ALTER TABLE comments ADD CONSTRAINT
40
+ # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id)
41
+ #
42
+ # ==== Creating a named foreign key
43
+ # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
44
+ # generates
45
+ # ALTER TABLE comments ADD CONSTRAINT
46
+ # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id)
47
+ #
48
+ # ==== Creating a cascading foreign_key on a custom column
49
+ # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
50
+ # generates
51
+ # ALTER TABLE people ADD CONSTRAINT
52
+ # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id)
53
+ # ON DELETE SET NULL
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
+ #
61
+ # === Supported options
62
+ # [:column]
63
+ # Specify the column name on the from_table that references the to_table. By default this is guessed
64
+ # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
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.
68
+ # [:primary_key]
69
+ # Specify the column name on the to_table that is referenced by this foreign key. By default this is
70
+ # assumed to be "id". Ignored when defining composite foreign keys.
71
+ # [:name]
72
+ # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
73
+ # [:dependent]
74
+ # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
75
+ # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
76
+ def add_foreign_key(from_table, to_table, options = {})
77
+ columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id"
78
+ constraint_name = foreign_key_constraint_name(from_table, columns, options)
79
+ sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
80
+ sql << foreign_key_definition(to_table, options)
81
+ execute sql
82
+ end
83
+
84
+ def foreign_key_definition(to_table, options = {}) #:nodoc:
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
+ table_name = ActiveRecord::Migrator.proper_table_name(to_table)
99
+
100
+ sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})"
101
+
102
+ case options[:dependent]
103
+ when :nullify
104
+ sql << " ON DELETE SET NULL"
105
+ when :delete
106
+ sql << " ON DELETE CASCADE"
107
+ end
108
+ sql
109
+ end
110
+
111
+ # Remove the given foreign key from the table.
112
+ #
113
+ # ===== Examples
114
+ # ====== Remove the suppliers_company_id_fk in the suppliers table.
115
+ # remove_foreign_key :suppliers, :companies
116
+ # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
117
+ # remove_foreign_key :accounts, :column => :branch_id
118
+ # ====== Remove the foreign key named party_foreign_key in the accounts table.
119
+ # remove_foreign_key :accounts, :name => :party_foreign_key
120
+ def remove_foreign_key(from_table, options)
121
+ if Hash === options
122
+ constraint_name = foreign_key_constraint_name(from_table, options[:column], options)
123
+ else
124
+ constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id")
125
+ end
126
+ execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}"
127
+ end
128
+
129
+ private
130
+
131
+ def foreign_key_constraint_name(table_name, columns, options = {})
132
+ columns = Array(columns)
133
+ constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk"
134
+
135
+ return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
136
+
137
+ # leave just first three letters from each word
138
+ constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_')
139
+ # generate unique name using hash function
140
+ if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
141
+ constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
142
+ end
143
+ @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger
144
+ constraint_name
145
+ end
146
+
147
+
148
+ public
149
+
150
+ # get table foreign keys for schema dump
151
+ def foreign_keys(table_name) #:nodoc:
152
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
153
+
154
+ fk_info = select_all(<<-SQL, 'Foreign Keys')
155
+ SELECT r.table_name to_table
156
+ ,rc.column_name references_column
157
+ ,cc.column_name
158
+ ,c.constraint_name name
159
+ ,c.delete_rule
160
+ FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc,
161
+ user_constraints#{db_link} r, user_cons_columns#{db_link} rc
162
+ WHERE c.owner = '#{owner}'
163
+ AND c.table_name = '#{desc_table_name}'
164
+ AND c.constraint_type = 'R'
165
+ AND cc.owner = c.owner
166
+ AND cc.constraint_name = c.constraint_name
167
+ AND r.constraint_name = c.r_constraint_name
168
+ AND r.owner = c.owner
169
+ AND rc.owner = r.owner
170
+ AND rc.constraint_name = r.constraint_name
171
+ AND rc.position = cc.position
172
+ ORDER BY name, to_table, column_name, references_column
173
+ SQL
174
+
175
+ fks = {}
176
+
177
+ fk_info.map do |row|
178
+ name = oracle_downcase(row['name'])
179
+ fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] }
180
+ fks[name][:columns] << oracle_downcase(row['column_name'])
181
+ fks[name][:references] << oracle_downcase(row['references_column'])
182
+ case row['delete_rule']
183
+ when 'CASCADE'
184
+ fks[name][:dependent] = :delete
185
+ when 'SET NULL'
186
+ fks[name][:dependent] = :nullify
187
+ end
188
+ end
189
+
190
+ fks.map do |k, v|
191
+ options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]}
192
+ OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options)
193
+ end
194
+ end
195
+
196
+ # REFERENTIAL INTEGRITY ====================================
197
+
198
+ def disable_referential_integrity(&block) #:nodoc:
199
+ sql_constraints = <<-SQL
200
+ SELECT constraint_name, owner, table_name
201
+ FROM user_constraints
202
+ WHERE constraint_type = 'R'
203
+ AND status = 'ENABLED'
204
+ SQL
205
+ old_constraints = select_all(sql_constraints)
206
+ begin
207
+ old_constraints.each do |constraint|
208
+ execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}"
209
+ end
210
+ yield
211
+ ensure
212
+ old_constraints.each do |constraint|
213
+ execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}"
214
+ end
215
+ end
216
+ end
217
+
218
+ # Add synonym to existing table or view or sequence. Can be used to create local synonym to
219
+ # remote table in other schema or in other database
220
+ # Examples:
221
+ #
222
+ # add_synonym :posts, "blog.posts"
223
+ # add_synonym :posts_seq, "blog.posts_seq"
224
+ # add_synonym :employees, "hr.employees@dblink", :force => true
225
+ #
226
+ def add_synonym(name, table_name, options = {})
227
+ sql = "CREATE"
228
+ if options[:force] == true
229
+ sql << " OR REPLACE"
230
+ end
231
+ sql << " SYNONYM #{quote_table_name(name)} FOR #{quote_table_name(table_name)}"
232
+ execute sql
233
+ end
234
+
235
+ # Remove existing synonym to table or view or sequence
236
+ # Example:
237
+ #
238
+ # remove_synonym :posts, "blog.posts"
239
+ #
240
+ def remove_synonym(name)
241
+ execute "DROP SYNONYM #{quote_table_name(name)}"
242
+ end
243
+
244
+ # get synonyms for schema dump
245
+ def synonyms #:nodoc:
246
+ select_all("SELECT synonym_name, table_owner, table_name, db_link FROM user_synonyms").collect do |row|
247
+ OracleEnhancedSynonymDefinition.new(oracle_downcase(row['synonym_name']),
248
+ oracle_downcase(row['table_owner']), oracle_downcase(row['table_name']), oracle_downcase(row['db_link']))
249
+ end
250
+ end
251
+
252
+ end
253
+ end
254
+ end
255
+
256
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
257
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatementsExt
258
+ 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', '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_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', '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} ORDER BY version")
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
+ # 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