activerecord-oracle_enhanced-adapter 1.2.4 → 1.3.0
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 +0 -1
- data/History.txt +20 -0
- data/README.rdoc +7 -3
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/activerecord-oracle_enhanced-adapter.gemspec +96 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +11 -8
- data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +37 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +317 -180
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +282 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +3 -2
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +3 -3
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +6 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +143 -52
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +2 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +39 -20
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +2 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +70 -11
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +27 -20
- data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +334 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +28 -22
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +24 -28
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +13 -11
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +72 -69
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +112 -6
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +49 -1
- data/spec/spec_helper.rb +97 -19
- metadata +33 -22
- data/Manifest.txt +0 -32
- data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +0 -126
@@ -6,7 +6,8 @@ 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, :
|
9
|
+
class OracleEnhancedIndexDefinition < Struct.new(:table, :name, :unique, :type, :parameters, :statement_parameters,
|
10
|
+
:tablespace, :columns) #:nodoc:
|
10
11
|
end
|
11
12
|
|
12
13
|
module OracleEnhancedSchemaDefinitions #:nodoc:
|
@@ -11,26 +11,30 @@ module ActiveRecord #:nodoc:
|
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
|
-
|
14
|
+
|
15
|
+
def ignore_table?(table)
|
16
|
+
[ActiveRecord::Migrator.proper_table_name('schema_migrations'), ignore_tables].flatten.any? do |ignored|
|
17
|
+
case ignored
|
18
|
+
when String; table == ignored
|
19
|
+
when Regexp; table =~ ignored
|
20
|
+
else
|
21
|
+
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
15
26
|
def tables_with_oracle_enhanced(stream)
|
16
|
-
|
27
|
+
return tables_without_oracle_enhanced(stream) unless @connection.respond_to?(:materialized_views)
|
28
|
+
# do not include materialized views in schema dump - they should be created separately after schema creation
|
29
|
+
sorted_tables = (@connection.tables - @connection.materialized_views).sort
|
17
30
|
sorted_tables.each do |tbl|
|
18
31
|
# add table prefix or suffix for schema_migrations
|
19
|
-
next if
|
20
|
-
case ignored
|
21
|
-
when String; tbl == ignored
|
22
|
-
when Regexp; tbl =~ ignored
|
23
|
-
else
|
24
|
-
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
25
|
-
end
|
26
|
-
end
|
32
|
+
next if ignore_table? tbl
|
27
33
|
# change table name inspect method
|
28
34
|
tbl.extend TableInspect
|
29
35
|
oracle_enhanced_table(tbl, stream)
|
30
36
|
# add primary key trigger if table has it
|
31
37
|
primary_key_trigger(tbl, stream)
|
32
|
-
end
|
33
|
-
sorted_tables.each do |tbl|
|
34
38
|
# add foreign keys if table has them
|
35
39
|
foreign_keys(tbl, stream)
|
36
40
|
end
|
@@ -76,6 +80,7 @@ module ActiveRecord #:nodoc:
|
|
76
80
|
if @connection.respond_to?(:synonyms)
|
77
81
|
syns = @connection.synonyms
|
78
82
|
syns.each do |syn|
|
83
|
+
next if ignore_table? syn.name
|
79
84
|
table_name = syn.table_name
|
80
85
|
table_name = "#{syn.table_owner}.#{table_name}" if syn.table_owner
|
81
86
|
table_name = "#{table_name}@#{syn.db_link}" if syn.db_link
|
@@ -89,14 +94,28 @@ module ActiveRecord #:nodoc:
|
|
89
94
|
def indexes_with_oracle_enhanced(table, stream)
|
90
95
|
if (indexes = @connection.indexes(table)).any?
|
91
96
|
add_index_statements = indexes.map do |index|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
case index.type
|
98
|
+
when nil
|
99
|
+
# use table.inspect as it will remove prefix and suffix
|
100
|
+
statement_parts = [ ('add_index ' + table.inspect) ]
|
101
|
+
statement_parts << index.columns.inspect
|
102
|
+
statement_parts << (':name => ' + index.name.inspect)
|
103
|
+
statement_parts << ':unique => true' if index.unique
|
104
|
+
statement_parts << ':tablespace => ' + index.tablespace.inspect if index.tablespace
|
105
|
+
when 'CTXSYS.CONTEXT'
|
106
|
+
if index.statement_parameters
|
107
|
+
statement_parts = [ ('add_context_index ' + table.inspect) ]
|
108
|
+
statement_parts << index.statement_parameters
|
109
|
+
else
|
110
|
+
statement_parts = [ ('add_context_index ' + table.inspect) ]
|
111
|
+
statement_parts << index.columns.inspect
|
112
|
+
statement_parts << (':name => ' + index.name.inspect)
|
113
|
+
end
|
114
|
+
else
|
115
|
+
# unrecognized index type
|
116
|
+
statement_parts = ["# unrecognized index #{index.name.inspect} with type #{index.type.inspect}"]
|
117
|
+
end
|
118
|
+
' ' + statement_parts.join(', ')
|
100
119
|
end
|
101
120
|
|
102
121
|
stream.puts add_index_statements.sort.join("\n")
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# implementation idea taken from JDBC adapter
|
2
|
-
if defined?(Rake.application) && Rake.application &&
|
2
|
+
if defined?(Rake.application) && Rake.application &&
|
3
|
+
ActiveRecord::Base.configurations[defined?(Rails.env) ? Rails.env : RAILS_ENV]['adapter'] == 'oracle_enhanced'
|
3
4
|
oracle_enhanced_rakefile = File.dirname(__FILE__) + "/oracle_enhanced.rake"
|
4
5
|
if Rake.application.lookup("environment")
|
5
6
|
# rails tasks already defined; load the override tasks now
|
@@ -1 +1 @@
|
|
1
|
-
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::VERSION =
|
1
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::VERSION = File.read(File.dirname(__FILE__)+'/../../../VERSION').chomp
|
@@ -112,7 +112,7 @@ describe "OracleEnhancedAdapter" do
|
|
112
112
|
before(:all) do
|
113
113
|
@conn.execute <<-SQL
|
114
114
|
CREATE TABLE test_employees (
|
115
|
-
id NUMBER,
|
115
|
+
id NUMBER PRIMARY KEY,
|
116
116
|
first_name VARCHAR2(20),
|
117
117
|
last_name VARCHAR2(25),
|
118
118
|
email VARCHAR2(25),
|
@@ -180,7 +180,7 @@ describe "OracleEnhancedAdapter" do
|
|
180
180
|
@conn.execute "DROP TABLE test_employees" rescue nil
|
181
181
|
@conn.execute <<-SQL
|
182
182
|
CREATE TABLE test_employees (
|
183
|
-
id NUMBER,
|
183
|
+
id NUMBER PRIMARY KEY,
|
184
184
|
first_name VARCHAR2(20),
|
185
185
|
last_name VARCHAR2(25),
|
186
186
|
hire_date DATE
|
@@ -198,12 +198,15 @@ describe "OracleEnhancedAdapter" do
|
|
198
198
|
end
|
199
199
|
|
200
200
|
before(:each) do
|
201
|
-
|
202
|
-
log_to @buffer
|
201
|
+
set_logger
|
203
202
|
@conn = ActiveRecord::Base.connection
|
204
203
|
@conn.clear_columns_cache
|
205
204
|
end
|
206
205
|
|
206
|
+
after(:each) do
|
207
|
+
clear_logger
|
208
|
+
end
|
209
|
+
|
207
210
|
describe "without column caching" do
|
208
211
|
|
209
212
|
before(:each) do
|
@@ -212,14 +215,26 @@ describe "OracleEnhancedAdapter" do
|
|
212
215
|
|
213
216
|
it "should get columns from database at first time" do
|
214
217
|
TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
|
215
|
-
@
|
218
|
+
@logger.logged(:debug).last.should =~ /select .* from all_tab_columns/im
|
216
219
|
end
|
217
220
|
|
218
221
|
it "should get columns from database at second time" do
|
219
222
|
TestEmployee.connection.columns('test_employees')
|
220
|
-
@
|
223
|
+
@logger.clear(:debug)
|
221
224
|
TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
|
222
|
-
@
|
225
|
+
@logger.logged(:debug).last.should =~ /select .* from all_tab_columns/im
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should get primary key from database at first time" do
|
229
|
+
TestEmployee.connection.pk_and_sequence_for('test_employees').should == ['id', nil]
|
230
|
+
@logger.logged(:debug).last.should =~ /select .* from all_constraints/im
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should get primary key from database at first time" do
|
234
|
+
TestEmployee.connection.pk_and_sequence_for('test_employees').should == ['id', nil]
|
235
|
+
@logger.clear(:debug)
|
236
|
+
TestEmployee.connection.pk_and_sequence_for('test_employees').should == ['id', nil]
|
237
|
+
@logger.logged(:debug).last.should =~ /select .* from all_constraints/im
|
223
238
|
end
|
224
239
|
|
225
240
|
end
|
@@ -232,14 +247,26 @@ describe "OracleEnhancedAdapter" do
|
|
232
247
|
|
233
248
|
it "should get columns from database at first time" do
|
234
249
|
TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
|
235
|
-
@
|
250
|
+
@logger.logged(:debug).last.should =~ /select .* from all_tab_columns/im
|
236
251
|
end
|
237
252
|
|
238
253
|
it "should get columns from cache at second time" do
|
239
254
|
TestEmployee.connection.columns('test_employees')
|
240
|
-
@
|
255
|
+
@logger.clear(:debug)
|
241
256
|
TestEmployee.connection.columns('test_employees').map(&:name).should == @column_names
|
242
|
-
@
|
257
|
+
@logger.logged(:debug).last.should be_blank
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should get primary key from database at first time" do
|
261
|
+
TestEmployee.connection.pk_and_sequence_for('test_employees').should == ['id', nil]
|
262
|
+
@logger.logged(:debug).last.should =~ /select .* from all_constraints/im
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should get primary key from cache at first time" do
|
266
|
+
TestEmployee.connection.pk_and_sequence_for('test_employees').should == ['id', nil]
|
267
|
+
@logger.clear(:debug)
|
268
|
+
TestEmployee.connection.pk_and_sequence_for('test_employees').should == ['id', nil]
|
269
|
+
@logger.logged(:debug).last.should be_blank
|
243
270
|
end
|
244
271
|
|
245
272
|
end
|
@@ -329,6 +356,14 @@ describe "OracleEnhancedAdapter" do
|
|
329
356
|
@adapter.valid_table_name?("sys.v$session").should be_true
|
330
357
|
end
|
331
358
|
|
359
|
+
it "should be valid with upcase schema name" do
|
360
|
+
@adapter.valid_table_name?("ABC_123.DEF_456").should be_true
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should be valid with irregular schema name and database links" do
|
364
|
+
@adapter.valid_table_name?('abc$#_123.abc$#_123@abc$#@._123').should be_true
|
365
|
+
end
|
366
|
+
|
332
367
|
it "should not be valid with two dots in name" do
|
333
368
|
@adapter.valid_table_name?("abc_123.def_456.ghi_789").should be_false
|
334
369
|
end
|
@@ -337,6 +372,30 @@ describe "OracleEnhancedAdapter" do
|
|
337
372
|
@adapter.valid_table_name?("warehouse-things").should be_false
|
338
373
|
end
|
339
374
|
|
375
|
+
it "should not be valid with for camel-case" do
|
376
|
+
@adapter.valid_table_name?("Abc").should be_false
|
377
|
+
@adapter.valid_table_name?("aBc").should be_false
|
378
|
+
@adapter.valid_table_name?("abC").should be_false
|
379
|
+
end
|
380
|
+
|
381
|
+
it "should not be valid for names > 30 characters" do
|
382
|
+
@adapter.valid_table_name?("a" * 31).should be_false
|
383
|
+
end
|
384
|
+
|
385
|
+
it "should not be valid for schema names > 30 characters" do
|
386
|
+
@adapter.valid_table_name?(("a" * 31) + ".validname").should be_false
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should not be valid for database links > 128 characters" do
|
390
|
+
@adapter.valid_table_name?("name@" + "a" * 129).should be_false
|
391
|
+
end
|
392
|
+
|
393
|
+
it "should not be valid for names that do not begin with alphabetic characters" do
|
394
|
+
@adapter.valid_table_name?("1abc").should be_false
|
395
|
+
@adapter.valid_table_name?("_abc").should be_false
|
396
|
+
@adapter.valid_table_name?("abc.1xyz").should be_false
|
397
|
+
@adapter.valid_table_name?("abc._xyz").should be_false
|
398
|
+
end
|
340
399
|
end
|
341
400
|
|
342
401
|
describe "table quoting" do
|
@@ -415,7 +474,7 @@ describe "OracleEnhancedAdapter" do
|
|
415
474
|
@db_link_password = SYSTEM_CONNECTION_PARAMS[:password]
|
416
475
|
@db_link_database = SYSTEM_CONNECTION_PARAMS[:database]
|
417
476
|
@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}'"
|
477
|
+
@conn.execute "CREATE DATABASE LINK #{@db_link} CONNECT TO #{@db_link_username} IDENTIFIED BY \"#{@db_link_password}\" USING '#{@db_link_database}'"
|
419
478
|
@conn.execute "CREATE OR REPLACE SYNONYM test_posts FOR test_posts@#{@db_link}"
|
420
479
|
@conn.execute "CREATE OR REPLACE SYNONYM test_posts_seq FOR test_posts_seq@#{@db_link}"
|
421
480
|
class ::TestPost < ActiveRecord::Base
|
data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb
CHANGED
@@ -58,7 +58,7 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
58
58
|
SQL
|
59
59
|
dump = ActiveRecord::Base.connection.structure_dump_fk_constraints
|
60
60
|
dump.split('\n').length.should == 1
|
61
|
-
dump.should =~ /ALTER TABLE TEST_POSTS ADD CONSTRAINT
|
61
|
+
dump.should =~ /ALTER TABLE \"?TEST_POSTS\"? ADD CONSTRAINT \"?FK_TEST_POST_FOO\"? FOREIGN KEY \(\"?FOO_ID\"?\) REFERENCES \"?FOOS\"?\(\"?ID\"?\)/i
|
62
62
|
end
|
63
63
|
|
64
64
|
it "should not error when no foreign keys are present" do
|
@@ -78,7 +78,7 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
78
78
|
END;
|
79
79
|
SQL
|
80
80
|
dump = ActiveRecord::Base.connection.structure_dump_db_stored_code.gsub(/\n|\s+/,' ')
|
81
|
-
dump.should =~ /
|
81
|
+
dump.should =~ /CREATE OR REPLACE TRIGGER TEST_POST_TRIGGER/
|
82
82
|
end
|
83
83
|
|
84
84
|
it "should dump types" do
|
@@ -86,7 +86,7 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
86
86
|
create or replace TYPE TEST_TYPE AS TABLE OF VARCHAR2(10);
|
87
87
|
SQL
|
88
88
|
dump = ActiveRecord::Base.connection.structure_dump_db_stored_code.gsub(/\n|\s+/,' ')
|
89
|
-
dump.should =~ /
|
89
|
+
dump.should =~ /CREATE OR REPLACE TYPE TEST_TYPE/
|
90
90
|
end
|
91
91
|
|
92
92
|
it "should dump virtual columns" do
|
@@ -99,7 +99,7 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
99
99
|
)
|
100
100
|
SQL
|
101
101
|
dump = ActiveRecord::Base.connection.structure_dump
|
102
|
-
dump.should =~ /
|
102
|
+
dump.should =~ /ID_PLUS NUMBER GENERATED ALWAYS AS \(ID\+2\) VIRTUAL/
|
103
103
|
end
|
104
104
|
|
105
105
|
it "should dump unique keys" do
|
@@ -124,9 +124,9 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
124
124
|
SQL
|
125
125
|
|
126
126
|
dump = ActiveRecord::Base.connection.structure_dump
|
127
|
-
dump.should =~ /
|
128
|
-
dump.should =~ /
|
129
|
-
dump.should_not =~ /
|
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
130
|
end
|
131
131
|
end
|
132
132
|
describe "temporary tables" do
|
@@ -138,7 +138,7 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
138
138
|
t.integer :post_id
|
139
139
|
end
|
140
140
|
dump = ActiveRecord::Base.connection.structure_dump
|
141
|
-
dump.should =~ /
|
141
|
+
dump.should =~ /CREATE GLOBAL TEMPORARY TABLE "?TEST_COMMENTS"?/i
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
@@ -173,8 +173,8 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
173
173
|
end
|
174
174
|
it "should dump drop sql for just temp tables" do
|
175
175
|
dump = @conn.temp_table_drop
|
176
|
-
dump.should =~ /
|
177
|
-
dump.should_not =~ /
|
176
|
+
dump.should =~ /DROP TABLE "TEMP_TBL"/
|
177
|
+
dump.should_not =~ /DROP TABLE "?NOT_TEMP_TBL"?/i
|
178
178
|
end
|
179
179
|
after(:each) do
|
180
180
|
@conn.drop_table :temp_tbl
|
@@ -194,6 +194,10 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
194
194
|
@conn.execute <<-SQL
|
195
195
|
create or replace view full_drop_test_view (foo) as select id as "foo" from full_drop_test
|
196
196
|
SQL
|
197
|
+
#materialized view
|
198
|
+
@conn.execute <<-SQL
|
199
|
+
create materialized view full_drop_test_mview (foo) as select id as "foo" from full_drop_test
|
200
|
+
SQL
|
197
201
|
#package
|
198
202
|
@conn.execute <<-SQL
|
199
203
|
create or replace package full_drop_test_package as
|
@@ -241,6 +245,7 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
241
245
|
@conn.drop_table :full_drop_test
|
242
246
|
@conn.drop_table :full_drop_test_temp
|
243
247
|
@conn.execute "DROP VIEW FULL_DROP_TEST_VIEW" rescue nil
|
248
|
+
@conn.execute "DROP MATERIALIZED VIEW FULL_DROP_TEST_MVIEW" rescue nil
|
244
249
|
@conn.execute "DROP SYNONYM FULL_DROP_TEST_SYNONYM" rescue nil
|
245
250
|
@conn.execute "DROP PACKAGE FULL_DROP_TEST_PACKAGE" rescue nil
|
246
251
|
@conn.execute "DROP FUNCTION FULL_DROP_TEST_FUNCTION" rescue nil
|
@@ -249,19 +254,21 @@ describe "OracleEnhancedAdapter structure dump" do
|
|
249
254
|
end
|
250
255
|
it "should contain correct sql" do
|
251
256
|
drop = @conn.full_drop
|
252
|
-
drop.should =~ /
|
253
|
-
drop.should =~ /
|
254
|
-
drop.should =~ /
|
255
|
-
drop.
|
256
|
-
drop.should =~ /
|
257
|
-
drop.should =~ /
|
258
|
-
drop.should =~ /
|
259
|
-
drop.should =~ /
|
257
|
+
drop.should =~ /DROP TABLE "FULL_DROP_TEST" CASCADE CONSTRAINTS/
|
258
|
+
drop.should =~ /DROP SEQUENCE "FULL_DROP_TEST_SEQ"/
|
259
|
+
drop.should =~ /DROP VIEW "FULL_DROP_TEST_VIEW"/
|
260
|
+
drop.should_not =~ /DROP TABLE "?FULL_DROP_TEST_MVIEW"?/i
|
261
|
+
drop.should =~ /DROP MATERIALIZED VIEW "FULL_DROP_TEST_MVIEW"/
|
262
|
+
drop.should =~ /DROP PACKAGE "FULL_DROP_TEST_PACKAGE"/
|
263
|
+
drop.should =~ /DROP FUNCTION "FULL_DROP_TEST_FUNCTION"/
|
264
|
+
drop.should =~ /DROP PROCEDURE "FULL_DROP_TEST_PROCEDURE"/
|
265
|
+
drop.should =~ /DROP SYNONYM "FULL_DROP_TEST_SYNONYM"/
|
266
|
+
drop.should =~ /DROP TYPE "FULL_DROP_TEST_TYPE"/
|
260
267
|
end
|
261
268
|
it "should not drop tables when preserve_tables is true" do
|
262
269
|
drop = @conn.full_drop(true)
|
263
|
-
drop.should =~ /
|
264
|
-
drop.should_not =~ /
|
270
|
+
drop.should =~ /DROP TABLE "FULL_DROP_TEST_TEMP"/
|
271
|
+
drop.should_not =~ /DROP TABLE "?FULL_DROP_TEST"? CASCADE CONSTRAINTS/i
|
265
272
|
end
|
266
273
|
end
|
267
274
|
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe "OracleEnhancedAdapter context index" do
|
4
|
+
include SchemaSpecHelper
|
5
|
+
include LoggerSpecHelper
|
6
|
+
|
7
|
+
def create_table_posts
|
8
|
+
schema_define do
|
9
|
+
create_table :posts, :force => true do |t|
|
10
|
+
t.string :title
|
11
|
+
t.text :body
|
12
|
+
t.integer :comments_count
|
13
|
+
t.timestamps
|
14
|
+
t.string :all_text, :limit => 2 # will be used for multi-column index
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_table_comments
|
20
|
+
schema_define do
|
21
|
+
create_table :comments, :force => true do |t|
|
22
|
+
t.integer :post_id
|
23
|
+
t.string :author
|
24
|
+
t.text :body
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_tables
|
31
|
+
create_table_posts
|
32
|
+
create_table_comments
|
33
|
+
end
|
34
|
+
|
35
|
+
def drop_table_posts
|
36
|
+
schema_define { drop_table :posts }
|
37
|
+
end
|
38
|
+
|
39
|
+
def drop_table_comments
|
40
|
+
schema_define { drop_table :comments }
|
41
|
+
end
|
42
|
+
|
43
|
+
def drop_tables
|
44
|
+
drop_table_comments
|
45
|
+
drop_table_posts
|
46
|
+
end
|
47
|
+
|
48
|
+
before(:all) do
|
49
|
+
# database user should have CTXAPP role to be able to set CONTEXT index parameters
|
50
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
51
|
+
@conn = ActiveRecord::Base.connection
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "on single table" do
|
55
|
+
before(:all) do
|
56
|
+
@title_words = %w{aaa bbb ccc}
|
57
|
+
@body_words = %w{foo bar baz}
|
58
|
+
create_table_posts
|
59
|
+
class ::Post < ActiveRecord::Base
|
60
|
+
has_context_index
|
61
|
+
end
|
62
|
+
@post0 = Post.create(:title => "dummy title", :body => "dummy body")
|
63
|
+
@post1 = Post.create(:title => @title_words.join(' '), :body => @body_words.join(' '))
|
64
|
+
@post2 = Post.create(:title => (@title_words*2).join(' '), :body => (@body_words*2).join(' '))
|
65
|
+
end
|
66
|
+
|
67
|
+
after(:all) do
|
68
|
+
drop_table_posts
|
69
|
+
Object.send(:remove_const, "Post")
|
70
|
+
end
|
71
|
+
|
72
|
+
after(:each) do
|
73
|
+
@post.destroy if @post
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should create single VARCHAR2 column index" do
|
77
|
+
@conn.add_context_index :posts, :title
|
78
|
+
@title_words.each do |word|
|
79
|
+
Post.contains(:title, word).all.should == [@post2, @post1]
|
80
|
+
end
|
81
|
+
@conn.remove_context_index :posts, :title
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should create single CLOB column index" do
|
85
|
+
@conn.add_context_index :posts, :body
|
86
|
+
@body_words.each do |word|
|
87
|
+
Post.contains(:body, word).all.should == [@post2, @post1]
|
88
|
+
end
|
89
|
+
@conn.remove_context_index :posts, :body
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should not include text index secondary tables in user tables list" do
|
93
|
+
@conn.add_context_index :posts, :title
|
94
|
+
@conn.tables.any?{|t| t =~ /^dr\$/i}.should be_false
|
95
|
+
@conn.remove_context_index :posts, :title
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should create multiple column index" do
|
99
|
+
@conn.add_context_index :posts, [:title, :body]
|
100
|
+
(@title_words+@body_words).each do |word|
|
101
|
+
Post.contains(:title, word).all.should == [@post2, @post1]
|
102
|
+
end
|
103
|
+
@conn.remove_context_index :posts, [:title, :body]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should create multiple column index with specified main index column" do
|
107
|
+
@conn.add_context_index :posts, [:title, :body],
|
108
|
+
:index_column => :all_text, :sync => 'ON COMMIT'
|
109
|
+
@post = Post.create(:title => "abc", :body => "def")
|
110
|
+
Post.contains(:all_text, "abc").all.should == [@post]
|
111
|
+
Post.contains(:all_text, "def").all.should == [@post]
|
112
|
+
@post.update_attributes!(:title => "ghi")
|
113
|
+
# index will not be updated as all_text column is not changed
|
114
|
+
Post.contains(:all_text, "ghi").all.should be_empty
|
115
|
+
@post.update_attributes!(:all_text => "1")
|
116
|
+
# index will be updated when all_text column is changed
|
117
|
+
Post.contains(:all_text, "ghi").all.should == [@post]
|
118
|
+
@conn.remove_context_index :posts, :index_column => :all_text
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should create multiple column index with trigger updated main index column" do
|
122
|
+
@conn.add_context_index :posts, [:title, :body],
|
123
|
+
:index_column => :all_text, :index_column_trigger_on => [:created_at, :updated_at],
|
124
|
+
:sync => 'ON COMMIT'
|
125
|
+
@post = Post.create(:title => "abc", :body => "def")
|
126
|
+
Post.contains(:all_text, "abc").all.should == [@post]
|
127
|
+
Post.contains(:all_text, "def").all.should == [@post]
|
128
|
+
@post.update_attributes!(:title => "ghi")
|
129
|
+
# index should be updated as created_at column is changed
|
130
|
+
Post.contains(:all_text, "ghi").all.should == [@post]
|
131
|
+
@conn.remove_context_index :posts, :index_column => :all_text
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "on multiple tables" do
|
137
|
+
before(:all) do
|
138
|
+
create_tables
|
139
|
+
class ::Post < ActiveRecord::Base
|
140
|
+
has_many :comments, :dependent => :destroy
|
141
|
+
has_context_index
|
142
|
+
end
|
143
|
+
class ::Comment < ActiveRecord::Base
|
144
|
+
belongs_to :post, :counter_cache => true
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
after(:all) do
|
149
|
+
drop_tables
|
150
|
+
Object.send(:remove_const, "Comment")
|
151
|
+
Object.send(:remove_const, "Post")
|
152
|
+
end
|
153
|
+
|
154
|
+
after(:each) do
|
155
|
+
Post.destroy_all
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should create multiple table index with specified main index column" do
|
159
|
+
@conn.add_context_index :posts,
|
160
|
+
[:title, :body,
|
161
|
+
# specify aliases always with AS keyword
|
162
|
+
"SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
|
163
|
+
],
|
164
|
+
:name => 'post_and_comments_index',
|
165
|
+
:index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count],
|
166
|
+
:sync => 'ON COMMIT'
|
167
|
+
@post = Post.create!(:title => "aaa", :body => "bbb")
|
168
|
+
@post.comments.create!(:author => "ccc", :body => "ddd")
|
169
|
+
@post.comments.create!(:author => "eee", :body => "fff")
|
170
|
+
["aaa", "bbb", "ccc", "ddd", "eee", "fff"].each do |word|
|
171
|
+
Post.contains(:all_text, word).all.should == [@post]
|
172
|
+
end
|
173
|
+
@conn.remove_context_index :posts, :name => 'post_and_comments_index'
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should find by search term within specified field" do
|
177
|
+
@post = Post.create!(:title => "aaa", :body => "bbb")
|
178
|
+
@post.comments.create!(:author => "ccc", :body => "ddd")
|
179
|
+
@conn.add_context_index :posts,
|
180
|
+
[:title, :body,
|
181
|
+
# specify aliases always with AS keyword
|
182
|
+
"SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
|
183
|
+
],
|
184
|
+
:index_column => :all_text
|
185
|
+
Post.contains(:all_text, "aaa within title").all.should == [@post]
|
186
|
+
Post.contains(:all_text, "aaa within body").all.should be_empty
|
187
|
+
Post.contains(:all_text, "bbb within body").all.should == [@post]
|
188
|
+
Post.contains(:all_text, "bbb within title").all.should be_empty
|
189
|
+
Post.contains(:all_text, "ccc within comment_author").all.should == [@post]
|
190
|
+
Post.contains(:all_text, "ccc within comment_body").all.should be_empty
|
191
|
+
Post.contains(:all_text, "ddd within comment_body").all.should == [@post]
|
192
|
+
Post.contains(:all_text, "ddd within comment_author").all.should be_empty
|
193
|
+
@conn.remove_context_index :posts, :index_column => :all_text
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "with specified tablespace" do
|
199
|
+
before(:all) do
|
200
|
+
create_table_posts
|
201
|
+
class ::Post < ActiveRecord::Base
|
202
|
+
has_context_index
|
203
|
+
end
|
204
|
+
@post = Post.create(:title => 'aaa', :body => 'bbb')
|
205
|
+
@tablespace = @conn.default_tablespace
|
206
|
+
set_logger
|
207
|
+
@conn = ActiveRecord::Base.connection
|
208
|
+
end
|
209
|
+
|
210
|
+
after(:all) do
|
211
|
+
drop_table_posts
|
212
|
+
Object.send(:remove_const, "Post")
|
213
|
+
end
|
214
|
+
|
215
|
+
after(:each) do
|
216
|
+
clear_logger
|
217
|
+
end
|
218
|
+
|
219
|
+
def verify_logged_statements
|
220
|
+
['K_TABLE_CLAUSE', 'R_TABLE_CLAUSE', 'N_TABLE_CLAUSE', 'I_INDEX_CLAUSE', 'P_TABLE_CLAUSE'].each do |clause|
|
221
|
+
@logger.output(:debug).should =~ /CTX_DDL\.SET_ATTRIBUTE\('index_posts_on_title_sto', '#{clause}', '.*TABLESPACE #{@tablespace}'\)/
|
222
|
+
end
|
223
|
+
@logger.output(:debug).should =~ /CREATE INDEX .* PARAMETERS \('STORAGE index_posts_on_title_sto'\)/
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should create index on single column" do
|
227
|
+
@conn.add_context_index :posts, :title, :tablespace => @tablespace
|
228
|
+
verify_logged_statements
|
229
|
+
Post.contains(:title, 'aaa').all.should == [@post]
|
230
|
+
@conn.remove_context_index :posts, :title
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should create index on multiple columns" do
|
234
|
+
@conn.add_context_index :posts, [:title, :body], :name => 'index_posts_text', :tablespace => @conn.default_tablespace
|
235
|
+
verify_logged_statements
|
236
|
+
Post.contains(:title, 'aaa AND bbb').all.should == [@post]
|
237
|
+
@conn.remove_context_index :posts, :name => 'index_posts_text'
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
describe "schema dump" do
|
243
|
+
|
244
|
+
def standard_dump
|
245
|
+
stream = StringIO.new
|
246
|
+
ActiveRecord::SchemaDumper.ignore_tables = []
|
247
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
248
|
+
stream.string
|
249
|
+
end
|
250
|
+
|
251
|
+
describe "without table prefixe and suffix" do
|
252
|
+
|
253
|
+
before(:all) do
|
254
|
+
create_tables
|
255
|
+
end
|
256
|
+
|
257
|
+
after(:all) do
|
258
|
+
drop_tables
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should dump definition of single column index" do
|
262
|
+
@conn.add_context_index :posts, :title
|
263
|
+
standard_dump.should =~ /add_context_index "posts", \["title"\], :name => \"index_posts_on_title\"$/
|
264
|
+
@conn.remove_context_index :posts, :title
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should dump definition of multiple column index" do
|
268
|
+
@conn.add_context_index :posts, [:title, :body]
|
269
|
+
standard_dump.should =~ /add_context_index "posts", \[:title, :body\]$/
|
270
|
+
@conn.remove_context_index :posts, [:title, :body]
|
271
|
+
end
|
272
|
+
|
273
|
+
it "should dump definition of multiple table index with options" do
|
274
|
+
options = {
|
275
|
+
:name => 'post_and_comments_index',
|
276
|
+
:index_column => :all_text, :index_column_trigger_on => :updated_at,
|
277
|
+
:sync => 'ON COMMIT'
|
278
|
+
}
|
279
|
+
@conn.add_context_index :posts,
|
280
|
+
[:title, :body,
|
281
|
+
"SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
|
282
|
+
], options
|
283
|
+
standard_dump.should =~ /add_context_index "posts", \[:title, :body, "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"\], #{options.inspect[1..-2]}$/
|
284
|
+
@conn.remove_context_index :posts, :name => 'post_and_comments_index'
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
describe "with table prefix and suffix" do
|
290
|
+
before(:all) do
|
291
|
+
ActiveRecord::Base.table_name_prefix = 'xxx_'
|
292
|
+
ActiveRecord::Base.table_name_suffix = '_xxx'
|
293
|
+
create_tables
|
294
|
+
end
|
295
|
+
|
296
|
+
after(:all) do
|
297
|
+
drop_tables
|
298
|
+
ActiveRecord::Base.table_name_prefix = ''
|
299
|
+
ActiveRecord::Base.table_name_suffix = ''
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should dump definition of single column index" do
|
303
|
+
schema_define { add_context_index :posts, :title }
|
304
|
+
standard_dump.should =~ /add_context_index "posts", \["title"\], :name => "i_xxx_posts_xxx_title"$/
|
305
|
+
schema_define { remove_context_index :posts, :title }
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should dump definition of multiple column index" do
|
309
|
+
schema_define { add_context_index :posts, [:title, :body] }
|
310
|
+
standard_dump.should =~ /add_context_index "posts", \[:title, :body\]$/
|
311
|
+
schema_define { remove_context_index :posts, [:title, :body] }
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should dump definition of multiple table index with options" do
|
315
|
+
options = {
|
316
|
+
:name => 'xxx_post_and_comments_i',
|
317
|
+
:index_column => :all_text, :index_column_trigger_on => :updated_at,
|
318
|
+
:sync => 'ON COMMIT'
|
319
|
+
}
|
320
|
+
schema_define do
|
321
|
+
add_context_index :posts,
|
322
|
+
[:title, :body,
|
323
|
+
"SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
|
324
|
+
], options
|
325
|
+
end
|
326
|
+
standard_dump.should =~ /add_context_index "posts", \[:title, :body, "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"\], #{options.inspect[1..-2]}$/
|
327
|
+
schema_define { remove_context_index :posts, :name => 'xxx_post_and_comments_i' }
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|