unclebilly-activerecord-oracle_enhanced-adapter 1.2.4

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