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.
- data/.gitignore +10 -0
- data/History.txt +182 -0
- data/License.txt +20 -0
- data/Manifest.txt +32 -0
- data/README.rdoc +77 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1661 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +370 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +203 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +114 -0
- 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
|