activerecord-oracle_enhanced-adapter 1.2.4 → 1.3.0

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