activerecord-oracle_enhanced-adapter 1.2.4 → 1.3.0

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 (33) hide show
  1. data/.gitignore +0 -1
  2. data/History.txt +20 -0
  3. data/README.rdoc +7 -3
  4. data/Rakefile +1 -2
  5. data/VERSION +1 -1
  6. data/activerecord-oracle_enhanced-adapter.gemspec +96 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced.rake +11 -8
  8. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +37 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +317 -180
  10. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +282 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +3 -2
  12. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +1 -1
  13. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +3 -3
  14. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +6 -1
  15. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +143 -52
  16. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +2 -1
  17. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +39 -20
  18. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +2 -1
  19. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -1
  20. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +70 -11
  21. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +27 -20
  22. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +334 -0
  23. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +28 -22
  24. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +24 -28
  25. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +13 -11
  26. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -1
  27. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +72 -69
  28. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +112 -6
  29. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +49 -1
  30. data/spec/spec_helper.rb +97 -19
  31. metadata +33 -22
  32. data/Manifest.txt +0 -32
  33. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +0 -126
data/.gitignore CHANGED
@@ -6,5 +6,4 @@ pkg
6
6
  log
7
7
  tmp
8
8
  sqlnet.log
9
- activerecord-oracle_enhanced-adapter.gemspec
10
9
 
data/History.txt CHANGED
@@ -1,3 +1,23 @@
1
+ == 1.3.0 2010-06-21
2
+
3
+ * Enhancements:
4
+ * Rails 3.0.0.beta4 and Rails 2.3.x compatible
5
+ * When used with Rails 3 then works together with Oracle SQL compiler included in Arel gem (http://github.com/rails/arel)
6
+ * Rails 3: Better support for limit and offset (when possible adds just ROWNUM condition in WHERE clause without using subqueries)
7
+ * Table and column names are always quoted and in uppercase to avoid the need for checking Oracle reserved words
8
+ * Full text search index creation (add_context_index and remove_context_index methods in migrations and #contains method in ActiveRecord models)
9
+ * add_index and remove_index give just warnings on wrong index names (new expected behavior in Rails 2.3.8 and 3.0.0)
10
+ * :tablespace and :options options for create_table and add_index
11
+ * Workarounds:
12
+ * Rails 3: set ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true in initializer file for all environments
13
+ (to avoid too many data dictionary queries from Arel)
14
+ * Rails 2.3: patch several ActiveRecord methods to work correctly with quoted table names in uppercase (see oracle_enhanced_activerecord_patches.rb).
15
+ These patches are already included in Rails 3.0.0.beta4.
16
+ * Bug fixes:
17
+ * Fixes for schema purge (drop correctly materialized views)
18
+ * Fixes for schema dump and structure dump (use correct statement separator)
19
+ * Only use Oracle specific schema dump for Oracle connections
20
+
1
21
  == 1.2.4 2010-02-23
2
22
 
3
23
  * Enhancements:
data/README.rdoc CHANGED
@@ -15,10 +15,10 @@ Blog posts about oracle-enahnced can be found at http://blog.rayapps.com/categor
15
15
 
16
16
  == REQUIREMENTS:
17
17
 
18
- * Works (has been tested) with ActiveRecord version 2.0, 2.1, 2.2 and 2.3 (these are the same as Rails versions)
18
+ * Latest version works (has been tested) with ActiveRecord version 2.3 and 3.0 (these are the same as Rails versions)
19
19
  * Can be used on the following Ruby platforms:
20
- * MRI - requires ruby-oci8 1.x or 2.x gem to connect to Oracle (2.0.3 or later recommended)
21
- * Ruby/YARV 1.9.1 - requires ruby-oci8 2.x library to connect to Oracle
20
+ * MRI - requires ruby-oci8 2.0 gem to connect to Oracle (2.0.4 or later recommended)
21
+ * Ruby/YARV 1.9.1 or 1.9.2 - requires ruby-oci8 2.0 library to connect to Oracle
22
22
  unicode_utils gem is recommended for Unicode aware string upcase and downcase
23
23
  * JRuby - uses JDBC driver ojdbc14.jar to connect to Oracle (should be in JRUBY_HOME/lib or in Java class path)
24
24
  * Requires ruby-plsql gem to support custom create, update and delete methods (but can be used without ruby-plsql if this functionality is not needed)
@@ -50,6 +50,10 @@ In addition install either ruby-oci8 (for MRI/YARV) or copy Oracle JDBC driver t
50
50
  * Alex Rothenberg
51
51
  * Billy Reisinger
52
52
  * David Blain
53
+ * Joe Khoobyar
54
+ * Edvard Majakari
55
+ * Beau Fabry
56
+ * Simon Chiang
53
57
 
54
58
  == LICENSE:
55
59
 
data/Rakefile CHANGED
@@ -13,8 +13,7 @@ EOS
13
13
  gem.email = "raimonds.simanovskis@gmail.com"
14
14
  gem.homepage = "http://github.com/rsim/oracle-enhanced"
15
15
  gem.authors = ["Raimonds Simanovskis"]
16
- gem.add_dependency "activerecord", ">= 2.0.0"
17
- gem.add_development_dependency "rspec", ">= 1.2.9"
16
+ gem.add_development_dependency "rspec", ">= 1.3.0"
18
17
  gem.extra_rdoc_files = ['README.rdoc']
19
18
  end
20
19
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.4
1
+ 1.3.0
@@ -0,0 +1,96 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{activerecord-oracle_enhanced-adapter}
8
+ s.version = "1.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Raimonds Simanovskis"]
12
+ s.date = %q{2010-06-21}
13
+ s.description = %q{Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases.
14
+ This adapter is superset of original ActiveRecord Oracle adapter.
15
+ }
16
+ s.email = %q{raimonds.simanovskis@gmail.com}
17
+ s.extra_rdoc_files = [
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "History.txt",
23
+ "License.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "activerecord-oracle_enhanced-adapter.gemspec",
28
+ "lib/active_record/connection_adapters/emulation/oracle_adapter.rb",
29
+ "lib/active_record/connection_adapters/oracle_enhanced.rake",
30
+ "lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb",
31
+ "lib/active_record/connection_adapters/oracle_enhanced_adapter.rb",
32
+ "lib/active_record/connection_adapters/oracle_enhanced_connection.rb",
33
+ "lib/active_record/connection_adapters/oracle_enhanced_context_index.rb",
34
+ "lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb",
35
+ "lib/active_record/connection_adapters/oracle_enhanced_cpk.rb",
36
+ "lib/active_record/connection_adapters/oracle_enhanced_dirty.rb",
37
+ "lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb",
38
+ "lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb",
39
+ "lib/active_record/connection_adapters/oracle_enhanced_procedures.rb",
40
+ "lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb",
41
+ "lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb",
42
+ "lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb",
43
+ "lib/active_record/connection_adapters/oracle_enhanced_tasks.rb",
44
+ "lib/active_record/connection_adapters/oracle_enhanced_version.rb",
45
+ "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb",
46
+ "spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb",
47
+ "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb",
48
+ "spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb",
49
+ "spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb",
50
+ "spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb",
51
+ "spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb",
52
+ "spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb",
53
+ "spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb",
54
+ "spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb",
55
+ "spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb",
56
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb",
57
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb",
58
+ "spec/spec.opts",
59
+ "spec/spec_helper.rb"
60
+ ]
61
+ s.homepage = %q{http://github.com/rsim/oracle-enhanced}
62
+ s.rdoc_options = ["--charset=UTF-8"]
63
+ s.require_paths = ["lib"]
64
+ s.rubygems_version = %q{1.3.7}
65
+ s.summary = %q{Oracle enhanced adapter for ActiveRecord}
66
+ s.test_files = [
67
+ "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb",
68
+ "spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb",
69
+ "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb",
70
+ "spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb",
71
+ "spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb",
72
+ "spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb",
73
+ "spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb",
74
+ "spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb",
75
+ "spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb",
76
+ "spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb",
77
+ "spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb",
78
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb",
79
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb",
80
+ "spec/spec_helper.rb"
81
+ ]
82
+
83
+ if s.respond_to? :specification_version then
84
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
85
+ s.specification_version = 3
86
+
87
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
88
+ s.add_development_dependency(%q<rspec>, [">= 1.3.0"])
89
+ else
90
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
91
+ end
92
+ else
93
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
94
+ end
95
+ end
96
+
@@ -15,14 +15,14 @@ namespace :db do
15
15
  namespace :structure do
16
16
  redefine_task :dump => :environment do
17
17
  abcs = ActiveRecord::Base.configurations
18
- ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
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 }
18
+ rails_env = defined?(Rails.env) ? Rails.env : RAILS_ENV
19
+ ActiveRecord::Base.establish_connection(abcs[rails_env])
20
+ File.open("db/#{rails_env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
21
21
  if ActiveRecord::Base.connection.supports_migrations?
22
- File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
22
+ File.open("db/#{rails_env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
23
23
  end
24
- if abcs[RAILS_ENV]['structure_dump'] == "db_stored_code"
25
- File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
24
+ if abcs[rails_env]['structure_dump'] == "db_stored_code"
25
+ File.open("db/#{rails_env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
26
26
  end
27
27
 
28
28
  end
@@ -31,8 +31,10 @@ namespace :db do
31
31
  namespace :test do
32
32
  redefine_task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
33
33
  abcs = ActiveRecord::Base.configurations
34
+ rails_env = defined?(Rails.env) ? Rails.env : RAILS_ENV
34
35
  ActiveRecord::Base.establish_connection(:test)
35
- IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
36
+ File.read("db/#{rails_env}_structure.sql").
37
+ split(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::STATEMENT_TOKEN).each do |ddl|
36
38
  ddl.chop! if ddl.last == ";"
37
39
  ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
38
40
  end
@@ -41,7 +43,8 @@ namespace :db do
41
43
  redefine_task :purge => :environment do
42
44
  abcs = ActiveRecord::Base.configurations
43
45
  ActiveRecord::Base.establish_connection(:test)
44
- ActiveRecord::Base.connection.full_drop.split("\n\n").each do |ddl|
46
+ ActiveRecord::Base.connection.full_drop.
47
+ split(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::STATEMENT_TOKEN).each do |ddl|
45
48
  ddl.chop! if ddl.last == ";"
46
49
  ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
47
50
  end
@@ -0,0 +1,37 @@
1
+ # ActiveRecord 2.3 patches
2
+ if ActiveRecord::VERSION::MAJOR == 2 && ActiveRecord::VERSION::MINOR == 3
3
+ require "active_record/associations"
4
+
5
+ ActiveRecord::Associations::ClassMethods.module_eval do
6
+ private
7
+ def tables_in_string(string)
8
+ return [] if string.blank?
9
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
10
+ # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
11
+ string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_']
12
+ end
13
+ end
14
+
15
+ ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.class_eval do
16
+ protected
17
+ def aliased_table_name_for(name, suffix = nil)
18
+ # always downcase quoted table name as Oracle quoted table names are in uppercase
19
+ if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{active_record.connection.quote_table_name(name).downcase}\son}
20
+ @join_dependency.table_aliases[name] += 1
21
+ end
22
+
23
+ unless @join_dependency.table_aliases[name].zero?
24
+ # if the table name has been used, then use an alias
25
+ name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
26
+ table_index = @join_dependency.table_aliases[name]
27
+ @join_dependency.table_aliases[name] += 1
28
+ name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
29
+ else
30
+ @join_dependency.table_aliases[name] += 1
31
+ end
32
+
33
+ name
34
+ end
35
+ end
36
+
37
+ end
@@ -29,8 +29,10 @@
29
29
  # contribution.
30
30
  # portions Copyright 2005 Graham Jenkins
31
31
 
32
- require 'active_record/connection_adapters/abstract_adapter'
32
+ # ActiveRecord 2.2 does not load version file automatically
33
+ require 'active_record/version' unless defined?(ActiveRecord::VERSION)
33
34
 
35
+ require 'active_record/connection_adapters/abstract_adapter'
34
36
  require 'active_record/connection_adapters/oracle_enhanced_connection'
35
37
 
36
38
  require 'digest/sha1'
@@ -91,7 +93,7 @@ module ActiveRecord
91
93
  # Specify which table columns should be typecasted to string values.
92
94
  # Might be useful to specify that columns should be string even if its name matches boolean column criteria.
93
95
  #
94
- # set_integer_columns :active_flag
96
+ # set_string_columns :active_flag
95
97
  def self.set_string_columns(*args)
96
98
  connection.set_type_for_columns(table_name,:string,*args)
97
99
  end
@@ -106,33 +108,7 @@ module ActiveRecord
106
108
  end
107
109
  end
108
110
  private :enhanced_write_lobs
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_without_lobs!, :add_order!
132
- alias_method :add_order!, :add_order_with_lobs!
133
- #:startdoc:
134
- end
135
-
111
+
136
112
  # Get table comment from schema definition.
137
113
  def self.table_comment
138
114
  connection.table_comment(self.table_name)
@@ -236,7 +212,7 @@ module ActiveRecord
236
212
  if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
237
213
  return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
238
214
  end
239
- DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
215
+ DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format).to_date
240
216
  end
241
217
 
242
218
  end
@@ -403,6 +379,7 @@ module ActiveRecord
403
379
  def initialize(connection, logger = nil) #:nodoc:
404
380
  super
405
381
  @quoted_column_names, @quoted_table_names = {}, {}
382
+ @enable_dbms_output = false
406
383
  end
407
384
 
408
385
  ADAPTER_NAME = 'OracleEnhanced'.freeze
@@ -415,6 +392,10 @@ module ActiveRecord
415
392
  true
416
393
  end
417
394
 
395
+ def supports_primary_key? #:nodoc:
396
+ true
397
+ end
398
+
418
399
  def supports_savepoints? #:nodoc:
419
400
  true
420
401
  end
@@ -453,6 +434,21 @@ module ActiveRecord
453
434
  IDENTIFIER_MAX_LENGTH
454
435
  end
455
436
 
437
+ # the maximum length of a table name
438
+ def table_name_length
439
+ IDENTIFIER_MAX_LENGTH
440
+ end
441
+
442
+ # the maximum length of a column name
443
+ def column_name_length
444
+ IDENTIFIER_MAX_LENGTH
445
+ end
446
+
447
+ # the maximum length of an index name
448
+ def index_name_length
449
+ IDENTIFIER_MAX_LENGTH
450
+ end
451
+
456
452
  # QUOTING ==================================================
457
453
  #
458
454
  # see: abstract/quoting.rb
@@ -460,26 +456,52 @@ module ActiveRecord
460
456
  def quote_column_name(name) #:nodoc:
461
457
  # camelCase column names need to be quoted; not that anyone using Oracle
462
458
  # 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)
459
+ name = name.to_s
460
+ @quoted_column_names[name] ||= begin
461
+ case name
462
+ # if only valid column characters in name
463
+ when /^[a-z][a-z_0-9\$#]*$/
464
+ "\"#{name.upcase}\""
465
+ when /^[a-z][a-z_0-9\$#\-]*$/i
466
+ "\"#{name}\""
467
+ # if other characters present then assume that it is expression
468
+ # which should not be quoted
469
+ else
470
+ name
471
+ end
472
+ end
464
473
  end
465
474
 
475
+ # Names must be from 1 to 30 bytes long with these exceptions:
476
+ # * Names of databases are limited to 8 bytes.
477
+ # * Names of database links can be as long as 128 bytes.
478
+ #
479
+ # Nonquoted identifiers cannot be Oracle Database reserved words
480
+ #
481
+ # Nonquoted identifiers must begin with an alphabetic character from
482
+ # your database character set
483
+ #
484
+ # Nonquoted identifiers can contain only alphanumeric characters from
485
+ # your database character set and the underscore (_), dollar sign ($),
486
+ # and pound sign (#). Database links can also contain periods (.) and
487
+ # "at" signs (@). Oracle strongly discourages you from using $ and # in
488
+ # nonquoted identifiers.
489
+ NONQUOTED_OBJECT_NAME = /[A-Za-z][A-z0-9$#]{0,29}/
490
+ NONQUOTED_DATABASE_LINK = /[A-Za-z][A-z0-9$#\.@]{0,127}/
491
+ VALID_TABLE_NAME = /\A(?:#{NONQUOTED_OBJECT_NAME}\.)?#{NONQUOTED_OBJECT_NAME}(?:@#{NONQUOTED_DATABASE_LINK})?\Z/
492
+
466
493
  # unescaped table name should start with letter and
467
494
  # contain letters, digits, _, $ or #
468
495
  # can be prefixed with schema name
469
496
  # CamelCase table names should be quoted
470
497
  def self.valid_table_name?(name) #:nodoc:
471
- name = name.to_s
472
- name =~ /\A([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ||
473
- name =~ /\A([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ? true : false
498
+ name = name.to_s
499
+ name =~ VALID_TABLE_NAME && !(name =~ /[A-Z]/ && name =~ /[a-z]/) ? true : false
474
500
  end
475
501
 
476
502
  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
503
+ name = name.to_s
504
+ @quoted_table_names[name] ||= name.split('.').map{|n| n.split('@').map{|m| quote_column_name(m)}.join('@')}.join('.')
483
505
  end
484
506
 
485
507
  def quote_string(s) #:nodoc:
@@ -580,7 +602,8 @@ module ActiveRecord
580
602
  # Executes a SQL statement
581
603
  def execute(sql, name = nil)
582
604
  # hack to pass additional "with_returning" option without changing argument list
583
- log(sql, name) { sql.instance_variable_get(:@with_returning) ? @connection.exec_with_returning(sql) : @connection.exec(sql) }
605
+ log(sql, name) { sql.instance_variable_defined?(:@with_returning) && sql.instance_variable_get(:@with_returning) ?
606
+ @connection.exec_with_returning(sql) : @connection.exec(sql) }
584
607
  end
585
608
 
586
609
  # Returns an array of arrays containing the field values.
@@ -603,7 +626,6 @@ module ActiveRecord
603
626
  sql_with_returning = sql.dup << @connection.returning_clause(quote_column_name(pk))
604
627
  # hack to pass additional "with_returning" option without changing argument list
605
628
  sql_with_returning.instance_variable_set(:@with_returning, true)
606
- clear_query_cache
607
629
  execute(sql_with_returning, name)
608
630
  end
609
631
  protected :insert_sql
@@ -651,10 +673,12 @@ module ActiveRecord
651
673
  def add_limit_offset!(sql, options) #:nodoc:
652
674
  # added to_i for limit and offset to protect from SQL injection
653
675
  offset = (options[:offset] || 0).to_i
654
-
655
- if limit = options[:limit]
656
- limit = limit.to_i
676
+ limit = options[:limit]
677
+ limit = limit.is_a?(String) && limit.blank? ? nil : limit && limit.to_i
678
+ if limit && offset > 0
657
679
  sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
680
+ elsif limit
681
+ sql.replace "select * from (#{sql}) where rownum <= #{limit}"
658
682
  elsif offset > 0
659
683
  sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
660
684
  end
@@ -706,13 +730,12 @@ module ActiveRecord
706
730
  next if value.nil? || (value == '')
707
731
  value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
708
732
  uncached do
709
- if is_with_cpk
710
- lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE",
711
- 'Writable Large Object')[col.name]
712
- else
713
- lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
714
- 'Writable Large Object')[col.name]
733
+ sql = is_with_cpk ? "SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE" :
734
+ "SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE"
735
+ unless lob_record = select_one(sql, 'Writable Large Object')
736
+ raise ActiveRecord::RecordNotFound, "statement #{sql} returned no rows"
715
737
  end
738
+ lob = lob_record[col.name]
716
739
  @connection.write_lob(lob, value.to_s, col.type == :binary)
717
740
  end
718
741
  end
@@ -755,8 +778,21 @@ module ActiveRecord
755
778
  end
756
779
 
757
780
  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']}
781
+ select_values(
782
+ "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'",
783
+ name)
784
+ end
785
+
786
+ # Will return true if database object exists (to be able to use also views and synonyms for ActiveRecord models)
787
+ def table_exists?(table_name)
788
+ (owner, table_name, db_link) = @connection.describe(table_name)
789
+ true
790
+ rescue
791
+ false
792
+ end
793
+
794
+ def materialized_views #:nodoc:
795
+ select_values("select lower(mview_name) from all_mviews where owner = sys_context('userenv','session_user')")
760
796
  end
761
797
 
762
798
  cattr_accessor :all_schema_indexes #:nodoc:
@@ -768,14 +804,19 @@ module ActiveRecord
768
804
  unless all_schema_indexes
769
805
  default_tablespace_name = default_tablespace
770
806
  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}'
807
+ SELECT LOWER(i.table_name) AS table_name, LOWER(i.index_name) AS index_name, i.uniqueness,
808
+ i.index_type, i.ityp_owner, i.ityp_name, i.parameters,
809
+ LOWER(i.tablespace_name) AS tablespace_name,
810
+ LOWER(c.column_name) AS column_name, e.column_expression
811
+ FROM all_indexes#{db_link} i
812
+ JOIN all_ind_columns#{db_link} c ON c.index_name = i.index_name AND c.index_owner = i.owner
813
+ LEFT OUTER JOIN all_ind_expressions#{db_link} e ON e.index_name = i.index_name AND
814
+ e.index_owner = i.owner AND e.column_position = c.column_position
815
+ WHERE i.owner = '#{owner}'
776
816
  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
817
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc
818
+ WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
819
+ ORDER BY i.index_name, c.column_position
779
820
  SQL
780
821
 
781
822
  current_index = nil
@@ -785,11 +826,24 @@ module ActiveRecord
785
826
  # have to keep track of indexes because above query returns dups
786
827
  # there is probably a better query we could figure out
787
828
  if current_index != row['index_name']
788
- all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE",
829
+ statement_parameters = nil
830
+ if row['index_type'] == 'DOMAIN' && row['ityp_owner'] == 'CTXSYS' && row['ityp_name'] == 'CONTEXT'
831
+ procedure_name = default_datastore_procedure(row['index_name'])
832
+ statement_parameters = select_value(<<-SQL)
833
+ SELECT SUBSTR(text,LENGTH('-- add_context_index_parameters ')+1)
834
+ FROM all_source#{db_link}
835
+ WHERE owner = '#{owner}'
836
+ AND name = '#{procedure_name.upcase}'
837
+ AND text LIKE '-- add_context_index_parameters %'
838
+ SQL
839
+ end
840
+ all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'],
841
+ row['uniqueness'] == "UNIQUE", row['index_type'] == 'DOMAIN' ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil,
842
+ row['parameters'], statement_parameters,
789
843
  row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
790
844
  current_index = row['index_name']
791
845
  end
792
- all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] : row['column_expression'].gsub('"','').downcase)
846
+ all_schema_indexes.last.columns << (row['column_expression'] || row['column_name'].downcase)
793
847
  end
794
848
  end
795
849
 
@@ -852,7 +906,7 @@ module ActiveRecord
852
906
  AND table_name = '#{desc_table_name}'
853
907
  AND status = 'ENABLED'
854
908
  SQL
855
- select_value(pkt_sql) ? true : false
909
+ select_value(pkt_sql, 'Primary Key Trigger') ? true : false
856
910
  end
857
911
 
858
912
  ##
@@ -866,8 +920,7 @@ module ActiveRecord
866
920
  self.cache_columns = false
867
921
 
868
922
  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)
923
+ if @@cache_columns
871
924
  @@columns_cache ||= {}
872
925
  @@columns_cache[table_name] ||= columns_without_cache(table_name, name)
873
926
  else
@@ -876,14 +929,15 @@ module ActiveRecord
876
929
  end
877
930
 
878
931
  def columns_without_cache(table_name, name = nil) #:nodoc:
932
+ table_name = table_name.to_s
879
933
  # get ignored_columns by original table name
880
934
  ignored_columns = ignored_table_columns(table_name)
881
935
 
882
936
  (owner, desc_table_name, db_link) = @connection.describe(table_name)
883
937
 
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
938
+ @@do_not_prefetch_primary_key[table_name] =
939
+ !has_primary_key?(table_name, owner, desc_table_name, db_link) ||
940
+ has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
887
941
 
888
942
  table_cols = <<-SQL
889
943
  select column_name as name, data_type as sql_type, data_default, nullable,
@@ -911,7 +965,10 @@ module ActiveRecord
911
965
  # clean up odd default spacing from Oracle
912
966
  if row['data_default']
913
967
  row['data_default'].sub!(/^(.*?)\s*$/, '\1')
914
- row['data_default'].sub!(/^'(.*)'$/, '\1')
968
+
969
+ # If a default contains a newline these cleanup regexes need to
970
+ # match newlines.
971
+ row['data_default'].sub!(/^'(.*)'$/m, '\1')
915
972
  row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
916
973
  end
917
974
 
@@ -929,11 +986,15 @@ module ActiveRecord
929
986
  # used just in tests to clear column cache
930
987
  def clear_columns_cache #:nodoc:
931
988
  @@columns_cache = nil
989
+ @@pk_and_sequence_for_cache = nil
932
990
  end
933
991
 
934
992
  # used in migrations to clear column cache for specified table
935
993
  def clear_table_columns_cache(table_name)
936
- @@columns_cache[table_name.to_s] = nil if @@cache_columns
994
+ if @@cache_columns
995
+ @@columns_cache ||= {}
996
+ @@columns_cache[table_name.to_s] = nil
997
+ end
937
998
  end
938
999
 
939
1000
  ##
@@ -1008,7 +1069,7 @@ module ActiveRecord
1008
1069
  result = block.call(table_definition) if block
1009
1070
  create_sequence = create_sequence || table_definition.create_sequence
1010
1071
  column_comments = table_definition.column_comments if table_definition.column_comments
1011
-
1072
+ tablespace = options[:tablespace] ? " TABLESPACE #{options[:tablespace]}" : ""
1012
1073
 
1013
1074
  if options[:force] && table_exists?(name)
1014
1075
  drop_table(name, options)
@@ -1017,7 +1078,7 @@ module ActiveRecord
1017
1078
  create_sql = "CREATE#{' GLOBAL TEMPORARY' if options[:temporary]} TABLE "
1018
1079
  create_sql << "#{quote_table_name(name)} ("
1019
1080
  create_sql << table_definition.to_sql
1020
- create_sql << ") #{options[:options]}"
1081
+ create_sql << ")#{tablespace} #{options[:options]}"
1021
1082
  execute create_sql
1022
1083
 
1023
1084
  create_sequence_and_trigger(name, options) if create_sequence
@@ -1044,51 +1105,86 @@ module ActiveRecord
1044
1105
 
1045
1106
  # clear cached indexes when adding new index
1046
1107
  def add_index(table_name, column_name, options = {}) #:nodoc:
1047
- self.all_schema_indexes = nil
1048
1108
  column_names = Array(column_name)
1049
1109
  index_name = index_name(table_name, :column => column_names)
1050
1110
 
1051
1111
  if Hash === options # legacy support, since this param was a string
1052
1112
  index_type = options[:unique] ? "UNIQUE" : ""
1053
1113
  index_name = options[:name] || index_name
1054
- tablespace = if options[:tablespace]
1055
- " TABLESPACE #{options[:tablespace]}"
1056
- else
1057
- ""
1058
- end
1114
+ tablespace = options[:tablespace] ? " TABLESPACE #{options[:tablespace]}" : ""
1059
1115
  else
1060
1116
  index_type = options
1061
1117
  end
1118
+
1119
+ if index_name.to_s.length > index_name_length
1120
+ @logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.") if @logger
1121
+ return
1122
+ end
1123
+ if index_exists?(table_name, index_name, false)
1124
+ @logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.") if @logger
1125
+ return
1126
+ end
1062
1127
  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}"
1128
+
1129
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{options[:options]}"
1130
+ ensure
1131
+ self.all_schema_indexes = nil
1064
1132
  end
1065
1133
 
1066
- # clear cached indexes when removing index
1134
+ # Remove the given index from the table.
1135
+ # Gives warning if index does not exist
1067
1136
  def remove_index(table_name, options = {}) #:nodoc:
1137
+ index_name = index_name(table_name, options)
1138
+ unless index_exists?(table_name, index_name, true)
1139
+ @logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.") if @logger
1140
+ return
1141
+ end
1142
+ remove_index!(table_name, index_name)
1143
+ end
1144
+
1145
+ # clear cached indexes when removing index
1146
+ def remove_index!(table_name, index_name) #:nodoc:
1147
+ execute "DROP INDEX #{quote_column_name(index_name)}"
1148
+ ensure
1068
1149
  self.all_schema_indexes = nil
1069
- execute "DROP INDEX #{index_name(table_name, options)}"
1070
1150
  end
1071
-
1151
+
1072
1152
  # returned shortened index name if default is too large
1073
1153
  def index_name(table_name, options) #:nodoc:
1074
1154
  default_name = super(table_name, options)
1075
- return default_name if default_name.length <= IDENTIFIER_MAX_LENGTH
1155
+ # sometimes options can be String or Array with column names
1156
+ options = {} unless options.is_a?(Hash)
1157
+ identifier_max_length = options[:identifier_max_length] || IDENTIFIER_MAX_LENGTH
1158
+ return default_name if default_name.length <= identifier_max_length
1076
1159
 
1077
1160
  # remove 'index', 'on' and 'and' keywords
1078
1161
  shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
1079
1162
 
1080
1163
  # leave just first three letters from each word
1081
- if shortened_name.length > IDENTIFIER_MAX_LENGTH
1164
+ if shortened_name.length > identifier_max_length
1082
1165
  shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
1083
1166
  end
1084
1167
  # 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]
1168
+ if shortened_name.length > identifier_max_length
1169
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
1087
1170
  end
1088
1171
  @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
1089
1172
  shortened_name
1090
1173
  end
1091
1174
 
1175
+ # Verify the existence of an index (always query database).
1176
+ def index_exists?(table_name, index_name, default) #:nodoc:
1177
+ (owner, table_name, db_link) = @connection.describe(table_name)
1178
+ result = select_value(<<-SQL)
1179
+ SELECT 1 FROM all_indexes#{db_link} i
1180
+ WHERE i.owner = '#{owner}'
1181
+ AND i.table_owner = '#{owner}'
1182
+ AND i.table_name = '#{table_name}'
1183
+ AND i.index_name = '#{index_name.to_s.upcase}'
1184
+ SQL
1185
+ result == 1 ? true : false
1186
+ end
1187
+
1092
1188
  def add_column(table_name, column_name, type, options = {}) #:nodoc:
1093
1189
  add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1094
1190
  options[:type] = type
@@ -1182,15 +1278,24 @@ module ActiveRecord
1182
1278
 
1183
1279
  # Find a table's primary key and sequence.
1184
1280
  # *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)
1281
+ def pk_and_sequence_for(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1282
+ if @@cache_columns
1283
+ @@pk_and_sequence_for_cache ||= {}
1284
+ @@pk_and_sequence_for_cache[table_name] ||= pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1285
+ else
1286
+ pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1287
+ end
1288
+ end
1187
1289
 
1188
- # changed select from all_constraints to user_constraints - much faster in large data dictionaries
1290
+ def pk_and_sequence_for_without_cache(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1291
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1292
+
1293
+ # changed back from user_constraints to all_constraints for consistency
1189
1294
  pks = select_values(<<-SQL, 'Primary Key')
1190
1295
  select cc.column_name
1191
- from user_constraints#{db_link} c, user_cons_columns#{db_link} cc
1296
+ from all_constraints#{db_link} c, all_cons_columns#{db_link} cc
1192
1297
  where c.owner = '#{owner}'
1193
- and c.table_name = '#{table_name}'
1298
+ and c.table_name = '#{desc_table_name}'
1194
1299
  and c.constraint_type = 'P'
1195
1300
  and cc.owner = c.owner
1196
1301
  and cc.constraint_name = c.constraint_name
@@ -1200,16 +1305,29 @@ module ActiveRecord
1200
1305
  pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
1201
1306
  end
1202
1307
 
1308
+ # Returns just a table's primary key
1309
+ def primary_key(table_name)
1310
+ pk_and_sequence = pk_and_sequence_for(table_name)
1311
+ pk_and_sequence && pk_and_sequence.first
1312
+ end
1313
+
1314
+ def has_primary_key?(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1315
+ !pk_and_sequence_for(table_name, owner, desc_table_name, db_link).nil?
1316
+ end
1317
+
1318
+ # Statements separator used in structure dump to allow loading of structure dump also with SQL*Plus
1319
+ STATEMENT_TOKEN = "\n\n/\n\n"
1320
+
1203
1321
  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}"
1322
+ structure = select_values("select sequence_name from user_sequences order by 1").map do |seq|
1323
+ "CREATE SEQUENCE \"#{seq}\""
1206
1324
  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']
1325
+ select_values("select table_name from all_tables t
1326
+ where owner = sys_context('userenv','session_user') and secondary='N'
1327
+ and not exists (select mv.mview_name from all_mviews mv where mv.owner = t.owner and mv.mview_name = t.table_name)
1328
+ order by 1").each do |table_name|
1211
1329
  virtual_columns = virtual_columns_for(table_name)
1212
- ddl = "create#{ ' global temporary' if temporary_table?(table_name)} table #{table_name} (\n "
1330
+ ddl = "CREATE#{ ' GLOBAL TEMPORARY' if temporary_table?(table_name)} TABLE \"#{table_name}\" (\n"
1213
1331
  cols = select_all(%Q{
1214
1332
  select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
1215
1333
  from user_tab_columns
@@ -1224,15 +1342,16 @@ module ActiveRecord
1224
1342
  end
1225
1343
  ddl << cols.join(",\n ")
1226
1344
  ddl << structure_dump_constraints(table_name)
1227
- ddl << "\n)#{STATEMENT_TOKEN}"
1345
+ ddl << "\n)"
1228
1346
  structure << ddl
1229
1347
  structure << structure_dump_indexes(table_name)
1230
1348
  end
1349
+
1350
+ join_with_statement_token(structure) << structure_dump_fk_constraints
1231
1351
  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}"
1352
+
1353
+ def structure_dump_column(column) #:nodoc:
1354
+ col = "\"#{column['column_name']}\" #{column['data_type']}"
1236
1355
  if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1237
1356
  col << "(#{column['data_precision'].to_i}"
1238
1357
  col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
@@ -1241,11 +1360,14 @@ module ActiveRecord
1241
1360
  length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1242
1361
  col << "(#{length})"
1243
1362
  end
1244
- col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
1363
+ col << " DEFAULT #{column['data_default']}" if !column['data_default'].nil?
1364
+ col << ' NOT NULL' if column['nullable'] == 'N'
1365
+ col
1245
1366
  end
1246
-
1247
- def structure_dump_column(column) #:nodoc:
1248
- col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1367
+
1368
+ def structure_dump_virtual_column(column, data_default) #:nodoc:
1369
+ data_default = data_default.gsub(/"/, '')
1370
+ col = "\"#{column['column_name']}\" #{column['data_type']}"
1249
1371
  if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1250
1372
  col << "(#{column['data_precision'].to_i}"
1251
1373
  col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
@@ -1254,16 +1376,14 @@ module ActiveRecord
1254
1376
  length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1255
1377
  col << "(#{length})"
1256
1378
  end
1257
- col << " default #{column['data_default']}" if !column['data_default'].nil?
1258
- col << ' not null' if column['nullable'] == 'N'
1259
- col
1379
+ col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
1260
1380
  end
1261
-
1381
+
1262
1382
  def structure_dump_constraints(table) #:nodoc:
1263
1383
  out = [structure_dump_primary_key(table), structure_dump_unique_keys(table)].flatten.compact
1264
1384
  out.length > 0 ? ",\n#{out.join(",\n")}" : ''
1265
1385
  end
1266
-
1386
+
1267
1387
  def structure_dump_primary_key(table) #:nodoc:
1268
1388
  opts = {:name => '', :cols => []}
1269
1389
  pks = select_all(<<-SQL, "Primary Keys")
@@ -1281,7 +1401,7 @@ module ActiveRecord
1281
1401
  end
1282
1402
  opts[:cols].length > 0 ? " CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : nil
1283
1403
  end
1284
-
1404
+
1285
1405
  def structure_dump_unique_keys(table) #:nodoc:
1286
1406
  keys = {}
1287
1407
  uks = select_all(<<-SQL, "Primary Keys")
@@ -1301,7 +1421,23 @@ module ActiveRecord
1301
1421
  " CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
1302
1422
  end
1303
1423
  end
1304
-
1424
+
1425
+ def structure_dump_indexes(table_name) #:nodoc:
1426
+ indexes(table_name).map do |options|
1427
+ column_names = options[:columns]
1428
+ options = {:name => options[:name], :unique => options[:unique]}
1429
+ index_name = index_name(table_name, :column => column_names)
1430
+ if Hash === options # legacy support, since this param was a string
1431
+ index_type = options[:unique] ? "UNIQUE" : ""
1432
+ index_name = options[:name] || index_name
1433
+ else
1434
+ index_type = options
1435
+ end
1436
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1437
+ "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
1438
+ end
1439
+ end
1440
+
1305
1441
  def structure_dump_fk_constraints #:nodoc:
1306
1442
  fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
1307
1443
  if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
@@ -1312,18 +1448,24 @@ module ActiveRecord
1312
1448
  sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
1313
1449
  end
1314
1450
  end
1315
- end.flatten.compact.join(STATEMENT_TOKEN)
1316
- fks.length > 1 ? "#{fks}#{STATEMENT_TOKEN}" : ''
1451
+ end.flatten.compact
1452
+ join_with_statement_token(fks)
1317
1453
  end
1318
-
1454
+
1455
+ def dump_schema_information #:nodoc:
1456
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
1457
+ migrated = select_values("SELECT version FROM #{sm_table}")
1458
+ join_with_statement_token(migrated.map{|v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" })
1459
+ end
1460
+
1319
1461
  # Extract all stored procedures, packages, synonyms and views.
1320
1462
  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')
1463
+ structure = []
1464
+ select_all("select distinct name, type
1465
+ from all_source
1466
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
1325
1467
  and owner = sys_context('userenv','session_user') order by type").each do |source|
1326
- ddl = "create or replace \n "
1468
+ ddl = "CREATE OR REPLACE \n"
1327
1469
  lines = select_all(%Q{
1328
1470
  select text
1329
1471
  from all_source
@@ -1332,79 +1474,62 @@ module ActiveRecord
1332
1474
  and owner = sys_context('userenv','session_user')
1333
1475
  order by line
1334
1476
  }).map do |row|
1335
- ddl << row['text'] if row['text'].size > 1
1477
+ ddl << row['text']
1336
1478
  end
1337
- ddl << ";" unless ddl.strip.last == ";"
1338
- structure << ddl << STATEMENT_TOKEN
1479
+ ddl << ";" unless ddl.strip[-1,1] == ";"
1480
+ structure << ddl
1339
1481
  end
1340
1482
 
1341
1483
  # export views
1342
1484
  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
1485
+ structure << "CREATE OR REPLACE VIEW #{view['view_name']} AS\n #{view['text']}"
1347
1486
  end
1348
1487
 
1349
- # export synonyms
1488
+ # export synonyms
1350
1489
  select_all("select owner, synonym_name, table_name, table_owner
1351
1490
  from all_synonyms
1352
1491
  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
1492
+ structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']}"
1493
+ structure << " FOR #{synonym['table_owner']}.#{synonym['table_name']}"
1355
1494
  end
1356
1495
 
1357
- structure
1496
+ join_with_statement_token(structure)
1358
1497
  end
1359
1498
 
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
1499
  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"
1500
+ statements = select_values("select sequence_name from user_sequences order by 1").map do |seq|
1501
+ "DROP SEQUENCE \"#{seq}\""
1381
1502
  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"
1503
+ select_values("select table_name from all_tables t
1504
+ where owner = sys_context('userenv','session_user') and secondary='N'
1505
+ and not exists (select mv.mview_name from all_mviews mv where mv.owner = t.owner and mv.mview_name = t.table_name)
1506
+ order by 1").each do |table|
1507
+ statements << "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
1386
1508
  end
1509
+ join_with_statement_token(statements)
1387
1510
  end
1388
-
1511
+
1389
1512
  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
1513
+ join_with_statement_token(select_values(
1514
+ "select table_name from all_tables
1515
+ where owner = sys_context('userenv','session_user') and secondary='N' and temporary = 'Y' order by 1").map do |table|
1516
+ "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
1517
+ end)
1394
1518
  end
1395
-
1519
+
1396
1520
  def full_drop(preserve_tables=false) #:nodoc:
1397
1521
  s = preserve_tables ? [] : [structure_drop]
1398
1522
  s << temp_table_drop if preserve_tables
1399
1523
  s << drop_sql_for_feature("view")
1524
+ s << drop_sql_for_feature("materialized view")
1400
1525
  s << drop_sql_for_feature("synonym")
1401
1526
  s << drop_sql_for_feature("type")
1402
1527
  s << drop_sql_for_object("package")
1403
1528
  s << drop_sql_for_object("function")
1404
1529
  s << drop_sql_for_object("procedure")
1405
- s.join("\n\n")
1530
+ s.join
1406
1531
  end
1407
-
1532
+
1408
1533
  def add_column_options!(sql, options) #:nodoc:
1409
1534
  type = options[:type] || ((column = options[:column]) && column.type)
1410
1535
  type = type && type.to_sym
@@ -1452,9 +1577,6 @@ module ActiveRecord
1452
1577
  select_value("select temporary from user_tables where table_name = '#{table_name.upcase}'") == 'Y'
1453
1578
  end
1454
1579
 
1455
- # statements separator used in structure dump
1456
- STATEMENT_TOKEN = "\n\n--@@@--\n\n"
1457
-
1458
1580
  # ORDER BY clause for the passed order option.
1459
1581
  #
1460
1582
  # Uses column aliases as defined by #distinct.
@@ -1530,8 +1652,8 @@ module ActiveRecord
1530
1652
  "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_pkt"
1531
1653
  end
1532
1654
 
1533
- def compress_lines(string, spaced = true)
1534
- string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
1655
+ def compress_lines(string, join_with = "\n")
1656
+ string.split($/).map { |line| line.strip }.join(join_with)
1535
1657
  end
1536
1658
 
1537
1659
  # virtual columns are an 11g feature. This returns [] if feature is not
@@ -1550,15 +1672,28 @@ module ActiveRecord
1550
1672
  []
1551
1673
  end
1552
1674
  end
1553
-
1675
+
1554
1676
  def drop_sql_for_feature(type)
1555
- select_values("select 'DROP #{type.upcase} \"' || #{type}_name || '\";' from user_#{type.tableize}").join("\n\n")
1677
+ short_type = type == 'materialized view' ? 'mview' : type
1678
+ join_with_statement_token(
1679
+ select_values("select #{short_type}_name from user_#{short_type.tableize}").map do |name|
1680
+ "DROP #{type.upcase} \"#{name}\""
1681
+ end)
1556
1682
  end
1557
-
1683
+
1558
1684
  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")
1685
+ join_with_statement_token(
1686
+ select_values("select object_name from user_objects where object_type = '#{type.upcase}'").map do |name|
1687
+ "DROP #{type.upcase} \"#{name}\""
1688
+ end)
1560
1689
  end
1561
-
1690
+
1691
+ def join_with_statement_token(array)
1692
+ string = array.join(STATEMENT_TOKEN)
1693
+ string << STATEMENT_TOKEN unless string.blank?
1694
+ string
1695
+ end
1696
+
1562
1697
  public
1563
1698
  # DBMS_OUTPUT =============================================
1564
1699
  #
@@ -1607,7 +1742,7 @@ module ActiveRecord
1607
1742
  while true do
1608
1743
  result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
1609
1744
  break unless result[:status] == 0
1610
- @logger.debug "DBMS_OUTPUT: #{result[:line]}"
1745
+ @logger.debug "DBMS_OUTPUT: #{result[:line]}" if @logger
1611
1746
  end
1612
1747
  end
1613
1748
 
@@ -1641,10 +1776,7 @@ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1641
1776
  begin
1642
1777
  require 'active_record/connection_adapters/oracle_enhanced_tasks'
1643
1778
  rescue LoadError
1644
- end if defined?(RAILS_ROOT)
1645
-
1646
- # Handles quoting of oracle reserved words
1647
- require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
1779
+ end if defined?(Rails.root) || defined?(RAILS_ROOT)
1648
1780
 
1649
1781
  # Patches and enhancements for schema dumper
1650
1782
  require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
@@ -1655,7 +1787,12 @@ require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext
1655
1787
  # Extensions for schema definition
1656
1788
  require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1657
1789
 
1790
+ # Extensions for context index definition
1791
+ require 'active_record/connection_adapters/oracle_enhanced_context_index'
1792
+
1658
1793
  # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1659
1794
  require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1660
1795
 
1796
+ require 'active_record/connection_adapters/oracle_enhanced_activerecord_patches'
1797
+
1661
1798
  require 'active_record/connection_adapters/oracle_enhanced_version'