c3-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 +182 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +32 -0
  4. data/README.rdoc +77 -0
  5. data/Rakefile +49 -0
  6. data/VERSION +1 -0
  7. data/activerecord-oracle_enhanced-adapter.gemspec +79 -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 +1664 -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 +218 -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 +137 -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