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
@@ -0,0 +1,224 @@
1
+ require 'digest/sha1'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OracleEnhancedSchemaStatementsExt
6
+ def supports_foreign_keys? #:nodoc:
7
+ true
8
+ end
9
+
10
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
11
+ # By default trigger name will be "table_name_pkt", you can override the name with
12
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
13
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
14
+ #
15
+ # add_primary_key_trigger :users
16
+ #
17
+ # You can also create primary key trigger using +create_table+ with :primary_key_trigger
18
+ # option:
19
+ #
20
+ # create_table :users, :primary_key_trigger => true do |t|
21
+ # # ...
22
+ # end
23
+ #
24
+ def add_primary_key_trigger(table_name, options)
25
+ # call the same private method that is used for create_table :primary_key_trigger => true
26
+ create_primary_key_trigger(table_name, options)
27
+ end
28
+
29
+ # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
30
+ # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner)
31
+ #
32
+ # The foreign key will be named after the from and to tables unless you pass
33
+ # <tt>:name</tt> as an option.
34
+ #
35
+ # === Examples
36
+ # ==== Creating a foreign key
37
+ # add_foreign_key(:comments, :posts)
38
+ # generates
39
+ # ALTER TABLE comments ADD CONSTRAINT
40
+ # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id)
41
+ #
42
+ # ==== Creating a named foreign key
43
+ # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
44
+ # generates
45
+ # ALTER TABLE comments ADD CONSTRAINT
46
+ # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id)
47
+ #
48
+ # ==== Creating a cascading foreign_key on a custom column
49
+ # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
50
+ # generates
51
+ # ALTER TABLE people ADD CONSTRAINT
52
+ # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id)
53
+ # ON DELETE SET NULL
54
+ #
55
+ # === Supported options
56
+ # [:column]
57
+ # Specify the column name on the from_table that references the to_table. By default this is guessed
58
+ # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
59
+ # as the default <tt>:column</tt>.
60
+ # [:primary_key]
61
+ # Specify the column name on the to_table that is referenced by this foreign key. By default this is
62
+ # assumed to be "id".
63
+ # [:name]
64
+ # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
65
+ # [:dependent]
66
+ # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
67
+ # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
68
+ def add_foreign_key(from_table, to_table, options = {})
69
+ column = options[:column] || "#{to_table.to_s.singularize}_id"
70
+ constraint_name = foreign_key_constraint_name(from_table, column, options)
71
+ sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
72
+ sql << foreign_key_definition(to_table, options)
73
+ execute sql
74
+ end
75
+
76
+ def foreign_key_definition(to_table, options = {}) #:nodoc:
77
+ column = options[:column] || "#{to_table.to_s.singularize}_id"
78
+ primary_key = options[:primary_key] || "id"
79
+ sql = "FOREIGN KEY (#{quote_column_name(column)}) REFERENCES #{quote_table_name(to_table)}(#{primary_key})"
80
+ case options[:dependent]
81
+ when :nullify
82
+ sql << " ON DELETE SET NULL"
83
+ when :delete
84
+ sql << " ON DELETE CASCADE"
85
+ end
86
+ sql
87
+ end
88
+
89
+ # Remove the given foreign key from the table.
90
+ #
91
+ # ===== Examples
92
+ # ====== Remove the suppliers_company_id_fk in the suppliers table.
93
+ # remove_foreign_key :suppliers, :companies
94
+ # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
95
+ # remove_foreign_key :accounts, :column => :branch_id
96
+ # ====== Remove the foreign key named party_foreign_key in the accounts table.
97
+ # remove_foreign_key :accounts, :name => :party_foreign_key
98
+ def remove_foreign_key(from_table, options)
99
+ if Hash === options
100
+ constraint_name = foreign_key_constraint_name(from_table, options[:column], options)
101
+ else
102
+ constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id")
103
+ end
104
+ execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}"
105
+ end
106
+
107
+ private
108
+
109
+ def foreign_key_constraint_name(table_name, column, options = {})
110
+ constraint_name = original_name = options[:name] || "#{table_name}_#{column}_fk"
111
+ return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
112
+ # leave just first three letters from each word
113
+ constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_')
114
+ # generate unique name using hash function
115
+ if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
116
+ constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
117
+ end
118
+ @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger
119
+ constraint_name
120
+ end
121
+
122
+
123
+ public
124
+
125
+ # get table foreign keys for schema dump
126
+ def foreign_keys(table_name) #:nodoc:
127
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
128
+
129
+ fk_info = select_all(<<-SQL, 'Foreign Keys')
130
+ SELECT r.table_name to_table
131
+ ,rc.column_name primary_key
132
+ ,cc.column_name
133
+ ,c.constraint_name name
134
+ ,c.delete_rule
135
+ FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc,
136
+ user_constraints#{db_link} r, user_cons_columns#{db_link} rc
137
+ WHERE c.owner = '#{owner}'
138
+ AND c.table_name = '#{desc_table_name}'
139
+ AND c.constraint_type = 'R'
140
+ AND cc.owner = c.owner
141
+ AND cc.constraint_name = c.constraint_name
142
+ AND r.constraint_name = c.r_constraint_name
143
+ AND r.owner = c.owner
144
+ AND rc.owner = r.owner
145
+ AND rc.constraint_name = r.constraint_name
146
+ AND rc.position = cc.position
147
+ SQL
148
+
149
+ fk_info.map do |row|
150
+ options = {:column => oracle_downcase(row['column_name']), :name => oracle_downcase(row['name']),
151
+ :primary_key => oracle_downcase(row['primary_key'])}
152
+ case row['delete_rule']
153
+ when 'CASCADE'
154
+ options[:dependent] = :delete
155
+ when 'SET NULL'
156
+ options[:dependent] = :nullify
157
+ end
158
+ OracleEnhancedForeignKeyDefinition.new(table_name, oracle_downcase(row['to_table']), options)
159
+ end
160
+ end
161
+
162
+ # REFERENTIAL INTEGRITY ====================================
163
+
164
+ def disable_referential_integrity(&block) #:nodoc:
165
+ sql_constraints = <<-SQL
166
+ SELECT constraint_name, owner, table_name
167
+ FROM user_constraints
168
+ WHERE constraint_type = 'R'
169
+ AND status = 'ENABLED'
170
+ SQL
171
+ old_constraints = select_all(sql_constraints)
172
+ begin
173
+ old_constraints.each do |constraint|
174
+ execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}"
175
+ end
176
+ yield
177
+ ensure
178
+ old_constraints.each do |constraint|
179
+ execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}"
180
+ end
181
+ end
182
+ end
183
+
184
+ # Add synonym to existing table or view or sequence. Can be used to create local synonym to
185
+ # remote table in other schema or in other database
186
+ # Examples:
187
+ #
188
+ # add_synonym :posts, "blog.posts"
189
+ # add_synonym :posts_seq, "blog.posts_seq"
190
+ # add_synonym :employees, "hr.employees@dblink", :force => true
191
+ #
192
+ def add_synonym(name, table_name, options = {})
193
+ sql = "CREATE"
194
+ if options[:force] == true
195
+ sql << " OR REPLACE"
196
+ end
197
+ sql << " SYNONYM #{quote_table_name(name)} FOR #{quote_table_name(table_name)}"
198
+ execute sql
199
+ end
200
+
201
+ # Remove existing synonym to table or view or sequence
202
+ # Example:
203
+ #
204
+ # remove_synonym :posts, "blog.posts"
205
+ #
206
+ def remove_synonym(name)
207
+ execute "DROP SYNONYM #{quote_table_name(name)}"
208
+ end
209
+
210
+ # get synonyms for schema dump
211
+ def synonyms #:nodoc:
212
+ select_all("SELECT synonym_name, table_owner, table_name, db_link FROM user_synonyms").collect do |row|
213
+ OracleEnhancedSynonymDefinition.new(oracle_downcase(row['synonym_name']),
214
+ oracle_downcase(row['table_owner']), oracle_downcase(row['table_name']), oracle_downcase(row['db_link']))
215
+ end
216
+ end
217
+
218
+ end
219
+ end
220
+ end
221
+
222
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
223
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatementsExt
224
+ end
@@ -0,0 +1,11 @@
1
+ # implementation idea taken from JDBC adapter
2
+ if defined?(Rake.application) && Rake.application && ActiveRecord::Base.configurations[RAILS_ENV]['adapter'] == 'oracle_enhanced'
3
+ oracle_enhanced_rakefile = File.dirname(__FILE__) + "/oracle_enhanced.rake"
4
+ if Rake.application.lookup("environment")
5
+ # rails tasks already defined; load the override tasks now
6
+ load oracle_enhanced_rakefile
7
+ else
8
+ # rails tasks not loaded yet; load as an import
9
+ Rake.application.add_import(oracle_enhanced_rakefile)
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::VERSION = '1.2.4'
@@ -0,0 +1,35 @@
1
+ module ActiveRecord #:nodoc:
2
+ module ConnectionAdapters #:nodoc:
3
+ module OracleEnhancedVirtualColumn #:nodoc:
4
+
5
+ module InstanceMethods #:nodoc:
6
+ private
7
+ def attributes_with_quotes_with_virtual_columns(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
8
+ quoted = attributes_with_quotes_without_virtual_columns(include_primary_key, include_readonly_attributes, attribute_names)
9
+ self.class.columns.select(& :virtual?).each { |c| quoted.delete(c.name) }
10
+ quoted
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+
17
+
18
+ TableDefinition.class_eval do
19
+ def virtual(* args)
20
+ options = args.extract_options!
21
+ column_names = args
22
+ column_names.each { |name| column(name, :virtual, options) }
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ ActiveRecord::Base.class_eval do
29
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedVirtualColumn::InstanceMethods
30
+ alias_method_chain :attributes_with_quotes, :virtual_columns
31
+ end
32
+
33
+
34
+
35
+
@@ -0,0 +1,610 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "OracleEnhancedAdapter establish connection" do
4
+
5
+ it "should connect to database" do
6
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
7
+ ActiveRecord::Base.connection.should_not be_nil
8
+ ActiveRecord::Base.connection.class.should == ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
9
+ end
10
+
11
+ it "should connect to database as SYSDBA" do
12
+ ActiveRecord::Base.establish_connection(SYS_CONNECTION_PARAMS)
13
+ ActiveRecord::Base.connection.should_not be_nil
14
+ ActiveRecord::Base.connection.class.should == ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
15
+ end
16
+
17
+ it "should be active after connection to database" do
18
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
19
+ ActiveRecord::Base.connection.should be_active
20
+ end
21
+
22
+ it "should not be active after disconnection to database" do
23
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
24
+ ActiveRecord::Base.connection.disconnect!
25
+ ActiveRecord::Base.connection.should_not be_active
26
+ end
27
+
28
+ it "should be active after reconnection to database" do
29
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
30
+ ActiveRecord::Base.connection.reconnect!
31
+ ActiveRecord::Base.connection.should be_active
32
+ end
33
+
34
+ end
35
+
36
+ describe "OracleEnhancedAdapter" do
37
+ include LoggerSpecHelper
38
+
39
+ before(:all) do
40
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
41
+ @conn = ActiveRecord::Base.connection
42
+ end
43
+
44
+ describe "database session store" do
45
+ before(:all) do
46
+ @conn.execute <<-SQL
47
+ CREATE TABLE sessions (
48
+ id NUMBER(38,0) NOT NULL,
49
+ session_id VARCHAR2(255) DEFAULT NULL,
50
+ data CLOB DEFAULT NULL,
51
+ created_at DATE DEFAULT NULL,
52
+ updated_at DATE DEFAULT NULL,
53
+ PRIMARY KEY (ID)
54
+ )
55
+ SQL
56
+ @conn.execute <<-SQL
57
+ CREATE SEQUENCE sessions_seq MINVALUE 1 MAXVALUE 999999999999999999999999999
58
+ INCREMENT BY 1 START WITH 10040 CACHE 20 NOORDER NOCYCLE
59
+ SQL
60
+ if ENV['RAILS_GEM_VERSION'] >= '2.3'
61
+ @session_class = ActiveRecord::SessionStore::Session
62
+ else
63
+ @session_class = CGI::Session::ActiveRecordStore::Session
64
+ end
65
+ end
66
+
67
+ after(:all) do
68
+ @conn.execute "DROP TABLE sessions"
69
+ @conn.execute "DROP SEQUENCE sessions_seq"
70
+ end
71
+
72
+ it "should create sessions table" do
73
+ ActiveRecord::Base.connection.tables.grep("sessions").should_not be_empty
74
+ end
75
+
76
+ it "should save session data" do
77
+ @session = @session_class.new :session_id => "111111", :data => "something" #, :updated_at => Time.now
78
+ @session.save!
79
+ @session = @session_class.find_by_session_id("111111")
80
+ @session.data.should == "something"
81
+ end
82
+
83
+ it "should change session data when partial updates enabled" do
84
+ return pending("Not in this ActiveRecord version") unless @session_class.respond_to?(:partial_updates=)
85
+ @session_class.partial_updates = true
86
+ @session = @session_class.new :session_id => "222222", :data => "something" #, :updated_at => Time.now
87
+ @session.save!
88
+ @session = @session_class.find_by_session_id("222222")
89
+ @session.data = "other thing"
90
+ @session.save!
91
+ # second save should call again blob writing callback
92
+ @session.save!
93
+ @session = @session_class.find_by_session_id("222222")
94
+ @session.data.should == "other thing"
95
+ end
96
+
97
+ it "should have one enhanced_write_lobs callback" do
98
+ return pending("Not in this ActiveRecord version") unless @session_class.respond_to?(:after_save_callback_chain)
99
+ @session_class.after_save_callback_chain.select{|cb| cb.method == :enhanced_write_lobs}.should have(1).record
100
+ end
101
+
102
+ it "should not set sessions table session_id column type as integer if emulate_integers_by_column_name is true" do
103
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
104
+ columns = @conn.columns('sessions')
105
+ column = columns.detect{|c| c.name == "session_id"}
106
+ column.type.should == :string
107
+ end
108
+
109
+ end
110
+
111
+ describe "ignore specified table columns" do
112
+ before(:all) do
113
+ @conn.execute <<-SQL
114
+ CREATE TABLE test_employees (
115
+ id NUMBER,
116
+ first_name VARCHAR2(20),
117
+ last_name VARCHAR2(25),
118
+ email VARCHAR2(25),
119
+ phone_number VARCHAR2(20),
120
+ hire_date DATE,
121
+ job_id NUMBER,
122
+ salary NUMBER,
123
+ commission_pct NUMBER(2,2),
124
+ manager_id NUMBER(6),
125
+ department_id NUMBER(4,0),
126
+ created_at DATE
127
+ )
128
+ SQL
129
+ @conn.execute <<-SQL
130
+ CREATE SEQUENCE test_employees_seq MINVALUE 1
131
+ INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE
132
+ SQL
133
+ end
134
+
135
+ after(:all) do
136
+ @conn.execute "DROP TABLE test_employees"
137
+ @conn.execute "DROP SEQUENCE test_employees_seq"
138
+ end
139
+
140
+ after(:each) do
141
+ Object.send(:remove_const, "TestEmployee")
142
+ ActiveRecord::Base.connection.clear_ignored_table_columns
143
+ end
144
+
145
+ it "should ignore specified table columns" do
146
+ class ::TestEmployee < ActiveRecord::Base
147
+ ignore_table_columns :phone_number, :hire_date
148
+ end
149
+ TestEmployee.connection.columns('test_employees').select{|c| ['phone_number','hire_date'].include?(c.name) }.should be_empty
150
+ end
151
+
152
+ it "should ignore specified table columns specified in several lines" do
153
+ class ::TestEmployee < ActiveRecord::Base
154
+ ignore_table_columns :phone_number
155
+ ignore_table_columns :hire_date
156
+ end
157
+ TestEmployee.connection.columns('test_employees').select{|c| ['phone_number','hire_date'].include?(c.name) }.should be_empty
158
+ end
159
+
160
+ it "should not ignore unspecified table columns" do
161
+ class ::TestEmployee < ActiveRecord::Base
162
+ ignore_table_columns :phone_number, :hire_date
163
+ end
164
+ TestEmployee.connection.columns('test_employees').select{|c| c.name == 'email' }.should_not be_empty
165
+ end
166
+
167
+ it "should ignore specified table columns in other connection" do
168
+ class ::TestEmployee < ActiveRecord::Base
169
+ ignore_table_columns :phone_number, :hire_date
170
+ end
171
+ # establish other connection
172
+ other_conn = ActiveRecord::Base.oracle_enhanced_connection(CONNECTION_PARAMS)
173
+ other_conn.columns('test_employees').select{|c| ['phone_number','hire_date'].include?(c.name) }.should be_empty
174
+ end
175
+
176
+ end
177
+
178
+ describe "cache table columns" do
179
+ before(:all) do
180
+ @conn.execute "DROP TABLE test_employees" rescue nil
181
+ @conn.execute <<-SQL
182
+ CREATE TABLE test_employees (
183
+ id NUMBER,
184
+ first_name VARCHAR2(20),
185
+ last_name VARCHAR2(25),
186
+ hire_date DATE
187
+ )
188
+ SQL
189
+ @column_names = ['id', 'first_name', 'last_name', 'hire_date']
190
+ class ::TestEmployee < ActiveRecord::Base
191
+ end
192
+ end
193
+
194
+ after(:all) do
195
+ Object.send(:remove_const, "TestEmployee")
196
+ @conn.execute "DROP TABLE test_employees"
197
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = nil
198
+ end
199
+
200
+ before(:each) do
201
+ @buffer = StringIO.new
202
+ log_to @buffer
203
+ @conn = ActiveRecord::Base.connection
204
+ @conn.clear_columns_cache
205
+ end
206
+
207
+ describe "without column caching" do
208
+
209
+ before(:each) do
210
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = false
211
+ end
212
+
213
+ it "should get columns from database at first time" do
214
+ TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
215
+ @buffer.string.should =~ /select .* from all_tab_cols/im
216
+ end
217
+
218
+ it "should get columns from database at second time" do
219
+ TestEmployee.connection.columns('test_employees')
220
+ @buffer.truncate(0)
221
+ TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
222
+ @buffer.string.should =~ /select .* from all_tab_cols/im
223
+ end
224
+
225
+ end
226
+
227
+ describe "with column caching" do
228
+
229
+ before(:each) do
230
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
231
+ end
232
+
233
+ it "should get columns from database at first time" do
234
+ TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
235
+ @buffer.string.should =~ /select .* from all_tab_cols/im
236
+ end
237
+
238
+ it "should get columns from cache at second time" do
239
+ TestEmployee.connection.columns('test_employees')
240
+ @buffer.truncate(0)
241
+ TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
242
+ @buffer.string.should be_blank
243
+ end
244
+
245
+ end
246
+
247
+ end
248
+
249
+ describe "without composite_primary_keys" do
250
+
251
+ before(:all) do
252
+ ActiveRecord::Schema.define do
253
+ suppress_messages do
254
+ create_table :employees do |t|
255
+ t.integer :employee_id
256
+ end
257
+ end
258
+ end
259
+ Object.send(:remove_const, 'CompositePrimaryKeys') if defined?(CompositePrimaryKeys)
260
+ class ::Employee < ActiveRecord::Base
261
+ set_primary_key :employee_id
262
+ end
263
+ end
264
+ after(:all) do
265
+ ActiveRecord::Schema.define do
266
+ suppress_messages do
267
+ drop_table :employees
268
+ end
269
+ end
270
+ end
271
+
272
+ it "should tell ActiveRecord that count distinct is supported" do
273
+ ActiveRecord::Base.connection.supports_count_distinct?.should be_true
274
+ end
275
+
276
+ it "should execute correct SQL COUNT DISTINCT statement" do
277
+ lambda { Employee.count(:employee_id, :distinct => true) }.should_not raise_error
278
+ end
279
+
280
+ end
281
+
282
+
283
+ describe "column quoting" do
284
+
285
+ def create_test_reserved_words_table
286
+ ActiveRecord::Schema.define do
287
+ suppress_messages do
288
+ create_table :test_reserved_words do |t|
289
+ t.string :varchar2
290
+ t.integer :integer
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ after(:each) do
297
+ ActiveRecord::Schema.define do
298
+ suppress_messages do
299
+ drop_table :test_reserved_words
300
+ end
301
+ end
302
+ Object.send(:remove_const, "TestReservedWord")
303
+ ActiveRecord::Base.table_name_prefix = nil
304
+ end
305
+
306
+ it "should allow creation of a table with oracle reserved words as column names" do
307
+ create_test_reserved_words_table
308
+ class ::TestReservedWord < ActiveRecord::Base; end
309
+
310
+ [:varchar2, :integer].each do |attr|
311
+ TestReservedWord.columns_hash[attr.to_s].name.should == attr.to_s
312
+ end
313
+ end
314
+
315
+ end
316
+
317
+ describe "valid table names" do
318
+ before(:all) do
319
+ @adapter = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
320
+ end
321
+
322
+ it "should be valid with letters and digits" do
323
+ @adapter.valid_table_name?("abc_123").should be_true
324
+ end
325
+
326
+ it "should be valid with schema name" do
327
+ @adapter.valid_table_name?("abc_123.def_456").should be_true
328
+ end
329
+
330
+ it "should be valid with $ in name" do
331
+ @adapter.valid_table_name?("sys.v$session").should be_true
332
+ end
333
+
334
+ it "should not be valid with two dots in name" do
335
+ @adapter.valid_table_name?("abc_123.def_456.ghi_789").should be_false
336
+ end
337
+
338
+ it "should not be valid with invalid characters" do
339
+ @adapter.valid_table_name?("warehouse-things").should be_false
340
+ end
341
+
342
+ end
343
+
344
+ describe "table quoting" do
345
+
346
+ def create_warehouse_things_table
347
+ ActiveRecord::Schema.define do
348
+ suppress_messages do
349
+ create_table "warehouse-things" do |t|
350
+ t.string :name
351
+ t.integer :foo
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ def create_camel_case_table
358
+ ActiveRecord::Schema.define do
359
+ suppress_messages do
360
+ create_table "CamelCase" do |t|
361
+ t.string :name
362
+ t.integer :foo
363
+ end
364
+ end
365
+ end
366
+ end
367
+
368
+ after(:each) do
369
+ ActiveRecord::Schema.define do
370
+ suppress_messages do
371
+ drop_table "warehouse-things" rescue nil
372
+ drop_table "CamelCase" rescue nil
373
+ end
374
+ end
375
+ Object.send(:remove_const, "WarehouseThing") rescue nil
376
+ Object.send(:remove_const, "CamelCase") rescue nil
377
+ end
378
+
379
+ it "should allow creation of a table with non alphanumeric characters" do
380
+ create_warehouse_things_table
381
+ class ::WarehouseThing < ActiveRecord::Base
382
+ set_table_name "warehouse-things"
383
+ end
384
+
385
+ wh = WarehouseThing.create!(:name => "Foo", :foo => 2)
386
+ wh.id.should_not be_nil
387
+
388
+ @conn.tables.should include("warehouse-things")
389
+ end
390
+
391
+ it "should allow creation of a table with CamelCase name" do
392
+ create_camel_case_table
393
+ class ::CamelCase < ActiveRecord::Base
394
+ set_table_name "CamelCase"
395
+ end
396
+
397
+ cc = CamelCase.create!(:name => "Foo", :foo => 2)
398
+ cc.id.should_not be_nil
399
+
400
+ @conn.tables.should include("CamelCase")
401
+ end
402
+
403
+ end
404
+
405
+ describe "access table over database link" do
406
+ before(:all) do
407
+ @db_link = "db_link"
408
+ @sys_conn = ActiveRecord::Base.oracle_enhanced_connection(SYSTEM_CONNECTION_PARAMS)
409
+ @sys_conn.drop_table :test_posts rescue nil
410
+ @sys_conn.create_table :test_posts do |t|
411
+ t.string :title
412
+ # cannot update LOBs over database link
413
+ t.string :body
414
+ t.timestamps
415
+ end
416
+ @db_link_username = SYSTEM_CONNECTION_PARAMS[:username]
417
+ @db_link_password = SYSTEM_CONNECTION_PARAMS[:password]
418
+ @db_link_database = SYSTEM_CONNECTION_PARAMS[:database]
419
+ @conn.execute "DROP DATABASE LINK #{@db_link}" rescue nil
420
+ @conn.execute "CREATE DATABASE LINK #{@db_link} CONNECT TO #{@db_link_username} IDENTIFIED BY #{@db_link_password} USING '#{@db_link_database}'"
421
+ @conn.execute "CREATE OR REPLACE SYNONYM test_posts FOR test_posts@#{@db_link}"
422
+ @conn.execute "CREATE OR REPLACE SYNONYM test_posts_seq FOR test_posts_seq@#{@db_link}"
423
+ class ::TestPost < ActiveRecord::Base
424
+ end
425
+ TestPost.set_table_name "test_posts"
426
+ end
427
+
428
+ after(:all) do
429
+ @conn.execute "DROP SYNONYM test_posts"
430
+ @conn.execute "DROP SYNONYM test_posts_seq"
431
+ @conn.execute "DROP DATABASE LINK #{@db_link}" rescue nil
432
+ @sys_conn.drop_table :test_posts rescue nil
433
+ Object.send(:remove_const, "TestPost") rescue nil
434
+ end
435
+
436
+ it "should verify database link" do
437
+ @conn.select_value("select * from dual@#{@db_link}") == 'X'
438
+ end
439
+
440
+ it "should get column names" do
441
+ TestPost.column_names.should == ["id", "title", "body", "created_at", "updated_at"]
442
+ end
443
+
444
+ it "should create record" do
445
+ p = TestPost.create(:title => "Title", :body => "Body")
446
+ p.id.should_not be_nil
447
+ TestPost.find(p.id).should_not be_nil
448
+ end
449
+
450
+ end
451
+
452
+ describe "session information" do
453
+ it "should get current database name" do
454
+ @conn.current_database.should == CONNECTION_PARAMS[:database]
455
+ end
456
+
457
+ it "should get current database session user" do
458
+ @conn.current_user.should == CONNECTION_PARAMS[:username].upcase
459
+ end
460
+ end
461
+
462
+ describe "temporary tables" do
463
+
464
+ after(:each) do
465
+ @conn.drop_table :foos rescue nil
466
+ end
467
+ it "should create ok" do
468
+ @conn.create_table :foos, :temporary => true, :id => false do |t|
469
+ t.integer :id
470
+ end
471
+ end
472
+ it "should show up as temporary" do
473
+ @conn.create_table :foos, :temporary => true, :id => false do |t|
474
+ t.integer :id
475
+ end
476
+ @conn.temporary?("foos").should be_true
477
+ end
478
+ end
479
+
480
+ describe "creating a table with a tablespace" do
481
+ after(:each) do
482
+ @conn.drop_table :tablespace_tests rescue nil
483
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete 'TABLE'
484
+ end
485
+ it "should use correct tablespace" do
486
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces['TABLE'] = DATABASE_NON_DEFAULT_TABLESPACE
487
+ @conn.create_table :tablespace_tests do |t|
488
+ t.integer :id
489
+ end
490
+ @conn.tablespace(:tablespace_tests).should == DATABASE_NON_DEFAULT_TABLESPACE
491
+ end
492
+ end
493
+
494
+ describe "going over the oracle in clause limit" do
495
+ before(:each) do
496
+ @conn.drop_table :hands rescue nil
497
+ @conn.drop_table :fingers rescue nil
498
+ ActiveRecord::Schema.define do
499
+ suppress_messages do
500
+ create_table :hands
501
+ create_table :fingers do |t|
502
+ t.integer :integer
503
+ t.references :hand
504
+ end
505
+ end
506
+ end
507
+ class ::Hand < ActiveRecord::Base
508
+ has_many :fingers
509
+ end
510
+ class ::Finger < ActiveRecord::Base
511
+ belongs_to :hand
512
+ end
513
+ hand = Hand.create!
514
+ 1001.times do
515
+ hand.fingers.create!
516
+ end
517
+ end
518
+ after(:each) do
519
+ @conn.drop_table :hands rescue nil
520
+ @conn.drop_table :fingers rescue nil
521
+ end
522
+ it "should run sql without errors" do
523
+ hand = Hand.first
524
+ hand.fingers.find(:all, :conditions => {:id => Finger.all})
525
+ end
526
+ end
527
+
528
+ describe "using a counter cache" do
529
+ before(:each) do
530
+ @conn.drop_table :baskets rescue nil
531
+ @conn.drop_table :widgets rescue nil
532
+ ActiveRecord::Schema.define do
533
+ suppress_messages do
534
+ create_table :baskets do |t|
535
+ t.integer :widgets_count, :default => 0
536
+ end
537
+ create_table :widgets do |t|
538
+ t.integer :integer
539
+ t.references :basket
540
+ end
541
+ end
542
+ end
543
+ class ::Basket < ActiveRecord::Base
544
+ has_many :widgets
545
+ end
546
+ class ::Widget < ActiveRecord::Base
547
+ belongs_to :basket, :counter_cache => true
548
+ end
549
+ basket = Basket.create!
550
+ 1001.times do
551
+ basket.widgets.create!
552
+ end
553
+ end
554
+ after(:each) do
555
+ @conn.drop_table :baskets rescue nil
556
+ @conn.drop_table :widgets rescue nil
557
+ end
558
+ it "should run sql without errors" do
559
+ basket = Basket.first
560
+ widgets = basket.widgets.find(:all, :conditions => {:id => Widget.all})
561
+ widgets.each {|w| w.destroy }
562
+ end
563
+ end
564
+
565
+
566
+ describe "tables method" do
567
+ before(:each) do
568
+ @conn.drop_table :ones rescue nil
569
+ ActiveRecord::Schema.define do
570
+ suppress_messages do
571
+ create_table :ones
572
+ end
573
+ end
574
+ @conn.execute <<-SQL
575
+ begin
576
+ dbms_aqadm.create_queue_table
577
+ ( queue_table => 'MY_QUEUE_TABLE',
578
+ queue_payload_type => 'SYS.AQ$_JMS_MAP_MESSAGE'
579
+ );
580
+ end;
581
+ SQL
582
+ @conn.execute <<-SQL
583
+ begin
584
+ dbms_aqadm.create_queue
585
+ ( queue_name => 'MY_QUEUE',
586
+ queue_table => 'MY_QUEUE_TABLE',
587
+ queue_type => dbms_aqadm.normal_queue
588
+ );
589
+ end;
590
+ SQL
591
+ end
592
+ after(:each) do
593
+ begin
594
+ @conn.drop_table :ones
595
+ @conn.execute <<-SQL
596
+ BEGIN
597
+ dbms_aqadm.stop_queue(queue_name =>'MY_QUEUE');
598
+ DBMS_AQADM.DROP_QUEUE(queue_name => 'MY_QUEUE');
599
+ DBMS_AQADM.DROP_QUEUE_TABLE(queue_table => 'MY_QUEUE_TABLE', force => true);
600
+ END;
601
+ SQL
602
+ rescue
603
+ nil
604
+ end
605
+ end
606
+ it "should not include queue tables" do
607
+ @conn.tables.grep(/my_queue_table/).should be_empty
608
+ end
609
+ end
610
+ end