oracle_enhanced 1.3.0.pre → 1.3.0.pre2

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 (27) hide show
  1. data/.gitignore +0 -1
  2. data/History.txt +1 -1
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/oracle_enhanced.rake +3 -2
  6. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +223 -154
  7. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +227 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +1 -1
  9. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +1 -1
  10. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +3 -3
  11. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +6 -1
  12. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +3 -3
  13. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +2 -1
  14. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +24 -9
  15. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -1
  16. data/lib/{oracle_enhanced.rb → active_record/oracle_enhanced.rb} +0 -2
  17. data/oracle_enhanced.gemspec +7 -4
  18. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +37 -10
  19. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +27 -20
  20. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +292 -0
  21. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +24 -28
  22. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +13 -11
  23. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -1
  24. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +70 -68
  25. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +14 -1
  26. data/spec/spec_helper.rb +97 -24
  27. metadata +8 -5
@@ -107,31 +107,33 @@ module ActiveRecord
107
107
  end
108
108
  private :enhanced_write_lobs
109
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!, :add_order_with_lobs! #rails 3 issues
132
- alias_method :add_order_without_lobs!, :add_order!
133
- #:startdoc:
134
- end
110
+ # No such method in ActiveRecord 3.0
111
+ # and there is no real need to order by LOBs
112
+ # class << self
113
+ # # patch ORDER BY to work with LOBs
114
+ # def add_order_with_lobs!(sql, order, scope = :auto)
115
+ # if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
116
+ # order = connection.lob_order_by_expression(self, order) if order
117
+ #
118
+ # orig_scope = scope
119
+ # scope = scope(:find) if :auto == scope
120
+ # if scope
121
+ # new_scope_order = connection.lob_order_by_expression(self, scope[:order])
122
+ # if new_scope_order != scope[:order]
123
+ # scope = scope.merge(:order => new_scope_order)
124
+ # else
125
+ # scope = orig_scope
126
+ # end
127
+ # end
128
+ # end
129
+ # add_order_without_lobs!(sql, order, scope = :auto)
130
+ # end
131
+ # private :add_order_with_lobs!
132
+ # #:stopdoc:
133
+ # alias_method :add_order_without_lobs!, :add_order!
134
+ # alias_method :add_order!, :add_order_with_lobs!
135
+ # #:startdoc:
136
+ # end
135
137
 
136
138
  # Get table comment from schema definition.
137
139
  def self.table_comment
@@ -236,7 +238,7 @@ module ActiveRecord
236
238
  if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
237
239
  return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
238
240
  end
239
- DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
241
+ DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format).to_date
240
242
  end
241
243
 
242
244
  end
@@ -460,7 +462,8 @@ module ActiveRecord
460
462
  def quote_column_name(name) #:nodoc:
461
463
  # camelCase column names need to be quoted; not that anyone using Oracle
462
464
  # 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)
465
+ name = name.to_s
466
+ @quoted_column_names[name] ||= name =~ /^[a-z][a-z_0-9\$#]*$/ ? "\"#{name.upcase}\"" : "\"#{name}\""
464
467
  end
465
468
 
466
469
  # unescaped table name should start with letter and
@@ -474,12 +477,8 @@ module ActiveRecord
474
477
  end
475
478
 
476
479
  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
480
+ name = name.to_s
481
+ @quoted_table_names[name] ||= name.split('.').map{|n| n.split('@').map{|m| quote_column_name(m)}.join('@')}.join('.')
483
482
  end
484
483
 
485
484
  def quote_string(s) #:nodoc:
@@ -603,7 +602,6 @@ module ActiveRecord
603
602
  sql_with_returning = sql.dup << @connection.returning_clause(quote_column_name(pk))
604
603
  # hack to pass additional "with_returning" option without changing argument list
605
604
  sql_with_returning.instance_variable_set(:@with_returning, true)
606
- clear_query_cache
607
605
  execute(sql_with_returning, name)
608
606
  end
609
607
  protected :insert_sql
@@ -651,10 +649,12 @@ module ActiveRecord
651
649
  def add_limit_offset!(sql, options) #:nodoc:
652
650
  # added to_i for limit and offset to protect from SQL injection
653
651
  offset = (options[:offset] || 0).to_i
654
-
655
- if limit = options[:limit]
656
- limit = limit.to_i
652
+ limit = options[:limit]
653
+ limit = limit.is_a?(String) && limit.blank? ? nil : limit && limit.to_i
654
+ if limit && offset > 0
657
655
  sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
656
+ elsif limit
657
+ sql.replace "select * from (#{sql}) where rownum <= #{limit}"
658
658
  elsif offset > 0
659
659
  sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
660
660
  end
@@ -755,8 +755,13 @@ module ActiveRecord
755
755
  end
756
756
 
757
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']}
758
+ select_values(
759
+ "select decode(table_name,upper(table_name),lower(table_name),table_name) from all_tables where owner = sys_context('userenv','session_user') and secondary='N'",
760
+ name)
761
+ end
762
+
763
+ def materialized_views #:nodoc:
764
+ select_values("select lower(mview_name) from all_mviews where owner = sys_context('userenv','session_user')")
760
765
  end
761
766
 
762
767
  cattr_accessor :all_schema_indexes #:nodoc:
@@ -768,14 +773,19 @@ module ActiveRecord
768
773
  unless all_schema_indexes
769
774
  default_tablespace_name = default_tablespace
770
775
  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
+ SELECT LOWER(i.table_name) AS table_name, LOWER(i.index_name) AS index_name, i.uniqueness,
777
+ i.index_type, i.ityp_owner, i.ityp_name, i.parameters,
778
+ LOWER(i.tablespace_name) AS tablespace_name,
779
+ LOWER(c.column_name) AS column_name, e.column_expression
780
+ FROM all_indexes#{db_link} i
781
+ JOIN all_ind_columns#{db_link} c ON c.index_name = i.index_name AND c.index_owner = i.owner
782
+ LEFT OUTER JOIN all_ind_expressions#{db_link} e ON e.index_name = i.index_name AND
783
+ e.index_owner = i.owner AND e.column_position = c.column_position
784
+ WHERE i.owner = '#{owner}'
776
785
  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
786
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc
787
+ WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
788
+ ORDER BY i.index_name, c.column_position
779
789
  SQL
780
790
 
781
791
  current_index = nil
@@ -785,11 +795,25 @@ module ActiveRecord
785
795
  # have to keep track of indexes because above query returns dups
786
796
  # there is probably a better query we could figure out
787
797
  if current_index != row['index_name']
788
- all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE",
798
+ statement_parameters = nil
799
+ if row['index_type'] == 'DOMAIN' && row['ityp_owner'] == 'CTXSYS' && row['ityp_name'] == 'CONTEXT'
800
+ procedure_name = default_datastore_procedure(row['index_name'])
801
+ statement_parameters = select_value(<<-SQL)
802
+ SELECT SUBSTR(text,LENGTH('-- add_context_index_parameters ')+1)
803
+ FROM all_source#{db_link}
804
+ WHERE owner = '#{owner}'
805
+ AND name = '#{procedure_name.upcase}'
806
+ AND text LIKE '-- add_context_index_parameters %'
807
+ SQL
808
+ end
809
+ all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'],
810
+ row['uniqueness'] == "UNIQUE", row['index_type'] == 'DOMAIN' ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil,
811
+ row['parameters'], statement_parameters,
789
812
  row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
790
813
  current_index = row['index_name']
791
814
  end
792
- all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] : row['column_expression'].gsub('"','').downcase)
815
+ all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] :
816
+ row['column_expression'].gsub('"','').downcase)
793
817
  end
794
818
  end
795
819
 
@@ -852,7 +876,7 @@ module ActiveRecord
852
876
  AND table_name = '#{desc_table_name}'
853
877
  AND status = 'ENABLED'
854
878
  SQL
855
- select_value(pkt_sql) ? true : false
879
+ select_value(pkt_sql, 'Primary Key Trigger') ? true : false
856
880
  end
857
881
 
858
882
  ##
@@ -866,8 +890,7 @@ module ActiveRecord
866
890
  self.cache_columns = false
867
891
 
868
892
  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)
893
+ if @@cache_columns
871
894
  @@columns_cache ||= {}
872
895
  @@columns_cache[table_name] ||= columns_without_cache(table_name, name)
873
896
  else
@@ -876,14 +899,15 @@ module ActiveRecord
876
899
  end
877
900
 
878
901
  def columns_without_cache(table_name, name = nil) #:nodoc:
902
+ table_name = table_name.to_s
879
903
  # get ignored_columns by original table name
880
904
  ignored_columns = ignored_table_columns(table_name)
881
905
 
882
906
  (owner, desc_table_name, db_link) = @connection.describe(table_name)
883
907
 
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
908
+ @@do_not_prefetch_primary_key[table_name] =
909
+ !has_primary_key?(table_name, owner, desc_table_name, db_link) ||
910
+ has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
887
911
 
888
912
  table_cols = <<-SQL
889
913
  select column_name as name, data_type as sql_type, data_default, nullable,
@@ -929,11 +953,15 @@ module ActiveRecord
929
953
  # used just in tests to clear column cache
930
954
  def clear_columns_cache #:nodoc:
931
955
  @@columns_cache = nil
956
+ @@pk_and_sequence_for_cache = nil
932
957
  end
933
958
 
934
959
  # used in migrations to clear column cache for specified table
935
960
  def clear_table_columns_cache(table_name)
936
- @@columns_cache[table_name.to_s] = nil if @@cache_columns
961
+ if @@cache_columns
962
+ @@columns_cache ||= {}
963
+ @@columns_cache[table_name.to_s] = nil
964
+ end
937
965
  end
938
966
 
939
967
  ##
@@ -1051,11 +1079,7 @@ module ActiveRecord
1051
1079
  if Hash === options # legacy support, since this param was a string
1052
1080
  index_type = options[:unique] ? "UNIQUE" : ""
1053
1081
  index_name = options[:name] || index_name
1054
- tablespace = if options[:tablespace]
1055
- " TABLESPACE #{options[:tablespace]}"
1056
- else
1057
- ""
1058
- end
1082
+ tablespace = options[:tablespace] ? " TABLESPACE #{options[:tablespace]}" : ""
1059
1083
  else
1060
1084
  index_type = options
1061
1085
  end
@@ -1072,18 +1096,21 @@ module ActiveRecord
1072
1096
  # returned shortened index name if default is too large
1073
1097
  def index_name(table_name, options) #:nodoc:
1074
1098
  default_name = super(table_name, options)
1075
- return default_name if default_name.length <= IDENTIFIER_MAX_LENGTH
1099
+ # sometimes options can be String or Array with column names
1100
+ options = {} unless options.is_a?(Hash)
1101
+ identifier_max_length = options[:identifier_max_length] || IDENTIFIER_MAX_LENGTH
1102
+ return default_name if default_name.length <= identifier_max_length
1076
1103
 
1077
1104
  # remove 'index', 'on' and 'and' keywords
1078
1105
  shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
1079
1106
 
1080
1107
  # leave just first three letters from each word
1081
- if shortened_name.length > IDENTIFIER_MAX_LENGTH
1108
+ if shortened_name.length > identifier_max_length
1082
1109
  shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
1083
1110
  end
1084
1111
  # generate unique name using hash function
1085
- if shortened_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
1086
- shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
1112
+ if shortened_name.length > identifier_max_length
1113
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
1087
1114
  end
1088
1115
  @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
1089
1116
  shortened_name
@@ -1182,15 +1209,24 @@ module ActiveRecord
1182
1209
 
1183
1210
  # Find a table's primary key and sequence.
1184
1211
  # *Note*: Only primary key is implemented - sequence will be nil.
1185
- def pk_and_sequence_for(table_name) #:nodoc:
1186
- (owner, table_name, db_link) = @connection.describe(table_name)
1212
+ def pk_and_sequence_for(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1213
+ if @@cache_columns
1214
+ @@pk_and_sequence_for_cache ||= {}
1215
+ @@pk_and_sequence_for_cache[table_name] ||= pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1216
+ else
1217
+ pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1218
+ end
1219
+ end
1220
+
1221
+ def pk_and_sequence_for_without_cache(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1222
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1187
1223
 
1188
- # changed select from all_constraints to user_constraints - much faster in large data dictionaries
1224
+ # changed back from user_constraints to all_constraints for consistency
1189
1225
  pks = select_values(<<-SQL, 'Primary Key')
1190
1226
  select cc.column_name
1191
- from user_constraints#{db_link} c, user_cons_columns#{db_link} cc
1227
+ from all_constraints#{db_link} c, all_cons_columns#{db_link} cc
1192
1228
  where c.owner = '#{owner}'
1193
- and c.table_name = '#{table_name}'
1229
+ and c.table_name = '#{desc_table_name}'
1194
1230
  and c.constraint_type = 'P'
1195
1231
  and cc.owner = c.owner
1196
1232
  and cc.constraint_name = c.constraint_name
@@ -1200,16 +1236,29 @@ module ActiveRecord
1200
1236
  pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
1201
1237
  end
1202
1238
 
1239
+ # Returns just a table's primary key
1240
+ def primary_key(table_name)
1241
+ pk_and_sequence = pk_and_sequence_for(table_name)
1242
+ pk_and_sequence && pk_and_sequence.first
1243
+ end
1244
+
1245
+ def has_primary_key?(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1246
+ !pk_and_sequence_for(table_name, owner, desc_table_name, db_link).nil?
1247
+ end
1248
+
1249
+ # Statements separator used in structure dump to allow loading of structure dump also with SQL*Plus
1250
+ STATEMENT_TOKEN = "\n\n/\n\n"
1251
+
1203
1252
  def structure_dump #:nodoc:
1204
- s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
1205
- structure << "create sequence #{seq.to_a.first.last}#{STATEMENT_TOKEN}"
1253
+ structure = select_values("select sequence_name from user_sequences order by 1").map do |seq|
1254
+ "CREATE SEQUENCE \"#{seq}\""
1206
1255
  end
1207
-
1208
- # changed select from user_tables to all_tables - much faster in large data dictionaries
1209
- select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
1210
- table_name = table['table_name']
1256
+ select_values("select table_name from all_tables t
1257
+ where owner = sys_context('userenv','session_user') and secondary='N'
1258
+ and not exists (select mv.mview_name from all_mviews mv where mv.owner = t.owner and mv.mview_name = t.table_name)
1259
+ order by 1").each do |table_name|
1211
1260
  virtual_columns = virtual_columns_for(table_name)
1212
- ddl = "create#{ ' global temporary' if temporary_table?(table_name)} table #{table_name} (\n "
1261
+ ddl = "CREATE#{ ' GLOBAL TEMPORARY' if temporary_table?(table_name)} TABLE \"#{table_name}\" (\n"
1213
1262
  cols = select_all(%Q{
1214
1263
  select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
1215
1264
  from user_tab_columns
@@ -1224,15 +1273,16 @@ module ActiveRecord
1224
1273
  end
1225
1274
  ddl << cols.join(",\n ")
1226
1275
  ddl << structure_dump_constraints(table_name)
1227
- ddl << "\n)#{STATEMENT_TOKEN}"
1276
+ ddl << "\n)"
1228
1277
  structure << ddl
1229
1278
  structure << structure_dump_indexes(table_name)
1230
1279
  end
1280
+
1281
+ join_with_statement_token(structure) << structure_dump_fk_constraints
1231
1282
  end
1232
-
1233
- def structure_dump_virtual_column(column, data_default) #:nodoc:
1234
- data_default = data_default.gsub(/"/, '')
1235
- col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1283
+
1284
+ def structure_dump_column(column) #:nodoc:
1285
+ col = "\"#{column['column_name']}\" #{column['data_type']}"
1236
1286
  if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1237
1287
  col << "(#{column['data_precision'].to_i}"
1238
1288
  col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
@@ -1241,11 +1291,14 @@ module ActiveRecord
1241
1291
  length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1242
1292
  col << "(#{length})"
1243
1293
  end
1244
- col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
1294
+ col << " DEFAULT #{column['data_default']}" if !column['data_default'].nil?
1295
+ col << ' NOT NULL' if column['nullable'] == 'N'
1296
+ col
1245
1297
  end
1246
-
1247
- def structure_dump_column(column) #:nodoc:
1248
- col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1298
+
1299
+ def structure_dump_virtual_column(column, data_default) #:nodoc:
1300
+ data_default = data_default.gsub(/"/, '')
1301
+ col = "\"#{column['column_name']}\" #{column['data_type']}"
1249
1302
  if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1250
1303
  col << "(#{column['data_precision'].to_i}"
1251
1304
  col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
@@ -1254,16 +1307,14 @@ module ActiveRecord
1254
1307
  length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1255
1308
  col << "(#{length})"
1256
1309
  end
1257
- col << " default #{column['data_default']}" if !column['data_default'].nil?
1258
- col << ' not null' if column['nullable'] == 'N'
1259
- col
1310
+ col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
1260
1311
  end
1261
-
1312
+
1262
1313
  def structure_dump_constraints(table) #:nodoc:
1263
1314
  out = [structure_dump_primary_key(table), structure_dump_unique_keys(table)].flatten.compact
1264
1315
  out.length > 0 ? ",\n#{out.join(",\n")}" : ''
1265
1316
  end
1266
-
1317
+
1267
1318
  def structure_dump_primary_key(table) #:nodoc:
1268
1319
  opts = {:name => '', :cols => []}
1269
1320
  pks = select_all(<<-SQL, "Primary Keys")
@@ -1281,7 +1332,7 @@ module ActiveRecord
1281
1332
  end
1282
1333
  opts[:cols].length > 0 ? " CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : nil
1283
1334
  end
1284
-
1335
+
1285
1336
  def structure_dump_unique_keys(table) #:nodoc:
1286
1337
  keys = {}
1287
1338
  uks = select_all(<<-SQL, "Primary Keys")
@@ -1301,7 +1352,23 @@ module ActiveRecord
1301
1352
  " CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
1302
1353
  end
1303
1354
  end
1304
-
1355
+
1356
+ def structure_dump_indexes(table_name) #:nodoc:
1357
+ indexes(table_name).map do |options|
1358
+ column_names = options[:columns]
1359
+ options = {:name => options[:name], :unique => options[:unique]}
1360
+ index_name = index_name(table_name, :column => column_names)
1361
+ if Hash === options # legacy support, since this param was a string
1362
+ index_type = options[:unique] ? "UNIQUE" : ""
1363
+ index_name = options[:name] || index_name
1364
+ else
1365
+ index_type = options
1366
+ end
1367
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1368
+ "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
1369
+ end
1370
+ end
1371
+
1305
1372
  def structure_dump_fk_constraints #:nodoc:
1306
1373
  fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
1307
1374
  if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
@@ -1312,18 +1379,24 @@ module ActiveRecord
1312
1379
  sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
1313
1380
  end
1314
1381
  end
1315
- end.flatten.compact.join(STATEMENT_TOKEN)
1316
- fks.length > 1 ? "#{fks}#{STATEMENT_TOKEN}" : ''
1382
+ end.flatten.compact
1383
+ join_with_statement_token(fks)
1317
1384
  end
1318
-
1385
+
1386
+ def dump_schema_information #:nodoc:
1387
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
1388
+ migrated = select_values("SELECT version FROM #{sm_table}")
1389
+ join_with_statement_token(migrated.map{|v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" })
1390
+ end
1391
+
1319
1392
  # Extract all stored procedures, packages, synonyms and views.
1320
1393
  def structure_dump_db_stored_code #:nodoc:
1321
- structure = ""
1322
- select_all("select distinct name, type
1323
- from all_source
1324
- where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
1394
+ structure = []
1395
+ select_all("select distinct name, type
1396
+ from all_source
1397
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
1325
1398
  and owner = sys_context('userenv','session_user') order by type").each do |source|
1326
- ddl = "create or replace \n "
1399
+ ddl = "CREATE OR REPLACE \n"
1327
1400
  lines = select_all(%Q{
1328
1401
  select text
1329
1402
  from all_source
@@ -1332,79 +1405,62 @@ module ActiveRecord
1332
1405
  and owner = sys_context('userenv','session_user')
1333
1406
  order by line
1334
1407
  }).map do |row|
1335
- ddl << row['text'] if row['text'].size > 1
1408
+ ddl << row['text']
1336
1409
  end
1337
- ddl << ";" unless ddl.strip.last == ";"
1338
- structure << ddl << STATEMENT_TOKEN
1410
+ ddl << ";" unless ddl.strip[-1,1] == ";"
1411
+ structure << ddl
1339
1412
  end
1340
1413
 
1341
1414
  # export views
1342
1415
  select_all("select view_name, text from user_views").each do |view|
1343
- ddl = "create or replace view #{view['view_name']} AS\n "
1344
- # any views with empty lines will cause OCI to barf when loading. remove blank lines =/
1345
- ddl << view['text'].gsub(/^\n/, '')
1346
- structure << ddl << STATEMENT_TOKEN
1416
+ structure << "CREATE OR REPLACE VIEW #{view['view_name']} AS\n #{view['text']}"
1347
1417
  end
1348
1418
 
1349
- # export synonyms
1419
+ # export synonyms
1350
1420
  select_all("select owner, synonym_name, table_name, table_owner
1351
1421
  from all_synonyms
1352
1422
  where owner = sys_context('userenv','session_user') ").each do |synonym|
1353
- ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']}"
1354
- structure << ddl << STATEMENT_TOKEN
1423
+ structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']}"
1424
+ structure << " FOR #{synonym['table_owner']}.#{synonym['table_name']}"
1355
1425
  end
1356
1426
 
1357
- structure
1427
+ join_with_statement_token(structure)
1358
1428
  end
1359
1429
 
1360
- def structure_dump_indexes(table_name) #:nodoc:
1361
- statements = indexes(table_name).map do |options|
1362
- #def add_index(table_name, column_name, options = {})
1363
- column_names = options[:columns]
1364
- options = {:name => options[:name], :unique => options[:unique]}
1365
- index_name = index_name(table_name, :column => column_names)
1366
- if Hash === options # legacy support, since this param was a string
1367
- index_type = options[:unique] ? "UNIQUE" : ""
1368
- index_name = options[:name] || index_name
1369
- else
1370
- index_type = options
1371
- end
1372
- quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1373
- "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
1374
- end
1375
- statements.length > 0 ? "#{statements.join(STATEMENT_TOKEN)}#{STATEMENT_TOKEN}" : ''
1376
- end
1377
-
1378
1430
  def structure_drop #:nodoc:
1379
- s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
1380
- drop << "drop sequence #{seq.to_a.first.last};\n\n"
1431
+ statements = select_values("select sequence_name from user_sequences order by 1").map do |seq|
1432
+ "DROP SEQUENCE \"#{seq}\""
1381
1433
  end
1382
-
1383
- # changed select from user_tables to all_tables - much faster in large data dictionaries
1384
- select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
1385
- drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1434
+ select_values("select table_name from all_tables t
1435
+ where owner = sys_context('userenv','session_user') and secondary='N'
1436
+ and not exists (select mv.mview_name from all_mviews mv where mv.owner = t.owner and mv.mview_name = t.table_name)
1437
+ order by 1").each do |table|
1438
+ statements << "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
1386
1439
  end
1440
+ join_with_statement_token(statements)
1387
1441
  end
1388
-
1442
+
1389
1443
  def temp_table_drop #:nodoc:
1390
- # changed select from user_tables to all_tables - much faster in large data dictionaries
1391
- 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|
1392
- drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1393
- end
1444
+ join_with_statement_token(select_values(
1445
+ "select table_name from all_tables
1446
+ where owner = sys_context('userenv','session_user') and secondary='N' and temporary = 'Y' order by 1").map do |table|
1447
+ "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
1448
+ end)
1394
1449
  end
1395
-
1450
+
1396
1451
  def full_drop(preserve_tables=false) #:nodoc:
1397
1452
  s = preserve_tables ? [] : [structure_drop]
1398
1453
  s << temp_table_drop if preserve_tables
1399
1454
  s << drop_sql_for_feature("view")
1455
+ s << drop_sql_for_feature("materialized view")
1400
1456
  s << drop_sql_for_feature("synonym")
1401
1457
  s << drop_sql_for_feature("type")
1402
1458
  s << drop_sql_for_object("package")
1403
1459
  s << drop_sql_for_object("function")
1404
1460
  s << drop_sql_for_object("procedure")
1405
- s.join("\n\n")
1461
+ s.join
1406
1462
  end
1407
-
1463
+
1408
1464
  def add_column_options!(sql, options) #:nodoc:
1409
1465
  type = options[:type] || ((column = options[:column]) && column.type)
1410
1466
  type = type && type.to_sym
@@ -1452,9 +1508,6 @@ module ActiveRecord
1452
1508
  select_value("select temporary from user_tables where table_name = '#{table_name.upcase}'") == 'Y'
1453
1509
  end
1454
1510
 
1455
- # statements separator used in structure dump
1456
- STATEMENT_TOKEN = "\n\n--@@@--\n\n"
1457
-
1458
1511
  # ORDER BY clause for the passed order option.
1459
1512
  #
1460
1513
  # Uses column aliases as defined by #distinct.
@@ -1530,8 +1583,8 @@ module ActiveRecord
1530
1583
  "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_pkt"
1531
1584
  end
1532
1585
 
1533
- def compress_lines(string, spaced = true)
1534
- string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
1586
+ def compress_lines(string, join_with = "\n")
1587
+ string.split($/).map { |line| line.strip }.join(join_with)
1535
1588
  end
1536
1589
 
1537
1590
  # virtual columns are an 11g feature. This returns [] if feature is not
@@ -1550,15 +1603,28 @@ module ActiveRecord
1550
1603
  []
1551
1604
  end
1552
1605
  end
1553
-
1606
+
1554
1607
  def drop_sql_for_feature(type)
1555
- select_values("select 'DROP #{type.upcase} \"' || #{type}_name || '\";' from user_#{type.tableize}").join("\n\n")
1608
+ short_type = type == 'materialized view' ? 'mview' : type
1609
+ join_with_statement_token(
1610
+ select_values("select #{short_type}_name from user_#{short_type.tableize}").map do |name|
1611
+ "DROP #{type.upcase} \"#{name}\""
1612
+ end)
1556
1613
  end
1557
-
1614
+
1558
1615
  def drop_sql_for_object(type)
1559
- select_values("select 'DROP #{type.upcase} ' || object_name || ';' from user_objects where object_type = '#{type.upcase}'").join("\n\n")
1616
+ join_with_statement_token(
1617
+ select_values("select object_name from user_objects where object_type = '#{type.upcase}'").map do |name|
1618
+ "DROP #{type.upcase} \"#{name}\""
1619
+ end)
1560
1620
  end
1561
-
1621
+
1622
+ def join_with_statement_token(array)
1623
+ string = array.join(STATEMENT_TOKEN)
1624
+ string << STATEMENT_TOKEN unless string.blank?
1625
+ string
1626
+ end
1627
+
1562
1628
  public
1563
1629
  # DBMS_OUTPUT =============================================
1564
1630
  #
@@ -1641,7 +1707,7 @@ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1641
1707
  begin
1642
1708
  require 'active_record/connection_adapters/oracle_enhanced_tasks'
1643
1709
  rescue LoadError
1644
- end if defined?(Rails.root)
1710
+ end if defined?(RAILS_ROOT)
1645
1711
 
1646
1712
  # Handles quoting of oracle reserved words
1647
1713
  require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
@@ -1655,6 +1721,9 @@ require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext
1655
1721
  # Extensions for schema definition
1656
1722
  require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1657
1723
 
1724
+ # Extensions for context index definition
1725
+ require 'active_record/connection_adapters/oracle_enhanced_context_index'
1726
+
1658
1727
  # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1659
1728
  require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1660
1729