unclebilly-activerecord-oracle_enhanced-adapter 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/History.txt +165 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +32 -0
  4. data/README.rdoc +75 -0
  5. data/Rakefile +49 -0
  6. data/VERSION +1 -0
  7. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1723 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +369 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +396 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +164 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +177 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +214 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_virtual_column.rb +35 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +610 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +266 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
  27. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
  29. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
  30. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
  31. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +370 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +268 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +761 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +130 -0
  38. metadata +149 -0
@@ -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