activerecord-oracle_enhanced-adapter 1.2.3 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt CHANGED
@@ -1,3 +1,20 @@
1
+ == 1.2.4 2010-02-23
2
+
3
+ * Enhancements:
4
+ * rake db:test:purge will drop all schema objects from test schema (including views, synonyms, packages, functions, procedures) -
5
+ they should be always reloaded before tests run if necessary
6
+ * added views, synonyms, packages, functions, procedures, indexes, triggers, types, primary, unique and foreign key constraints to structure dump
7
+ * added :temporary option for create_table to create temporary tables
8
+ * added :tablespace option for add_index
9
+ * support function based indexes in schema dump
10
+ * support JNDI database connections in JRuby
11
+ * check ruby-oci8 minimum version 2.0.3
12
+ * added savepoints support (nested ActiveRecord transactions)
13
+ * Bug fixes:
14
+ * typecast returned BigDecimal integer values to Fixnum or Bignum
15
+ (to avoid issues with _before_type_cast values for id attributes because _before_type_cast is used in form helpers)
16
+ * clear table columns cache after columns definition change in migrations
17
+
1
18
  == 1.2.3 2009-12-09
2
19
 
3
20
  * Enhancements
data/License.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
1
+ Copyright (c) 2008-2010 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -48,12 +48,14 @@ In addition install either ruby-oci8 (for MRI/YARV) or copy Oracle JDBC driver t
48
48
  * Anton Jenkins
49
49
  * Dave Smylie
50
50
  * Alex Rothenberg
51
+ * Billy Reisinger
52
+ * David Blain
51
53
 
52
54
  == LICENSE:
53
55
 
54
56
  (The MIT License)
55
57
 
56
- Copyright (c) 2009 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
58
+ Copyright (c) 2008-2010 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
57
59
 
58
60
  Permission is hereby granted, free of charge, to any person obtaining
59
61
  a copy of this software and associated documentation files (the
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.3
1
+ 1.2.4
@@ -17,6 +17,7 @@ namespace :db do
17
17
  abcs = ActiveRecord::Base.configurations
18
18
  ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
19
19
  File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
20
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_fk_constraints }
20
21
  if ActiveRecord::Base.connection.supports_migrations?
21
22
  File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
22
23
  end
@@ -32,15 +33,17 @@ namespace :db do
32
33
  abcs = ActiveRecord::Base.configurations
33
34
  ActiveRecord::Base.establish_connection(:test)
34
35
  IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
35
- ActiveRecord::Base.connection.execute(ddl.chop)
36
+ ddl.chop! if ddl.last == ";"
37
+ ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
36
38
  end
37
39
  end
38
40
 
39
41
  redefine_task :purge => :environment do
40
42
  abcs = ActiveRecord::Base.configurations
41
43
  ActiveRecord::Base.establish_connection(:test)
42
- ActiveRecord::Base.connection.structure_drop.split("\n\n").each do |ddl|
43
- ActiveRecord::Base.connection.execute(ddl.chop)
44
+ ActiveRecord::Base.connection.full_drop.split("\n\n").each do |ddl|
45
+ ddl.chop! if ddl.last == ";"
46
+ ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
44
47
  end
45
48
  end
46
49
 
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
2
3
  #
3
4
  # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
@@ -214,7 +215,7 @@ module ActiveRecord
214
215
  Date.new(value.year, value.month, value.day) : value
215
216
  end
216
217
 
217
- class <<self
218
+ class << self
218
219
  protected
219
220
 
220
221
  def fallback_string_to_date(string) #:nodoc:
@@ -404,33 +405,45 @@ module ActiveRecord
404
405
  @quoted_column_names, @quoted_table_names = {}, {}
405
406
  end
406
407
 
408
+ ADAPTER_NAME = 'OracleEnhanced'.freeze
409
+
407
410
  def adapter_name #:nodoc:
408
- 'OracleEnhanced'
411
+ ADAPTER_NAME
409
412
  end
410
413
 
411
414
  def supports_migrations? #:nodoc:
412
415
  true
413
416
  end
414
417
 
418
+ def supports_savepoints? #:nodoc:
419
+ true
420
+ end
421
+
422
+ #:stopdoc:
423
+ NATIVE_DATABASE_TYPES = {
424
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
425
+ :string => { :name => "VARCHAR2", :limit => 255 },
426
+ :text => { :name => "CLOB" },
427
+ :integer => { :name => "NUMBER", :limit => 38 },
428
+ :float => { :name => "NUMBER" },
429
+ :decimal => { :name => "DECIMAL" },
430
+ :datetime => { :name => "DATE" },
431
+ # changed to native TIMESTAMP type
432
+ # :timestamp => { :name => "DATE" },
433
+ :timestamp => { :name => "TIMESTAMP" },
434
+ :time => { :name => "DATE" },
435
+ :date => { :name => "DATE" },
436
+ :binary => { :name => "BLOB" },
437
+ :boolean => { :name => "NUMBER", :limit => 1 }
438
+ }
439
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
440
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
441
+ :boolean => { :name => "VARCHAR2", :limit => 1 }
442
+ )
443
+ #:startdoc:
444
+
415
445
  def native_database_types #:nodoc:
416
- {
417
- :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
418
- :string => { :name => "VARCHAR2", :limit => 255 },
419
- :text => { :name => "CLOB" },
420
- :integer => { :name => "NUMBER", :limit => 38 },
421
- :float => { :name => "NUMBER" },
422
- :decimal => { :name => "DECIMAL" },
423
- :datetime => { :name => "DATE" },
424
- # changed to native TIMESTAMP type
425
- # :timestamp => { :name => "DATE" },
426
- :timestamp => { :name => "TIMESTAMP" },
427
- :time => { :name => "DATE" },
428
- :date => { :name => "DATE" },
429
- :binary => { :name => "BLOB" },
430
- # if emulate_booleans_from_strings then store booleans in VARCHAR2
431
- :boolean => emulate_booleans_from_strings ?
432
- { :name => "VARCHAR2", :limit => 1 } : { :name => "NUMBER", :limit => 1 }
433
- }
446
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
434
447
  end
435
448
 
436
449
  # maximum length of Oracle identifiers
@@ -595,7 +608,8 @@ module ActiveRecord
595
608
  end
596
609
  protected :insert_sql
597
610
 
598
- AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze #:nodoc:
611
+ # use in set_sequence_name to avoid fetching primary key value from sequence
612
+ AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
599
613
 
600
614
  # Returns the next sequence value from a sequence generator. Not generally
601
615
  # called directly; used by ActiveRecord to get the next primary key value
@@ -622,6 +636,18 @@ module ActiveRecord
622
636
  @connection.autocommit = true
623
637
  end
624
638
 
639
+ def create_savepoint #:nodoc:
640
+ execute("SAVEPOINT #{current_savepoint_name}")
641
+ end
642
+
643
+ def rollback_to_savepoint #:nodoc:
644
+ execute("ROLLBACK TO #{current_savepoint_name}")
645
+ end
646
+
647
+ def release_savepoint #:nodoc:
648
+ # there is no RELEASE SAVEPOINT statement in Oracle
649
+ end
650
+
625
651
  def add_limit_offset!(sql, options) #:nodoc:
626
652
  # added to_i for limit and offset to protect from SQL injection
627
653
  offset = (options[:offset] || 0).to_i
@@ -713,14 +739,19 @@ module ActiveRecord
713
739
  #
714
740
  # see: abstract/schema_statements.rb
715
741
 
716
- # current database name
742
+ # Current database name
717
743
  def current_database
718
- select_one("select sys_context('userenv','db_name') db from dual")["db"]
744
+ select_value("select sys_context('userenv','db_name') from dual")
719
745
  end
720
746
 
721
- # current database session user
747
+ # Current database session user
722
748
  def current_user
723
- select_one("select sys_context('userenv','session_user') u from dual")['u']
749
+ select_value("select sys_context('userenv','session_user') from dual")
750
+ end
751
+
752
+ # Default tablespace name of current user
753
+ def default_tablespace
754
+ select_value("select lower(default_tablespace) from user_users where username = sys_context('userenv','session_user')")
724
755
  end
725
756
 
726
757
  def tables(name = nil) #:nodoc:
@@ -735,37 +766,38 @@ module ActiveRecord
735
766
  def indexes(table_name, name = nil) #:nodoc:
736
767
  (owner, table_name, db_link) = @connection.describe(table_name)
737
768
  unless all_schema_indexes
769
+ default_tablespace_name = default_tablespace
738
770
  result = select_all(<<-SQL)
739
- SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
740
- FROM all_indexes#{db_link} i, all_ind_columns#{db_link} c
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
741
775
  WHERE i.owner = '#{owner}'
742
776
  AND i.table_owner = '#{owner}'
743
- AND c.index_name = i.index_name
744
- AND c.index_owner = i.owner
745
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')
746
778
  ORDER BY i.index_name, c.column_position
747
779
  SQL
748
-
780
+
749
781
  current_index = nil
750
782
  self.all_schema_indexes = []
751
-
783
+
752
784
  result.each do |row|
753
785
  # have to keep track of indexes because above query returns dups
754
786
  # there is probably a better query we could figure out
755
787
  if current_index != row['index_name']
756
- self.all_schema_indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE", [])
788
+ all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE",
789
+ row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
757
790
  current_index = row['index_name']
758
791
  end
759
-
760
- self.all_schema_indexes.last.columns << row['column_name']
792
+ all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] : row['column_expression'].gsub('"','').downcase)
761
793
  end
762
794
  end
763
-
795
+
764
796
  # Return the indexes just for the requested table, since AR is structured that way
765
797
  table_name = table_name.downcase
766
798
  all_schema_indexes.select{|i| i.table == table_name}
767
799
  end
768
-
800
+
769
801
  @@ignore_table_columns = nil #:nodoc:
770
802
 
771
803
  # set ignored columns for table
@@ -899,6 +931,11 @@ module ActiveRecord
899
931
  @@columns_cache = nil
900
932
  end
901
933
 
934
+ # used in migrations to clear column cache for specified table
935
+ def clear_table_columns_cache(table_name)
936
+ @@columns_cache[table_name.to_s] = nil if @@cache_columns
937
+ end
938
+
902
939
  ##
903
940
  # :singleton-method:
904
941
  # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
@@ -937,38 +974,52 @@ module ActiveRecord
937
974
  # t.string :first_name, :comment => “Given name”
938
975
  # t.string :last_name, :comment => “Surname”
939
976
  # end
977
+
940
978
  def create_table(name, options = {}, &block)
941
979
  create_sequence = options[:id] != false
942
980
  column_comments = {}
943
- super(name, options) do |t|
944
- # store that primary key was defined in create_table block
945
- unless create_sequence
946
- class <<t
947
- attr_accessor :create_sequence
948
- def primary_key(*args)
949
- self.create_sequence = true
950
- super(*args)
951
- end
981
+
982
+ table_definition = TableDefinition.new(self)
983
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false
984
+
985
+ # store that primary key was defined in create_table block
986
+ unless create_sequence
987
+ class << table_definition
988
+ attr_accessor :create_sequence
989
+ def primary_key(*args)
990
+ self.create_sequence = true
991
+ super(*args)
952
992
  end
953
993
  end
994
+ end
954
995
 
955
- # store column comments
956
- class <<t
957
- attr_accessor :column_comments
958
- def column(name, type, options = {})
959
- if options[:comment]
960
- self.column_comments ||= {}
961
- self.column_comments[name] = options[:comment]
962
- end
963
- super(name, type, options)
996
+ # store column comments
997
+ class << table_definition
998
+ attr_accessor :column_comments
999
+ def column(name, type, options = {})
1000
+ if options[:comment]
1001
+ self.column_comments ||= {}
1002
+ self.column_comments[name] = options[:comment]
964
1003
  end
1004
+ super(name, type, options)
965
1005
  end
1006
+ end
966
1007
 
967
- result = block.call(t) if block
968
- create_sequence = create_sequence || t.create_sequence
969
- column_comments = t.column_comments if t.column_comments
1008
+ result = block.call(table_definition) if block
1009
+ create_sequence = create_sequence || table_definition.create_sequence
1010
+ column_comments = table_definition.column_comments if table_definition.column_comments
1011
+
1012
+
1013
+ if options[:force] && table_exists?(name)
1014
+ drop_table(name, options)
970
1015
  end
971
1016
 
1017
+ create_sql = "CREATE#{' GLOBAL TEMPORARY' if options[:temporary]} TABLE "
1018
+ create_sql << "#{quote_table_name(name)} ("
1019
+ create_sql << table_definition.to_sql
1020
+ create_sql << ") #{options[:options]}"
1021
+ execute create_sql
1022
+
972
1023
  create_sequence_and_trigger(name, options) if create_sequence
973
1024
 
974
1025
  add_table_comment name, options[:comment]
@@ -987,12 +1038,29 @@ module ActiveRecord
987
1038
  super(name)
988
1039
  seq_name = options[:sequence_name] || default_sequence_name(name)
989
1040
  execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
1041
+ ensure
1042
+ clear_table_columns_cache(name)
990
1043
  end
991
1044
 
992
1045
  # clear cached indexes when adding new index
993
1046
  def add_index(table_name, column_name, options = {}) #:nodoc:
994
1047
  self.all_schema_indexes = nil
995
- super
1048
+ column_names = Array(column_name)
1049
+ index_name = index_name(table_name, :column => column_names)
1050
+
1051
+ if Hash === options # legacy support, since this param was a string
1052
+ index_type = options[:unique] ? "UNIQUE" : ""
1053
+ index_name = options[:name] || index_name
1054
+ tablespace = if options[:tablespace]
1055
+ " TABLESPACE #{options[:tablespace]}"
1056
+ else
1057
+ ""
1058
+ end
1059
+ else
1060
+ index_type = options
1061
+ end
1062
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1063
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace}"
996
1064
  end
997
1065
 
998
1066
  # clear cached indexes when removing index
@@ -1026,10 +1094,14 @@ module ActiveRecord
1026
1094
  options[:type] = type
1027
1095
  add_column_options!(add_column_sql, options)
1028
1096
  execute(add_column_sql)
1097
+ ensure
1098
+ clear_table_columns_cache(table_name)
1029
1099
  end
1030
1100
 
1031
1101
  def change_column_default(table_name, column_name, default) #:nodoc:
1032
1102
  execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
1103
+ ensure
1104
+ clear_table_columns_cache(table_name)
1033
1105
  end
1034
1106
 
1035
1107
  def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
@@ -1055,14 +1127,20 @@ module ActiveRecord
1055
1127
  options[:type] = type
1056
1128
  add_column_options!(change_column_sql, options)
1057
1129
  execute(change_column_sql)
1130
+ ensure
1131
+ clear_table_columns_cache(table_name)
1058
1132
  end
1059
1133
 
1060
1134
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
1061
1135
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
1136
+ ensure
1137
+ clear_table_columns_cache(table_name)
1062
1138
  end
1063
1139
 
1064
1140
  def remove_column(table_name, column_name) #:nodoc:
1065
1141
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
1142
+ ensure
1143
+ clear_table_columns_cache(table_name)
1066
1144
  end
1067
1145
 
1068
1146
  def add_comment(table_name, column_name, comment) #:nodoc:
@@ -1124,78 +1202,179 @@ module ActiveRecord
1124
1202
 
1125
1203
  def structure_dump #:nodoc:
1126
1204
  s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
1127
- structure << "create sequence #{seq.to_a.first.last};\n\n"
1205
+ structure << "create sequence #{seq.to_a.first.last}#{STATEMENT_TOKEN}"
1128
1206
  end
1129
1207
 
1130
1208
  # changed select from user_tables to all_tables - much faster in large data dictionaries
1131
1209
  select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
1132
- ddl = "create table #{table.to_a.first.last} (\n "
1210
+ table_name = table['table_name']
1211
+ virtual_columns = virtual_columns_for(table_name)
1212
+ ddl = "create#{ ' global temporary' if temporary_table?(table_name)} table #{table_name} (\n "
1133
1213
  cols = select_all(%Q{
1134
1214
  select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
1135
1215
  from user_tab_columns
1136
- where table_name = '#{table.to_a.first.last}'
1216
+ where table_name = '#{table_name}'
1137
1217
  order by column_id
1138
1218
  }).map do |row|
1139
- col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
1140
- if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
1141
- col << "(#{row['data_precision'].to_i}"
1142
- col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
1143
- col << ')'
1144
- elsif row['data_type'].include?('CHAR')
1145
- length = row['char_used'] == 'C' ? row['char_length'].to_i : row['data_length'].to_i
1146
- col << "(#{length})"
1219
+ if(v = virtual_columns.find {|col| col['column_name'] == row['column_name']})
1220
+ structure_dump_virtual_column(row, v['data_default'])
1221
+ else
1222
+ structure_dump_column(row)
1147
1223
  end
1148
- col << " default #{row['data_default']}" if !row['data_default'].nil?
1149
- col << ' not null' if row['nullable'] == 'N'
1150
- col
1151
1224
  end
1152
1225
  ddl << cols.join(",\n ")
1153
- ddl << ");\n\n"
1226
+ ddl << structure_dump_constraints(table_name)
1227
+ ddl << "\n)#{STATEMENT_TOKEN}"
1154
1228
  structure << ddl
1229
+ structure << structure_dump_indexes(table_name)
1155
1230
  end
1156
1231
  end
1157
-
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}"
1236
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1237
+ col << "(#{column['data_precision'].to_i}"
1238
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
1239
+ col << ')'
1240
+ elsif column['data_type'].include?('CHAR')
1241
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1242
+ col << "(#{length})"
1243
+ end
1244
+ col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
1245
+ end
1246
+
1247
+ def structure_dump_column(column) #:nodoc:
1248
+ col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1249
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1250
+ col << "(#{column['data_precision'].to_i}"
1251
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
1252
+ col << ')'
1253
+ elsif column['data_type'].include?('CHAR')
1254
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1255
+ col << "(#{length})"
1256
+ end
1257
+ col << " default #{column['data_default']}" if !column['data_default'].nil?
1258
+ col << ' not null' if column['nullable'] == 'N'
1259
+ col
1260
+ end
1261
+
1262
+ def structure_dump_constraints(table) #:nodoc:
1263
+ out = [structure_dump_primary_key(table), structure_dump_unique_keys(table)].flatten.compact
1264
+ out.length > 0 ? ",\n#{out.join(",\n")}" : ''
1265
+ end
1266
+
1267
+ def structure_dump_primary_key(table) #:nodoc:
1268
+ opts = {:name => '', :cols => []}
1269
+ pks = select_all(<<-SQL, "Primary Keys")
1270
+ select a.constraint_name, a.column_name, a.position
1271
+ from user_cons_columns a
1272
+ join user_constraints c
1273
+ on a.constraint_name = c.constraint_name
1274
+ where c.table_name = '#{table.upcase}'
1275
+ and c.constraint_type = 'P'
1276
+ and c.owner = sys_context('userenv', 'session_user')
1277
+ SQL
1278
+ pks.each do |row|
1279
+ opts[:name] = row['constraint_name']
1280
+ opts[:cols][row['position']-1] = row['column_name']
1281
+ end
1282
+ opts[:cols].length > 0 ? " CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : nil
1283
+ end
1284
+
1285
+ def structure_dump_unique_keys(table) #:nodoc:
1286
+ keys = {}
1287
+ uks = select_all(<<-SQL, "Primary Keys")
1288
+ select a.constraint_name, a.column_name, a.position
1289
+ from user_cons_columns a
1290
+ join user_constraints c
1291
+ on a.constraint_name = c.constraint_name
1292
+ where c.table_name = '#{table.upcase}'
1293
+ and c.constraint_type = 'U'
1294
+ and c.owner = sys_context('userenv', 'session_user')
1295
+ SQL
1296
+ uks.each do |uk|
1297
+ keys[uk['constraint_name']] ||= []
1298
+ keys[uk['constraint_name']][uk['position']-1] = uk['column_name']
1299
+ end
1300
+ keys.map do |k,v|
1301
+ " CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
1302
+ end
1303
+ end
1304
+
1305
+ def structure_dump_fk_constraints #:nodoc:
1306
+ fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
1307
+ if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
1308
+ foreign_keys.map do |fk|
1309
+ column = fk.options[:column] || "#{fk.to_table.to_s.singularize}_id"
1310
+ constraint_name = foreign_key_constraint_name(fk.from_table, column, fk.options)
1311
+ sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
1312
+ sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
1313
+ end
1314
+ end
1315
+ end.flatten.compact.join(STATEMENT_TOKEN)
1316
+ fks.length > 1 ? "#{fks}#{STATEMENT_TOKEN}" : ''
1317
+ end
1318
+
1158
1319
  # Extract all stored procedures, packages, synonyms and views.
1159
1320
  def structure_dump_db_stored_code #:nodoc:
1160
- structure = "\n"
1321
+ structure = ""
1161
1322
  select_all("select distinct name, type
1162
1323
  from all_source
1163
- where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION')
1164
- and owner = sys_context('userenv','session_user')").inject("\n\n") do |structure, source|
1165
- ddl = "create or replace \n "
1166
- lines = select_all(%Q{
1167
- select text
1168
- from all_source
1169
- where name = '#{source['name']}'
1170
- and type = '#{source['type']}'
1171
- and owner = sys_context('userenv','session_user')
1172
- order by line
1173
- }).map do |row|
1174
- ddl << row['text'] if row['text'].size > 1
1175
- end
1176
- ddl << ";"
1177
- structure << ddl << "\n"
1324
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
1325
+ and owner = sys_context('userenv','session_user') order by type").each do |source|
1326
+ ddl = "create or replace \n "
1327
+ lines = select_all(%Q{
1328
+ select text
1329
+ from all_source
1330
+ where name = '#{source['name']}'
1331
+ and type = '#{source['type']}'
1332
+ and owner = sys_context('userenv','session_user')
1333
+ order by line
1334
+ }).map do |row|
1335
+ ddl << row['text'] if row['text'].size > 1
1336
+ end
1337
+ ddl << ";" unless ddl.strip.last == ";"
1338
+ structure << ddl << STATEMENT_TOKEN
1178
1339
  end
1179
1340
 
1180
1341
  # export views
1181
- select_all("select view_name, text from user_views").inject(structure) do |structure, view|
1342
+ select_all("select view_name, text from user_views").each do |view|
1182
1343
  ddl = "create or replace view #{view['view_name']} AS\n "
1183
1344
  # any views with empty lines will cause OCI to barf when loading. remove blank lines =/
1184
1345
  ddl << view['text'].gsub(/^\n/, '')
1185
- ddl << ";\n\n"
1186
- structure << ddl
1346
+ structure << ddl << STATEMENT_TOKEN
1187
1347
  end
1188
1348
 
1189
1349
  # export synonyms
1190
1350
  select_all("select owner, synonym_name, table_name, table_owner
1191
1351
  from all_synonyms
1192
- where table_owner = sys_context('userenv','session_user') ").inject(structure) do |structure, synonym|
1193
- ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']};\n\n"
1194
- structure << ddl;
1352
+ 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
1195
1355
  end
1196
- end
1197
1356
 
1357
+ structure
1358
+ end
1198
1359
 
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
+
1199
1378
  def structure_drop #:nodoc:
1200
1379
  s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
1201
1380
  drop << "drop sequence #{seq.to_a.first.last};\n\n"
@@ -1206,8 +1385,26 @@ module ActiveRecord
1206
1385
  drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1207
1386
  end
1208
1387
  end
1209
-
1210
-
1388
+
1389
+ 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
1394
+ end
1395
+
1396
+ def full_drop(preserve_tables=false) #:nodoc:
1397
+ s = preserve_tables ? [] : [structure_drop]
1398
+ s << temp_table_drop if preserve_tables
1399
+ s << drop_sql_for_feature("view")
1400
+ s << drop_sql_for_feature("synonym")
1401
+ s << drop_sql_for_feature("type")
1402
+ s << drop_sql_for_object("package")
1403
+ s << drop_sql_for_object("function")
1404
+ s << drop_sql_for_object("procedure")
1405
+ s.join("\n\n")
1406
+ end
1407
+
1211
1408
  def add_column_options!(sql, options) #:nodoc:
1212
1409
  type = options[:type] || ((column = options[:column]) && column.type)
1213
1410
  type = type && type.to_sym
@@ -1251,6 +1448,13 @@ module ActiveRecord
1251
1448
  sql << order_columns * ", "
1252
1449
  end
1253
1450
 
1451
+ def temporary_table?(table_name) #:nodoc:
1452
+ select_value("select temporary from user_tables where table_name = '#{table_name.upcase}'") == 'Y'
1453
+ end
1454
+
1455
+ # statements separator used in structure dump
1456
+ STATEMENT_TOKEN = "\n\n--@@@--\n\n"
1457
+
1254
1458
  # ORDER BY clause for the passed order option.
1255
1459
  #
1256
1460
  # Uses column aliases as defined by #distinct.
@@ -1330,13 +1534,41 @@ module ActiveRecord
1330
1534
  string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
1331
1535
  end
1332
1536
 
1537
+ # virtual columns are an 11g feature. This returns [] if feature is not
1538
+ # present or none are found.
1539
+ # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
1540
+ def virtual_columns_for(table)
1541
+ begin
1542
+ select_all <<-SQL
1543
+ select column_name, data_default
1544
+ from user_tab_cols
1545
+ where virtual_column='YES'
1546
+ and table_name='#{table.upcase}'
1547
+ SQL
1548
+ # feature not supported previous to 11g
1549
+ rescue ActiveRecord::StatementInvalid => e
1550
+ []
1551
+ end
1552
+ end
1553
+
1554
+ def drop_sql_for_feature(type)
1555
+ select_values("select 'DROP #{type.upcase} \"' || #{type}_name || '\";' from user_#{type.tableize}").join("\n\n")
1556
+ end
1557
+
1558
+ 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")
1560
+ end
1561
+
1333
1562
  public
1334
1563
  # DBMS_OUTPUT =============================================
1335
1564
  #
1336
1565
  # PL/SQL in Oracle uses dbms_output for logging print statements
1337
1566
  # These methods stick that output into the Rails log so Ruby and PL/SQL
1338
1567
  # code can can be debugged together in a single application
1339
- DBMS_OUTPUT_BUFFER_SIZE = 10000 #can be 1-1000000
1568
+
1569
+ # Maximum DBMS_OUTPUT buffer size
1570
+ DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
1571
+
1340
1572
  # Turn DBMS_Output logging on
1341
1573
  def enable_dbms_output
1342
1574
  set_dbms_output_plsql_connection
@@ -1397,15 +1629,7 @@ if defined?(CGI::Session::ActiveRecordStore::Session)
1397
1629
  end
1398
1630
 
1399
1631
  # Load custom create, update, delete methods functionality
1400
- # rescue LoadError if ruby-plsql gem cannot be loaded
1401
- begin
1402
- require 'active_record/connection_adapters/oracle_enhanced_procedures'
1403
- rescue LoadError
1404
- if defined?(RAILS_DEFAULT_LOGGER)
1405
- RAILS_DEFAULT_LOGGER.info "INFO: ActiveRecord oracle_enhanced adapter could not load ruby-plsql gem. "+
1406
- "Custom create, update and delete methods will not be available."
1407
- end
1408
- end
1632
+ require 'active_record/connection_adapters/oracle_enhanced_procedures'
1409
1633
 
1410
1634
  # Load additional methods for composite_primary_keys support
1411
1635
  require 'active_record/connection_adapters/oracle_enhanced_cpk'