activerecord-oracle_enhanced-adapter-with-schema 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +52 -0
  3. data/History.md +301 -0
  4. data/License.txt +20 -0
  5. data/README.md +123 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +59 -0
  8. data/VERSION +1 -0
  9. data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -0
  10. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1399 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +46 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  30. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  31. data/lib/activerecord-oracle_enhanced-adapter-with-schema.rb +25 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
  36. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
  37. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1388 -0
  38. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +141 -0
  40. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  41. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +378 -0
  42. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +440 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
  45. data/spec/spec_helper.rb +189 -0
  46. metadata +260 -0
data/RUNNING_TESTS.md ADDED
@@ -0,0 +1,45 @@
1
+ Creating the test database
2
+ --------------------------
3
+
4
+ You need Oracle database (version 10.2 or later) with SYS and SYSTEM user access.
5
+
6
+ If you are on a Mac OS X 10.6 then use [these instructions](http://blog.rayapps.com/2009/09/14/how-to-install-oracle-database-10g-on-mac-os-x-snow-leopard) to install local Oracle DB 10.2.0.4. Other option is to use Linux VM and install Oracle DB on it.
7
+
8
+ If you are on Linux (or will use Linux virtual machine) and need Oracle DB just for running tests then Oracle DB XE edition is enough. See [Oracle XE downloads page](http://www.oracle.com/technetwork/database/express-edition/downloads/index.html) for download links and instructions.
9
+
10
+ If you are getting ORA-12520 errors when running tests then it means that Oracle cannot create enough processes to handle many connections (as during tests many connections are created and destroyed). In this case you need to log in as SYSTEM user and execute e.g.
11
+
12
+ alter system set processes=200 scope=spfile;
13
+
14
+ to increase process limit and then restart the database (this will be necessary if Oracle XE will be used as default processes limit is 40).
15
+
16
+ Ruby versions
17
+ -------------
18
+
19
+ It is recommended to use [RVM](http://rvm.beginrescueend.com) to run tests with different Ruby implementations. oracle_enhanced is mainly tested with MRI 1.8.7 (all Rails versions) and 1.9.2 (Rails 3) and JRuby 1.6.
20
+
21
+ Running tests
22
+ -------------
23
+
24
+ * Create Oracle database schema for test purposes. Review `spec/spec_helper.rb` to see default schema/user names and database names (use environment variables to override defaults)
25
+
26
+ SQL> CREATE USER oracle_enhanced IDENTIFIED BY oracle_enhanced;
27
+ SQL> GRANT unlimited tablespace, create session, create table, create sequence, create procedure, create trigger, create view, create materialized view, create database link, create synonym, create type, ctxapp TO oracle_enhanced;
28
+
29
+ * If you use RVM then switch to corresponding Ruby (1.8.7, 1.9.2 or JRuby) and it is recommended to create isolated gemset for test purposes (e.g. rvm create gemset oracle_enhanced)
30
+
31
+ * Install bundler with
32
+
33
+ gem install bundler
34
+
35
+ * Set RAILS_GEM_VERSION to Rails version that you would like to use in oracle_enhanced tests, e.g.
36
+
37
+ export RAILS_GEM_VERSION=3.0.3
38
+
39
+ * Install necessary gems with
40
+
41
+ bundle install
42
+
43
+ * Run tests with
44
+
45
+ rake spec
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+
11
+ require 'rake'
12
+
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gem|
15
+ gem.name = "activerecord-oracle_enhanced-adapter"
16
+ gem.summary = "Oracle enhanced adapter for ActiveRecord"
17
+ gem.description = <<-EOS
18
+ Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases.
19
+ This adapter is superset of original ActiveRecord Oracle adapter.
20
+ EOS
21
+ gem.email = "raimonds.simanovskis@gmail.com"
22
+ gem.homepage = "http://github.com/rsim/oracle-enhanced"
23
+ gem.authors = ["Raimonds Simanovskis"]
24
+ gem.extra_rdoc_files = ['README.md']
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new(:spec)
30
+
31
+ RSpec::Core::RakeTask.new(:rcov) do |t|
32
+ t.rcov = true
33
+ t.rcov_opts = ['--exclude', '/Library,spec/']
34
+ end
35
+
36
+ desc "Clear test database"
37
+ task :clear do
38
+ require "./spec/spec_helper"
39
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
40
+ require "active_support/core_ext"
41
+ ActiveRecord::Base.connection.execute_structure_dump(ActiveRecord::Base.connection.full_drop)
42
+ ActiveRecord::Base.connection.execute("PURGE RECYCLEBIN") rescue nil
43
+ end
44
+
45
+ # Clear test database before running spec and rcov
46
+ task :spec => :clear
47
+ task :rcov => :clear
48
+
49
+ task :default => :spec
50
+
51
+ require 'rdoc/task'
52
+ Rake::RDocTask.new do |rdoc|
53
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
54
+
55
+ rdoc.rdoc_dir = 'doc'
56
+ rdoc.title = "activerecord-oracle_enhanced-adapter #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.4.2
@@ -0,0 +1,130 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{activerecord-oracle_enhanced-adapter-with-schema}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{Arunkumar Balu}]
12
+ s.date = %q{2013-03-18}
13
+ s.description = %q{Forked from rsim's Oracle "enhanced" ActiveRecord adapter. Using this gem, you can specify the schema details in your database.yml file.
14
+ This gem is a tweaked version of original activerecord-oracle_enhanced-adapter gem. Thanks to rsim for such a great library.
15
+ }
16
+ s.email = %q{arunkarthick.it@gmail.com}
17
+ s.extra_rdoc_files = [
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".rspec",
22
+ "Gemfile",
23
+ "History.md",
24
+ "License.txt",
25
+ "README.md",
26
+ "RUNNING_TESTS.md",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "activerecord-oracle_enhanced-adapter-with-schema.gemspec",
30
+ "lib/active_record/connection_adapters/emulation/oracle_adapter.rb",
31
+ "lib/active_record/connection_adapters/oracle_enhanced.rake",
32
+ "lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb",
33
+ "lib/active_record/connection_adapters/oracle_enhanced_adapter.rb",
34
+ "lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb",
35
+ "lib/active_record/connection_adapters/oracle_enhanced_column.rb",
36
+ "lib/active_record/connection_adapters/oracle_enhanced_connection.rb",
37
+ "lib/active_record/connection_adapters/oracle_enhanced_context_index.rb",
38
+ "lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb",
39
+ "lib/active_record/connection_adapters/oracle_enhanced_cpk.rb",
40
+ "lib/active_record/connection_adapters/oracle_enhanced_dirty.rb",
41
+ "lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb",
42
+ "lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb",
43
+ "lib/active_record/connection_adapters/oracle_enhanced_procedures.rb",
44
+ "lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb",
45
+ "lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb",
46
+ "lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb",
47
+ "lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb",
48
+ "lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb",
49
+ "lib/active_record/connection_adapters/oracle_enhanced_tasks.rb",
50
+ "lib/active_record/connection_adapters/oracle_enhanced_version.rb",
51
+ "lib/activerecord-oracle_enhanced-adapter-with-schema.rb",
52
+ "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb",
53
+ "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb",
54
+ "spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb",
55
+ "spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb",
56
+ "spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb",
57
+ "spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb",
58
+ "spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb",
59
+ "spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb",
60
+ "spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb",
61
+ "spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb",
62
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb",
63
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb",
64
+ "spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb",
65
+ "spec/spec_helper.rb"
66
+ ]
67
+ s.homepage = %q{http://github.com/arunkarthick/activerecord-oracle_enhanced-adapter-with-schema}
68
+ s.require_paths = [%q{lib}]
69
+ s.rubygems_version = %q{1.8.6}
70
+ s.summary = %q{Oracle enhanced adapter for ActiveRecord}
71
+ s.test_files = [
72
+ "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb",
73
+ "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb",
74
+ "spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb",
75
+ "spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb",
76
+ "spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb",
77
+ "spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb",
78
+ "spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb",
79
+ "spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb",
80
+ "spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb",
81
+ "spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb",
82
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb",
83
+ "spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb",
84
+ "spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb",
85
+ "spec/spec_helper.rb"
86
+ ]
87
+
88
+ if s.respond_to? :specification_version then
89
+ s.specification_version = 3
90
+
91
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
92
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
93
+ s.add_development_dependency(%q<rspec>, ["~> 2.4"])
94
+ s.add_development_dependency(%q<activerecord>, [">= 0"])
95
+ s.add_development_dependency(%q<activemodel>, [">= 0"])
96
+ s.add_development_dependency(%q<activesupport>, [">= 0"])
97
+ s.add_development_dependency(%q<actionpack>, [">= 0"])
98
+ s.add_development_dependency(%q<railties>, [">= 0"])
99
+ s.add_development_dependency(%q<arel>, [">= 0"])
100
+ s.add_development_dependency(%q<journey>, [">= 0"])
101
+ s.add_development_dependency(%q<ruby-plsql>, [">= 0.4.4"])
102
+ s.add_development_dependency(%q<ruby-oci8>, [">= 2.0.4"])
103
+ else
104
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
105
+ s.add_dependency(%q<rspec>, ["~> 2.4"])
106
+ s.add_dependency(%q<activerecord>, [">= 0"])
107
+ s.add_dependency(%q<activemodel>, [">= 0"])
108
+ s.add_dependency(%q<activesupport>, [">= 0"])
109
+ s.add_dependency(%q<actionpack>, [">= 0"])
110
+ s.add_dependency(%q<railties>, [">= 0"])
111
+ s.add_dependency(%q<arel>, [">= 0"])
112
+ s.add_dependency(%q<journey>, [">= 0"])
113
+ s.add_dependency(%q<ruby-plsql>, [">= 0.4.4"])
114
+ s.add_dependency(%q<ruby-oci8>, [">= 2.0.4"])
115
+ end
116
+ else
117
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
118
+ s.add_dependency(%q<rspec>, ["~> 2.4"])
119
+ s.add_dependency(%q<activerecord>, [">= 0"])
120
+ s.add_dependency(%q<activemodel>, [">= 0"])
121
+ s.add_dependency(%q<activesupport>, [">= 0"])
122
+ s.add_dependency(%q<actionpack>, [">= 0"])
123
+ s.add_dependency(%q<railties>, [">= 0"])
124
+ s.add_dependency(%q<arel>, [">= 0"])
125
+ s.add_dependency(%q<journey>, [">= 0"])
126
+ s.add_dependency(%q<ruby-plsql>, [">= 0.4.4"])
127
+ s.add_dependency(%q<ruby-oci8>, [">= 2.0.4"])
128
+ end
129
+ end
130
+
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::OracleAdapter < ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter #:nodoc:
2
+ def adapter_name
3
+ 'Oracle'
4
+ end
5
+ end
@@ -0,0 +1,105 @@
1
+ # implementation idea taken from JDBC adapter
2
+ # added possibility to execute previously defined task (passed as argument to task block)
3
+ def redefine_task(*args, &block)
4
+ task_name = Hash === args.first ? args.first.keys[0] : args.first
5
+ existing_task = Rake.application.lookup task_name
6
+ existing_actions = nil
7
+ if existing_task
8
+ class << existing_task; public :instance_variable_set, :instance_variable_get; end
9
+ existing_task.instance_variable_set "@prerequisites", FileList[]
10
+ existing_actions = existing_task.instance_variable_get "@actions"
11
+ existing_task.instance_variable_set "@actions", []
12
+ end
13
+ task(*args) do
14
+ block.call(existing_actions)
15
+ end
16
+ end
17
+
18
+ # Creates database user with db:create
19
+ if defined?(create_database) == 'method'
20
+ def create_database_with_oracle_enhanced(config)
21
+ if config['adapter'] == 'oracle_enhanced'
22
+ print "Please provide the SYSTEM password for your oracle installation\n>"
23
+ system_password = $stdin.gets.strip
24
+ ActiveRecord::Base.establish_connection(config.merge('username' => 'SYSTEM', 'password' => system_password))
25
+ begin
26
+ ActiveRecord::Base.connection.execute "CREATE USER #{config['username']} IDENTIFIED BY #{config['password']}"
27
+ rescue => e
28
+ if e.message =~ /ORA-01920/ # user name conflicts with another user or role name
29
+ ActiveRecord::Base.connection.execute "ALTER USER #{config['username']} IDENTIFIED BY #{config['password']}"
30
+ else
31
+ raise e
32
+ end
33
+ end
34
+ ActiveRecord::Base.connection.execute "GRANT unlimited tablespace TO #{config['username']}"
35
+ ActiveRecord::Base.connection.execute "GRANT create session TO #{config['username']}"
36
+ ActiveRecord::Base.connection.execute "GRANT create table TO #{config['username']}"
37
+ ActiveRecord::Base.connection.execute "GRANT create sequence TO #{config['username']}"
38
+ else
39
+ create_database_without_oracle_enhanced(config)
40
+ end
41
+ end
42
+ alias :create_database_without_oracle_enhanced :create_database
43
+ alias :create_database :create_database_with_oracle_enhanced
44
+ end
45
+
46
+ # Drops database user with db:drop
47
+ if defined?(drop_database) == 'method'
48
+ def drop_database_with_oracle_enhanced(config)
49
+ if config['adapter'] == 'oracle_enhanced'
50
+ ActiveRecord::Base.establish_connection(config)
51
+ ActiveRecord::Base.connection.execute_structure_dump(ActiveRecord::Base.connection.full_drop)
52
+ else
53
+ drop_database_without_oracle_enhanced(config)
54
+ end
55
+ end
56
+ alias :drop_database_without_oracle_enhanced :drop_database
57
+ alias :drop_database :drop_database_with_oracle_enhanced
58
+ end
59
+
60
+ namespace :db do
61
+
62
+ namespace :structure do
63
+ redefine_task :dump => :environment do |existing_actions|
64
+ abcs = ActiveRecord::Base.configurations
65
+ rails_env = defined?(Rails.env) ? Rails.env : RAILS_ENV
66
+ if abcs[rails_env]['adapter'] == 'oracle_enhanced'
67
+ ActiveRecord::Base.establish_connection(abcs[rails_env])
68
+ File.open("db/#{rails_env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
69
+ if ActiveRecord::Base.connection.supports_migrations?
70
+ File.open("db/#{rails_env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
71
+ end
72
+ if abcs[rails_env]['structure_dump'] == "db_stored_code"
73
+ File.open("db/#{rails_env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
74
+ end
75
+ else
76
+ Array(existing_actions).each{|action| action.call}
77
+ end
78
+ end
79
+ end
80
+
81
+ namespace :test do
82
+ redefine_task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do |existing_actions|
83
+ abcs = ActiveRecord::Base.configurations
84
+ rails_env = defined?(Rails.env) ? Rails.env : RAILS_ENV
85
+ if abcs[rails_env]['adapter'] == 'oracle_enhanced' && abcs['test']['adapter'] == 'oracle_enhanced'
86
+ ActiveRecord::Base.establish_connection(:test)
87
+ ActiveRecord::Base.connection.execute_structure_dump(File.read("db/#{rails_env}_structure.sql"))
88
+ else
89
+ Array(existing_actions).each{|action| action.call}
90
+ end
91
+ end
92
+
93
+ redefine_task :purge => :environment do |existing_actions|
94
+ abcs = ActiveRecord::Base.configurations
95
+ if abcs['test']['adapter'] == 'oracle_enhanced'
96
+ ActiveRecord::Base.establish_connection(:test)
97
+ ActiveRecord::Base.connection.execute_structure_dump(ActiveRecord::Base.connection.full_drop)
98
+ ActiveRecord::Base.connection.execute("PURGE RECYCLEBIN") rescue nil
99
+ else
100
+ Array(existing_actions).each{|action| action.call}
101
+ end
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,41 @@
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
+ if self.connection.adapter_name == "OracleEnhanced"
10
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
11
+ # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
12
+ string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_']
13
+ else
14
+ string.scan(/([\.a-zA-Z_]+).?\./).flatten
15
+ end
16
+ end
17
+ end
18
+
19
+ ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.class_eval do
20
+ protected
21
+ def aliased_table_name_for(name, suffix = nil)
22
+ # always downcase quoted table name as Oracle quoted table names are in uppercase
23
+ 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}
24
+ @join_dependency.table_aliases[name] += 1
25
+ end
26
+
27
+ unless @join_dependency.table_aliases[name].zero?
28
+ # if the table name has been used, then use an alias
29
+ name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
30
+ table_index = @join_dependency.table_aliases[name]
31
+ @join_dependency.table_aliases[name] += 1
32
+ name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
33
+ else
34
+ @join_dependency.table_aliases[name] += 1
35
+ end
36
+
37
+ name
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,1399 @@
1
+ # -*- coding: utf-8 -*-
2
+ # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
3
+ #
4
+ # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
5
+ #
6
+ # Current maintainer: Raimonds Simanovskis (http://blog.rayapps.com)
7
+ #
8
+ #########################################################################
9
+ #
10
+ # See History.md for changes added to original oracle_adapter.rb
11
+ #
12
+ #########################################################################
13
+ #
14
+ # From original oracle_adapter.rb:
15
+ #
16
+ # Implementation notes:
17
+ # 1. Redefines (safely) a method in ActiveRecord to make it possible to
18
+ # implement an autonumbering solution for Oracle.
19
+ # 2. The OCI8 driver is patched to properly handle values for LONG and
20
+ # TIMESTAMP columns. The driver-author has indicated that a future
21
+ # release of the driver will obviate this patch.
22
+ # 3. LOB support is implemented through an after_save callback.
23
+ # 4. Oracle does not offer native LIMIT and OFFSET options; this
24
+ # functionality is mimiced through the use of nested selects.
25
+ # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
26
+ #
27
+ # Do what you want with this code, at your own peril, but if any
28
+ # significant portion of my code remains then please acknowledge my
29
+ # contribution.
30
+ # portions Copyright 2005 Graham Jenkins
31
+
32
+ # ActiveRecord 2.2 does not load version file automatically
33
+ require 'active_record/version' unless defined?(ActiveRecord::VERSION)
34
+
35
+ require 'active_record/connection_adapters/abstract_adapter'
36
+ require 'active_record/connection_adapters/oracle_enhanced_connection'
37
+
38
+ require 'active_record/connection_adapters/oracle_enhanced_base_ext'
39
+ require 'active_record/connection_adapters/oracle_enhanced_column'
40
+
41
+ require 'digest/sha1'
42
+
43
+ module ActiveRecord
44
+ module ConnectionAdapters #:nodoc:
45
+
46
+ # Oracle enhanced adapter will work with both
47
+ # Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
48
+ # or with JRuby and Oracle JDBC driver.
49
+ #
50
+ # It should work with Oracle 9i, 10g and 11g databases.
51
+ # Limited set of functionality should work on Oracle 8i as well but several features
52
+ # rely on newer functionality in Oracle database.
53
+ #
54
+ # Usage notes:
55
+ # * Key generation assumes a "${table_name}_seq" sequence is available
56
+ # for all tables; the sequence name can be changed using
57
+ # ActiveRecord::Base.set_sequence_name. When using Migrations, these
58
+ # sequences are created automatically.
59
+ # Use set_sequence_name :autogenerated with legacy tables that have
60
+ # triggers that populate primary keys automatically.
61
+ # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
62
+ # Consequently some hacks are employed to map data back to Date or Time
63
+ # in Ruby. Timezones and sub-second precision on timestamps are
64
+ # not supported.
65
+ # * Default values that are functions (such as "SYSDATE") are not
66
+ # supported. This is a restriction of the way ActiveRecord supports
67
+ # default values.
68
+ #
69
+ # Required parameters:
70
+ #
71
+ # * <tt>:username</tt>
72
+ # * <tt>:password</tt>
73
+ # * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
74
+ #
75
+ # Optional parameters:
76
+ #
77
+ # * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
78
+ # * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
79
+ # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
80
+ # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
81
+ # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
82
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "force"
83
+ # * <tt>:time_zone</tt> - database session time zone
84
+ # (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone)
85
+ #
86
+ # Optionals NLS parameters:
87
+ #
88
+ # * <tt>:nls_calendar</tt>
89
+ # * <tt>:nls_comp</tt>
90
+ # * <tt>:nls_currency</tt>
91
+ # * <tt>:nls_date_format</tt> - format for :date columns, defaults to <tt>YYYY-MM-DD HH24:MI:SS</tt>
92
+ # * <tt>:nls_date_language</tt>
93
+ # * <tt>:nls_dual_currency</tt>
94
+ # * <tt>:nls_iso_currency</tt>
95
+ # * <tt>:nls_language</tt>
96
+ # * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to <tt>CHAR</tt>
97
+ # (meaning that size specifies number of characters and not bytes)
98
+ # * <tt>:nls_nchar_conv_excp</tt>
99
+ # * <tt>:nls_numeric_characters</tt>
100
+ # * <tt>:nls_sort</tt>
101
+ # * <tt>:nls_territory</tt>
102
+ # * <tt>:nls_timestamp_format</tt> - format for :timestamp columns, defaults to <tt>YYYY-MM-DD HH24:MI:SS:FF6</tt>
103
+ # * <tt>:nls_timestamp_tz_format</tt>
104
+ # * <tt>:nls_time_format</tt>
105
+ # * <tt>:nls_time_tz_format</tt>
106
+ #
107
+ class OracleEnhancedAdapter < AbstractAdapter
108
+
109
+ ##
110
+ # :singleton-method:
111
+ # By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
112
+ # as boolean. If you wish to disable this emulation you can add the following line
113
+ # to your initializer file:
114
+ #
115
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
116
+ cattr_accessor :emulate_booleans
117
+ self.emulate_booleans = true
118
+
119
+ ##
120
+ # :singleton-method:
121
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
122
+ # to Time or DateTime (if value is out of Time value range) value.
123
+ # If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
124
+ # to Date then you can add the following line to your initializer file:
125
+ #
126
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
127
+ #
128
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
129
+ # that Date columns are explicily defined with +set_date_columns+ method.
130
+ cattr_accessor :emulate_dates
131
+ self.emulate_dates = false
132
+
133
+ ##
134
+ # :singleton-method:
135
+ # OracleEnhancedAdapter will use the default tablespace, but if you want specific types of
136
+ # objects to go into specific tablespaces, specify them like this in an initializer:
137
+ #
138
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces =
139
+ # {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'}
140
+ #
141
+ # Using the :tablespace option where available (e.g create_table) will take precedence
142
+ # over these settings.
143
+ cattr_accessor :default_tablespaces
144
+ self.default_tablespaces={}
145
+
146
+ ##
147
+ # :singleton-method:
148
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
149
+ # to Time or DateTime (if value is out of Time value range) value.
150
+ # If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
151
+ # to Date then you can add the following line to your initializer file:
152
+ #
153
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
154
+ #
155
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
156
+ # that Date columns are explicily defined with +set_date_columns+ method.
157
+ cattr_accessor :emulate_dates_by_column_name
158
+ self.emulate_dates_by_column_name = false
159
+
160
+ # Check column name to identify if it is Date (and not Time) column.
161
+ # Is used if +emulate_dates_by_column_name+ option is set to +true+.
162
+ # Override this method definition in initializer file if different Date column recognition is needed.
163
+ def self.is_date_column?(name, table_name = nil)
164
+ name =~ /(^|_)date(_|$)/i
165
+ end
166
+
167
+ # instance method uses at first check if column type defined at class level
168
+ def is_date_column?(name, table_name = nil) #:nodoc:
169
+ case get_type_for_column(table_name, name)
170
+ when nil
171
+ self.class.is_date_column?(name, table_name)
172
+ when :date
173
+ true
174
+ else
175
+ false
176
+ end
177
+ end
178
+
179
+ ##
180
+ # :singleton-method:
181
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
182
+ # (without precision or scale) to Float or BigDecimal value.
183
+ # If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
184
+ # to Integer then you can add the following line to your initializer file:
185
+ #
186
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
187
+ cattr_accessor :emulate_integers_by_column_name
188
+ self.emulate_integers_by_column_name = false
189
+
190
+ # Check column name to identify if it is Integer (and not Float or BigDecimal) column.
191
+ # Is used if +emulate_integers_by_column_name+ option is set to +true+.
192
+ # Override this method definition in initializer file if different Integer column recognition is needed.
193
+ def self.is_integer_column?(name, table_name = nil)
194
+ name =~ /(^|_)id$/i
195
+ end
196
+
197
+ ##
198
+ # :singleton-method:
199
+ # If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
200
+ # are typecasted to booleans then you can add the following line to your initializer file:
201
+ #
202
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
203
+ cattr_accessor :emulate_booleans_from_strings
204
+ self.emulate_booleans_from_strings = false
205
+
206
+ # Check column name to identify if it is boolean (and not String) column.
207
+ # Is used if +emulate_booleans_from_strings+ option is set to +true+.
208
+ # Override this method definition in initializer file if different boolean column recognition is needed.
209
+ def self.is_boolean_column?(name, field_type, table_name = nil)
210
+ return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
211
+ field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
212
+ end
213
+
214
+ # How boolean value should be quoted to String.
215
+ # Used if +emulate_booleans_from_strings+ option is set to +true+.
216
+ def self.boolean_to_string(bool)
217
+ bool ? "Y" : "N"
218
+ end
219
+
220
+ ##
221
+ # :singleton-method:
222
+ # Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
223
+ #
224
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
225
+ cattr_accessor :string_to_date_format
226
+ self.string_to_date_format = nil
227
+
228
+ ##
229
+ # :singleton-method:
230
+ # Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
231
+ #
232
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
233
+ cattr_accessor :string_to_time_format
234
+ self.string_to_time_format = nil
235
+
236
+ class StatementPool
237
+ include Enumerable
238
+
239
+ def initialize(connection, max = 300)
240
+ @connection = connection
241
+ @max = max
242
+ @cache = {}
243
+ end
244
+
245
+ def each(&block); @cache.each(&block); end
246
+ def key?(key); @cache.key?(key); end
247
+ def [](key); @cache[key]; end
248
+ def length; @cache.length; end
249
+ def delete(key); @cache.delete(key); end
250
+
251
+ def []=(sql, key)
252
+ while @max <= @cache.size
253
+ @cache.shift.last.close
254
+ end
255
+ @cache[sql] = key
256
+ end
257
+
258
+ def clear
259
+ @cache.values.each do |cursor|
260
+ cursor.close
261
+ end
262
+ @cache.clear
263
+ end
264
+ end
265
+
266
+ def initialize(connection, logger, config) #:nodoc:
267
+ super(connection, logger)
268
+ @quoted_column_names, @quoted_table_names = {}, {}
269
+ @config = config
270
+ @statements = StatementPool.new(connection, config.fetch(:statement_limit) { 250 })
271
+ @enable_dbms_output = false
272
+ @visitor = Arel::Visitors::Oracle.new self if defined?(Arel::Visitors::Oracle)
273
+ end
274
+
275
+ def self.visitor_for(pool) # :nodoc:
276
+ Arel::Visitors::Oracle.new(pool)
277
+ end
278
+
279
+ ADAPTER_NAME = 'OracleEnhanced'.freeze
280
+
281
+ def adapter_name #:nodoc:
282
+ ADAPTER_NAME
283
+ end
284
+
285
+ def supports_migrations? #:nodoc:
286
+ true
287
+ end
288
+
289
+ def supports_primary_key? #:nodoc:
290
+ true
291
+ end
292
+
293
+ def supports_savepoints? #:nodoc:
294
+ true
295
+ end
296
+
297
+ #:stopdoc:
298
+ DEFAULT_NLS_PARAMETERS = {
299
+ :nls_calendar => nil,
300
+ :nls_comp => nil,
301
+ :nls_currency => nil,
302
+ :nls_date_format => 'YYYY-MM-DD HH24:MI:SS',
303
+ :nls_date_language => nil,
304
+ :nls_dual_currency => nil,
305
+ :nls_iso_currency => nil,
306
+ :nls_language => nil,
307
+ :nls_length_semantics => 'CHAR',
308
+ :nls_nchar_conv_excp => nil,
309
+ :nls_numeric_characters => nil,
310
+ :nls_sort => nil,
311
+ :nls_territory => nil,
312
+ :nls_timestamp_format => 'YYYY-MM-DD HH24:MI:SS:FF6',
313
+ :nls_timestamp_tz_format => nil,
314
+ :nls_time_format => nil,
315
+ :nls_time_tz_format => nil
316
+ }
317
+
318
+ #:stopdoc:
319
+ NATIVE_DATABASE_TYPES = {
320
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
321
+ :string => { :name => "VARCHAR2", :limit => 255 },
322
+ :text => { :name => "CLOB" },
323
+ :integer => { :name => "NUMBER", :limit => 38 },
324
+ :float => { :name => "NUMBER" },
325
+ :decimal => { :name => "DECIMAL" },
326
+ :datetime => { :name => "DATE" },
327
+ # changed to native TIMESTAMP type
328
+ # :timestamp => { :name => "DATE" },
329
+ :timestamp => { :name => "TIMESTAMP" },
330
+ :time => { :name => "DATE" },
331
+ :date => { :name => "DATE" },
332
+ :binary => { :name => "BLOB" },
333
+ :boolean => { :name => "NUMBER", :limit => 1 },
334
+ :raw => { :name => "RAW", :limit => 2000 }
335
+ }
336
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
337
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
338
+ :boolean => { :name => "VARCHAR2", :limit => 1 }
339
+ )
340
+ #:startdoc:
341
+
342
+ def native_database_types #:nodoc:
343
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
344
+ end
345
+
346
+ # maximum length of Oracle identifiers
347
+ IDENTIFIER_MAX_LENGTH = 30
348
+
349
+ def table_alias_length #:nodoc:
350
+ IDENTIFIER_MAX_LENGTH
351
+ end
352
+
353
+ # the maximum length of a table name
354
+ def table_name_length
355
+ IDENTIFIER_MAX_LENGTH
356
+ end
357
+
358
+ # the maximum length of a column name
359
+ def column_name_length
360
+ IDENTIFIER_MAX_LENGTH
361
+ end
362
+
363
+ # the maximum length of an index name
364
+ def index_name_length
365
+ IDENTIFIER_MAX_LENGTH
366
+ end
367
+
368
+ # the maximum length of a sequence name
369
+ def sequence_name_length
370
+ IDENTIFIER_MAX_LENGTH
371
+ end
372
+
373
+ # To avoid ORA-01795: maximum number of expressions in a list is 1000
374
+ # tell ActiveRecord to limit us to 1000 ids at a time
375
+ def in_clause_length
376
+ 1000
377
+ end
378
+ alias ids_in_list_limit in_clause_length
379
+
380
+ # QUOTING ==================================================
381
+ #
382
+ # see: abstract/quoting.rb
383
+
384
+ def quote_column_name(name) #:nodoc:
385
+ name = name.to_s
386
+ @quoted_column_names[name] ||= begin
387
+ # if only valid lowercase column characters in name
388
+ if name =~ /\A[a-z][a-z_0-9\$#]*\Z/
389
+ "\"#{name.upcase}\""
390
+ else
391
+ # remove double quotes which cannot be used inside quoted identifier
392
+ "\"#{name.gsub('"', '')}\""
393
+ end
394
+ end
395
+ end
396
+
397
+ # This method is used in add_index to identify either column name (which is quoted)
398
+ # or function based index (in which case function expression is not quoted)
399
+ def quote_column_name_or_expression(name) #:nodoc:
400
+ name = name.to_s
401
+ case name
402
+ # if only valid lowercase column characters in name
403
+ when /^[a-z][a-z_0-9\$#]*$/
404
+ "\"#{name.upcase}\""
405
+ when /^[a-z][a-z_0-9\$#\-]*$/i
406
+ "\"#{name}\""
407
+ # if other characters present then assume that it is expression
408
+ # which should not be quoted
409
+ else
410
+ name
411
+ end
412
+ end
413
+
414
+ # Names must be from 1 to 30 bytes long with these exceptions:
415
+ # * Names of databases are limited to 8 bytes.
416
+ # * Names of database links can be as long as 128 bytes.
417
+ #
418
+ # Nonquoted identifiers cannot be Oracle Database reserved words
419
+ #
420
+ # Nonquoted identifiers must begin with an alphabetic character from
421
+ # your database character set
422
+ #
423
+ # Nonquoted identifiers can contain only alphanumeric characters from
424
+ # your database character set and the underscore (_), dollar sign ($),
425
+ # and pound sign (#). Database links can also contain periods (.) and
426
+ # "at" signs (@). Oracle strongly discourages you from using $ and # in
427
+ # nonquoted identifiers.
428
+ NONQUOTED_OBJECT_NAME = /[A-Za-z][A-z0-9$#]{0,29}/
429
+ NONQUOTED_DATABASE_LINK = /[A-Za-z][A-z0-9$#\.@]{0,127}/
430
+ VALID_TABLE_NAME = /\A(?:#{NONQUOTED_OBJECT_NAME}\.)?#{NONQUOTED_OBJECT_NAME}(?:@#{NONQUOTED_DATABASE_LINK})?\Z/
431
+
432
+ # unescaped table name should start with letter and
433
+ # contain letters, digits, _, $ or #
434
+ # can be prefixed with schema name
435
+ # CamelCase table names should be quoted
436
+ def self.valid_table_name?(name) #:nodoc:
437
+ name = name.to_s
438
+ name =~ VALID_TABLE_NAME && !(name =~ /[A-Z]/ && name =~ /[a-z]/) ? true : false
439
+ end
440
+
441
+ def quote_table_name(name) #:nodoc:
442
+ name = name.to_s
443
+ @quoted_table_names[name] ||= name.split('.').map{|n| n.split('@').map{|m| quote_column_name(m)}.join('@')}.join('.')
444
+ end
445
+
446
+ def quote_string(s) #:nodoc:
447
+ s.gsub(/'/, "''")
448
+ end
449
+
450
+ def quote(value, column = nil) #:nodoc:
451
+ if value && column
452
+ case column.type
453
+ when :text, :binary
454
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
455
+ # NLS_DATE_FORMAT independent TIMESTAMP support
456
+ when :timestamp
457
+ quote_timestamp_with_to_timestamp(value)
458
+ # NLS_DATE_FORMAT independent DATE support
459
+ when :date, :time, :datetime
460
+ quote_date_with_to_date(value)
461
+ when :raw
462
+ quote_raw(value)
463
+ when :string
464
+ # NCHAR and NVARCHAR2 literals should be quoted with N'...'.
465
+ # Read directly instance variable as otherwise migrations with table column default values are failing
466
+ # as migrations pass ColumnDefinition object to this method.
467
+ # Check if instance variable is defined to avoid warnings about accessing undefined instance variable.
468
+ column.instance_variable_defined?('@nchar') && column.instance_variable_get('@nchar') ? 'N' << super : super
469
+ else
470
+ super
471
+ end
472
+ elsif value.acts_like?(:date)
473
+ quote_date_with_to_date(value)
474
+ elsif value.acts_like?(:time)
475
+ value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
476
+ else
477
+ super
478
+ end
479
+ end
480
+
481
+ def quoted_true #:nodoc:
482
+ return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
483
+ "1"
484
+ end
485
+
486
+ def quoted_false #:nodoc:
487
+ return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
488
+ "0"
489
+ end
490
+
491
+ def quote_date_with_to_date(value) #:nodoc:
492
+ # should support that composite_primary_keys gem will pass date as string
493
+ value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
494
+ "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
495
+ end
496
+
497
+ # Encode a string or byte array as string of hex codes
498
+ def self.encode_raw(value)
499
+ # When given a string, convert to a byte array.
500
+ value = value.unpack('C*') if value.is_a?(String)
501
+ value.map { |x| "%02X" % x }.join
502
+ end
503
+
504
+ # quote encoded raw value
505
+ def quote_raw(value) #:nodoc:
506
+ "'#{self.class.encode_raw(value)}'"
507
+ end
508
+
509
+ def quote_timestamp_with_to_timestamp(value) #:nodoc:
510
+ # add up to 9 digits of fractional seconds to inserted time
511
+ value = "#{quoted_date(value)}:#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
512
+ "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS:FF6')"
513
+ end
514
+
515
+ # Cast a +value+ to a type that the database understands.
516
+ def type_cast(value, column)
517
+ case value
518
+ when true, false
519
+ if emulate_booleans_from_strings || column && column.type == :string
520
+ self.class.boolean_to_string(value)
521
+ else
522
+ value ? 1 : 0
523
+ end
524
+ when Date, Time
525
+ if value.acts_like?(:time)
526
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
527
+ value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
528
+ else
529
+ value
530
+ end
531
+ else
532
+ super
533
+ end
534
+ end
535
+
536
+ # CONNECTION MANAGEMENT ====================================
537
+ #
538
+
539
+ # If SQL statement fails due to lost connection then reconnect
540
+ # and retry SQL statement if autocommit mode is enabled.
541
+ # By default this functionality is disabled.
542
+ attr_reader :auto_retry #:nodoc:
543
+ @auto_retry = false
544
+
545
+ def auto_retry=(value) #:nodoc:
546
+ @auto_retry = value
547
+ @connection.auto_retry = value if @connection
548
+ end
549
+
550
+ # return raw OCI8 or JDBC connection
551
+ def raw_connection
552
+ @connection.raw_connection
553
+ end
554
+
555
+ # Returns true if the connection is active.
556
+ def active? #:nodoc:
557
+ # Pings the connection to check if it's still good. Note that an
558
+ # #active? method is also available, but that simply returns the
559
+ # last known state, which isn't good enough if the connection has
560
+ # gone stale since the last use.
561
+ @connection.ping
562
+ rescue OracleEnhancedConnectionException
563
+ false
564
+ end
565
+
566
+ # Reconnects to the database.
567
+ def reconnect! #:nodoc:
568
+ clear_cache!
569
+ @connection.reset!
570
+ rescue OracleEnhancedConnectionException => e
571
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
572
+ end
573
+
574
+ def reset!
575
+ clear_cache!
576
+ super
577
+ end
578
+
579
+ # Disconnects from the database.
580
+ def disconnect! #:nodoc:
581
+ clear_cache!
582
+ @connection.logoff rescue nil
583
+ end
584
+
585
+ # DATABASE STATEMENTS ======================================
586
+ #
587
+ # see: abstract/database_statements.rb
588
+
589
+ # Executes a SQL statement
590
+ def execute(sql, name = nil)
591
+ log(sql, name) { @connection.exec(sql) }
592
+ end
593
+
594
+ def substitute_at(column, index)
595
+ Arel.sql(":a#{index + 1}")
596
+ end
597
+
598
+ def clear_cache!
599
+ @statements.clear
600
+ end
601
+
602
+ def exec_query(sql, name = 'SQL', binds = [])
603
+ log(sql, name, binds) do
604
+ cursor = nil
605
+ cached = false
606
+ if binds.empty?
607
+ cursor = @connection.prepare(sql)
608
+ else
609
+ unless @statements.key? sql
610
+ @statements[sql] = @connection.prepare(sql)
611
+ end
612
+
613
+ cursor = @statements[sql]
614
+
615
+ binds.each_with_index do |bind, i|
616
+ col, val = bind
617
+ cursor.bind_param(i + 1, type_cast(val, col), col && col.type)
618
+ end
619
+
620
+ cached = true
621
+ end
622
+
623
+ cursor.exec
624
+
625
+ if name == 'EXPLAIN'
626
+ res = true
627
+ else
628
+ columns = cursor.get_col_names.map do |col_name|
629
+ @connection.oracle_downcase(col_name)
630
+ end
631
+ rows = []
632
+ fetch_options = {:get_lob_value => (name != 'Writable Large Object')}
633
+ while row = cursor.fetch(fetch_options)
634
+ rows << row
635
+ end
636
+ res = ActiveRecord::Result.new(columns, rows)
637
+ end
638
+
639
+ cursor.close unless cached
640
+ res
641
+ end
642
+ end
643
+
644
+ def supports_statement_cache?
645
+ true
646
+ end
647
+
648
+ def supports_explain?
649
+ true
650
+ end
651
+
652
+ def explain(arel, binds = [])
653
+ sql = "EXPLAIN PLAN FOR #{to_sql(arel)}"
654
+ return if sql =~ /FROM all_/
655
+ if ORACLE_ENHANCED_CONNECTION == :jdbc
656
+ exec_query(sql, 'EXPLAIN', binds)
657
+ else
658
+ exec_query(sql, 'EXPLAIN')
659
+ end
660
+ select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
661
+ end
662
+
663
+ # Returns an array of arrays containing the field values.
664
+ # Order is the same as that returned by #columns.
665
+ def select_rows(sql, name = nil)
666
+ # last parameter indicates to return also column list
667
+ result = columns = nil
668
+ log(sql, name) do
669
+ result, columns = @connection.select(sql, name, true)
670
+ end
671
+ result.map{ |v| columns.map{|c| v[c]} }
672
+ end
673
+
674
+ # Executes an INSERT statement and returns the new record's ID
675
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
676
+ # if primary key value is already prefetched from sequence
677
+ # or if there is no primary key
678
+ if id_value || pk.nil?
679
+ execute(sql, name)
680
+ return id_value
681
+ end
682
+
683
+ sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk))
684
+ log(sql, name) do
685
+ @connection.exec_with_returning(sql_with_returning)
686
+ end
687
+ end
688
+ protected :insert_sql
689
+
690
+ # New method in ActiveRecord 3.1
691
+ # Will add RETURNING clause in case of trigger generated primary keys
692
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
693
+ unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
694
+ sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
695
+ returning_id_col = OracleEnhancedColumn.new("returning_id", nil, "number", true, "dual", :integer, true, true)
696
+ (binds = binds.dup) << [returning_id_col, nil]
697
+ end
698
+ [sql, binds]
699
+ end
700
+
701
+ # New method in ActiveRecord 3.1
702
+ def exec_insert(sql, name, binds)
703
+ log(sql, name, binds) do
704
+ returning_id_col = returning_id_index = nil
705
+ cursor = if @statements.key?(sql)
706
+ @statements[sql]
707
+ else
708
+ @statements[sql] = @connection.prepare(sql)
709
+ end
710
+
711
+ binds.each_with_index do |bind, i|
712
+ col, val = bind
713
+ if col.returning_id?
714
+ returning_id_col = [col]
715
+ returning_id_index = i + 1
716
+ cursor.bind_returning_param(returning_id_index, Integer)
717
+ else
718
+ cursor.bind_param(i + 1, type_cast(val, col), col && col.type)
719
+ end
720
+ end
721
+
722
+ cursor.exec_update
723
+
724
+ rows = []
725
+ if returning_id_index
726
+ returning_id = cursor.get_returning_param(returning_id_index, Integer)
727
+ rows << [returning_id]
728
+ end
729
+ ActiveRecord::Result.new(returning_id_col || [], rows)
730
+ end
731
+ end
732
+
733
+ # New method in ActiveRecord 3.1
734
+ def exec_update(sql, name, binds)
735
+ log(sql, name, binds) do
736
+ cached = false
737
+ if binds.empty?
738
+ cursor = @connection.prepare(sql)
739
+ else
740
+ cursor = if @statements.key?(sql)
741
+ @statements[sql]
742
+ else
743
+ @statements[sql] = @connection.prepare(sql)
744
+ end
745
+
746
+ binds.each_with_index do |bind, i|
747
+ col, val = bind
748
+ cursor.bind_param(i + 1, type_cast(val, col), col && col.type)
749
+ end
750
+ cached = true
751
+ end
752
+
753
+ res = cursor.exec_update
754
+ cursor.close unless cached
755
+ res
756
+ end
757
+ end
758
+
759
+ alias :exec_delete :exec_update
760
+
761
+ # use in set_sequence_name to avoid fetching primary key value from sequence
762
+ AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
763
+
764
+ # Returns the next sequence value from a sequence generator. Not generally
765
+ # called directly; used by ActiveRecord to get the next primary key value
766
+ # when inserting a new database record (see #prefetch_primary_key?).
767
+ def next_sequence_value(sequence_name)
768
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
769
+ return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
770
+ # call directly connection method to avoid prepared statement which causes fetching of next sequence value twice
771
+ @connection.select_value("SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual")
772
+ end
773
+
774
+ def begin_db_transaction #:nodoc:
775
+ @connection.autocommit = false
776
+ end
777
+
778
+ def commit_db_transaction #:nodoc:
779
+ @connection.commit
780
+ ensure
781
+ @connection.autocommit = true
782
+ end
783
+
784
+ def rollback_db_transaction #:nodoc:
785
+ @connection.rollback
786
+ ensure
787
+ @connection.autocommit = true
788
+ end
789
+
790
+ def create_savepoint #:nodoc:
791
+ execute("SAVEPOINT #{current_savepoint_name}")
792
+ end
793
+
794
+ def rollback_to_savepoint #:nodoc:
795
+ execute("ROLLBACK TO #{current_savepoint_name}")
796
+ end
797
+
798
+ def release_savepoint #:nodoc:
799
+ # there is no RELEASE SAVEPOINT statement in Oracle
800
+ end
801
+
802
+ def add_limit_offset!(sql, options) #:nodoc:
803
+ # added to_i for limit and offset to protect from SQL injection
804
+ offset = (options[:offset] || 0).to_i
805
+ limit = options[:limit]
806
+ limit = limit.is_a?(String) && limit.blank? ? nil : limit && limit.to_i
807
+ if limit && offset > 0
808
+ sql.replace "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM (#{sql}) raw_sql_ WHERE ROWNUM <= #{offset+limit}) WHERE raw_rnum_ > #{offset}"
809
+ elsif limit
810
+ sql.replace "SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}"
811
+ elsif offset > 0
812
+ sql.replace "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM (#{sql}) raw_sql_) WHERE raw_rnum_ > #{offset}"
813
+ end
814
+ end
815
+
816
+ @@do_not_prefetch_primary_key = {}
817
+
818
+ # Returns true for Oracle adapter (since Oracle requires primary key
819
+ # values to be pre-fetched before insert). See also #next_sequence_value.
820
+ def prefetch_primary_key?(table_name = nil)
821
+ return true if table_name.nil?
822
+ table_name = table_name.to_s
823
+ do_not_prefetch = @@do_not_prefetch_primary_key[table_name]
824
+ if do_not_prefetch.nil?
825
+ owner, desc_table_name, db_link = @connection.describe(table_name)
826
+ @@do_not_prefetch_primary_key[table_name] = do_not_prefetch =
827
+ !has_primary_key?(table_name, owner, desc_table_name, db_link) ||
828
+ has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
829
+ end
830
+ !do_not_prefetch
831
+ end
832
+
833
+ # used just in tests to clear prefetch primary key flag for all tables
834
+ def clear_prefetch_primary_key #:nodoc:
835
+ @@do_not_prefetch_primary_key = {}
836
+ end
837
+
838
+ # Returns default sequence name for table.
839
+ # Will take all or first 26 characters of table name and append _seq suffix
840
+ def default_sequence_name(table_name, primary_key = nil)
841
+ # TODO: remove schema prefix if present before truncating
842
+ # truncate table name if necessary to fit in max length of identifier
843
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_seq"
844
+ end
845
+
846
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
847
+ def insert_fixture(fixture, table_name) #:nodoc:
848
+ super
849
+
850
+ if ActiveRecord::Base.pluralize_table_names
851
+ klass = table_name.singularize.camelize
852
+ else
853
+ klass = table_name.camelize
854
+ end
855
+
856
+ klass = klass.constantize rescue nil
857
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
858
+ write_lobs(table_name, klass, fixture, klass.lob_columns)
859
+ end
860
+ end
861
+
862
+ # Writes LOB values from attributes for specified columns
863
+ def write_lobs(table_name, klass, attributes, columns) #:nodoc:
864
+ # is class with composite primary key>
865
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
866
+ if is_with_cpk
867
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
868
+ else
869
+ id = quote(attributes[klass.primary_key])
870
+ end
871
+ columns.each do |col|
872
+ value = attributes[col.name]
873
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
874
+ next if value.nil? || (value == '')
875
+ value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
876
+ uncached do
877
+ sql = is_with_cpk ? "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{klass.composite_where_clause(id)} FOR UPDATE" :
878
+ "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE"
879
+ unless lob_record = select_one(sql, 'Writable Large Object')
880
+ raise ActiveRecord::RecordNotFound, "statement #{sql} returned no rows"
881
+ end
882
+ lob = lob_record[col.name]
883
+ @connection.write_lob(lob, value.to_s, col.type == :binary)
884
+ end
885
+ end
886
+ end
887
+
888
+ # Current database name
889
+ def current_database
890
+ select_value("SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual")
891
+ end
892
+
893
+ # Current database session user
894
+ def current_user
895
+ select_value("SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual")
896
+ end
897
+
898
+ # Default tablespace name of current user
899
+ def default_tablespace
900
+ select_value("SELECT LOWER(default_tablespace) FROM user_users WHERE username = SYS_CONTEXT('userenv', 'session_user')")
901
+ end
902
+
903
+ def tables(name = nil) #:nodoc:
904
+ select_values(
905
+ "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'",
906
+ name)
907
+ end
908
+
909
+ # Will return true if database object exists (to be able to use also views and synonyms for ActiveRecord models)
910
+ def table_exists?(table_name)
911
+ (owner, table_name, db_link) = @connection.describe(table_name)
912
+ true
913
+ rescue
914
+ false
915
+ end
916
+
917
+ def materialized_views #:nodoc:
918
+ select_values("SELECT LOWER(mview_name) FROM all_mviews WHERE owner = SYS_CONTEXT('userenv', 'session_user')")
919
+ end
920
+
921
+ cattr_accessor :all_schema_indexes #:nodoc:
922
+
923
+ # This method selects all indexes at once, and caches them in a class variable.
924
+ # Subsequent index calls get them from the variable, without going to the DB.
925
+ def indexes(table_name, name = nil) #:nodoc:
926
+ (owner, table_name, db_link) = @connection.describe(table_name)
927
+ unless all_schema_indexes
928
+ default_tablespace_name = default_tablespace
929
+ result = select_all(<<-SQL.strip.gsub(/\s+/, ' '))
930
+ SELECT LOWER(i.table_name) AS table_name, LOWER(i.index_name) AS index_name, i.uniqueness,
931
+ i.index_type, i.ityp_owner, i.ityp_name, i.parameters,
932
+ LOWER(i.tablespace_name) AS tablespace_name,
933
+ LOWER(c.column_name) AS column_name, e.column_expression,
934
+ atc.virtual_column
935
+ FROM all_indexes#{db_link} i
936
+ JOIN all_ind_columns#{db_link} c ON c.index_name = i.index_name AND c.index_owner = i.owner
937
+ LEFT OUTER JOIN all_ind_expressions#{db_link} e ON e.index_name = i.index_name AND
938
+ e.index_owner = i.owner AND e.column_position = c.column_position
939
+ LEFT OUTER JOIN all_tab_cols#{db_link} atc ON i.table_name = atc.table_name AND
940
+ c.column_name = atc.column_name AND i.owner = atc.owner AND atc.hidden_column = 'NO'
941
+ WHERE i.owner = '#{owner}'
942
+ AND i.table_owner = '#{owner}'
943
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc
944
+ WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
945
+ ORDER BY i.index_name, c.column_position
946
+ SQL
947
+
948
+ current_index = nil
949
+ self.all_schema_indexes = []
950
+
951
+ result.each do |row|
952
+ # have to keep track of indexes because above query returns dups
953
+ # there is probably a better query we could figure out
954
+ if current_index != row['index_name']
955
+ statement_parameters = nil
956
+ if row['index_type'] == 'DOMAIN' && row['ityp_owner'] == 'CTXSYS' && row['ityp_name'] == 'CONTEXT'
957
+ procedure_name = default_datastore_procedure(row['index_name'])
958
+ source = select_values(<<-SQL).join
959
+ SELECT text
960
+ FROM all_source#{db_link}
961
+ WHERE owner = '#{owner}'
962
+ AND name = '#{procedure_name.upcase}'
963
+ ORDER BY line
964
+ SQL
965
+ if source =~ /-- add_context_index_parameters (.+)\n/
966
+ statement_parameters = $1
967
+ end
968
+ end
969
+ all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'],
970
+ row['uniqueness'] == "UNIQUE", row['index_type'] == 'DOMAIN' ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil,
971
+ row['parameters'], statement_parameters,
972
+ row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
973
+ current_index = row['index_name']
974
+ end
975
+
976
+ # Functional index columns and virtual columns both get stored as column expressions,
977
+ # but re-creating a virtual column index as an expression (instead of using the virtual column's name)
978
+ # results in a ORA-54018 error. Thus, we only want the column expression value returned
979
+ # when the column is not virtual.
980
+ if row['column_expression'] && row['virtual_column'] != 'YES'
981
+ all_schema_indexes.last.columns << row['column_expression']
982
+ else
983
+ all_schema_indexes.last.columns << row['column_name'].downcase
984
+ end
985
+ end
986
+ end
987
+
988
+ # Return the indexes just for the requested table, since AR is structured that way
989
+ table_name = table_name.downcase
990
+ all_schema_indexes.select{|i| i.table == table_name}
991
+ end
992
+
993
+ @@ignore_table_columns = nil #:nodoc:
994
+
995
+ # set ignored columns for table
996
+ def ignore_table_columns(table_name, *args) #:nodoc:
997
+ @@ignore_table_columns ||= {}
998
+ @@ignore_table_columns[table_name] ||= []
999
+ @@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
1000
+ @@ignore_table_columns[table_name].uniq!
1001
+ end
1002
+
1003
+ def ignored_table_columns(table_name) #:nodoc:
1004
+ @@ignore_table_columns ||= {}
1005
+ @@ignore_table_columns[table_name]
1006
+ end
1007
+
1008
+ # used just in tests to clear ignored table columns
1009
+ def clear_ignored_table_columns #:nodoc:
1010
+ @@ignore_table_columns = nil
1011
+ end
1012
+
1013
+ @@table_column_type = nil #:nodoc:
1014
+
1015
+ # set explicit type for specified table columns
1016
+ def set_type_for_columns(table_name, column_type, *args) #:nodoc:
1017
+ @@table_column_type ||= {}
1018
+ @@table_column_type[table_name] ||= {}
1019
+ args.each do |col|
1020
+ @@table_column_type[table_name][col.to_s.downcase] = column_type
1021
+ end
1022
+ end
1023
+
1024
+ def get_type_for_column(table_name, column_name) #:nodoc:
1025
+ @@table_column_type && @@table_column_type[table_name] && @@table_column_type[table_name][column_name.to_s.downcase]
1026
+ end
1027
+
1028
+ # used just in tests to clear column data type definitions
1029
+ def clear_types_for_columns #:nodoc:
1030
+ @@table_column_type = nil
1031
+ end
1032
+
1033
+ # check if table has primary key trigger with _pkt suffix
1034
+ def has_primary_key_trigger?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
1035
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1036
+
1037
+ trigger_name = default_trigger_name(table_name).upcase
1038
+ pkt_sql = <<-SQL
1039
+ SELECT trigger_name
1040
+ FROM all_triggers#{db_link}
1041
+ WHERE owner = '#{owner}'
1042
+ AND trigger_name = '#{trigger_name}'
1043
+ AND table_owner = '#{owner}'
1044
+ AND table_name = '#{desc_table_name}'
1045
+ AND status = 'ENABLED'
1046
+ SQL
1047
+ select_value(pkt_sql, 'Primary Key Trigger') ? true : false
1048
+ end
1049
+
1050
+ ##
1051
+ # :singleton-method:
1052
+ # Cache column description between requests.
1053
+ # Could be used in development environment to avoid selecting table columns from data dictionary tables for each request.
1054
+ # This can speed up request processing in development mode if development database is not on local computer.
1055
+ #
1056
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
1057
+ cattr_accessor :cache_columns
1058
+ self.cache_columns = false
1059
+
1060
+ def columns(table_name, name = nil) #:nodoc:
1061
+ if @@cache_columns
1062
+ @@columns_cache ||= {}
1063
+ @@columns_cache[table_name] ||= columns_without_cache(table_name, name)
1064
+ else
1065
+ columns_without_cache(table_name, name)
1066
+ end
1067
+ end
1068
+
1069
+ def columns_without_cache(table_name, name = nil) #:nodoc:
1070
+ table_name = table_name.to_s
1071
+ # get ignored_columns by original table name
1072
+ ignored_columns = ignored_table_columns(table_name)
1073
+
1074
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
1075
+
1076
+ # reset do_not_prefetch_primary_key cache for this table
1077
+ @@do_not_prefetch_primary_key[table_name] = nil
1078
+
1079
+ table_cols = <<-SQL.strip.gsub(/\s+/, ' ')
1080
+ SELECT column_name AS name, data_type AS sql_type, data_default, nullable, virtual_column, hidden_column,
1081
+ DECODE(data_type, 'NUMBER', data_precision,
1082
+ 'FLOAT', data_precision,
1083
+ 'VARCHAR2', DECODE(char_used, 'C', char_length, data_length),
1084
+ 'RAW', DECODE(char_used, 'C', char_length, data_length),
1085
+ 'CHAR', DECODE(char_used, 'C', char_length, data_length),
1086
+ NULL) AS limit,
1087
+ DECODE(data_type, 'NUMBER', data_scale, NULL) AS scale
1088
+ FROM all_tab_cols#{db_link}
1089
+ WHERE owner = '#{owner}'
1090
+ AND table_name = '#{desc_table_name}'
1091
+ AND hidden_column = 'NO'
1092
+ ORDER BY column_id
1093
+ SQL
1094
+
1095
+ # added deletion of ignored columns
1096
+ select_all(table_cols, name).delete_if do |row|
1097
+ ignored_columns && ignored_columns.include?(row['name'].downcase)
1098
+ end.map do |row|
1099
+ limit, scale = row['limit'], row['scale']
1100
+ if limit || scale
1101
+ row['sql_type'] += "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
1102
+ end
1103
+
1104
+ is_virtual = row['virtual_column']=='YES'
1105
+
1106
+ # clean up odd default spacing from Oracle
1107
+ if row['data_default'] && !is_virtual
1108
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
1109
+
1110
+ # If a default contains a newline these cleanup regexes need to
1111
+ # match newlines.
1112
+ row['data_default'].sub!(/^'(.*)'$/m, '\1')
1113
+ row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
1114
+ end
1115
+
1116
+ OracleEnhancedColumn.new(oracle_downcase(row['name']),
1117
+ row['data_default'],
1118
+ row['sql_type'],
1119
+ row['nullable'] == 'Y',
1120
+ # pass table name for table specific column definitions
1121
+ table_name,
1122
+ # pass column type if specified in class definition
1123
+ get_type_for_column(table_name, oracle_downcase(row['name'])), is_virtual)
1124
+ end
1125
+ end
1126
+
1127
+ # used just in tests to clear column cache
1128
+ def clear_columns_cache #:nodoc:
1129
+ @@columns_cache = nil
1130
+ @@pk_and_sequence_for_cache = nil
1131
+ end
1132
+
1133
+ # used in migrations to clear column cache for specified table
1134
+ def clear_table_columns_cache(table_name)
1135
+ if @@cache_columns
1136
+ @@columns_cache ||= {}
1137
+ @@columns_cache[table_name.to_s] = nil
1138
+ end
1139
+ end
1140
+
1141
+ ##
1142
+ # :singleton-method:
1143
+ # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
1144
+ #
1145
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
1146
+ cattr_accessor :default_sequence_start_value
1147
+ self.default_sequence_start_value = 10000
1148
+
1149
+ # Find a table's primary key and sequence.
1150
+ # *Note*: Only primary key is implemented - sequence will be nil.
1151
+ def pk_and_sequence_for(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1152
+ if @@cache_columns
1153
+ @@pk_and_sequence_for_cache ||= {}
1154
+ if @@pk_and_sequence_for_cache.key?(table_name)
1155
+ @@pk_and_sequence_for_cache[table_name]
1156
+ else
1157
+ @@pk_and_sequence_for_cache[table_name] = pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1158
+ end
1159
+ else
1160
+ pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1161
+ end
1162
+ end
1163
+
1164
+ def pk_and_sequence_for_without_cache(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1165
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1166
+
1167
+ # changed back from user_constraints to all_constraints for consistency
1168
+ pks = select_values(<<-SQL.strip.gsub(/\s+/, ' '), 'Primary Key')
1169
+ SELECT cc.column_name
1170
+ FROM all_constraints#{db_link} c, all_cons_columns#{db_link} cc
1171
+ WHERE c.owner = '#{owner}'
1172
+ AND c.table_name = '#{desc_table_name}'
1173
+ AND c.constraint_type = 'P'
1174
+ AND cc.owner = c.owner
1175
+ AND cc.constraint_name = c.constraint_name
1176
+ SQL
1177
+
1178
+ # only support single column keys
1179
+ pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
1180
+ end
1181
+
1182
+ # Returns just a table's primary key
1183
+ def primary_key(table_name)
1184
+ pk_and_sequence = pk_and_sequence_for(table_name)
1185
+ pk_and_sequence && pk_and_sequence.first
1186
+ end
1187
+
1188
+ def has_primary_key?(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1189
+ !pk_and_sequence_for(table_name, owner, desc_table_name, db_link).nil?
1190
+ end
1191
+
1192
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1193
+ #
1194
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
1195
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
1196
+ # won't actually get a distinct list of the column you want (presuming the column
1197
+ # has duplicates with multiple values for the ordered-by columns. So we use the
1198
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
1199
+ # making every row the same.
1200
+ #
1201
+ # distinct("posts.id", "posts.created_at desc")
1202
+ def distinct(columns, order_by) #:nodoc:
1203
+ return "DISTINCT #{columns}" if order_by.blank?
1204
+
1205
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
1206
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
1207
+ order_columns = if order_by.is_a?(String)
1208
+ order_by.split(',').map { |s| s.strip }.reject(&:blank?)
1209
+ else # in latest ActiveRecord versions order_by is already Array
1210
+ order_by
1211
+ end
1212
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
1213
+ # remove any ASC/DESC modifiers
1214
+ value = c =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : c
1215
+ "FIRST_VALUE(#{value}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
1216
+ end
1217
+ sql = "DISTINCT #{columns}, "
1218
+ sql << order_columns * ", "
1219
+ end
1220
+
1221
+ def temporary_table?(table_name) #:nodoc:
1222
+ select_value("SELECT temporary FROM user_tables WHERE table_name = '#{table_name.upcase}'") == 'Y'
1223
+ end
1224
+
1225
+ # ORDER BY clause for the passed order option.
1226
+ #
1227
+ # Uses column aliases as defined by #distinct.
1228
+ #
1229
+ # In Rails 3.x this method is moved to Arel
1230
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
1231
+ return sql if options[:order].blank?
1232
+
1233
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
1234
+ order.map! {|s| $1 if s =~ / (.*)/}
1235
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
1236
+
1237
+ sql << " ORDER BY #{order}"
1238
+ end
1239
+
1240
+ # construct additional wrapper subquery if select.offset is used to avoid generation of invalid subquery
1241
+ # ... IN ( SELECT * FROM ( SELECT raw_sql_.*, rownum raw_rnum_ FROM ( ... ) raw_sql_ ) WHERE raw_rnum_ > ... )
1242
+ def join_to_update(update, select) #:nodoc:
1243
+ if select.offset
1244
+ subsubselect = select.clone
1245
+ subsubselect.projections = [update.key]
1246
+
1247
+ subselect = Arel::SelectManager.new(select.engine)
1248
+ subselect.project Arel.sql(quote_column_name update.key.name)
1249
+ subselect.from subsubselect.as('alias_join_to_update')
1250
+
1251
+ update.where update.key.in(subselect)
1252
+ else
1253
+ super
1254
+ end
1255
+ end
1256
+
1257
+ protected
1258
+
1259
+ def translate_exception(exception, message) #:nodoc:
1260
+ case @connection.error_code(exception)
1261
+ when 1
1262
+ RecordNotUnique.new(message, exception)
1263
+ when 2291
1264
+ InvalidForeignKey.new(message, exception)
1265
+ else
1266
+ super
1267
+ end
1268
+ end
1269
+
1270
+ private
1271
+
1272
+ def select(sql, name = nil, binds = [])
1273
+ if ActiveRecord.const_defined?(:Result)
1274
+ exec_query(sql, name, binds).to_a
1275
+ else
1276
+ log(sql, name) do
1277
+ @connection.select(sql, name, false)
1278
+ end
1279
+ end
1280
+ end
1281
+
1282
+ def oracle_downcase(column_name)
1283
+ @connection.oracle_downcase(column_name)
1284
+ end
1285
+
1286
+ def compress_lines(string, join_with = "\n")
1287
+ string.split($/).map { |line| line.strip }.join(join_with)
1288
+ end
1289
+
1290
+ public
1291
+ # DBMS_OUTPUT =============================================
1292
+ #
1293
+ # PL/SQL in Oracle uses dbms_output for logging print statements
1294
+ # These methods stick that output into the Rails log so Ruby and PL/SQL
1295
+ # code can can be debugged together in a single application
1296
+
1297
+ # Maximum DBMS_OUTPUT buffer size
1298
+ DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
1299
+
1300
+ # Turn DBMS_Output logging on
1301
+ def enable_dbms_output
1302
+ set_dbms_output_plsql_connection
1303
+ @enable_dbms_output = true
1304
+ plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
1305
+ end
1306
+ # Turn DBMS_Output logging off
1307
+ def disable_dbms_output
1308
+ set_dbms_output_plsql_connection
1309
+ @enable_dbms_output = false
1310
+ plsql(:dbms_output).sys.dbms_output.disable
1311
+ end
1312
+ # Is DBMS_Output logging enabled?
1313
+ def dbms_output_enabled?
1314
+ @enable_dbms_output
1315
+ end
1316
+
1317
+ protected
1318
+ def log(sql, name, binds = nil) #:nodoc:
1319
+ if binds
1320
+ super sql, name, binds
1321
+ else
1322
+ super sql, name
1323
+ end
1324
+ ensure
1325
+ log_dbms_output if dbms_output_enabled?
1326
+ end
1327
+
1328
+ private
1329
+
1330
+ def set_dbms_output_plsql_connection
1331
+ raise OracleEnhancedConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
1332
+ # do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
1333
+ unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
1334
+ plsql(:dbms_output).connection = raw_connection
1335
+ end
1336
+ end
1337
+
1338
+ def log_dbms_output
1339
+ while true do
1340
+ result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
1341
+ break unless result[:status] == 0
1342
+ @logger.debug "DBMS_OUTPUT: #{result[:line]}" if @logger
1343
+ end
1344
+ end
1345
+
1346
+ end
1347
+ end
1348
+ end
1349
+
1350
+ # Added LOB writing callback for sessions stored in database
1351
+ # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
1352
+ if defined?(CGI::Session::ActiveRecordStore::Session)
1353
+ if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
1354
+ CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
1355
+ #:stopdoc:
1356
+ class CGI::Session::ActiveRecordStore::Session
1357
+ after_save :enhanced_write_lobs
1358
+ end
1359
+ #:startdoc:
1360
+ end
1361
+ end
1362
+
1363
+ # Implementation of standard schema definition statements and extensions for schema definition
1364
+ require 'active_record/connection_adapters/oracle_enhanced_schema_statements'
1365
+ require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext'
1366
+
1367
+ # Extensions for schema definition
1368
+ require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1369
+
1370
+ # Extensions for context index definition
1371
+ require 'active_record/connection_adapters/oracle_enhanced_context_index'
1372
+
1373
+ # Load custom create, update, delete methods functionality
1374
+ require 'active_record/connection_adapters/oracle_enhanced_procedures'
1375
+
1376
+ # Load additional methods for composite_primary_keys support
1377
+ require 'active_record/connection_adapters/oracle_enhanced_cpk'
1378
+
1379
+ # Load patch for dirty tracking methods
1380
+ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1381
+
1382
+ # Load rake tasks definitions
1383
+ begin
1384
+ require 'active_record/connection_adapters/oracle_enhanced_tasks'
1385
+ rescue LoadError
1386
+ end if defined?(Rails) || defined?(RAILS_ROOT)
1387
+
1388
+ # Patches and enhancements for schema dumper
1389
+ require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
1390
+
1391
+ # Implementation of structure dump
1392
+ require 'active_record/connection_adapters/oracle_enhanced_structure_dump'
1393
+
1394
+ # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1395
+ require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1396
+
1397
+ require 'active_record/connection_adapters/oracle_enhanced_activerecord_patches'
1398
+
1399
+ require 'active_record/connection_adapters/oracle_enhanced_version'