unclebilly-activerecord-oracle_enhanced-adapter 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/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'