oracle_enhanced 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/.gitignore +10 -0
  2. data/History.txt +182 -0
  3. data/License.txt +20 -0
  4. data/Manifest.txt +32 -0
  5. data/README.rdoc +77 -0
  6. data/Rakefile +49 -0
  7. data/VERSION +1 -0
  8. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1661 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
  27. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
  29. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
  30. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
  31. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +370 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +203 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +114 -0
  38. metadata +140 -0
@@ -0,0 +1,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.3'
@@ -0,0 +1,477 @@
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_columns/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_columns/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_columns/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
+ @conn.execute "DROP TABLE test_employees" rescue nil
253
+ @conn.execute <<-SQL
254
+ CREATE TABLE test_employees (
255
+ employee_id NUMBER PRIMARY KEY,
256
+ name VARCHAR2(50)
257
+ )
258
+ SQL
259
+ Object.send(:remove_const, 'CompositePrimaryKeys') if defined?(CompositePrimaryKeys)
260
+ class ::TestEmployee < ActiveRecord::Base
261
+ set_primary_key :employee_id
262
+ end
263
+ end
264
+
265
+ after(:all) do
266
+ Object.send(:remove_const, "TestEmployee")
267
+ @conn.execute "DROP TABLE test_employees"
268
+ end
269
+
270
+ it "should tell ActiveRecord that count distinct is supported" do
271
+ ActiveRecord::Base.connection.supports_count_distinct?.should be_true
272
+ end
273
+
274
+ it "should execute correct SQL COUNT DISTINCT statement" do
275
+ lambda { TestEmployee.count(:employee_id, :distinct => true) }.should_not raise_error
276
+ end
277
+
278
+ end
279
+
280
+
281
+ describe "column quoting" do
282
+
283
+ def create_test_reserved_words_table
284
+ ActiveRecord::Schema.define do
285
+ suppress_messages do
286
+ create_table :test_reserved_words do |t|
287
+ t.string :varchar2
288
+ t.integer :integer
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ after(:each) do
295
+ ActiveRecord::Schema.define do
296
+ suppress_messages do
297
+ drop_table :test_reserved_words
298
+ end
299
+ end
300
+ Object.send(:remove_const, "TestReservedWord")
301
+ ActiveRecord::Base.table_name_prefix = nil
302
+ end
303
+
304
+ it "should allow creation of a table with oracle reserved words as column names" do
305
+ create_test_reserved_words_table
306
+ class ::TestReservedWord < ActiveRecord::Base; end
307
+
308
+ [:varchar2, :integer].each do |attr|
309
+ TestReservedWord.columns_hash[attr.to_s].name.should == attr.to_s
310
+ end
311
+ end
312
+
313
+ end
314
+
315
+ describe "valid table names" do
316
+ before(:all) do
317
+ @adapter = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
318
+ end
319
+
320
+ it "should be valid with letters and digits" do
321
+ @adapter.valid_table_name?("abc_123").should be_true
322
+ end
323
+
324
+ it "should be valid with schema name" do
325
+ @adapter.valid_table_name?("abc_123.def_456").should be_true
326
+ end
327
+
328
+ it "should be valid with $ in name" do
329
+ @adapter.valid_table_name?("sys.v$session").should be_true
330
+ end
331
+
332
+ it "should not be valid with two dots in name" do
333
+ @adapter.valid_table_name?("abc_123.def_456.ghi_789").should be_false
334
+ end
335
+
336
+ it "should not be valid with invalid characters" do
337
+ @adapter.valid_table_name?("warehouse-things").should be_false
338
+ end
339
+
340
+ end
341
+
342
+ describe "table quoting" do
343
+
344
+ def create_warehouse_things_table
345
+ ActiveRecord::Schema.define do
346
+ suppress_messages do
347
+ create_table "warehouse-things" do |t|
348
+ t.string :name
349
+ t.integer :foo
350
+ end
351
+ end
352
+ end
353
+ end
354
+
355
+ def create_camel_case_table
356
+ ActiveRecord::Schema.define do
357
+ suppress_messages do
358
+ create_table "CamelCase" do |t|
359
+ t.string :name
360
+ t.integer :foo
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ after(:each) do
367
+ ActiveRecord::Schema.define do
368
+ suppress_messages do
369
+ drop_table "warehouse-things" rescue nil
370
+ drop_table "CamelCase" rescue nil
371
+ end
372
+ end
373
+ Object.send(:remove_const, "WarehouseThing") rescue nil
374
+ Object.send(:remove_const, "CamelCase") rescue nil
375
+ end
376
+
377
+ it "should allow creation of a table with non alphanumeric characters" do
378
+ create_warehouse_things_table
379
+ class ::WarehouseThing < ActiveRecord::Base
380
+ set_table_name "warehouse-things"
381
+ end
382
+
383
+ wh = WarehouseThing.create!(:name => "Foo", :foo => 2)
384
+ wh.id.should_not be_nil
385
+
386
+ @conn.tables.should include("warehouse-things")
387
+ end
388
+
389
+ it "should allow creation of a table with CamelCase name" do
390
+ create_camel_case_table
391
+ class ::CamelCase < ActiveRecord::Base
392
+ set_table_name "CamelCase"
393
+ end
394
+
395
+ cc = CamelCase.create!(:name => "Foo", :foo => 2)
396
+ cc.id.should_not be_nil
397
+
398
+ @conn.tables.should include("CamelCase")
399
+ end
400
+
401
+ end
402
+
403
+ describe "access table over database link" do
404
+ before(:all) do
405
+ @db_link = "db_link"
406
+ @sys_conn = ActiveRecord::Base.oracle_enhanced_connection(SYSTEM_CONNECTION_PARAMS)
407
+ @sys_conn.drop_table :test_posts rescue nil
408
+ @sys_conn.create_table :test_posts do |t|
409
+ t.string :title
410
+ # cannot update LOBs over database link
411
+ t.string :body
412
+ t.timestamps
413
+ end
414
+ @db_link_username = SYSTEM_CONNECTION_PARAMS[:username]
415
+ @db_link_password = SYSTEM_CONNECTION_PARAMS[:password]
416
+ @db_link_database = SYSTEM_CONNECTION_PARAMS[:database]
417
+ @conn.execute "DROP DATABASE LINK #{@db_link}" rescue nil
418
+ @conn.execute "CREATE DATABASE LINK #{@db_link} CONNECT TO #{@db_link_username} IDENTIFIED BY #{@db_link_password} USING '#{@db_link_database}'"
419
+ @conn.execute "CREATE OR REPLACE SYNONYM test_posts FOR test_posts@#{@db_link}"
420
+ @conn.execute "CREATE OR REPLACE SYNONYM test_posts_seq FOR test_posts_seq@#{@db_link}"
421
+ class ::TestPost < ActiveRecord::Base
422
+ end
423
+ TestPost.set_table_name "test_posts"
424
+ end
425
+
426
+ after(:all) do
427
+ @conn.execute "DROP SYNONYM test_posts"
428
+ @conn.execute "DROP SYNONYM test_posts_seq"
429
+ @conn.execute "DROP DATABASE LINK #{@db_link}" rescue nil
430
+ @sys_conn.drop_table :test_posts rescue nil
431
+ Object.send(:remove_const, "TestPost") rescue nil
432
+ end
433
+
434
+ it "should verify database link" do
435
+ @conn.select_value("select * from dual@#{@db_link}") == 'X'
436
+ end
437
+
438
+ it "should get column names" do
439
+ TestPost.column_names.should == ["id", "title", "body", "created_at", "updated_at"]
440
+ end
441
+
442
+ it "should create record" do
443
+ p = TestPost.create(:title => "Title", :body => "Body")
444
+ p.id.should_not be_nil
445
+ TestPost.find(p.id).should_not be_nil
446
+ end
447
+
448
+ end
449
+
450
+ describe "session information" do
451
+ it "should get current database name" do
452
+ @conn.current_database.should == CONNECTION_PARAMS[:database]
453
+ end
454
+
455
+ it "should get current database session user" do
456
+ @conn.current_user.should == CONNECTION_PARAMS[:username].upcase
457
+ end
458
+ end
459
+
460
+ describe "temporary tables" do
461
+
462
+ after(:each) do
463
+ @conn.drop_table :foos rescue nil
464
+ end
465
+ it "should create ok" do
466
+ @conn.create_table :foos, :temporary => true, :id => false do |t|
467
+ t.integer :id
468
+ end
469
+ end
470
+ it "should show up as temporary" do
471
+ @conn.create_table :foos, :temporary => true, :id => false do |t|
472
+ t.integer :id
473
+ end
474
+ @conn.temporary_table?("foos").should be_true
475
+ end
476
+ end
477
+ end