oracle_enhanced 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/.gitignore +10 -0
  2. data/History.txt +182 -0
  3. data/License.txt +20 -0
  4. data/Manifest.txt +32 -0
  5. data/README.rdoc +77 -0
  6. data/Rakefile +49 -0
  7. data/VERSION +1 -0
  8. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1661 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
  27. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
  29. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
  30. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
  31. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +370 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +203 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +114 -0
  38. metadata +140 -0
@@ -0,0 +1,10 @@
1
+ .svn
2
+ .DS_Store
3
+ coverage
4
+ doc
5
+ pkg
6
+ log
7
+ tmp
8
+ sqlnet.log
9
+ activerecord-oracle_enhanced-adapter.gemspec
10
+
@@ -0,0 +1,182 @@
1
+ == 1.2.4 2010-02-23
2
+
3
+ * Enhancements:
4
+ * rake db:test:purge will drop all schema objects from test schema (including views, synonyms, packages, functions, procedures) -
5
+ they should be always reloaded before tests run if necessary
6
+ * added views, synonyms, packages, functions, procedures, indexes, triggers, types, primary, unique and foreign key constraints to structure dump
7
+ * added :temporary option for create_table to create temporary tables
8
+ * added :tablespace option for add_index
9
+ * support function based indexes in schema dump
10
+ * support JNDI database connections in JRuby
11
+ * check ruby-oci8 minimum version 2.0.3
12
+ * added savepoints support (nested ActiveRecord transactions)
13
+ * Bug fixes:
14
+ * typecast returned BigDecimal integer values to Fixnum or Bignum
15
+ (to avoid issues with _before_type_cast values for id attributes because _before_type_cast is used in form helpers)
16
+ * clear table columns cache after columns definition change in migrations
17
+
18
+ == 1.2.3 2009-12-09
19
+
20
+ * Enhancements
21
+ * support fractional seconds in TIMESTAMP values
22
+ * support for ActiveRecord 2.3.5
23
+ * use ENV['TZ'] to set database session time zone
24
+ (as a result DATE and TIMESTAMP values are retrieved with correct time zone)
25
+ * added cache_columns adapter option
26
+ * added current_user adapter method
27
+ * added set_integer_columns and set_string_columns ActiveRecord model class methods
28
+ * Bug fixes:
29
+ * do not raise exception if ENV['PATH'] is nil
30
+ * do not add change_table behavior for ActiveRecord 2.0 (to avoid exception during loading)
31
+ * move foreign key definitions after definition of all tables in schema.rb
32
+ (to avoid definition of foreign keys before all tables are created)
33
+ * changed timestamp format mask to use ':' before fractional seconds
34
+ (workaround to avoid table detection in tables_in_string method in ActiveRecord associations.rb file)
35
+ * fixed custom create/update/delete methods with ActiveRecord 2.3+ and timestamps
36
+ * do not call oracle_enhanced specific schema dump methods when using other database adapters
37
+
38
+ == 1.2.2 2009-09-28
39
+
40
+ * Enhancements
41
+ * improved RDoc documentation of public methods
42
+ * structure dump optionally (database.yml environment has db_stored_code: yes) extracts
43
+ packages, procedures, functions, views, triggers and synonyms
44
+ * automatically generated too long index names are shortened down to 30 characters
45
+ * create tables with primary key triggers
46
+ * use 'set_sequence_name :autogenerated' for inserting into legacy tables with trigger populated primary keys
47
+ * access to tables over database link (need to define local synonym to remote table and use local synonym in set_table_name)
48
+ * [JRuby] support JDBC connection using TNS_ADMIN environment variable and TNS database alias
49
+ * changed cursor_sharing option default from 'similar' to 'force'
50
+ * optional dbms_output logging to ActiveRecord log file (requires ruby-plsql gem)
51
+ * use add_foreign_key and remove_foreign_key to define foreign key constraints
52
+ (the same syntax as in http://github.com/matthuhiggins/foreigner and similar
53
+ to http://github.com/eyestreet/active_record_oracle_extensions)
54
+ * raise RecordNotUnique and InvalidForeignKey exceptions if caused by corresponding ORA errors
55
+ (these new exceptions are supported just by current ActiveRecord master branch)
56
+ * implemented disable_referential_integrity
57
+ (enables safe loading of fixtures in schema with foreign key constraints)
58
+ * use add_synonym and remove_synonym to define database synonyms
59
+ * add_foreign_key and add_synonym are also exported to schema.rb
60
+ * Bug fixes:
61
+ * [JRuby] do not raise LoadError if ojdbc14.jar cannot be required (rely on application server to add it to class path)
62
+ * [JRuby] 'execute' can be used to create triggers with :NEW reference
63
+ * support create_table without a block
64
+ * support create_table with Symbol table name
65
+ * use ActiveRecord functionality to do time zone conversion
66
+ * rake tasks such as db:test:clone are redefined only if oracle_enhanced is current adapter in use
67
+ * VARCHAR2 and CHAR column sizes are defined in characters and not in bytes (expected behavior from ActiveRecord)
68
+ * set_date_columns, set_datetime_columns, ignore_table_columns will work after reestablishing connection
69
+ * ignore :limit option for :text and :binary columns in migrations
70
+ * patches for ActiveRecord schema dumper to remove table prefixes and suffixes from schema.rb
71
+
72
+ == 1.2.1 2009-06-07
73
+
74
+ * Enhancements
75
+ * caching of table indexes query which makes schema dump much faster
76
+ * Bug fixes:
77
+ * return Date (and not DateTime) values for :date column value before year 1970
78
+ * fixed after_create/update/destroy callbacks with plsql custom methods
79
+ * fixed creation of large integers in JRuby
80
+ * Made test tasks respect RAILS_ENV
81
+ * fixed support for composite primary keys for tables with LOBs
82
+
83
+ == 1.2.0 2009-03-22
84
+
85
+ * Enhancements
86
+ * support for JRuby and JDBC
87
+ * support for Ruby 1.9.1 and ruby-oci8 2.0
88
+ * support for Rails 2.3
89
+ * quoting of Oracle reserved words in table names and column names
90
+ * emulation of OracleAdapter (for ActiveRecord unit tests)
91
+ * Bug fixes:
92
+ * several bug fixes that were identified during running of ActiveRecord unit tests
93
+
94
+ == 1.1.9 2009-01-02
95
+
96
+ * Enhancements
97
+ * Added support for table and column comments in migrations
98
+ * Added support for specifying sequence start values
99
+ * Added :privilege option (e.g. :SYSDBA) to ActiveRecord::Base.establish_connection
100
+ * Bug fixes:
101
+ * Do not mark empty decimals, strings and texts (stored as NULL in database) as changed when reassigning them (starting from Rails 2.1)
102
+ * Create booleans as VARCHAR2(1) columns if emulate_booleans_from_strings is true
103
+
104
+ == 1.1.8 2008-10-10
105
+
106
+ * Bug fixes:
107
+ * Fixed storing of serialized LOB columns
108
+ * Prevent from SQL injection in :limit and :offset
109
+ * Order by LOB columns (by replacing column with function which returns first 100 characters of LOB)
110
+ * Sequence creation for tables with non-default primary key in create_table block
111
+ * Do count distinct workaround only when composite_primary_keys gem is used
112
+ (otherwise count distinct did not work with ActiveRecord 2.1.1)
113
+ * Fixed rake db:test:clone_structure task
114
+ (see http://rsim.lighthouseapp.com/projects/11468/tickets/11-rake-dbtestclone_structure-fails-in-117)
115
+ * Fixed bug when ActiveRecord::Base.allow_concurrency = true
116
+ (see http://dev.rubyonrails.org/ticket/11134)
117
+
118
+ == 1.1.7 2008-08-20
119
+
120
+ * Bug fixes:
121
+ * Fixed that adapter works without ruby-plsql gem (in this case just custom create/update/delete methods are not available)
122
+
123
+ == 1.1.6 2008-08-19
124
+
125
+ * Enhancements:
126
+ * Added support for set_date_columns and set_datetime_columns
127
+ * Added support for set_boolean_columns
128
+ * Added support for schema prefix in set_table_name (removed table name quoting)
129
+ * Added support for NVARCHAR2 column type
130
+ * Bug fixes:
131
+ * Do not call write_lobs callback when custom create or update methods are defined
132
+
133
+ == 1.1.5 2008-07-27
134
+
135
+ * Bug fixes:
136
+ * Fixed that write_lobs callback works with partial_updates enabled (added additional record lock before writing BLOB data to database)
137
+ * Enhancements:
138
+ * Changed SQL SELECT in indexes method so that it will execute faster on some large data dictionaries
139
+ * Support for other date and time formats when assigning string to :date or :datetime column
140
+
141
+ == 1.1.4 2008-07-14
142
+
143
+ * Enhancements:
144
+ * Date/Time quoting changes to support composite_primary_keys
145
+ * Added additional methods that are used by composite_primary_keys
146
+
147
+ == 1.1.3 2008-07-10
148
+
149
+ * Enhancements:
150
+ * Added support for custom create, update and delete methods when working with legacy databases where
151
+ PL/SQL API should be used for create, update and delete operations
152
+
153
+ == 1.1.2 2008-07-08
154
+
155
+ * Bug fixes:
156
+ * Fixed after_save callback addition for session store in ActiveRecord version 2.0.2
157
+ * Changed date column name recognition - now should match regex /(^|_)date(_|$)/i
158
+ (previously "updated_at" was recognized as :date column and not as :datetime)
159
+
160
+ == 1.1.1 2008-06-28
161
+
162
+ * Enhancements:
163
+ * Added ignore_table_columns option
164
+ * Added support for TIMESTAMP columns (without fractional seconds)
165
+ * NLS_DATE_FORMAT and NLS_TIMESTAMP_FORMAT independent DATE and TIMESTAMP columns support
166
+ * Bug fixes:
167
+ * Checks if CGI::Session::ActiveRecordStore::Session does not have enhanced_write_lobs callback before adding it
168
+ (Rails 2.0 does not add this callback, Rails 2.1 does)
169
+
170
+ == 1.1.0 2008-05-05
171
+
172
+ * Forked from original activerecord-oracle-adapter-1.0.0.9216
173
+ * Renamed oracle adapter to oracle_enhanced adapter
174
+ * Added "enhanced" to method and class definitions so that oracle_enhanced and original oracle adapter
175
+ could be used simultaniously
176
+ * Added Rails rake tasks as a copy from original oracle tasks
177
+ * Enhancements:
178
+ * Improved perfomance of schema dump methods when used on large data dictionaries
179
+ * Added LOB writing callback for sessions stored in database
180
+ * Added emulate_dates_by_column_name option
181
+ * Added emulate_integers_by_column_name option
182
+ * Added emulate_booleans_from_strings option
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2010 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ History.txt
2
+ License.txt
3
+ README.rdoc
4
+ lib/active_record/connection_adapters/emulation/oracle_adapter.rb
5
+ lib/active_record/connection_adapters/oracle_enhanced.rake
6
+ lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
7
+ lib/active_record/connection_adapters/oracle_enhanced_connection.rb
8
+ lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb
9
+ lib/active_record/connection_adapters/oracle_enhanced_cpk.rb
10
+ lib/active_record/connection_adapters/oracle_enhanced_dirty.rb
11
+ lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb
12
+ lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb
13
+ lib/active_record/connection_adapters/oracle_enhanced_procedures.rb
14
+ lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb
15
+ lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb
16
+ lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb
17
+ lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb
18
+ lib/active_record/connection_adapters/oracle_enhanced_tasks.rb
19
+ lib/active_record/connection_adapters/oracle_enhanced_version.rb
20
+ spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
21
+ spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb
22
+ spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb
23
+ spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb
24
+ spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb
25
+ spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb
26
+ spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb
27
+ spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb
28
+ spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb
29
+ spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb
30
+ spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb
31
+ spec/spec.opts
32
+ spec/spec_helper.rb
@@ -0,0 +1,77 @@
1
+ = activerecord-oracle_enhanced-adapter
2
+
3
+ Oracle enhanced adapter for ActiveRecord
4
+
5
+ == DESCRIPTION:
6
+
7
+ Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases
8
+ from Rails which are extracted from current real projects' monkey patches of original Oracle adapter.
9
+
10
+ See http://wiki.github.com/rsim/oracle-enhanced for usage information.
11
+
12
+ For questions and feature discussion please use http://groups.google.com/group/oracle-enhanced
13
+
14
+ Blog posts about oracle-enahnced can be found at http://blog.rayapps.com/category/oracle-enhanced
15
+
16
+ == REQUIREMENTS:
17
+
18
+ * Works (has been tested) with ActiveRecord version 2.0, 2.1, 2.2 and 2.3 (these are the same as Rails versions)
19
+ * Can be used on the following Ruby platforms:
20
+ * MRI - requires ruby-oci8 1.x or 2.x gem to connect to Oracle (2.0.3 or later recommended)
21
+ * Ruby/YARV 1.9.1 - requires ruby-oci8 2.x library to connect to Oracle
22
+ unicode_utils gem is recommended for Unicode aware string upcase and downcase
23
+ * JRuby - uses JDBC driver ojdbc14.jar to connect to Oracle (should be in JRUBY_HOME/lib or in Java class path)
24
+ * Requires ruby-plsql gem to support custom create, update and delete methods (but can be used without ruby-plsql if this functionality is not needed)
25
+
26
+ == INSTALL:
27
+
28
+ * [sudo] gem install activerecord-oracle_enhanced-adapter
29
+
30
+ In addition install either ruby-oci8 (for MRI/YARV) or copy Oracle JDBC driver to $JRUBY_HOME/lib (for JRuby).
31
+
32
+ == LINKS
33
+
34
+ * Source code: http://github.com/rsim/oracle-enhanced
35
+ * Bug reports / Feature requests: http://github.com/rsim/oracle-enhanced/issues
36
+ * Discuss at oracle_enhanced adapter group: http://groups.google.com/group/oracle-enhanced
37
+
38
+ == CONTRIBUTORS:
39
+
40
+ * Raimonds Simanovskis
41
+ * Jorge Dias
42
+ * James Wylder
43
+ * Rob Christie
44
+ * Nate Wieger
45
+ * Edgars Beigarts
46
+ * Lachlan Laycock
47
+ * toddwf
48
+ * Anton Jenkins
49
+ * Dave Smylie
50
+ * Alex Rothenberg
51
+ * Billy Reisinger
52
+ * David Blain
53
+
54
+ == LICENSE:
55
+
56
+ (The MIT License)
57
+
58
+ Copyright (c) 2008-2010 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
59
+
60
+ Permission is hereby granted, free of charge, to any person obtaining
61
+ a copy of this software and associated documentation files (the
62
+ 'Software'), to deal in the Software without restriction, including
63
+ without limitation the rights to use, copy, modify, merge, publish,
64
+ distribute, sublicense, and/or sell copies of the Software, and to
65
+ permit persons to whom the Software is furnished to do so, subject to
66
+ the following conditions:
67
+
68
+ The above copyright notice and this permission notice shall be
69
+ included in all copies or substantial portions of the Software.
70
+
71
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
72
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
73
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
74
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
75
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
76
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
77
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "oracle_enhanced"
8
+ gem.summary = "Oracle enhanced adapter for ActiveRecord"
9
+ gem.description = <<-EOS
10
+ Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases.
11
+ This adapter is superset of original ActiveRecord Oracle adapter.
12
+ EOS
13
+ gem.email = "raimonds.simanovskis@gmail.com"
14
+ gem.homepage = "http://github.com/plukevdh/oracle-enhanced"
15
+ gem.authors = ["Raimonds Simanovskis"]
16
+ gem.add_dependency "activerecord", ">= 2.0.0"
17
+ gem.add_development_dependency "rspec", ">= 1.2.9"
18
+ gem.extra_rdoc_files = ['README.rdoc']
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
24
+
25
+ require 'spec/rake/spectask'
26
+ Spec::Rake::SpecTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ end
36
+
37
+ task :spec => :check_dependencies
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'doc'
46
+ rdoc.title = "activerecord-oracle_enhanced-adapter #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.5
@@ -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,51 @@
1
+ # RSI: implementation idea taken from JDBC adapter
2
+ def redefine_task(*args, &block)
3
+ task_name = Hash === args.first ? args.first.keys[0] : args.first
4
+ existing_task = Rake.application.lookup task_name
5
+ if existing_task
6
+ class << existing_task; public :instance_variable_set; end
7
+ existing_task.instance_variable_set "@prerequisites", FileList[]
8
+ existing_task.instance_variable_set "@actions", []
9
+ end
10
+ task(*args, &block)
11
+ end
12
+
13
+ namespace :db do
14
+
15
+ namespace :structure do
16
+ redefine_task :dump => :environment do
17
+ abcs = ActiveRecord::Base.configurations
18
+ ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
19
+ File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
20
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_fk_constraints }
21
+ if ActiveRecord::Base.connection.supports_migrations?
22
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
23
+ end
24
+ if abcs[RAILS_ENV]['structure_dump'] == "db_stored_code"
25
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ namespace :test do
32
+ redefine_task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
33
+ abcs = ActiveRecord::Base.configurations
34
+ ActiveRecord::Base.establish_connection(:test)
35
+ IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
36
+ ddl.chop! if ddl.last == ";"
37
+ ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
38
+ end
39
+ end
40
+
41
+ redefine_task :purge => :environment do
42
+ abcs = ActiveRecord::Base.configurations
43
+ ActiveRecord::Base.establish_connection(:test)
44
+ ActiveRecord::Base.connection.full_drop.split("\n\n").each do |ddl|
45
+ ddl.chop! if ddl.last == ";"
46
+ ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,1661 @@
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.txt 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
+ require 'active_record/connection_adapters/abstract_adapter'
33
+
34
+ require 'active_record/connection_adapters/oracle_enhanced_connection'
35
+
36
+ require 'digest/sha1'
37
+
38
+ module ActiveRecord
39
+ class Base
40
+ # Establishes a connection to the database that's used by all Active Record objects.
41
+ def self.oracle_enhanced_connection(config) #:nodoc:
42
+ if config[:emulate_oracle_adapter] == true
43
+ # allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
44
+ # conditionals in the rails activerecord test suite
45
+ require 'active_record/connection_adapters/emulation/oracle_adapter'
46
+ ConnectionAdapters::OracleAdapter.new(
47
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
48
+ else
49
+ ConnectionAdapters::OracleEnhancedAdapter.new(
50
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
51
+ end
52
+ end
53
+
54
+ # Specify table columns which should be ignored by ActiveRecord, e.g.:
55
+ #
56
+ # ignore_table_columns :attribute1, :attribute2
57
+ def self.ignore_table_columns(*args)
58
+ connection.ignore_table_columns(table_name,*args)
59
+ end
60
+
61
+ # Specify which table columns should be typecasted to Date (without time), e.g.:
62
+ #
63
+ # set_date_columns :created_on, :updated_on
64
+ def self.set_date_columns(*args)
65
+ connection.set_type_for_columns(table_name,:date,*args)
66
+ end
67
+
68
+ # Specify which table columns should be typecasted to Time (or DateTime), e.g.:
69
+ #
70
+ # set_datetime_columns :created_date, :updated_date
71
+ def self.set_datetime_columns(*args)
72
+ connection.set_type_for_columns(table_name,:datetime,*args)
73
+ end
74
+
75
+ # Specify which table columns should be typecasted to boolean values +true+ or +false+, e.g.:
76
+ #
77
+ # set_boolean_columns :is_valid, :is_completed
78
+ def self.set_boolean_columns(*args)
79
+ connection.set_type_for_columns(table_name,:boolean,*args)
80
+ end
81
+
82
+ # Specify which table columns should be typecasted to integer values.
83
+ # Might be useful to force NUMBER(1) column to be integer and not boolean, or force NUMBER column without
84
+ # scale to be retrieved as integer and not decimal. Example:
85
+ #
86
+ # set_integer_columns :version_number, :object_identifier
87
+ def self.set_integer_columns(*args)
88
+ connection.set_type_for_columns(table_name,:integer,*args)
89
+ end
90
+
91
+ # Specify which table columns should be typecasted to string values.
92
+ # Might be useful to specify that columns should be string even if its name matches boolean column criteria.
93
+ #
94
+ # set_integer_columns :active_flag
95
+ def self.set_string_columns(*args)
96
+ connection.set_type_for_columns(table_name,:string,*args)
97
+ end
98
+
99
+ # After setting large objects to empty, select the OCI8::LOB
100
+ # and write back the data.
101
+ after_save :enhanced_write_lobs
102
+ def enhanced_write_lobs #:nodoc:
103
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
104
+ !(self.class.custom_create_method || self.class.custom_update_method)
105
+ connection.write_lobs(self.class.table_name, self.class, attributes)
106
+ end
107
+ end
108
+ private :enhanced_write_lobs
109
+
110
+ class << self
111
+ # patch ORDER BY to work with LOBs
112
+ def add_order_with_lobs!(sql, order, scope = :auto)
113
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
114
+ order = connection.lob_order_by_expression(self, order) if order
115
+
116
+ orig_scope = scope
117
+ scope = scope(:find) if :auto == scope
118
+ if scope
119
+ new_scope_order = connection.lob_order_by_expression(self, scope[:order])
120
+ if new_scope_order != scope[:order]
121
+ scope = scope.merge(:order => new_scope_order)
122
+ else
123
+ scope = orig_scope
124
+ end
125
+ end
126
+ end
127
+ add_order_without_lobs!(sql, order, scope = :auto)
128
+ end
129
+ private :add_order_with_lobs!
130
+ #:stopdoc:
131
+ alias_method :add_order_without_lobs!, :add_order!
132
+ alias_method :add_order!, :add_order_with_lobs!
133
+ #:startdoc:
134
+ end
135
+
136
+ # Get table comment from schema definition.
137
+ def self.table_comment
138
+ connection.table_comment(self.table_name)
139
+ end
140
+ end
141
+
142
+
143
+ module ConnectionAdapters #:nodoc:
144
+ class OracleEnhancedColumn < Column
145
+
146
+ attr_reader :table_name, :forced_column_type #:nodoc:
147
+
148
+ def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil) #:nodoc:
149
+ @table_name = table_name
150
+ @forced_column_type = forced_column_type
151
+ super(name, default, sql_type, null)
152
+ end
153
+
154
+ def type_cast(value) #:nodoc:
155
+ return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
156
+ super
157
+ end
158
+
159
+ # convert something to a boolean
160
+ # added y as boolean value
161
+ def self.value_to_boolean(value) #:nodoc:
162
+ if value == true || value == false
163
+ value
164
+ elsif value.is_a?(String) && value.blank?
165
+ nil
166
+ else
167
+ %w(true t 1 y +).include?(value.to_s.downcase)
168
+ end
169
+ end
170
+
171
+ # convert Time or DateTime value to Date for :date columns
172
+ def self.string_to_date(string) #:nodoc:
173
+ return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
174
+ super
175
+ end
176
+
177
+ # convert Date value to Time for :datetime columns
178
+ def self.string_to_time(string) #:nodoc:
179
+ return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
180
+ super
181
+ end
182
+
183
+ # Get column comment from schema definition.
184
+ # Will work only if using default ActiveRecord connection.
185
+ def comment
186
+ ActiveRecord::Base.connection.column_comment(@table_name, name)
187
+ end
188
+
189
+ private
190
+ def simplified_type(field_type)
191
+ forced_column_type ||
192
+ case field_type
193
+ when /decimal|numeric|number/i
194
+ return :boolean if OracleEnhancedAdapter.emulate_booleans && field_type == 'NUMBER(1)'
195
+ return :integer if extract_scale(field_type) == 0
196
+ # if column name is ID or ends with _ID
197
+ return :integer if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
198
+ :decimal
199
+ when /char/i
200
+ return :boolean if OracleEnhancedAdapter.emulate_booleans_from_strings &&
201
+ OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name)
202
+ :string
203
+ when /date/i
204
+ forced_column_type ||
205
+ (:date if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name)) ||
206
+ :datetime
207
+ when /timestamp/i then :timestamp
208
+ when /time/i then :datetime
209
+ else super
210
+ end
211
+ end
212
+
213
+ def guess_date_or_time(value)
214
+ value.respond_to?(:hour) && (value.hour == 0 and value.min == 0 and value.sec == 0) ?
215
+ Date.new(value.year, value.month, value.day) : value
216
+ end
217
+
218
+ class << self
219
+ protected
220
+
221
+ def fallback_string_to_date(string) #:nodoc:
222
+ if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
223
+ return (string_to_date_or_time_using_format(string).to_date rescue super)
224
+ end
225
+ super
226
+ end
227
+
228
+ def fallback_string_to_time(string) #:nodoc:
229
+ if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
230
+ return (string_to_date_or_time_using_format(string).to_time rescue super)
231
+ end
232
+ super
233
+ end
234
+
235
+ def string_to_date_or_time_using_format(string) #:nodoc:
236
+ if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
237
+ return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
238
+ end
239
+ DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
240
+ end
241
+
242
+ end
243
+ end
244
+
245
+
246
+ # Oracle enhanced adapter will work with both
247
+ # Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
248
+ # or with JRuby and Oracle JDBC driver.
249
+ #
250
+ # It should work with Oracle 9i, 10g and 11g databases.
251
+ # Limited set of functionality should work on Oracle 8i as well but several features
252
+ # rely on newer functionality in Oracle database.
253
+ #
254
+ # Usage notes:
255
+ # * Key generation assumes a "${table_name}_seq" sequence is available
256
+ # for all tables; the sequence name can be changed using
257
+ # ActiveRecord::Base.set_sequence_name. When using Migrations, these
258
+ # sequences are created automatically.
259
+ # Use set_sequence_name :autogenerated with legacy tables that have
260
+ # triggers that populate primary keys automatically.
261
+ # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
262
+ # Consequently some hacks are employed to map data back to Date or Time
263
+ # in Ruby. Timezones and sub-second precision on timestamps are
264
+ # not supported.
265
+ # * Default values that are functions (such as "SYSDATE") are not
266
+ # supported. This is a restriction of the way ActiveRecord supports
267
+ # default values.
268
+ #
269
+ # Required parameters:
270
+ #
271
+ # * <tt>:username</tt>
272
+ # * <tt>:password</tt>
273
+ # * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
274
+ #
275
+ # Optional parameters:
276
+ #
277
+ # * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
278
+ # * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
279
+ # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
280
+ # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
281
+ # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
282
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "force"
283
+ # * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to "CHAR"
284
+ # (meaning that size specifies number of characters and not bytes)
285
+ # * <tt>:time_zone</tt> - database session time zone
286
+ # (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone)
287
+ class OracleEnhancedAdapter < AbstractAdapter
288
+
289
+ ##
290
+ # :singleton-method:
291
+ # By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
292
+ # as boolean. If you wish to disable this emulation you can add the following line
293
+ # to your initializer file:
294
+ #
295
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
296
+ cattr_accessor :emulate_booleans
297
+ self.emulate_booleans = true
298
+
299
+ ##
300
+ # :singleton-method:
301
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
302
+ # to Time or DateTime (if value is out of Time value range) value.
303
+ # If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
304
+ # to Date then you can add the following line to your initializer file:
305
+ #
306
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
307
+ #
308
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
309
+ # that Date columns are explicily defined with +set_date_columns+ method.
310
+ cattr_accessor :emulate_dates
311
+ self.emulate_dates = false
312
+
313
+ ##
314
+ # :singleton-method:
315
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
316
+ # to Time or DateTime (if value is out of Time value range) value.
317
+ # If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
318
+ # to Date then you can add the following line to your initializer file:
319
+ #
320
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
321
+ #
322
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
323
+ # that Date columns are explicily defined with +set_date_columns+ method.
324
+ cattr_accessor :emulate_dates_by_column_name
325
+ self.emulate_dates_by_column_name = false
326
+
327
+ # Check column name to identify if it is Date (and not Time) column.
328
+ # Is used if +emulate_dates_by_column_name+ option is set to +true+.
329
+ # Override this method definition in initializer file if different Date column recognition is needed.
330
+ def self.is_date_column?(name, table_name = nil)
331
+ name =~ /(^|_)date(_|$)/i
332
+ end
333
+
334
+ # instance method uses at first check if column type defined at class level
335
+ def is_date_column?(name, table_name = nil) #:nodoc:
336
+ case get_type_for_column(table_name, name)
337
+ when nil
338
+ self.class.is_date_column?(name, table_name)
339
+ when :date
340
+ true
341
+ else
342
+ false
343
+ end
344
+ end
345
+
346
+ ##
347
+ # :singleton-method:
348
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
349
+ # (without precision or scale) to Float or BigDecimal value.
350
+ # If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
351
+ # to Integer then you can add the following line to your initializer file:
352
+ #
353
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
354
+ cattr_accessor :emulate_integers_by_column_name
355
+ self.emulate_integers_by_column_name = false
356
+
357
+ # Check column name to identify if it is Integer (and not Float or BigDecimal) column.
358
+ # Is used if +emulate_integers_by_column_name+ option is set to +true+.
359
+ # Override this method definition in initializer file if different Integer column recognition is needed.
360
+ def self.is_integer_column?(name, table_name = nil)
361
+ name =~ /(^|_)id$/i
362
+ end
363
+
364
+ ##
365
+ # :singleton-method:
366
+ # If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
367
+ # are typecasted to booleans then you can add the following line to your initializer file:
368
+ #
369
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
370
+ cattr_accessor :emulate_booleans_from_strings
371
+ self.emulate_booleans_from_strings = false
372
+
373
+ # Check column name to identify if it is boolean (and not String) column.
374
+ # Is used if +emulate_booleans_from_strings+ option is set to +true+.
375
+ # Override this method definition in initializer file if different boolean column recognition is needed.
376
+ def self.is_boolean_column?(name, field_type, table_name = nil)
377
+ return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
378
+ field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
379
+ end
380
+
381
+ # How boolean value should be quoted to String.
382
+ # Used if +emulate_booleans_from_strings+ option is set to +true+.
383
+ def self.boolean_to_string(bool)
384
+ bool ? "Y" : "N"
385
+ end
386
+
387
+ ##
388
+ # :singleton-method:
389
+ # Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
390
+ #
391
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
392
+ cattr_accessor :string_to_date_format
393
+ self.string_to_date_format = nil
394
+
395
+ ##
396
+ # :singleton-method:
397
+ # Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
398
+ #
399
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
400
+ cattr_accessor :string_to_time_format
401
+ self.string_to_time_format = nil
402
+
403
+ def initialize(connection, logger = nil) #:nodoc:
404
+ super
405
+ @quoted_column_names, @quoted_table_names = {}, {}
406
+ end
407
+
408
+ ADAPTER_NAME = 'OracleEnhanced'.freeze
409
+
410
+ def adapter_name #:nodoc:
411
+ ADAPTER_NAME
412
+ end
413
+
414
+ def supports_migrations? #:nodoc:
415
+ true
416
+ end
417
+
418
+ def supports_savepoints? #:nodoc:
419
+ true
420
+ end
421
+
422
+ #:stopdoc:
423
+ NATIVE_DATABASE_TYPES = {
424
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
425
+ :string => { :name => "VARCHAR2", :limit => 255 },
426
+ :text => { :name => "CLOB" },
427
+ :integer => { :name => "NUMBER", :limit => 38 },
428
+ :float => { :name => "NUMBER" },
429
+ :decimal => { :name => "DECIMAL" },
430
+ :datetime => { :name => "DATE" },
431
+ # changed to native TIMESTAMP type
432
+ # :timestamp => { :name => "DATE" },
433
+ :timestamp => { :name => "TIMESTAMP" },
434
+ :time => { :name => "DATE" },
435
+ :date => { :name => "DATE" },
436
+ :binary => { :name => "BLOB" },
437
+ :boolean => { :name => "NUMBER", :limit => 1 }
438
+ }
439
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
440
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
441
+ :boolean => { :name => "VARCHAR2", :limit => 1 }
442
+ )
443
+ #:startdoc:
444
+
445
+ def native_database_types #:nodoc:
446
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
447
+ end
448
+
449
+ # maximum length of Oracle identifiers
450
+ IDENTIFIER_MAX_LENGTH = 30
451
+
452
+ def table_alias_length #:nodoc:
453
+ IDENTIFIER_MAX_LENGTH
454
+ end
455
+
456
+ # QUOTING ==================================================
457
+ #
458
+ # see: abstract/quoting.rb
459
+
460
+ def quote_column_name(name) #:nodoc:
461
+ # camelCase column names need to be quoted; not that anyone using Oracle
462
+ # would really do this, but handling this case means we pass the test...
463
+ @quoted_column_names[name] = name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
464
+ end
465
+
466
+ # unescaped table name should start with letter and
467
+ # contain letters, digits, _, $ or #
468
+ # can be prefixed with schema name
469
+ # CamelCase table names should be quoted
470
+ def self.valid_table_name?(name) #:nodoc:
471
+ name = name.to_s
472
+ name =~ /\A([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ||
473
+ name =~ /\A([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ? true : false
474
+ end
475
+
476
+ def quote_table_name(name) #:nodoc:
477
+ # abstract_adapter calls quote_column_name from quote_table_name, so prevent that
478
+ @quoted_table_names[name] ||= if self.class.valid_table_name?(name)
479
+ name
480
+ else
481
+ "\"#{name}\""
482
+ end
483
+ end
484
+
485
+ def quote_string(s) #:nodoc:
486
+ s.gsub(/'/, "''")
487
+ end
488
+
489
+ def quote(value, column = nil) #:nodoc:
490
+ if value && column
491
+ case column.type
492
+ when :text, :binary
493
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
494
+ # NLS_DATE_FORMAT independent TIMESTAMP support
495
+ when :timestamp
496
+ quote_timestamp_with_to_timestamp(value)
497
+ # NLS_DATE_FORMAT independent DATE support
498
+ when :date, :time, :datetime
499
+ quote_date_with_to_date(value)
500
+ else
501
+ super
502
+ end
503
+ elsif value.acts_like?(:date)
504
+ quote_date_with_to_date(value)
505
+ elsif value.acts_like?(:time)
506
+ value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
507
+ else
508
+ super
509
+ end
510
+ end
511
+
512
+ def quoted_true #:nodoc:
513
+ return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
514
+ "1"
515
+ end
516
+
517
+ def quoted_false #:nodoc:
518
+ return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
519
+ "0"
520
+ end
521
+
522
+ def quote_date_with_to_date(value) #:nodoc:
523
+ # should support that composite_primary_keys gem will pass date as string
524
+ value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
525
+ "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
526
+ end
527
+
528
+ def quote_timestamp_with_to_timestamp(value) #:nodoc:
529
+ # add up to 9 digits of fractional seconds to inserted time
530
+ value = "#{quoted_date(value)}:#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
531
+ "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS:FF6')"
532
+ end
533
+
534
+ # CONNECTION MANAGEMENT ====================================
535
+ #
536
+
537
+ # If SQL statement fails due to lost connection then reconnect
538
+ # and retry SQL statement if autocommit mode is enabled.
539
+ # By default this functionality is disabled.
540
+ attr_reader :auto_retry #:nodoc:
541
+ @auto_retry = false
542
+
543
+ def auto_retry=(value) #:nodoc:
544
+ @auto_retry = value
545
+ @connection.auto_retry = value if @connection
546
+ end
547
+
548
+ # return raw OCI8 or JDBC connection
549
+ def raw_connection
550
+ @connection.raw_connection
551
+ end
552
+
553
+ # Returns true if the connection is active.
554
+ def active? #:nodoc:
555
+ # Pings the connection to check if it's still good. Note that an
556
+ # #active? method is also available, but that simply returns the
557
+ # last known state, which isn't good enough if the connection has
558
+ # gone stale since the last use.
559
+ @connection.ping
560
+ rescue OracleEnhancedConnectionException
561
+ false
562
+ end
563
+
564
+ # Reconnects to the database.
565
+ def reconnect! #:nodoc:
566
+ @connection.reset!
567
+ rescue OracleEnhancedConnectionException => e
568
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
569
+ end
570
+
571
+ # Disconnects from the database.
572
+ def disconnect! #:nodoc:
573
+ @connection.logoff rescue nil
574
+ end
575
+
576
+ # DATABASE STATEMENTS ======================================
577
+ #
578
+ # see: abstract/database_statements.rb
579
+
580
+ # Executes a SQL statement
581
+ def execute(sql, name = nil)
582
+ # hack to pass additional "with_returning" option without changing argument list
583
+ log(sql, name) { sql.instance_variable_get(:@with_returning) ? @connection.exec_with_returning(sql) : @connection.exec(sql) }
584
+ end
585
+
586
+ # Returns an array of arrays containing the field values.
587
+ # Order is the same as that returned by #columns.
588
+ def select_rows(sql, name = nil)
589
+ # last parameter indicates to return also column list
590
+ result, columns = select(sql, name, true)
591
+ result.map{ |v| columns.map{|c| v[c]} }
592
+ end
593
+
594
+ # Executes an INSERT statement and returns the new record's ID
595
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
596
+ # if primary key value is already prefetched from sequence
597
+ # or if there is no primary key
598
+ if id_value || pk.nil?
599
+ execute(sql, name)
600
+ return id_value
601
+ end
602
+
603
+ sql_with_returning = sql.dup << @connection.returning_clause(quote_column_name(pk))
604
+ # hack to pass additional "with_returning" option without changing argument list
605
+ sql_with_returning.instance_variable_set(:@with_returning, true)
606
+ clear_query_cache
607
+ execute(sql_with_returning, name)
608
+ end
609
+ protected :insert_sql
610
+
611
+ # use in set_sequence_name to avoid fetching primary key value from sequence
612
+ AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
613
+
614
+ # Returns the next sequence value from a sequence generator. Not generally
615
+ # called directly; used by ActiveRecord to get the next primary key value
616
+ # when inserting a new database record (see #prefetch_primary_key?).
617
+ def next_sequence_value(sequence_name)
618
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
619
+ return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
620
+ select_one("SELECT #{quote_table_name(sequence_name)}.NEXTVAL id FROM dual")['id']
621
+ end
622
+
623
+ def begin_db_transaction #:nodoc:
624
+ @connection.autocommit = false
625
+ end
626
+
627
+ def commit_db_transaction #:nodoc:
628
+ @connection.commit
629
+ ensure
630
+ @connection.autocommit = true
631
+ end
632
+
633
+ def rollback_db_transaction #:nodoc:
634
+ @connection.rollback
635
+ ensure
636
+ @connection.autocommit = true
637
+ end
638
+
639
+ def create_savepoint #:nodoc:
640
+ execute("SAVEPOINT #{current_savepoint_name}")
641
+ end
642
+
643
+ def rollback_to_savepoint #:nodoc:
644
+ execute("ROLLBACK TO #{current_savepoint_name}")
645
+ end
646
+
647
+ def release_savepoint #:nodoc:
648
+ # there is no RELEASE SAVEPOINT statement in Oracle
649
+ end
650
+
651
+ def add_limit_offset!(sql, options) #:nodoc:
652
+ # added to_i for limit and offset to protect from SQL injection
653
+ offset = (options[:offset] || 0).to_i
654
+
655
+ if limit = options[:limit]
656
+ limit = limit.to_i
657
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
658
+ elsif offset > 0
659
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
660
+ end
661
+ end
662
+
663
+ @@do_not_prefetch_primary_key = {}
664
+
665
+ # Returns true for Oracle adapter (since Oracle requires primary key
666
+ # values to be pre-fetched before insert). See also #next_sequence_value.
667
+ def prefetch_primary_key?(table_name = nil)
668
+ ! @@do_not_prefetch_primary_key[table_name.to_s]
669
+ end
670
+
671
+ # used just in tests to clear prefetch primary key flag for all tables
672
+ def clear_prefetch_primary_key #:nodoc:
673
+ @@do_not_prefetch_primary_key = {}
674
+ end
675
+
676
+ # Returns default sequence name for table.
677
+ # Will take all or first 26 characters of table name and append _seq suffix
678
+ def default_sequence_name(table_name, primary_key = nil)
679
+ # TODO: remove schema prefix if present before truncating
680
+ # truncate table name if necessary to fit in max length of identifier
681
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_seq"
682
+ end
683
+
684
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
685
+ def insert_fixture(fixture, table_name) #:nodoc:
686
+ super
687
+
688
+ klass = fixture.class_name.constantize rescue nil
689
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
690
+ write_lobs(table_name, klass, fixture)
691
+ end
692
+ end
693
+
694
+ # Writes LOB values from attributes, as indicated by the LOB columns of klass.
695
+ def write_lobs(table_name, klass, attributes) #:nodoc:
696
+ # is class with composite primary key>
697
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
698
+ if is_with_cpk
699
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
700
+ else
701
+ id = quote(attributes[klass.primary_key])
702
+ end
703
+ klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
704
+ value = attributes[col.name]
705
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
706
+ next if value.nil? || (value == '')
707
+ value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
708
+ uncached do
709
+ if is_with_cpk
710
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE",
711
+ 'Writable Large Object')[col.name]
712
+ else
713
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
714
+ 'Writable Large Object')[col.name]
715
+ end
716
+ @connection.write_lob(lob, value.to_s, col.type == :binary)
717
+ end
718
+ end
719
+ end
720
+
721
+ # change LOB column for ORDER BY clause
722
+ # just first 100 characters are taken for ordering
723
+ def lob_order_by_expression(klass, order) #:nodoc:
724
+ return order if order.nil?
725
+ changed = false
726
+ new_order = order.to_s.strip.split(/, */).map do |order_by_col|
727
+ column_name, asc_desc = order_by_col.split(/ +/)
728
+ if column = klass.columns.detect { |col| col.name == column_name && col.sql_type =~ /LOB$/i}
729
+ changed = true
730
+ "DBMS_LOB.SUBSTR(#{column_name},100,1) #{asc_desc}"
731
+ else
732
+ order_by_col
733
+ end
734
+ end.join(', ')
735
+ changed ? new_order : order
736
+ end
737
+
738
+ # SCHEMA STATEMENTS ========================================
739
+ #
740
+ # see: abstract/schema_statements.rb
741
+
742
+ # Current database name
743
+ def current_database
744
+ select_value("select sys_context('userenv','db_name') from dual")
745
+ end
746
+
747
+ # Current database session user
748
+ def current_user
749
+ select_value("select sys_context('userenv','session_user') from dual")
750
+ end
751
+
752
+ # Default tablespace name of current user
753
+ def default_tablespace
754
+ select_value("select lower(default_tablespace) from user_users where username = sys_context('userenv','session_user')")
755
+ end
756
+
757
+ def tables(name = nil) #:nodoc:
758
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
759
+ select_all("select decode(table_name,upper(table_name),lower(table_name),table_name) name from all_tables where owner = sys_context('userenv','session_user')").map {|t| t['name']}
760
+ end
761
+
762
+ cattr_accessor :all_schema_indexes #:nodoc:
763
+
764
+ # This method selects all indexes at once, and caches them in a class variable.
765
+ # Subsequent index calls get them from the variable, without going to the DB.
766
+ def indexes(table_name, name = nil) #:nodoc:
767
+ (owner, table_name, db_link) = @connection.describe(table_name)
768
+ unless all_schema_indexes
769
+ default_tablespace_name = default_tablespace
770
+ result = select_all(<<-SQL)
771
+ SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(i.tablespace_name) as tablespace_name, lower(c.column_name) as column_name, e.column_expression as column_expression
772
+ FROM all_indexes#{db_link} i
773
+ JOIN all_ind_columns#{db_link} c on c.index_name = i.index_name and c.index_owner = i.owner
774
+ LEFT OUTER JOIN all_ind_expressions#{db_link} e on e.index_name = i.index_name and e.index_owner = i.owner and e.column_position = c.column_position
775
+ WHERE i.owner = '#{owner}'
776
+ AND i.table_owner = '#{owner}'
777
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
778
+ ORDER BY i.index_name, c.column_position
779
+ SQL
780
+
781
+ current_index = nil
782
+ self.all_schema_indexes = []
783
+
784
+ result.each do |row|
785
+ # have to keep track of indexes because above query returns dups
786
+ # there is probably a better query we could figure out
787
+ if current_index != row['index_name']
788
+ all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE",
789
+ row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
790
+ current_index = row['index_name']
791
+ end
792
+ all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] : row['column_expression'].gsub('"','').downcase)
793
+ end
794
+ end
795
+
796
+ # Return the indexes just for the requested table, since AR is structured that way
797
+ table_name = table_name.downcase
798
+ all_schema_indexes.select{|i| i.table == table_name}
799
+ end
800
+
801
+ @@ignore_table_columns = nil #:nodoc:
802
+
803
+ # set ignored columns for table
804
+ def ignore_table_columns(table_name, *args) #:nodoc:
805
+ @@ignore_table_columns ||= {}
806
+ @@ignore_table_columns[table_name] ||= []
807
+ @@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
808
+ @@ignore_table_columns[table_name].uniq!
809
+ end
810
+
811
+ def ignored_table_columns(table_name) #:nodoc:
812
+ @@ignore_table_columns ||= {}
813
+ @@ignore_table_columns[table_name]
814
+ end
815
+
816
+ # used just in tests to clear ignored table columns
817
+ def clear_ignored_table_columns #:nodoc:
818
+ @@ignore_table_columns = nil
819
+ end
820
+
821
+ @@table_column_type = nil #:nodoc:
822
+
823
+ # set explicit type for specified table columns
824
+ def set_type_for_columns(table_name, column_type, *args) #:nodoc:
825
+ @@table_column_type ||= {}
826
+ @@table_column_type[table_name] ||= {}
827
+ args.each do |col|
828
+ @@table_column_type[table_name][col.to_s.downcase] = column_type
829
+ end
830
+ end
831
+
832
+ def get_type_for_column(table_name, column_name) #:nodoc:
833
+ @@table_column_type && @@table_column_type[table_name] && @@table_column_type[table_name][column_name.to_s.downcase]
834
+ end
835
+
836
+ # used just in tests to clear column data type definitions
837
+ def clear_types_for_columns #:nodoc:
838
+ @@table_column_type = nil
839
+ end
840
+
841
+ # check if table has primary key trigger with _pkt suffix
842
+ def has_primary_key_trigger?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
843
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
844
+
845
+ trigger_name = default_trigger_name(table_name).upcase
846
+ pkt_sql = <<-SQL
847
+ SELECT trigger_name
848
+ FROM all_triggers#{db_link}
849
+ WHERE owner = '#{owner}'
850
+ AND trigger_name = '#{trigger_name}'
851
+ AND table_owner = '#{owner}'
852
+ AND table_name = '#{desc_table_name}'
853
+ AND status = 'ENABLED'
854
+ SQL
855
+ select_value(pkt_sql) ? true : false
856
+ end
857
+
858
+ ##
859
+ # :singleton-method:
860
+ # Cache column description between requests.
861
+ # Could be used in development environment to avoid selecting table columns from data dictionary tables for each request.
862
+ # This can speed up request processing in development mode if development database is not on local computer.
863
+ #
864
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
865
+ cattr_accessor :cache_columns
866
+ self.cache_columns = false
867
+
868
+ def columns(table_name, name = nil) #:nodoc:
869
+ # Don't double cache if config.cache_classes is turned on
870
+ if @@cache_columns && !(defined?(Rails) && Rails.configuration.cache_classes)
871
+ @@columns_cache ||= {}
872
+ @@columns_cache[table_name] ||= columns_without_cache(table_name, name)
873
+ else
874
+ columns_without_cache(table_name, name)
875
+ end
876
+ end
877
+
878
+ def columns_without_cache(table_name, name = nil) #:nodoc:
879
+ # get ignored_columns by original table name
880
+ ignored_columns = ignored_table_columns(table_name)
881
+
882
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
883
+
884
+ if has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
885
+ @@do_not_prefetch_primary_key[table_name] = true
886
+ end
887
+
888
+ table_cols = <<-SQL
889
+ select column_name as name, data_type as sql_type, data_default, nullable,
890
+ decode(data_type, 'NUMBER', data_precision,
891
+ 'FLOAT', data_precision,
892
+ 'VARCHAR2', decode(char_used, 'C', char_length, data_length),
893
+ 'CHAR', decode(char_used, 'C', char_length, data_length),
894
+ null) as limit,
895
+ decode(data_type, 'NUMBER', data_scale, null) as scale
896
+ from all_tab_columns#{db_link}
897
+ where owner = '#{owner}'
898
+ and table_name = '#{desc_table_name}'
899
+ order by column_id
900
+ SQL
901
+
902
+ # added deletion of ignored columns
903
+ select_all(table_cols, name).delete_if do |row|
904
+ ignored_columns && ignored_columns.include?(row['name'].downcase)
905
+ end.map do |row|
906
+ limit, scale = row['limit'], row['scale']
907
+ if limit || scale
908
+ row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
909
+ end
910
+
911
+ # clean up odd default spacing from Oracle
912
+ if row['data_default']
913
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
914
+ row['data_default'].sub!(/^'(.*)'$/, '\1')
915
+ row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
916
+ end
917
+
918
+ OracleEnhancedColumn.new(oracle_downcase(row['name']),
919
+ row['data_default'],
920
+ row['sql_type'],
921
+ row['nullable'] == 'Y',
922
+ # pass table name for table specific column definitions
923
+ table_name,
924
+ # pass column type if specified in class definition
925
+ get_type_for_column(table_name, oracle_downcase(row['name'])))
926
+ end
927
+ end
928
+
929
+ # used just in tests to clear column cache
930
+ def clear_columns_cache #:nodoc:
931
+ @@columns_cache = nil
932
+ end
933
+
934
+ # used in migrations to clear column cache for specified table
935
+ def clear_table_columns_cache(table_name)
936
+ @@columns_cache[table_name.to_s] = nil if @@cache_columns
937
+ end
938
+
939
+ ##
940
+ # :singleton-method:
941
+ # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
942
+ #
943
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
944
+ cattr_accessor :default_sequence_start_value
945
+ self.default_sequence_start_value = 10000
946
+
947
+ # Additional options for +create_table+ method in migration files.
948
+ #
949
+ # You can specify individual starting value in table creation migration file, e.g.:
950
+ #
951
+ # create_table :users, :sequence_start_value => 100 do |t|
952
+ # # ...
953
+ # end
954
+ #
955
+ # You can also specify other sequence definition additional parameters, e.g.:
956
+ #
957
+ # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
958
+ # # ...
959
+ # end
960
+ #
961
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
962
+ # By default trigger name will be "table_name_pkt", you can override the name with
963
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
964
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
965
+ # Example:
966
+ #
967
+ # create_table :users, :primary_key_trigger => true do |t|
968
+ # # ...
969
+ # end
970
+ #
971
+ # It is possible to add table and column comments in table creation migration files:
972
+ #
973
+ # create_table :employees, :comment => “Employees and contractors” do |t|
974
+ # t.string :first_name, :comment => “Given name”
975
+ # t.string :last_name, :comment => “Surname”
976
+ # end
977
+
978
+ def create_table(name, options = {}, &block)
979
+ create_sequence = options[:id] != false
980
+ column_comments = {}
981
+
982
+ table_definition = TableDefinition.new(self)
983
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false
984
+
985
+ # store that primary key was defined in create_table block
986
+ unless create_sequence
987
+ class << table_definition
988
+ attr_accessor :create_sequence
989
+ def primary_key(*args)
990
+ self.create_sequence = true
991
+ super(*args)
992
+ end
993
+ end
994
+ end
995
+
996
+ # store column comments
997
+ class << table_definition
998
+ attr_accessor :column_comments
999
+ def column(name, type, options = {})
1000
+ if options[:comment]
1001
+ self.column_comments ||= {}
1002
+ self.column_comments[name] = options[:comment]
1003
+ end
1004
+ super(name, type, options)
1005
+ end
1006
+ end
1007
+
1008
+ result = block.call(table_definition) if block
1009
+ create_sequence = create_sequence || table_definition.create_sequence
1010
+ column_comments = table_definition.column_comments if table_definition.column_comments
1011
+
1012
+
1013
+ if options[:force] && table_exists?(name)
1014
+ drop_table(name, options)
1015
+ end
1016
+
1017
+ create_sql = "CREATE#{' GLOBAL TEMPORARY' if options[:temporary]} TABLE "
1018
+ create_sql << "#{quote_table_name(name)} ("
1019
+ create_sql << table_definition.to_sql
1020
+ create_sql << ") #{options[:options]}"
1021
+ execute create_sql
1022
+
1023
+ create_sequence_and_trigger(name, options) if create_sequence
1024
+
1025
+ add_table_comment name, options[:comment]
1026
+ column_comments.each do |column_name, comment|
1027
+ add_comment name, column_name, comment
1028
+ end
1029
+
1030
+ end
1031
+
1032
+ def rename_table(name, new_name) #:nodoc:
1033
+ execute "RENAME #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
1034
+ execute "RENAME #{quote_table_name("#{name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
1035
+ end
1036
+
1037
+ def drop_table(name, options = {}) #:nodoc:
1038
+ super(name)
1039
+ seq_name = options[:sequence_name] || default_sequence_name(name)
1040
+ execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
1041
+ ensure
1042
+ clear_table_columns_cache(name)
1043
+ end
1044
+
1045
+ # clear cached indexes when adding new index
1046
+ def add_index(table_name, column_name, options = {}) #:nodoc:
1047
+ self.all_schema_indexes = nil
1048
+ column_names = Array(column_name)
1049
+ index_name = index_name(table_name, :column => column_names)
1050
+
1051
+ if Hash === options # legacy support, since this param was a string
1052
+ index_type = options[:unique] ? "UNIQUE" : ""
1053
+ index_name = options[:name] || index_name
1054
+ tablespace = if options[:tablespace]
1055
+ " TABLESPACE #{options[:tablespace]}"
1056
+ else
1057
+ ""
1058
+ end
1059
+ else
1060
+ index_type = options
1061
+ end
1062
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1063
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace}"
1064
+ end
1065
+
1066
+ # clear cached indexes when removing index
1067
+ def remove_index(table_name, options = {}) #:nodoc:
1068
+ self.all_schema_indexes = nil
1069
+ execute "DROP INDEX #{index_name(table_name, options)}"
1070
+ end
1071
+
1072
+ # returned shortened index name if default is too large
1073
+ def index_name(table_name, options) #:nodoc:
1074
+ default_name = super(table_name, options)
1075
+ return default_name if default_name.length <= IDENTIFIER_MAX_LENGTH
1076
+
1077
+ # remove 'index', 'on' and 'and' keywords
1078
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
1079
+
1080
+ # leave just first three letters from each word
1081
+ if shortened_name.length > IDENTIFIER_MAX_LENGTH
1082
+ shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
1083
+ end
1084
+ # generate unique name using hash function
1085
+ if shortened_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
1086
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
1087
+ end
1088
+ @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
1089
+ shortened_name
1090
+ end
1091
+
1092
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
1093
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1094
+ options[:type] = type
1095
+ add_column_options!(add_column_sql, options)
1096
+ execute(add_column_sql)
1097
+ ensure
1098
+ clear_table_columns_cache(table_name)
1099
+ end
1100
+
1101
+ def change_column_default(table_name, column_name, default) #:nodoc:
1102
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
1103
+ ensure
1104
+ clear_table_columns_cache(table_name)
1105
+ end
1106
+
1107
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
1108
+ column = column_for(table_name, column_name)
1109
+
1110
+ unless null || default.nil?
1111
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
1112
+ end
1113
+
1114
+ change_column table_name, column_name, column.sql_type, :null => null
1115
+ end
1116
+
1117
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
1118
+ column = column_for(table_name, column_name)
1119
+
1120
+ # remove :null option if its value is the same as current column definition
1121
+ # otherwise Oracle will raise error
1122
+ if options.has_key?(:null) && options[:null] == column.null
1123
+ options[:null] = nil
1124
+ end
1125
+
1126
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1127
+ options[:type] = type
1128
+ add_column_options!(change_column_sql, options)
1129
+ execute(change_column_sql)
1130
+ ensure
1131
+ clear_table_columns_cache(table_name)
1132
+ end
1133
+
1134
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
1135
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
1136
+ ensure
1137
+ clear_table_columns_cache(table_name)
1138
+ end
1139
+
1140
+ def remove_column(table_name, column_name) #:nodoc:
1141
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
1142
+ ensure
1143
+ clear_table_columns_cache(table_name)
1144
+ end
1145
+
1146
+ def add_comment(table_name, column_name, comment) #:nodoc:
1147
+ return if comment.blank?
1148
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
1149
+ end
1150
+
1151
+ def add_table_comment(table_name, comment) #:nodoc:
1152
+ return if comment.blank?
1153
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
1154
+ end
1155
+
1156
+ def table_comment(table_name) #:nodoc:
1157
+ (owner, table_name, db_link) = @connection.describe(table_name)
1158
+ select_value <<-SQL
1159
+ SELECT comments FROM all_tab_comments#{db_link}
1160
+ WHERE owner = '#{owner}'
1161
+ AND table_name = '#{table_name}'
1162
+ SQL
1163
+ end
1164
+
1165
+ def column_comment(table_name, column_name) #:nodoc:
1166
+ (owner, table_name, db_link) = @connection.describe(table_name)
1167
+ select_value <<-SQL
1168
+ SELECT comments FROM all_col_comments#{db_link}
1169
+ WHERE owner = '#{owner}'
1170
+ AND table_name = '#{table_name}'
1171
+ AND column_name = '#{column_name.upcase}'
1172
+ SQL
1173
+ end
1174
+
1175
+ # Maps logical Rails types to Oracle-specific data types.
1176
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
1177
+ # Ignore options for :text and :binary columns
1178
+ return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
1179
+
1180
+ super
1181
+ end
1182
+
1183
+ # Find a table's primary key and sequence.
1184
+ # *Note*: Only primary key is implemented - sequence will be nil.
1185
+ def pk_and_sequence_for(table_name) #:nodoc:
1186
+ (owner, table_name, db_link) = @connection.describe(table_name)
1187
+
1188
+ # changed select from all_constraints to user_constraints - much faster in large data dictionaries
1189
+ pks = select_values(<<-SQL, 'Primary Key')
1190
+ select cc.column_name
1191
+ from user_constraints#{db_link} c, user_cons_columns#{db_link} cc
1192
+ where c.owner = '#{owner}'
1193
+ and c.table_name = '#{table_name}'
1194
+ and c.constraint_type = 'P'
1195
+ and cc.owner = c.owner
1196
+ and cc.constraint_name = c.constraint_name
1197
+ SQL
1198
+
1199
+ # only support single column keys
1200
+ pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
1201
+ end
1202
+
1203
+ def structure_dump #:nodoc:
1204
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
1205
+ structure << "create sequence #{seq.to_a.first.last}#{STATEMENT_TOKEN}"
1206
+ end
1207
+
1208
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
1209
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
1210
+ table_name = table['table_name']
1211
+ virtual_columns = virtual_columns_for(table_name)
1212
+ ddl = "create#{ ' global temporary' if temporary_table?(table_name)} table #{table_name} (\n "
1213
+ cols = select_all(%Q{
1214
+ select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
1215
+ from user_tab_columns
1216
+ where table_name = '#{table_name}'
1217
+ order by column_id
1218
+ }).map do |row|
1219
+ if(v = virtual_columns.find {|col| col['column_name'] == row['column_name']})
1220
+ structure_dump_virtual_column(row, v['data_default'])
1221
+ else
1222
+ structure_dump_column(row)
1223
+ end
1224
+ end
1225
+ ddl << cols.join(",\n ")
1226
+ ddl << structure_dump_constraints(table_name)
1227
+ ddl << "\n)#{STATEMENT_TOKEN}"
1228
+ structure << ddl
1229
+ structure << structure_dump_indexes(table_name)
1230
+ end
1231
+ end
1232
+
1233
+ def structure_dump_virtual_column(column, data_default) #:nodoc:
1234
+ data_default = data_default.gsub(/"/, '')
1235
+ col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1236
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1237
+ col << "(#{column['data_precision'].to_i}"
1238
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
1239
+ col << ')'
1240
+ elsif column['data_type'].include?('CHAR')
1241
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1242
+ col << "(#{length})"
1243
+ end
1244
+ col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
1245
+ end
1246
+
1247
+ def structure_dump_column(column) #:nodoc:
1248
+ col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1249
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1250
+ col << "(#{column['data_precision'].to_i}"
1251
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
1252
+ col << ')'
1253
+ elsif column['data_type'].include?('CHAR')
1254
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1255
+ col << "(#{length})"
1256
+ end
1257
+ col << " default #{column['data_default']}" if !column['data_default'].nil?
1258
+ col << ' not null' if column['nullable'] == 'N'
1259
+ col
1260
+ end
1261
+
1262
+ def structure_dump_constraints(table) #:nodoc:
1263
+ out = [structure_dump_primary_key(table), structure_dump_unique_keys(table)].flatten.compact
1264
+ out.length > 0 ? ",\n#{out.join(",\n")}" : ''
1265
+ end
1266
+
1267
+ def structure_dump_primary_key(table) #:nodoc:
1268
+ opts = {:name => '', :cols => []}
1269
+ pks = select_all(<<-SQL, "Primary Keys")
1270
+ select a.constraint_name, a.column_name, a.position
1271
+ from user_cons_columns a
1272
+ join user_constraints c
1273
+ on a.constraint_name = c.constraint_name
1274
+ where c.table_name = '#{table.upcase}'
1275
+ and c.constraint_type = 'P'
1276
+ and c.owner = sys_context('userenv', 'session_user')
1277
+ SQL
1278
+ pks.each do |row|
1279
+ opts[:name] = row['constraint_name']
1280
+ opts[:cols][row['position']-1] = row['column_name']
1281
+ end
1282
+ opts[:cols].length > 0 ? " CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : nil
1283
+ end
1284
+
1285
+ def structure_dump_unique_keys(table) #:nodoc:
1286
+ keys = {}
1287
+ uks = select_all(<<-SQL, "Primary Keys")
1288
+ select a.constraint_name, a.column_name, a.position
1289
+ from user_cons_columns a
1290
+ join user_constraints c
1291
+ on a.constraint_name = c.constraint_name
1292
+ where c.table_name = '#{table.upcase}'
1293
+ and c.constraint_type = 'U'
1294
+ and c.owner = sys_context('userenv', 'session_user')
1295
+ SQL
1296
+ uks.each do |uk|
1297
+ keys[uk['constraint_name']] ||= []
1298
+ keys[uk['constraint_name']][uk['position']-1] = uk['column_name']
1299
+ end
1300
+ keys.map do |k,v|
1301
+ " CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
1302
+ end
1303
+ end
1304
+
1305
+ def structure_dump_fk_constraints #:nodoc:
1306
+ fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
1307
+ if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
1308
+ foreign_keys.map do |fk|
1309
+ column = fk.options[:column] || "#{fk.to_table.to_s.singularize}_id"
1310
+ constraint_name = foreign_key_constraint_name(fk.from_table, column, fk.options)
1311
+ sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
1312
+ sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
1313
+ end
1314
+ end
1315
+ end.flatten.compact.join(STATEMENT_TOKEN)
1316
+ fks.length > 1 ? "#{fks}#{STATEMENT_TOKEN}" : ''
1317
+ end
1318
+
1319
+ # Extract all stored procedures, packages, synonyms and views.
1320
+ def structure_dump_db_stored_code #:nodoc:
1321
+ structure = ""
1322
+ select_all("select distinct name, type
1323
+ from all_source
1324
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
1325
+ and owner = sys_context('userenv','session_user') order by type").each do |source|
1326
+ ddl = "create or replace \n "
1327
+ lines = select_all(%Q{
1328
+ select text
1329
+ from all_source
1330
+ where name = '#{source['name']}'
1331
+ and type = '#{source['type']}'
1332
+ and owner = sys_context('userenv','session_user')
1333
+ order by line
1334
+ }).map do |row|
1335
+ ddl << row['text'] if row['text'].size > 1
1336
+ end
1337
+ ddl << ";" unless ddl.strip.last == ";"
1338
+ structure << ddl << STATEMENT_TOKEN
1339
+ end
1340
+
1341
+ # export views
1342
+ select_all("select view_name, text from user_views").each do |view|
1343
+ ddl = "create or replace view #{view['view_name']} AS\n "
1344
+ # any views with empty lines will cause OCI to barf when loading. remove blank lines =/
1345
+ ddl << view['text'].gsub(/^\n/, '')
1346
+ structure << ddl << STATEMENT_TOKEN
1347
+ end
1348
+
1349
+ # export synonyms
1350
+ select_all("select owner, synonym_name, table_name, table_owner
1351
+ from all_synonyms
1352
+ where owner = sys_context('userenv','session_user') ").each do |synonym|
1353
+ ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']}"
1354
+ structure << ddl << STATEMENT_TOKEN
1355
+ end
1356
+
1357
+ structure
1358
+ end
1359
+
1360
+ def structure_dump_indexes(table_name) #:nodoc:
1361
+ statements = indexes(table_name).map do |options|
1362
+ #def add_index(table_name, column_name, options = {})
1363
+ column_names = options[:columns]
1364
+ options = {:name => options[:name], :unique => options[:unique]}
1365
+ index_name = index_name(table_name, :column => column_names)
1366
+ if Hash === options # legacy support, since this param was a string
1367
+ index_type = options[:unique] ? "UNIQUE" : ""
1368
+ index_name = options[:name] || index_name
1369
+ else
1370
+ index_type = options
1371
+ end
1372
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1373
+ "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
1374
+ end
1375
+ statements.length > 0 ? "#{statements.join(STATEMENT_TOKEN)}#{STATEMENT_TOKEN}" : ''
1376
+ end
1377
+
1378
+ def structure_drop #:nodoc:
1379
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
1380
+ drop << "drop sequence #{seq.to_a.first.last};\n\n"
1381
+ end
1382
+
1383
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
1384
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
1385
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1386
+ end
1387
+ end
1388
+
1389
+ def temp_table_drop #:nodoc:
1390
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
1391
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') and temporary = 'Y' order by 1").inject('') do |drop, table|
1392
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1393
+ end
1394
+ end
1395
+
1396
+ def full_drop(preserve_tables=false) #:nodoc:
1397
+ s = preserve_tables ? [] : [structure_drop]
1398
+ s << temp_table_drop if preserve_tables
1399
+ s << drop_sql_for_feature("view")
1400
+ s << drop_sql_for_feature("synonym")
1401
+ s << drop_sql_for_feature("type")
1402
+ s << drop_sql_for_object("package")
1403
+ s << drop_sql_for_object("function")
1404
+ s << drop_sql_for_object("procedure")
1405
+ s.join("\n\n")
1406
+ end
1407
+
1408
+ def add_column_options!(sql, options) #:nodoc:
1409
+ type = options[:type] || ((column = options[:column]) && column.type)
1410
+ type = type && type.to_sym
1411
+ # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
1412
+ if options_include_default?(options)
1413
+ if type == :text
1414
+ sql << " DEFAULT #{quote(options[:default])}"
1415
+ else
1416
+ # from abstract adapter
1417
+ sql << " DEFAULT #{quote(options[:default], options[:column])}"
1418
+ end
1419
+ end
1420
+ # must explicitly add NULL or NOT NULL to allow change_column to work on migrations
1421
+ if options[:null] == false
1422
+ sql << " NOT NULL"
1423
+ elsif options[:null] == true
1424
+ sql << " NULL" unless type == :primary_key
1425
+ end
1426
+ end
1427
+
1428
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1429
+ #
1430
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
1431
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
1432
+ # won't actually get a distinct list of the column you want (presuming the column
1433
+ # has duplicates with multiple values for the ordered-by columns. So we use the
1434
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
1435
+ # making every row the same.
1436
+ #
1437
+ # distinct("posts.id", "posts.created_at desc")
1438
+ def distinct(columns, order_by) #:nodoc:
1439
+ return "DISTINCT #{columns}" if order_by.blank?
1440
+
1441
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
1442
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
1443
+ order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
1444
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
1445
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
1446
+ end
1447
+ sql = "DISTINCT #{columns}, "
1448
+ sql << order_columns * ", "
1449
+ end
1450
+
1451
+ def temporary_table?(table_name) #:nodoc:
1452
+ select_value("select temporary from user_tables where table_name = '#{table_name.upcase}'") == 'Y'
1453
+ end
1454
+
1455
+ # statements separator used in structure dump
1456
+ STATEMENT_TOKEN = "\n\n--@@@--\n\n"
1457
+
1458
+ # ORDER BY clause for the passed order option.
1459
+ #
1460
+ # Uses column aliases as defined by #distinct.
1461
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
1462
+ return sql if options[:order].blank?
1463
+
1464
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
1465
+ order.map! {|s| $1 if s =~ / (.*)/}
1466
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
1467
+
1468
+ sql << " ORDER BY #{order}"
1469
+ end
1470
+
1471
+ protected
1472
+
1473
+ def translate_exception(exception, message) #:nodoc:
1474
+ case @connection.error_code(exception)
1475
+ when 1
1476
+ RecordNotUnique.new(message, exception)
1477
+ when 2291
1478
+ InvalidForeignKey.new(message, exception)
1479
+ else
1480
+ super
1481
+ end
1482
+ end
1483
+
1484
+ private
1485
+
1486
+ def select(sql, name = nil, return_column_names = false)
1487
+ log(sql, name) do
1488
+ @connection.select(sql, name, return_column_names)
1489
+ end
1490
+ end
1491
+
1492
+ def oracle_downcase(column_name)
1493
+ @connection.oracle_downcase(column_name)
1494
+ end
1495
+
1496
+ def column_for(table_name, column_name)
1497
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
1498
+ raise "No such column: #{table_name}.#{column_name}"
1499
+ end
1500
+ column
1501
+ end
1502
+
1503
+ def create_sequence_and_trigger(table_name, options)
1504
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
1505
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
1506
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
1507
+
1508
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
1509
+ end
1510
+
1511
+ def create_primary_key_trigger(table_name, options)
1512
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
1513
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
1514
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
1515
+ execute compress_lines(<<-SQL)
1516
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
1517
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
1518
+ BEGIN
1519
+ IF inserting THEN
1520
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
1521
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
1522
+ END IF;
1523
+ END IF;
1524
+ END;
1525
+ SQL
1526
+ end
1527
+
1528
+ def default_trigger_name(table_name)
1529
+ # truncate table name if necessary to fit in max length of identifier
1530
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_pkt"
1531
+ end
1532
+
1533
+ def compress_lines(string, spaced = true)
1534
+ string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
1535
+ end
1536
+
1537
+ # virtual columns are an 11g feature. This returns [] if feature is not
1538
+ # present or none are found.
1539
+ # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
1540
+ def virtual_columns_for(table)
1541
+ begin
1542
+ select_all <<-SQL
1543
+ select column_name, data_default
1544
+ from user_tab_cols
1545
+ where virtual_column='YES'
1546
+ and table_name='#{table.upcase}'
1547
+ SQL
1548
+ # feature not supported previous to 11g
1549
+ rescue ActiveRecord::StatementInvalid => e
1550
+ []
1551
+ end
1552
+ end
1553
+
1554
+ def drop_sql_for_feature(type)
1555
+ select_values("select 'DROP #{type.upcase} \"' || #{type}_name || '\";' from user_#{type.tableize}").join("\n\n")
1556
+ end
1557
+
1558
+ def drop_sql_for_object(type)
1559
+ select_values("select 'DROP #{type.upcase} ' || object_name || ';' from user_objects where object_type = '#{type.upcase}'").join("\n\n")
1560
+ end
1561
+
1562
+ public
1563
+ # DBMS_OUTPUT =============================================
1564
+ #
1565
+ # PL/SQL in Oracle uses dbms_output for logging print statements
1566
+ # These methods stick that output into the Rails log so Ruby and PL/SQL
1567
+ # code can can be debugged together in a single application
1568
+
1569
+ # Maximum DBMS_OUTPUT buffer size
1570
+ DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
1571
+
1572
+ # Turn DBMS_Output logging on
1573
+ def enable_dbms_output
1574
+ set_dbms_output_plsql_connection
1575
+ @enable_dbms_output = true
1576
+ plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
1577
+ end
1578
+ # Turn DBMS_Output logging off
1579
+ def disable_dbms_output
1580
+ set_dbms_output_plsql_connection
1581
+ @enable_dbms_output = false
1582
+ plsql(:dbms_output).sys.dbms_output.disable
1583
+ end
1584
+ # Is DBMS_Output logging enabled?
1585
+ def dbms_output_enabled?
1586
+ @enable_dbms_output
1587
+ end
1588
+
1589
+ protected
1590
+ def log(sql, name) #:nodoc:
1591
+ super sql, name
1592
+ ensure
1593
+ log_dbms_output if dbms_output_enabled?
1594
+ end
1595
+
1596
+ private
1597
+
1598
+ def set_dbms_output_plsql_connection
1599
+ raise OracleEnhancedConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
1600
+ # do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
1601
+ unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
1602
+ plsql(:dbms_output).connection = raw_connection
1603
+ end
1604
+ end
1605
+
1606
+ def log_dbms_output
1607
+ while true do
1608
+ result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
1609
+ break unless result[:status] == 0
1610
+ @logger.debug "DBMS_OUTPUT: #{result[:line]}"
1611
+ end
1612
+ end
1613
+
1614
+ end
1615
+ end
1616
+ end
1617
+
1618
+ # Added LOB writing callback for sessions stored in database
1619
+ # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
1620
+ if defined?(CGI::Session::ActiveRecordStore::Session)
1621
+ if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
1622
+ CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
1623
+ #:stopdoc:
1624
+ class CGI::Session::ActiveRecordStore::Session
1625
+ after_save :enhanced_write_lobs
1626
+ end
1627
+ #:startdoc:
1628
+ end
1629
+ end
1630
+
1631
+ # Load custom create, update, delete methods functionality
1632
+ require 'active_record/connection_adapters/oracle_enhanced_procedures'
1633
+
1634
+ # Load additional methods for composite_primary_keys support
1635
+ require 'active_record/connection_adapters/oracle_enhanced_cpk'
1636
+
1637
+ # Load patch for dirty tracking methods
1638
+ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1639
+
1640
+ # Load rake tasks definitions
1641
+ begin
1642
+ require 'active_record/connection_adapters/oracle_enhanced_tasks'
1643
+ rescue LoadError
1644
+ end if defined?(RAILS_ROOT)
1645
+
1646
+ # Handles quoting of oracle reserved words
1647
+ require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
1648
+
1649
+ # Patches and enhancements for schema dumper
1650
+ require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
1651
+
1652
+ # Extensions for schema definition statements
1653
+ require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext'
1654
+
1655
+ # Extensions for schema definition
1656
+ require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1657
+
1658
+ # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1659
+ require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1660
+
1661
+ require 'active_record/connection_adapters/oracle_enhanced_version'