pmacs-activerecord-oracle_enhanced-adapter 1.4.2.rc1

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