activerecord-oracle_enhanced-adapter 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
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'