ctreatma-activerecord-oracle_enhanced-adapter 1.4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +51 -0
  3. data/History.md +269 -0
  4. data/License.txt +20 -0
  5. data/README.md +378 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +46 -0
  8. data/VERSION +1 -0
  9. data/activerecord-oracle_enhanced-adapter.gemspec +130 -0
  10. data/ctreatma-activerecord-oracle_enhanced-adapter.gemspec +129 -0
  11. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1390 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +106 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +136 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +328 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +553 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +492 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +213 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +252 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +373 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +265 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +290 -0
  30. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  31. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  32. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +749 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +310 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +426 -0
  36. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
  37. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
  38. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1330 -0
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
  40. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +121 -0
  41. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  42. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +374 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +380 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1112 -0
  45. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +323 -0
  46. data/spec/spec_helper.rb +185 -0
  47. metadata +287 -0
@@ -0,0 +1,310 @@
1
+ require 'spec_helper'
2
+
3
+ describe "OracleEnhancedConnection" do
4
+
5
+ describe "create connection" do
6
+ before(:all) do
7
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
8
+ end
9
+
10
+ before(:each) do
11
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS) unless @conn.active?
12
+ end
13
+
14
+ it "should create new connection" do
15
+ @conn.should be_active
16
+ end
17
+
18
+ it "should ping active connection" do
19
+ @conn.ping.should be_true
20
+ end
21
+
22
+ it "should not ping inactive connection" do
23
+ @conn.logoff
24
+ lambda { @conn.ping }.should raise_error(ActiveRecord::ConnectionAdapters::OracleEnhancedConnectionException)
25
+ end
26
+
27
+ it "should reset active connection" do
28
+ @conn.reset!
29
+ @conn.should be_active
30
+ end
31
+
32
+ it "should be in autocommit mode after connection" do
33
+ @conn.should be_autocommit
34
+ end
35
+
36
+ end
37
+
38
+ describe "create connection with NLS parameters" do
39
+ after do
40
+ ENV['NLS_DATE_FORMAT'] = nil
41
+ end
42
+
43
+ it "should use NLS_DATE_FORMAT environment variable" do
44
+ ENV['NLS_DATE_FORMAT'] = 'YYYY-MM-DD'
45
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
46
+ @conn.select("SELECT value FROM v$nls_parameters WHERE parameter = 'NLS_DATE_FORMAT'").should == [{'value' => 'YYYY-MM-DD'}]
47
+ end
48
+
49
+ it "should use configuration value and ignore NLS_DATE_FORMAT environment variable" do
50
+ ENV['NLS_DATE_FORMAT'] = 'YYYY-MM-DD'
51
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS.merge(:nls_date_format => 'YYYY-MM-DD HH24:MI'))
52
+ @conn.select("SELECT value FROM v$nls_parameters WHERE parameter = 'NLS_DATE_FORMAT'").should == [{'value' => 'YYYY-MM-DD HH24:MI'}]
53
+ end
54
+
55
+ it "should use default value when NLS_DATE_FORMAT environment variable is not set" do
56
+ ENV['NLS_DATE_FORMAT'] = nil
57
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
58
+ default = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS[:nls_date_format]
59
+ @conn.select("SELECT value FROM v$nls_parameters WHERE parameter = 'NLS_DATE_FORMAT'").should == [{'value' => default}]
60
+ end
61
+ end
62
+
63
+ describe "with non-string parameters" do
64
+ before(:all) do
65
+ params = CONNECTION_PARAMS.dup
66
+ params[:username] = params[:username].to_sym
67
+ params[:password] = params[:password].to_sym
68
+ params[:database] = params[:database].to_sym
69
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
70
+ end
71
+
72
+ it "should create new connection" do
73
+ @conn.should be_active
74
+ end
75
+ end
76
+
77
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
78
+
79
+ describe "create JDBC connection" do
80
+
81
+ it "should create new connection using :url" do
82
+ params = CONNECTION_PARAMS.dup
83
+ params[:url] = "jdbc:oracle:thin:@#{DATABASE_HOST && "#{DATABASE_HOST}:"}#{DATABASE_PORT && "#{DATABASE_PORT}:"}#{DATABASE_NAME}"
84
+ params[:host] = nil
85
+ params[:database] = nil
86
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
87
+ @conn.should be_active
88
+ end
89
+
90
+ it "should create new connection using :url and tnsnames alias" do
91
+ params = CONNECTION_PARAMS.dup
92
+ params[:url] = "jdbc:oracle:thin:@#{DATABASE_NAME}"
93
+ params[:host] = nil
94
+ params[:database] = nil
95
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
96
+ @conn.should be_active
97
+ end
98
+
99
+ it "should create new connection using just tnsnames alias" do
100
+ params = CONNECTION_PARAMS.dup
101
+ params[:host] = nil
102
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
103
+ @conn.should be_active
104
+ end
105
+
106
+ it "should create a new connection using JNDI" do
107
+
108
+ begin
109
+ import 'oracle.jdbc.driver.OracleDriver'
110
+ import 'org.apache.commons.pool.impl.GenericObjectPool'
111
+ import 'org.apache.commons.dbcp.PoolingDataSource'
112
+ import 'org.apache.commons.dbcp.PoolableConnectionFactory'
113
+ import 'org.apache.commons.dbcp.DriverManagerConnectionFactory'
114
+ rescue NameError => e
115
+ return pending e.message
116
+ end
117
+
118
+ class InitialContextMock
119
+ def initialize
120
+ connection_pool = GenericObjectPool.new(nil)
121
+ uri = "jdbc:oracle:thin:@#{DATABASE_HOST && "#{DATABASE_HOST}:"}#{DATABASE_PORT && "#{DATABASE_PORT}:"}#{DATABASE_NAME}"
122
+ connection_factory = DriverManagerConnectionFactory.new(uri, DATABASE_USER, DATABASE_PASSWORD)
123
+ poolable_connection_factory = PoolableConnectionFactory.new(connection_factory,connection_pool,nil,nil,false,true)
124
+ @data_source = PoolingDataSource.new(connection_pool)
125
+ @data_source.access_to_underlying_connection_allowed = true
126
+ end
127
+ def lookup(path)
128
+ if (path == 'java:/comp/env')
129
+ return self
130
+ else
131
+ return @data_source
132
+ end
133
+ end
134
+ end
135
+
136
+ javax.naming.InitialContext.stub!(:new).and_return(InitialContextMock.new)
137
+
138
+ params = {}
139
+ params[:jndi] = 'java:comp/env/jdbc/test'
140
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
141
+ @conn.should be_active
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+
148
+ describe "SQL execution" do
149
+ before(:all) do
150
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
151
+ end
152
+
153
+ it "should execute SQL statement" do
154
+ @conn.exec("SELECT * FROM dual").should_not be_nil
155
+ end
156
+
157
+ it "should execute SQL select" do
158
+ @conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
159
+ end
160
+
161
+ it "should execute SQL select and return also columns" do
162
+ @conn.select("SELECT * FROM dual", nil, true).should == [ [{'dummy' => 'X'}], ['dummy'] ]
163
+ end
164
+
165
+ end
166
+
167
+ describe "SQL with bind parameters" do
168
+ before(:all) do
169
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
170
+ end
171
+
172
+ it "should execute SQL statement with bind parameter" do
173
+ cursor = @conn.prepare("SELECT * FROM dual WHERE :1 = 1")
174
+ cursor.bind_param(1, 1)
175
+ cursor.exec
176
+ cursor.get_col_names.should == ['DUMMY']
177
+ cursor.fetch.should == ["X"]
178
+ cursor.close
179
+ end
180
+
181
+ it "should execute prepared statement with different bind parameters" do
182
+ cursor = @conn.prepare("SELECT * FROM dual WHERE :1 = 1")
183
+ cursor.bind_param(1, 1)
184
+ cursor.exec
185
+ cursor.fetch.should == ["X"]
186
+ cursor.bind_param(1, 0)
187
+ cursor.exec
188
+ cursor.fetch.should be_nil
189
+ cursor.close
190
+ end
191
+ end
192
+
193
+ describe "SQL with bind parameters when NLS_NUMERIC_CHARACTERS is set to ', '" do
194
+ before(:all) do
195
+ ENV['NLS_NUMERIC_CHARACTERS'] = ", "
196
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
197
+ @conn.exec "CREATE TABLE test_employees (age NUMBER(10,2))"
198
+ end
199
+
200
+ after(:all) do
201
+ ENV['NLS_NUMERIC_CHARACTERS'] = nil
202
+ @conn.exec "DROP TABLE test_employees" rescue nil
203
+ end
204
+
205
+ it "should execute prepared statement with decimal bind parameter " do
206
+ cursor = @conn.prepare("INSERT INTO test_employees VALUES(:1)")
207
+ cursor.bind_param(1, "1.5", :decimal)
208
+ cursor.exec
209
+ cursor.close
210
+ cursor = @conn.prepare("SELECT age FROM test_employees")
211
+ cursor.exec
212
+ cursor.fetch.should == [1.5]
213
+ cursor.close
214
+ end
215
+ end
216
+
217
+ describe "auto reconnection" do
218
+ before(:all) do
219
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
220
+ @conn = ActiveRecord::Base.connection.instance_variable_get("@connection")
221
+ @sys_conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(SYS_CONNECTION_PARAMS)
222
+ end
223
+
224
+ before(:each) do
225
+ ActiveRecord::Base.connection.reconnect! unless @conn.active?
226
+ end
227
+
228
+ def kill_current_session
229
+ audsid = @conn.select("SELECT userenv('sessionid') audsid FROM dual").first['audsid']
230
+ sid_serial = @sys_conn.select("SELECT s.sid||','||s.serial# sid_serial
231
+ FROM v$session s
232
+ WHERE audsid = '#{audsid}'").first['sid_serial']
233
+ @sys_conn.exec "ALTER SYSTEM KILL SESSION '#{sid_serial}' IMMEDIATE"
234
+ end
235
+
236
+ it "should reconnect and execute SQL statement if connection is lost and auto retry is enabled" do
237
+ # @conn.auto_retry = true
238
+ ActiveRecord::Base.connection.auto_retry = true
239
+ kill_current_session
240
+ @conn.exec("SELECT * FROM dual").should_not be_nil
241
+ end
242
+
243
+ it "should not reconnect and execute SQL statement if connection is lost and auto retry is disabled" do
244
+ # @conn.auto_retry = false
245
+ ActiveRecord::Base.connection.auto_retry = false
246
+ kill_current_session
247
+ lambda { @conn.exec("SELECT * FROM dual") }.should raise_error
248
+ end
249
+
250
+ it "should reconnect and execute SQL select if connection is lost and auto retry is enabled" do
251
+ # @conn.auto_retry = true
252
+ ActiveRecord::Base.connection.auto_retry = true
253
+ kill_current_session
254
+ @conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
255
+ end
256
+
257
+ it "should not reconnect and execute SQL select if connection is lost and auto retry is disabled" do
258
+ # @conn.auto_retry = false
259
+ ActiveRecord::Base.connection.auto_retry = false
260
+ kill_current_session
261
+ lambda { @conn.select("SELECT * FROM dual") }.should raise_error
262
+ end
263
+
264
+ end
265
+
266
+ describe "describe table" do
267
+ before(:all) do
268
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
269
+ @owner = CONNECTION_PARAMS[:username].upcase
270
+ end
271
+
272
+ it "should describe existing table" do
273
+ @conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
274
+ @conn.describe("test_employees").should == [@owner, "TEST_EMPLOYEES"]
275
+ @conn.exec "DROP TABLE test_employees" rescue nil
276
+ end
277
+
278
+ it "should not describe non-existing table" do
279
+ lambda { @conn.describe("test_xxx") }.should raise_error(ActiveRecord::ConnectionAdapters::OracleEnhancedConnectionException)
280
+ end
281
+
282
+ it "should describe table in other schema" do
283
+ @conn.describe("sys.dual").should == ["SYS", "DUAL"]
284
+ end
285
+
286
+ it "should describe existing view" do
287
+ @conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
288
+ @conn.exec "CREATE VIEW test_employees_v AS SELECT * FROM test_employees" rescue nil
289
+ @conn.describe("test_employees_v").should == [@owner, "TEST_EMPLOYEES_V"]
290
+ @conn.exec "DROP VIEW test_employees_v" rescue nil
291
+ @conn.exec "DROP TABLE test_employees" rescue nil
292
+ end
293
+
294
+ it "should describe view in other schema" do
295
+ @conn.describe("sys.v_$version").should == ["SYS", "V_$VERSION"]
296
+ end
297
+
298
+ it "should describe existing private synonym" do
299
+ @conn.exec "CREATE SYNONYM test_dual FOR sys.dual" rescue nil
300
+ @conn.describe("test_dual").should == ["SYS", "DUAL"]
301
+ @conn.exec "DROP SYNONYM test_dual" rescue nil
302
+ end
303
+
304
+ it "should describe existing public synonym" do
305
+ @conn.describe("all_tables").should == ["SYS", "ALL_TABLES"]
306
+ end
307
+
308
+ end
309
+
310
+ end
@@ -0,0 +1,426 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe "OracleEnhancedAdapter context index" do
5
+ include SchemaSpecHelper
6
+ include LoggerSpecHelper
7
+
8
+ def create_table_posts
9
+ schema_define do
10
+ create_table :posts, :force => true do |t|
11
+ t.string :title
12
+ t.text :body
13
+ t.integer :comments_count
14
+ t.timestamps
15
+ t.string :all_text, :limit => 2 # will be used for multi-column index
16
+ end
17
+ end
18
+ end
19
+
20
+ def create_table_comments
21
+ schema_define do
22
+ create_table :comments, :force => true do |t|
23
+ t.integer :post_id
24
+ t.string :author
25
+ t.text :body
26
+ t.timestamps
27
+ end
28
+ end
29
+ end
30
+
31
+ def create_tables
32
+ create_table_posts
33
+ create_table_comments
34
+ end
35
+
36
+ def drop_table_posts
37
+ schema_define { drop_table :posts }
38
+ end
39
+
40
+ def drop_table_comments
41
+ schema_define { drop_table :comments }
42
+ end
43
+
44
+ def drop_tables
45
+ drop_table_comments
46
+ drop_table_posts
47
+ end
48
+
49
+ # Try to grant CTXAPP role to be able to set CONTEXT index parameters.
50
+ def grant_ctxapp
51
+ @sys_conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(SYS_CONNECTION_PARAMS)
52
+ @sys_conn.exec "GRANT CTXAPP TO #{DATABASE_USER}"
53
+ rescue
54
+ nil
55
+ end
56
+
57
+ before(:all) do
58
+ grant_ctxapp
59
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
60
+ end
61
+
62
+ describe "on single table" do
63
+ before(:all) do
64
+ @conn = ActiveRecord::Base.connection
65
+ @title_words = %w{aaa bbb ccc}
66
+ @body_words = %w{foo bar baz}
67
+ create_table_posts
68
+ class ::Post < ActiveRecord::Base
69
+ has_context_index
70
+ end
71
+ @post0 = Post.create(:title => "dummy title", :body => "dummy body")
72
+ @post1 = Post.create(:title => @title_words.join(' '), :body => @body_words.join(' '))
73
+ @post2 = Post.create(:title => (@title_words*2).join(' '), :body => (@body_words*2).join(' '))
74
+ @post_with_null_body = Post.create(:title => "withnull", :body => nil)
75
+ @post_with_null_title = Post.create(:title => nil, :body => "withnull")
76
+ end
77
+
78
+ after(:all) do
79
+ drop_table_posts
80
+ Object.send(:remove_const, "Post")
81
+ ActiveRecord::Base.clear_cache! if ActiveRecord::Base.respond_to?(:"clear_cache!")
82
+ end
83
+
84
+ after(:each) do
85
+ @post.destroy if @post
86
+ end
87
+
88
+ it "should create single VARCHAR2 column index" do
89
+ @conn.add_context_index :posts, :title
90
+ @title_words.each do |word|
91
+ Post.contains(:title, word).all.should == [@post2, @post1]
92
+ end
93
+ @conn.remove_context_index :posts, :title
94
+ end
95
+
96
+ it "should create single CLOB column index" do
97
+ @conn.add_context_index :posts, :body
98
+ @body_words.each do |word|
99
+ Post.contains(:body, word).all.should == [@post2, @post1]
100
+ end
101
+ @conn.remove_context_index :posts, :body
102
+ end
103
+
104
+ it "should not include text index secondary tables in user tables list" do
105
+ @conn.add_context_index :posts, :title
106
+ @conn.tables.any?{|t| t =~ /^dr\$/i}.should be_false
107
+ @conn.remove_context_index :posts, :title
108
+ end
109
+
110
+ it "should create multiple column index" do
111
+ @conn.add_context_index :posts, [:title, :body]
112
+ (@title_words+@body_words).each do |word|
113
+ Post.contains(:title, word).all.should == [@post2, @post1]
114
+ end
115
+ @conn.remove_context_index :posts, [:title, :body]
116
+ end
117
+
118
+ it "should index records with null values" do
119
+ @conn.add_context_index :posts, [:title, :body]
120
+ Post.contains(:title, "withnull").all.should == [@post_with_null_body, @post_with_null_title]
121
+ @conn.remove_context_index :posts, [:title, :body]
122
+ end
123
+
124
+ it "should create multiple column index with specified main index column" do
125
+ @conn.add_context_index :posts, [:title, :body],
126
+ :index_column => :all_text, :sync => 'ON COMMIT'
127
+ @post = Post.create(:title => "abc", :body => "def")
128
+ Post.contains(:all_text, "abc").all.should == [@post]
129
+ Post.contains(:all_text, "def").all.should == [@post]
130
+ @post.update_attributes!(:title => "ghi")
131
+ # index will not be updated as all_text column is not changed
132
+ Post.contains(:all_text, "ghi").all.should be_empty
133
+ @post.update_attributes!(:all_text => "1")
134
+ # index will be updated when all_text column is changed
135
+ Post.contains(:all_text, "ghi").all.should == [@post]
136
+ @conn.remove_context_index :posts, :index_column => :all_text
137
+ end
138
+
139
+ it "should create multiple column index with trigger updated main index column" do
140
+ @conn.add_context_index :posts, [:title, :body],
141
+ :index_column => :all_text, :index_column_trigger_on => [:created_at, :updated_at],
142
+ :sync => 'ON COMMIT'
143
+ @post = Post.create(:title => "abc", :body => "def")
144
+ Post.contains(:all_text, "abc").all.should == [@post]
145
+ Post.contains(:all_text, "def").all.should == [@post]
146
+ @post.update_attributes!(:title => "ghi")
147
+ # index should be updated as created_at column is changed
148
+ Post.contains(:all_text, "ghi").all.should == [@post]
149
+ @conn.remove_context_index :posts, :index_column => :all_text
150
+ end
151
+
152
+ it "should use base letter conversion with BASIC_LEXER" do
153
+ @post = Post.create!(:title => "āčē", :body => "dummy")
154
+ @conn.add_context_index :posts, :title,
155
+ :lexer => { :type => "BASIC_LEXER", :base_letter_type => 'GENERIC', :base_letter => true }
156
+ Post.contains(:title, "āčē").all.should == [@post]
157
+ Post.contains(:title, "ace").all.should == [@post]
158
+ Post.contains(:title, "ACE").all.should == [@post]
159
+ @conn.remove_context_index :posts, :title
160
+ end
161
+
162
+ it "should create transactional index and sync index within transaction on inserts and updates" do
163
+ @conn.add_context_index :posts, :title, :transactional => true
164
+ Post.transaction do
165
+ @post = Post.create(:title => "abc")
166
+ Post.contains(:title, "abc").all.should == [@post]
167
+ @post.update_attributes!(:title => "ghi")
168
+ Post.contains(:title, "ghi").all.should == [@post]
169
+ end
170
+ @conn.remove_context_index :posts, :title
171
+ end
172
+ end
173
+
174
+ describe "on multiple tables" do
175
+ before(:all) do
176
+ @conn = ActiveRecord::Base.connection
177
+ create_tables
178
+ class ::Post < ActiveRecord::Base
179
+ has_many :comments, :dependent => :destroy
180
+ has_context_index
181
+ end
182
+ class ::Comment < ActiveRecord::Base
183
+ belongs_to :post, :counter_cache => true
184
+ end
185
+ end
186
+
187
+ after(:all) do
188
+ drop_tables
189
+ Object.send(:remove_const, "Comment")
190
+ Object.send(:remove_const, "Post")
191
+ ActiveRecord::Base.clear_cache! if ActiveRecord::Base.respond_to?(:"clear_cache!")
192
+ end
193
+
194
+ after(:each) do
195
+ Post.destroy_all
196
+ end
197
+
198
+ it "should create multiple table index with specified main index column" do
199
+ @conn.add_context_index :posts,
200
+ [:title, :body,
201
+ # specify aliases always with AS keyword
202
+ "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
203
+ ],
204
+ :name => 'post_and_comments_index',
205
+ :index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count],
206
+ :sync => 'ON COMMIT'
207
+ @post = Post.create!(:title => "aaa", :body => "bbb")
208
+ @post.comments.create!(:author => "ccc", :body => "ddd")
209
+ @post.comments.create!(:author => "eee", :body => "fff")
210
+ ["aaa", "bbb", "ccc", "ddd", "eee", "fff"].each do |word|
211
+ Post.contains(:all_text, word).all.should == [@post]
212
+ end
213
+ @conn.remove_context_index :posts, :name => 'post_and_comments_index'
214
+ end
215
+
216
+ it "should create multiple table index with specified main index column (when subquery has newlines)" do
217
+ @conn.add_context_index :posts,
218
+ [:title, :body,
219
+ # specify aliases always with AS keyword
220
+ %{ SELECT
221
+ comments.author AS comment_author,
222
+ comments.body AS comment_body
223
+ FROM comments
224
+ WHERE comments.post_id = :id }
225
+ ],
226
+ :name => 'post_and_comments_index',
227
+ :index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count],
228
+ :sync => 'ON COMMIT'
229
+ @post = Post.create!(:title => "aaa", :body => "bbb")
230
+ @post.comments.create!(:author => "ccc", :body => "ddd")
231
+ @post.comments.create!(:author => "eee", :body => "fff")
232
+ ["aaa", "bbb", "ccc", "ddd", "eee", "fff"].each do |word|
233
+ Post.contains(:all_text, word).all.should == [@post]
234
+ end
235
+ @conn.remove_context_index :posts, :name => 'post_and_comments_index'
236
+ end
237
+
238
+ it "should find by search term within specified field" do
239
+ @post = Post.create!(:title => "aaa", :body => "bbb")
240
+ @post.comments.create!(:author => "ccc", :body => "ddd")
241
+ @conn.add_context_index :posts,
242
+ [:title, :body,
243
+ # specify aliases always with AS keyword
244
+ "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
245
+ ],
246
+ :index_column => :all_text
247
+ Post.contains(:all_text, "aaa within title").all.should == [@post]
248
+ Post.contains(:all_text, "aaa within body").all.should be_empty
249
+ Post.contains(:all_text, "bbb within body").all.should == [@post]
250
+ Post.contains(:all_text, "bbb within title").all.should be_empty
251
+ Post.contains(:all_text, "ccc within comment_author").all.should == [@post]
252
+ Post.contains(:all_text, "ccc within comment_body").all.should be_empty
253
+ Post.contains(:all_text, "ddd within comment_body").all.should == [@post]
254
+ Post.contains(:all_text, "ddd within comment_author").all.should be_empty
255
+ @conn.remove_context_index :posts, :index_column => :all_text
256
+ end
257
+
258
+ end
259
+
260
+ describe "with specified tablespace" do
261
+ before(:all) do
262
+ @conn = ActiveRecord::Base.connection
263
+ create_table_posts
264
+ class ::Post < ActiveRecord::Base
265
+ has_context_index
266
+ end
267
+ @post = Post.create(:title => 'aaa', :body => 'bbb')
268
+ @tablespace = @conn.default_tablespace
269
+ set_logger
270
+ @conn = ActiveRecord::Base.connection
271
+ end
272
+
273
+ after(:all) do
274
+ drop_table_posts
275
+ Object.send(:remove_const, "Post")
276
+ ActiveRecord::Base.clear_cache! if ActiveRecord::Base.respond_to?(:"clear_cache!")
277
+ end
278
+
279
+ after(:each) do
280
+ clear_logger
281
+ end
282
+
283
+ def verify_logged_statements
284
+ ['K_TABLE_CLAUSE', 'R_TABLE_CLAUSE', 'N_TABLE_CLAUSE', 'I_INDEX_CLAUSE', 'P_TABLE_CLAUSE'].each do |clause|
285
+ @logger.output(:debug).should =~ /CTX_DDL\.SET_ATTRIBUTE\('index_posts_on_title_sto', '#{clause}', '.*TABLESPACE #{@tablespace}'\)/
286
+ end
287
+ @logger.output(:debug).should =~ /CREATE INDEX .* PARAMETERS \('STORAGE index_posts_on_title_sto'\)/
288
+ end
289
+
290
+ it "should create index on single column" do
291
+ @conn.add_context_index :posts, :title, :tablespace => @tablespace
292
+ verify_logged_statements
293
+ Post.contains(:title, 'aaa').all.should == [@post]
294
+ @conn.remove_context_index :posts, :title
295
+ end
296
+
297
+ it "should create index on multiple columns" do
298
+ @conn.add_context_index :posts, [:title, :body], :name => 'index_posts_text', :tablespace => @conn.default_tablespace
299
+ verify_logged_statements
300
+ Post.contains(:title, 'aaa AND bbb').all.should == [@post]
301
+ @conn.remove_context_index :posts, :name => 'index_posts_text'
302
+ end
303
+
304
+ end
305
+
306
+ describe "schema dump" do
307
+
308
+ def standard_dump
309
+ stream = StringIO.new
310
+ ActiveRecord::SchemaDumper.ignore_tables = []
311
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
312
+ stream.string
313
+ end
314
+
315
+ describe "without table prefixe and suffix" do
316
+
317
+ before(:all) do
318
+ @conn = ActiveRecord::Base.connection
319
+ create_tables
320
+ end
321
+
322
+ after(:all) do
323
+ drop_tables
324
+ end
325
+
326
+ it "should dump definition of single column index" do
327
+ @conn.add_context_index :posts, :title
328
+ standard_dump.should =~ /add_context_index "posts", \["title"\], :name => \"index_posts_on_title\"$/
329
+ @conn.remove_context_index :posts, :title
330
+ end
331
+
332
+ it "should dump definition of multiple column index" do
333
+ @conn.add_context_index :posts, [:title, :body]
334
+ standard_dump.should =~ /add_context_index "posts", \[:title, :body\]$/
335
+ @conn.remove_context_index :posts, [:title, :body]
336
+ end
337
+
338
+ it "should dump definition of multiple table index with options" do
339
+ options = {
340
+ :name => 'post_and_comments_index',
341
+ :index_column => :all_text, :index_column_trigger_on => :updated_at,
342
+ :transactional => true,
343
+ :sync => 'ON COMMIT'
344
+ }
345
+ sub_query = "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
346
+ @conn.add_context_index :posts, [:title, :body, sub_query], options
347
+ standard_dump.should =~ /add_context_index "posts", \[:title, :body, "#{sub_query}"\], #{options.inspect[1..-2]}$/
348
+ @conn.remove_context_index :posts, :name => 'post_and_comments_index'
349
+ end
350
+
351
+ it "should dump definition of multiple table index with options (when definition is larger than 4000 bytes)" do
352
+ options = {
353
+ :name => 'post_and_comments_index',
354
+ :index_column => :all_text, :index_column_trigger_on => :updated_at,
355
+ :transactional => true,
356
+ :sync => 'ON COMMIT'
357
+ }
358
+ sub_query = "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id#{' AND 1=1' * 500}"
359
+ @conn.add_context_index :posts, [:title, :body, sub_query], options
360
+ standard_dump.should =~ /add_context_index "posts", \[:title, :body, "#{sub_query}"\], #{options.inspect[1..-2]}$/
361
+ @conn.remove_context_index :posts, :name => 'post_and_comments_index'
362
+ end
363
+
364
+ it "should dump definition of multiple table index with options (when subquery has newlines)" do
365
+ options = {
366
+ :name => 'post_and_comments_index',
367
+ :index_column => :all_text, :index_column_trigger_on => :updated_at,
368
+ :transactional => true,
369
+ :sync => 'ON COMMIT'
370
+ }
371
+ sub_query = "SELECT comments.author AS comment_author, comments.body AS comment_body\nFROM comments\nWHERE comments.post_id = :id"
372
+ @conn.add_context_index :posts, [:title, :body, sub_query], options
373
+ standard_dump.should =~ /add_context_index "posts", \[:title, :body, "#{sub_query.gsub(/\n/, ' ')}"\], #{options.inspect[1..-2]}$/
374
+ @conn.remove_context_index :posts, :name => 'post_and_comments_index'
375
+ end
376
+
377
+ end
378
+
379
+ describe "with table prefix and suffix" do
380
+ before(:all) do
381
+ ActiveRecord::Base.table_name_prefix = 'xxx_'
382
+ ActiveRecord::Base.table_name_suffix = '_xxx'
383
+ create_tables
384
+ end
385
+
386
+ after(:all) do
387
+ drop_tables
388
+ ActiveRecord::Base.table_name_prefix = ''
389
+ ActiveRecord::Base.table_name_suffix = ''
390
+ end
391
+
392
+ it "should dump definition of single column index" do
393
+ schema_define { add_context_index :posts, :title }
394
+ standard_dump.should =~ /add_context_index "posts", \["title"\], :name => "i_xxx_posts_xxx_title"$/
395
+ schema_define { remove_context_index :posts, :title }
396
+ end
397
+
398
+ it "should dump definition of multiple column index" do
399
+ schema_define { add_context_index :posts, [:title, :body] }
400
+ standard_dump.should =~ /add_context_index "posts", \[:title, :body\]$/
401
+ schema_define { remove_context_index :posts, [:title, :body] }
402
+ end
403
+
404
+ it "should dump definition of multiple table index with options" do
405
+ options = {
406
+ :name => 'xxx_post_and_comments_i',
407
+ :index_column => :all_text, :index_column_trigger_on => :updated_at,
408
+ :lexer => { :type => "BASIC_LEXER", :base_letter_type => 'GENERIC', :base_letter => true },
409
+ :sync => 'ON COMMIT'
410
+ }
411
+ schema_define do
412
+ add_context_index :posts,
413
+ [:title, :body,
414
+ "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
415
+ ], options
416
+ end
417
+ 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"\], #{
418
+ options.inspect[1..-2].gsub(/[{}]/){|s| '\\'<<s }}$/
419
+ schema_define { remove_context_index :posts, :name => 'xxx_post_and_comments_i' }
420
+ end
421
+
422
+ end
423
+
424
+ end
425
+
426
+ end