activerecord-oracle_enhanced-adapter 1.2.3 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +17 -0
- data/License.txt +1 -1
- data/README.rdoc +3 -1
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +6 -3
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +333 -109
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +60 -36
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +13 -18
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +3 -4
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +3 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +86 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +31 -2
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +6 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +22 -75
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +61 -0
- data/spec/spec_helper.rb +3 -15
- metadata +4 -2
@@ -23,14 +23,7 @@ begin
|
|
23
23
|
|
24
24
|
rescue LoadError, NameError
|
25
25
|
# JDBC driver is unavailable.
|
26
|
-
|
27
|
-
"Please install ojdbc14.jar library."
|
28
|
-
if defined?(RAILS_DEFAULT_LOGGER)
|
29
|
-
RAILS_DEFAULT_LOGGER.error error_message
|
30
|
-
else
|
31
|
-
STDERR.puts error_message
|
32
|
-
end
|
33
|
-
raise LoadError
|
26
|
+
raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. Please install ojdbc14.jar library."
|
34
27
|
end
|
35
28
|
|
36
29
|
|
@@ -53,46 +46,77 @@ module ActiveRecord
|
|
53
46
|
new_connection(@config)
|
54
47
|
end
|
55
48
|
|
49
|
+
# modified method to support JNDI connections
|
56
50
|
def new_connection(config)
|
57
|
-
username
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
51
|
+
username = nil
|
52
|
+
|
53
|
+
if config[:jndi]
|
54
|
+
jndi = config[:jndi].to_s
|
55
|
+
ctx = javax.naming.InitialContext.new
|
56
|
+
ds = nil
|
57
|
+
|
58
|
+
# tomcat needs first lookup method, oc4j (and maybe other application servers) need second method
|
59
|
+
begin
|
60
|
+
env = ctx.lookup('java:/comp/env')
|
61
|
+
ds = env.lookup(jndi)
|
62
|
+
rescue
|
63
|
+
ds = ctx.lookup(jndi)
|
64
|
+
end
|
65
|
+
|
66
|
+
# check if datasource supports pooled connections, otherwise use default
|
67
|
+
if ds.respond_to?(:pooled_connection)
|
68
|
+
@raw_connection = ds.pooled_connection
|
69
|
+
else
|
70
|
+
@raw_connection = ds.connection
|
71
|
+
end
|
72
|
+
|
73
|
+
config[:driver] ||= @raw_connection.meta_data.connection.java_class.name
|
74
|
+
username = @raw_connection.meta_data.user_name
|
64
75
|
else
|
65
|
-
|
76
|
+
username = config[:username].to_s
|
77
|
+
password, database = config[:password].to_s, config[:database].to_s
|
78
|
+
privilege = config[:privilege] && config[:privilege].to_s
|
79
|
+
host, port = config[:host], config[:port]
|
80
|
+
|
81
|
+
# connection using TNS alias
|
82
|
+
if database && !host && !config[:url] && ENV['TNS_ADMIN']
|
83
|
+
url = "jdbc:oracle:thin:@#{database || 'XE'}"
|
84
|
+
else
|
85
|
+
url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}:#{database || 'XE'}"
|
86
|
+
end
|
87
|
+
|
88
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
89
|
+
# get session time_zone from configuration or from TZ environment variable
|
90
|
+
time_zone = config[:time_zone] || ENV['TZ'] || java.util.TimeZone.default.getID
|
91
|
+
|
92
|
+
properties = java.util.Properties.new
|
93
|
+
properties.put("user", username)
|
94
|
+
properties.put("password", password)
|
95
|
+
properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
|
96
|
+
properties.put("internal_logon", privilege) if privilege
|
97
|
+
|
98
|
+
@raw_connection = java.sql.DriverManager.getConnection(url, properties)
|
99
|
+
|
100
|
+
# Set session time zone to current time zone
|
101
|
+
@raw_connection.setSessionTimeZone(time_zone)
|
102
|
+
|
103
|
+
# Set default number of rows to prefetch
|
104
|
+
# @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
|
66
105
|
end
|
67
106
|
|
68
|
-
prefetch_rows = config[:prefetch_rows] || 100
|
69
|
-
cursor_sharing = config[:cursor_sharing] || 'force'
|
70
107
|
# by default VARCHAR2 column size will be interpreted as max number of characters (and not bytes)
|
71
108
|
nls_length_semantics = config[:nls_length_semantics] || 'CHAR'
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
properties = java.util.Properties.new
|
76
|
-
properties.put("user", username)
|
77
|
-
properties.put("password", password)
|
78
|
-
properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
|
79
|
-
properties.put("internal_logon", privilege) if privilege
|
80
|
-
|
81
|
-
@raw_connection = java.sql.DriverManager.getConnection(url, properties)
|
109
|
+
cursor_sharing = config[:cursor_sharing] || 'force'
|
110
|
+
|
111
|
+
# from here it remaings common for both connections types
|
82
112
|
exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
83
113
|
exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS:FF6'}
|
84
114
|
exec "alter session set cursor_sharing = #{cursor_sharing}"
|
85
115
|
exec "alter session set nls_length_semantics = '#{nls_length_semantics}'"
|
86
116
|
self.autocommit = true
|
87
|
-
|
88
|
-
# Set session time zone to current time zone
|
89
|
-
@raw_connection.setSessionTimeZone(time_zone)
|
90
|
-
|
91
|
-
# Set default number of rows to prefetch
|
92
|
-
# @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
|
93
|
-
|
117
|
+
|
94
118
|
# default schema owner
|
95
|
-
@owner = username.upcase
|
119
|
+
@owner = username.upcase unless username.nil?
|
96
120
|
|
97
121
|
@raw_connection
|
98
122
|
end
|
@@ -1,24 +1,17 @@
|
|
1
1
|
require 'delegate'
|
2
2
|
|
3
3
|
begin
|
4
|
-
require
|
5
|
-
|
6
|
-
# added mapping for TIMESTAMP / WITH TIME ZONE / LOCAL TIME ZONE types
|
7
|
-
# latest version of Ruby-OCI8 supports fractional seconds for timestamps
|
8
|
-
# therefore default binding to Time class should be used
|
9
|
-
# OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::OraDate
|
10
|
-
# OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::OraDate
|
11
|
-
# OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_LTZ] = OCI8::BindType::OraDate
|
4
|
+
require "oci8"
|
12
5
|
rescue LoadError
|
13
6
|
# OCI8 driver is unavailable.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
raise LoadError
|
7
|
+
raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. Please install ruby-oci8 gem."
|
8
|
+
end
|
9
|
+
|
10
|
+
# check ruby-oci8 version
|
11
|
+
required_oci8_version = [2, 0, 3]
|
12
|
+
oci8_version_ints = OCI8::VERSION.scan(/\d+/).map{|s| s.to_i}
|
13
|
+
if (oci8_version_ints <=> required_oci8_version) < 0
|
14
|
+
raise LoadError, "ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version #{required_oci8_version.join('.')} or later."
|
22
15
|
end
|
23
16
|
|
24
17
|
module ActiveRecord
|
@@ -156,10 +149,11 @@ module ActiveRecord
|
|
156
149
|
value
|
157
150
|
when String
|
158
151
|
value
|
159
|
-
when Float
|
152
|
+
when Float, BigDecimal
|
153
|
+
# return Fixnum or Bignum if value is integer (to avoid issues with _before_type_cast values for id attributes)
|
160
154
|
value == (v_to_i = value.to_i) ? v_to_i : value
|
161
|
-
# ruby-oci8 2.0 returns OraNumber if Oracle type is NUMBER
|
162
155
|
when OraNumber
|
156
|
+
# change OraNumber value (returned in early versions of ruby-oci8 2.0.x) to BigDecimal
|
163
157
|
value == (v_to_i = value.to_i) ? v_to_i : BigDecimal.new(value.to_s)
|
164
158
|
when OCI8::LOB
|
165
159
|
if get_lob_value
|
@@ -209,6 +203,7 @@ module ActiveRecord
|
|
209
203
|
::DateTime.civil(year, month, day, hour, min, sec, offset)
|
210
204
|
end
|
211
205
|
end
|
206
|
+
|
212
207
|
end
|
213
208
|
|
214
209
|
# The OracleEnhancedOCIFactory factors out the code necessary to connect and
|
@@ -4,7 +4,6 @@ ActiveRecord::Base.class_eval do
|
|
4
4
|
class_inheritable_accessor :custom_create_method, :custom_update_method, :custom_delete_method
|
5
5
|
end
|
6
6
|
|
7
|
-
require 'ruby_plsql'
|
8
7
|
require 'active_support'
|
9
8
|
|
10
9
|
module ActiveRecord #:nodoc:
|
@@ -53,7 +52,7 @@ module ActiveRecord #:nodoc:
|
|
53
52
|
self.custom_delete_method = block
|
54
53
|
end
|
55
54
|
|
56
|
-
def create_method_name_before_custom_methods
|
55
|
+
def create_method_name_before_custom_methods #:nodoc:
|
57
56
|
if private_method_defined?(:create_without_timestamps) && defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::STRING.to_f >= 2.3
|
58
57
|
:create_without_timestamps
|
59
58
|
elsif private_method_defined?(:create_without_callbacks)
|
@@ -63,7 +62,7 @@ module ActiveRecord #:nodoc:
|
|
63
62
|
end
|
64
63
|
end
|
65
64
|
|
66
|
-
def update_method_name_before_custom_methods
|
65
|
+
def update_method_name_before_custom_methods #:nodoc:
|
67
66
|
if private_method_defined?(:update_without_dirty)
|
68
67
|
:update_without_dirty
|
69
68
|
elsif private_method_defined?(:update_without_timestamps) && defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::STRING.to_f >= 2.3
|
@@ -75,7 +74,7 @@ module ActiveRecord #:nodoc:
|
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
78
|
-
def destroy_method_name_before_custom_methods
|
77
|
+
def destroy_method_name_before_custom_methods #:nodoc:
|
79
78
|
if public_method_defined?(:destroy_without_callbacks)
|
80
79
|
:destroy_without_callbacks
|
81
80
|
else
|
@@ -6,6 +6,9 @@ module ActiveRecord
|
|
6
6
|
class OracleEnhancedSynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc:
|
7
7
|
end
|
8
8
|
|
9
|
+
class OracleEnhancedIndexDefinition < Struct.new(:table, :name, :unique, :tablespace, :columns) #:nodoc:
|
10
|
+
end
|
11
|
+
|
9
12
|
module OracleEnhancedSchemaDefinitions #:nodoc:
|
10
13
|
def self.included(base)
|
11
14
|
base::TableDefinition.class_eval do
|
@@ -26,7 +26,7 @@ module ActiveRecord #:nodoc:
|
|
26
26
|
end
|
27
27
|
# change table name inspect method
|
28
28
|
tbl.extend TableInspect
|
29
|
-
|
29
|
+
oracle_enhanced_table(tbl, stream)
|
30
30
|
# add primary key trigger if table has it
|
31
31
|
primary_key_trigger(tbl, stream)
|
32
32
|
end
|
@@ -94,6 +94,7 @@ module ActiveRecord #:nodoc:
|
|
94
94
|
statment_parts << index.columns.inspect
|
95
95
|
statment_parts << (':name => ' + index.name.inspect)
|
96
96
|
statment_parts << ':unique => true' if index.unique
|
97
|
+
statment_parts << ':tablespace => ' + index.tablespace.inspect if index.tablespace
|
97
98
|
|
98
99
|
' ' + statment_parts.join(', ')
|
99
100
|
end
|
@@ -103,6 +104,90 @@ module ActiveRecord #:nodoc:
|
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
107
|
+
def oracle_enhanced_table(table, stream)
|
108
|
+
columns = @connection.columns(table)
|
109
|
+
begin
|
110
|
+
tbl = StringIO.new
|
111
|
+
|
112
|
+
# first dump primary key column
|
113
|
+
if @connection.respond_to?(:pk_and_sequence_for)
|
114
|
+
pk, pk_seq = @connection.pk_and_sequence_for(table)
|
115
|
+
elsif @connection.respond_to?(:primary_key)
|
116
|
+
pk = @connection.primary_key(table)
|
117
|
+
end
|
118
|
+
|
119
|
+
tbl.print " create_table #{table.inspect}"
|
120
|
+
|
121
|
+
# addition to make temporary option work
|
122
|
+
tbl.print ", :temporary => true" if @connection.temporary_table?(table)
|
123
|
+
|
124
|
+
if columns.detect { |c| c.name == pk }
|
125
|
+
if pk != 'id'
|
126
|
+
tbl.print %Q(, :primary_key => "#{pk}")
|
127
|
+
end
|
128
|
+
else
|
129
|
+
tbl.print ", :id => false"
|
130
|
+
end
|
131
|
+
tbl.print ", :force => true"
|
132
|
+
tbl.puts " do |t|"
|
133
|
+
|
134
|
+
# then dump all non-primary key columns
|
135
|
+
column_specs = columns.map do |column|
|
136
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
|
137
|
+
next if column.name == pk
|
138
|
+
spec = {}
|
139
|
+
spec[:name] = column.name.inspect
|
140
|
+
spec[:type] = column.type.to_s
|
141
|
+
spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
|
142
|
+
spec[:precision] = column.precision.inspect if !column.precision.nil?
|
143
|
+
spec[:scale] = column.scale.inspect if !column.scale.nil?
|
144
|
+
spec[:null] = 'false' if !column.null
|
145
|
+
spec[:default] = default_string(column.default) if column.has_default?
|
146
|
+
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
|
147
|
+
spec
|
148
|
+
end.compact
|
149
|
+
|
150
|
+
# find all migration keys used in this table
|
151
|
+
keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map(&:keys).flatten
|
152
|
+
|
153
|
+
# figure out the lengths for each column based on above keys
|
154
|
+
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
|
155
|
+
|
156
|
+
# the string we're going to sprintf our values against, with standardized column widths
|
157
|
+
format_string = lengths.map{ |len| "%-#{len}s" }
|
158
|
+
|
159
|
+
# find the max length for the 'type' column, which is special
|
160
|
+
type_length = column_specs.map{ |column| column[:type].length }.max
|
161
|
+
|
162
|
+
# add column type definition to our format string
|
163
|
+
format_string.unshift " t.%-#{type_length}s "
|
164
|
+
|
165
|
+
format_string *= ''
|
166
|
+
|
167
|
+
column_specs.each do |colspec|
|
168
|
+
values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
|
169
|
+
values.unshift colspec[:type]
|
170
|
+
tbl.print((format_string % values).gsub(/,\s*$/, ''))
|
171
|
+
tbl.puts
|
172
|
+
end
|
173
|
+
|
174
|
+
tbl.puts " end"
|
175
|
+
tbl.puts
|
176
|
+
|
177
|
+
indexes(table, tbl)
|
178
|
+
|
179
|
+
tbl.rewind
|
180
|
+
stream.print tbl.read
|
181
|
+
rescue => e
|
182
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
183
|
+
stream.puts "# #{e.message}"
|
184
|
+
stream.puts
|
185
|
+
end
|
186
|
+
|
187
|
+
stream
|
188
|
+
end
|
189
|
+
|
190
|
+
|
106
191
|
# remove table name prefix and suffix when doing #inspect (which is used in tables method)
|
107
192
|
module TableInspect #:nodoc:
|
108
193
|
def inspect
|
@@ -249,18 +249,30 @@ describe "OracleEnhancedAdapter" do
|
|
249
249
|
describe "without composite_primary_keys" do
|
250
250
|
|
251
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
|
252
259
|
Object.send(:remove_const, 'CompositePrimaryKeys') if defined?(CompositePrimaryKeys)
|
253
|
-
class ::
|
260
|
+
class ::TestEmployee < ActiveRecord::Base
|
254
261
|
set_primary_key :employee_id
|
255
262
|
end
|
256
263
|
end
|
257
264
|
|
265
|
+
after(:all) do
|
266
|
+
Object.send(:remove_const, "TestEmployee")
|
267
|
+
@conn.execute "DROP TABLE test_employees"
|
268
|
+
end
|
269
|
+
|
258
270
|
it "should tell ActiveRecord that count distinct is supported" do
|
259
271
|
ActiveRecord::Base.connection.supports_count_distinct?.should be_true
|
260
272
|
end
|
261
273
|
|
262
274
|
it "should execute correct SQL COUNT DISTINCT statement" do
|
263
|
-
lambda {
|
275
|
+
lambda { TestEmployee.count(:employee_id, :distinct => true) }.should_not raise_error
|
264
276
|
end
|
265
277
|
|
266
278
|
end
|
@@ -445,4 +457,21 @@ describe "OracleEnhancedAdapter" do
|
|
445
457
|
end
|
446
458
|
end
|
447
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
|
448
477
|
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe "OracleEnhancedAdapter structure dump" do
|
4
|
+
include LoggerSpecHelper
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
8
|
+
@conn = ActiveRecord::Base.connection
|
9
|
+
end
|
10
|
+
describe "structure dump" do
|
11
|
+
before(:each) do
|
12
|
+
@conn.create_table :test_posts, :force => true do |t|
|
13
|
+
t.string :title
|
14
|
+
t.string :foo
|
15
|
+
t.integer :foo_id
|
16
|
+
end
|
17
|
+
@conn.create_table :foos do |t|
|
18
|
+
end
|
19
|
+
class ::TestPost < ActiveRecord::Base
|
20
|
+
end
|
21
|
+
TestPost.set_table_name "test_posts"
|
22
|
+
end
|
23
|
+
|
24
|
+
after(:each) do
|
25
|
+
@conn.drop_table :test_posts
|
26
|
+
@conn.drop_table :foos
|
27
|
+
@conn.execute "DROP SEQUENCE test_posts_seq" rescue nil
|
28
|
+
@conn.execute "ALTER TABLE test_posts drop CONSTRAINT fk_test_post_foo" rescue nil
|
29
|
+
@conn.execute "DROP TRIGGER test_post_trigger" rescue nil
|
30
|
+
@conn.execute "DROP TYPE TEST_TYPE" rescue nil
|
31
|
+
@conn.execute "DROP TABLE bars" rescue nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should dump single primary key" do
|
35
|
+
dump = ActiveRecord::Base.connection.structure_dump
|
36
|
+
dump.should =~ /CONSTRAINT (.+) PRIMARY KEY \(ID\)\n/
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should dump composite primary keys" do
|
40
|
+
pk = @conn.send(:select_one, <<-SQL)
|
41
|
+
select constraint_name from user_constraints where table_name = 'TEST_POSTS' and constraint_type='P'
|
42
|
+
SQL
|
43
|
+
@conn.execute <<-SQL
|
44
|
+
alter table test_posts drop constraint #{pk["constraint_name"]}
|
45
|
+
SQL
|
46
|
+
@conn.execute <<-SQL
|
47
|
+
ALTER TABLE TEST_POSTS
|
48
|
+
add CONSTRAINT pk_id_title PRIMARY KEY (id, title)
|
49
|
+
SQL
|
50
|
+
dump = ActiveRecord::Base.connection.structure_dump
|
51
|
+
dump.should =~ /CONSTRAINT (.+) PRIMARY KEY \(ID,TITLE\)\n/
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should dump foreign keys" do
|
55
|
+
@conn.execute <<-SQL
|
56
|
+
ALTER TABLE TEST_POSTS
|
57
|
+
ADD CONSTRAINT fk_test_post_foo FOREIGN KEY (foo_id) REFERENCES foos(id)
|
58
|
+
SQL
|
59
|
+
dump = ActiveRecord::Base.connection.structure_dump_fk_constraints
|
60
|
+
dump.split('\n').length.should == 1
|
61
|
+
dump.should =~ /ALTER TABLE TEST_POSTS ADD CONSTRAINT fk_test_post_foo FOREIGN KEY \(foo_id\) REFERENCES foos\(id\)/
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should not error when no foreign keys are present" do
|
65
|
+
dump = ActiveRecord::Base.connection.structure_dump_fk_constraints
|
66
|
+
dump.split('\n').length.should == 0
|
67
|
+
dump.should == ''
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should dump triggers" do
|
71
|
+
@conn.execute <<-SQL
|
72
|
+
create or replace TRIGGER TEST_POST_TRIGGER
|
73
|
+
BEFORE INSERT
|
74
|
+
ON TEST_POSTS
|
75
|
+
FOR EACH ROW
|
76
|
+
BEGIN
|
77
|
+
SELECT 'bar' INTO :new.FOO FROM DUAL;
|
78
|
+
END;
|
79
|
+
SQL
|
80
|
+
dump = ActiveRecord::Base.connection.structure_dump_db_stored_code.gsub(/\n|\s+/,' ')
|
81
|
+
dump.should =~ /create or replace TRIGGER TEST_POST_TRIGGER/
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should dump types" do
|
85
|
+
@conn.execute <<-SQL
|
86
|
+
create or replace TYPE TEST_TYPE AS TABLE OF VARCHAR2(10);
|
87
|
+
SQL
|
88
|
+
dump = ActiveRecord::Base.connection.structure_dump_db_stored_code.gsub(/\n|\s+/,' ')
|
89
|
+
dump.should =~ /create or replace TYPE TEST_TYPE/
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should dump virtual columns" do
|
93
|
+
pending "Not supported in this database version" unless @conn.select_value("SELECT * FROM v$version WHERE banner LIKE 'Oracle%11g%'")
|
94
|
+
@conn.execute <<-SQL
|
95
|
+
CREATE TABLE bars (
|
96
|
+
id NUMBER(38,0) NOT NULL,
|
97
|
+
id_plus NUMBER GENERATED ALWAYS AS(id + 2) VIRTUAL,
|
98
|
+
PRIMARY KEY (ID)
|
99
|
+
)
|
100
|
+
SQL
|
101
|
+
dump = ActiveRecord::Base.connection.structure_dump
|
102
|
+
dump.should =~ /id_plus number GENERATED ALWAYS AS \(ID\+2\) VIRTUAL/
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should dump unique keys" do
|
106
|
+
@conn.execute <<-SQL
|
107
|
+
ALTER TABLE test_posts
|
108
|
+
add CONSTRAINT uk_foo_foo_id UNIQUE (foo, foo_id)
|
109
|
+
SQL
|
110
|
+
dump = ActiveRecord::Base.connection.structure_dump_unique_keys("test_posts")
|
111
|
+
dump.should == [" CONSTRAINT UK_FOO_FOO_ID UNIQUE (FOO,FOO_ID)"]
|
112
|
+
|
113
|
+
dump = ActiveRecord::Base.connection.structure_dump
|
114
|
+
dump.should =~ /CONSTRAINT UK_FOO_FOO_ID UNIQUE \(FOO,FOO_ID\)/
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should dump indexes" do
|
118
|
+
ActiveRecord::Base.connection.add_index(:test_posts, :foo, :name => :ix_test_posts_foo)
|
119
|
+
ActiveRecord::Base.connection.add_index(:test_posts, :foo_id, :name => :ix_test_posts_foo_id, :unique => true)
|
120
|
+
|
121
|
+
@conn.execute <<-SQL
|
122
|
+
ALTER TABLE test_posts
|
123
|
+
add CONSTRAINT uk_foo_foo_id UNIQUE (foo, foo_id)
|
124
|
+
SQL
|
125
|
+
|
126
|
+
dump = ActiveRecord::Base.connection.structure_dump
|
127
|
+
dump.should =~ /create unique index ix_test_posts_foo_id on test_posts \(foo_id\)/i
|
128
|
+
dump.should =~ /create index ix_test_posts_foo on test_posts \(foo\)/i
|
129
|
+
dump.should_not =~ /create unique index uk_test_posts_/i
|
130
|
+
end
|
131
|
+
end
|
132
|
+
describe "temporary tables" do
|
133
|
+
after(:all) do
|
134
|
+
@conn.drop_table :test_comments rescue nil
|
135
|
+
end
|
136
|
+
it "should dump correctly" do
|
137
|
+
@conn.create_table :test_comments, :temporary => true, :id => false do |t|
|
138
|
+
t.integer :post_id
|
139
|
+
end
|
140
|
+
dump = ActiveRecord::Base.connection.structure_dump
|
141
|
+
dump.should =~ /create global temporary table test_comments/i
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "database stucture dump extentions" do
|
146
|
+
before(:all) do
|
147
|
+
@conn.execute <<-SQL
|
148
|
+
CREATE TABLE nvarchartable (
|
149
|
+
unq_nvarchar NVARCHAR2(255) DEFAULT NULL
|
150
|
+
)
|
151
|
+
SQL
|
152
|
+
end
|
153
|
+
|
154
|
+
after(:all) do
|
155
|
+
@conn.execute "DROP TABLE nvarchartable"
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should return the character size of nvarchar fields" do
|
159
|
+
if /.*unq_nvarchar nvarchar2\((\d+)\).*/ =~ @conn.structure_dump
|
160
|
+
"#$1".should == "255"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "temp_table_drop" do
|
166
|
+
before(:each) do
|
167
|
+
@conn.create_table :temp_tbl, :temporary => true do |t|
|
168
|
+
t.string :foo
|
169
|
+
end
|
170
|
+
@conn.create_table :not_temp_tbl do |t|
|
171
|
+
t.string :foo
|
172
|
+
end
|
173
|
+
end
|
174
|
+
it "should dump drop sql for just temp tables" do
|
175
|
+
dump = @conn.temp_table_drop
|
176
|
+
dump.should =~ /drop table temp_tbl/i
|
177
|
+
dump.should_not =~ /drop table not_temp_tbl/i
|
178
|
+
end
|
179
|
+
after(:each) do
|
180
|
+
@conn.drop_table :temp_tbl
|
181
|
+
@conn.drop_table :not_temp_tbl
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "full drop" do
|
186
|
+
before(:each) do
|
187
|
+
@conn.create_table :full_drop_test do |t|
|
188
|
+
t.integer :id
|
189
|
+
end
|
190
|
+
@conn.create_table :full_drop_test_temp, :temporary => true do |t|
|
191
|
+
t.string :foo
|
192
|
+
end
|
193
|
+
#view
|
194
|
+
@conn.execute <<-SQL
|
195
|
+
create or replace view full_drop_test_view (foo) as select id as "foo" from full_drop_test
|
196
|
+
SQL
|
197
|
+
#package
|
198
|
+
@conn.execute <<-SQL
|
199
|
+
create or replace package full_drop_test_package as
|
200
|
+
function test_func return varchar2;
|
201
|
+
end test_package;
|
202
|
+
SQL
|
203
|
+
@conn.execute <<-SQL
|
204
|
+
create or replace package body full_drop_test_package as
|
205
|
+
function test_func return varchar2 is
|
206
|
+
begin
|
207
|
+
return ('foo');
|
208
|
+
end test_func;
|
209
|
+
end test_package;
|
210
|
+
SQL
|
211
|
+
#function
|
212
|
+
@conn.execute <<-SQL
|
213
|
+
create or replace function full_drop_test_function
|
214
|
+
return varchar2
|
215
|
+
is
|
216
|
+
foo varchar2(3);
|
217
|
+
begin
|
218
|
+
return('foo');
|
219
|
+
end;
|
220
|
+
SQL
|
221
|
+
#procedure
|
222
|
+
@conn.execute <<-SQL
|
223
|
+
create or replace procedure full_drop_test_procedure
|
224
|
+
begin
|
225
|
+
delete from full_drop_test where id=1231231231
|
226
|
+
exception
|
227
|
+
when no_data_found then
|
228
|
+
dbms_output.put_line('foo');
|
229
|
+
end;
|
230
|
+
SQL
|
231
|
+
#synonym
|
232
|
+
@conn.execute <<-SQL
|
233
|
+
create or replace synonym full_drop_test_synonym for full_drop_test
|
234
|
+
SQL
|
235
|
+
#type
|
236
|
+
@conn.execute <<-SQL
|
237
|
+
create or replace type full_drop_test_type as table of number
|
238
|
+
SQL
|
239
|
+
end
|
240
|
+
after(:each) do
|
241
|
+
@conn.drop_table :full_drop_test
|
242
|
+
@conn.drop_table :full_drop_test_temp
|
243
|
+
@conn.execute "DROP VIEW FULL_DROP_TEST_VIEW" rescue nil
|
244
|
+
@conn.execute "DROP SYNONYM FULL_DROP_TEST_SYNONYM" rescue nil
|
245
|
+
@conn.execute "DROP PACKAGE FULL_DROP_TEST_PACKAGE" rescue nil
|
246
|
+
@conn.execute "DROP FUNCTION FULL_DROP_TEST_FUNCTION" rescue nil
|
247
|
+
@conn.execute "DROP PROCEDURE FULL_DROP_TEST_PROCEDURE" rescue nil
|
248
|
+
@conn.execute "DROP TYPE FULL_DROP_TEST_TYPE" rescue nil
|
249
|
+
end
|
250
|
+
it "should contain correct sql" do
|
251
|
+
drop = @conn.full_drop
|
252
|
+
drop.should =~ /drop table full_drop_test cascade constraints/i
|
253
|
+
drop.should =~ /drop sequence full_drop_test_seq/i
|
254
|
+
drop.should =~ /drop view "full_drop_test_view"/i
|
255
|
+
drop.should =~ /drop package full_drop_test_package/i
|
256
|
+
drop.should =~ /drop function full_drop_test_function/i
|
257
|
+
drop.should =~ /drop procedure full_drop_test_procedure/i
|
258
|
+
drop.should =~ /drop synonym "full_drop_test_synonym"/i
|
259
|
+
drop.should =~ /drop type "full_drop_test_type"/i
|
260
|
+
end
|
261
|
+
it "should not drop tables when preserve_tables is true" do
|
262
|
+
drop = @conn.full_drop(true)
|
263
|
+
drop.should =~ /drop table full_drop_test_temp/i
|
264
|
+
drop.should_not =~ /drop table full_drop_test cascade constraints/i
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|