pmacs-activerecord-oracle_enhanced-adapter 1.4.2.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +52 -0
  3. data/History.md +284 -0
  4. data/License.txt +20 -0
  5. data/README.md +403 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +59 -0
  8. data/VERSION +1 -0
  9. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1408 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +118 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +141 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +135 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +44 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +491 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +231 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +257 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +397 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +265 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  30. data/lib/pmacs-activerecord-oracle_enhanced-adapter.rb +25 -0
  31. data/pmacs-activerecord-oracle_enhanced-adapter.gemspec +131 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
  36. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
  37. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1376 -0
  38. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +141 -0
  40. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  41. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +378 -0
  42. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +438 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1280 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
  45. data/spec/spec_helper.rb +187 -0
  46. metadata +302 -0
@@ -0,0 +1,332 @@
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
+ describe "with slash-prefixed database name (service name)" do
78
+ before(:all) do
79
+ params = CONNECTION_PARAMS.dup
80
+ params[:database] = "/#{params[:database]}" unless params[:database].match(/^\//)
81
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
82
+ end
83
+
84
+ it "should create new connection" do
85
+ @conn.should be_active
86
+ end
87
+ end
88
+
89
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
90
+
91
+ describe "create JDBC connection" do
92
+
93
+ it "should create new connection using :url" do
94
+ params = CONNECTION_PARAMS.dup
95
+ params[:url] = "jdbc:oracle:thin:@#{DATABASE_HOST && "#{DATABASE_HOST}:"}#{DATABASE_PORT && "#{DATABASE_PORT}:"}#{DATABASE_NAME}"
96
+ params[:host] = nil
97
+ params[:database] = nil
98
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
99
+ @conn.should be_active
100
+ end
101
+
102
+ it "should create new connection using :url and tnsnames alias" do
103
+ params = CONNECTION_PARAMS.dup
104
+ params[:url] = "jdbc:oracle:thin:@#{DATABASE_NAME}"
105
+ params[:host] = nil
106
+ params[:database] = nil
107
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
108
+ @conn.should be_active
109
+ end
110
+
111
+ it "should create new connection using just tnsnames alias" do
112
+ params = CONNECTION_PARAMS.dup
113
+ params[:host] = nil
114
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
115
+ @conn.should be_active
116
+ end
117
+
118
+ it "should create a new connection using JNDI" do
119
+
120
+ begin
121
+ import 'oracle.jdbc.driver.OracleDriver'
122
+ import 'org.apache.commons.pool.impl.GenericObjectPool'
123
+ import 'org.apache.commons.dbcp.PoolingDataSource'
124
+ import 'org.apache.commons.dbcp.PoolableConnectionFactory'
125
+ import 'org.apache.commons.dbcp.DriverManagerConnectionFactory'
126
+ rescue NameError => e
127
+ return pending e.message
128
+ end
129
+
130
+ class InitialContextMock
131
+ def initialize
132
+ connection_pool = GenericObjectPool.new(nil)
133
+ uri = "jdbc:oracle:thin:@#{DATABASE_HOST && "#{DATABASE_HOST}:"}#{DATABASE_PORT && "#{DATABASE_PORT}:"}#{DATABASE_NAME}"
134
+ connection_factory = DriverManagerConnectionFactory.new(uri, DATABASE_USER, DATABASE_PASSWORD)
135
+ poolable_connection_factory = PoolableConnectionFactory.new(connection_factory,connection_pool,nil,nil,false,true)
136
+ @data_source = PoolingDataSource.new(connection_pool)
137
+ @data_source.access_to_underlying_connection_allowed = true
138
+ end
139
+ def lookup(path)
140
+ if (path == 'java:/comp/env')
141
+ return self
142
+ else
143
+ return @data_source
144
+ end
145
+ end
146
+ end
147
+
148
+ javax.naming.InitialContext.stub!(:new).and_return(InitialContextMock.new)
149
+
150
+ params = {}
151
+ params[:jndi] = 'java:comp/env/jdbc/test'
152
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
153
+ @conn.should be_active
154
+ end
155
+
156
+ end
157
+
158
+ it "should fall back to directly instantiating OracleDriver" do
159
+ params = CONNECTION_PARAMS.dup
160
+ params[:url] = "jdbc:oracle:thin:@#{DATABASE_HOST && "#{DATABASE_HOST}:"}#{DATABASE_PORT && "#{DATABASE_PORT}:"}#{DATABASE_NAME}"
161
+ params[:host] = nil
162
+ params[:database] = nil
163
+ java.sql.DriverManager.stub!(:getConnection).and_raise('no suitable driver found')
164
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(params)
165
+ @conn.should be_active
166
+ end
167
+
168
+ end
169
+
170
+ describe "SQL execution" do
171
+ before(:all) do
172
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
173
+ end
174
+
175
+ it "should execute SQL statement" do
176
+ @conn.exec("SELECT * FROM dual").should_not be_nil
177
+ end
178
+
179
+ it "should execute SQL select" do
180
+ @conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
181
+ end
182
+
183
+ it "should execute SQL select and return also columns" do
184
+ @conn.select("SELECT * FROM dual", nil, true).should == [ [{'dummy' => 'X'}], ['dummy'] ]
185
+ end
186
+
187
+ end
188
+
189
+ describe "SQL with bind parameters" do
190
+ before(:all) do
191
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
192
+ end
193
+
194
+ it "should execute SQL statement with bind parameter" do
195
+ cursor = @conn.prepare("SELECT * FROM dual WHERE :1 = 1")
196
+ cursor.bind_param(1, 1)
197
+ cursor.exec
198
+ cursor.get_col_names.should == ['DUMMY']
199
+ cursor.fetch.should == ["X"]
200
+ cursor.close
201
+ end
202
+
203
+ it "should execute prepared statement with different bind parameters" do
204
+ cursor = @conn.prepare("SELECT * FROM dual WHERE :1 = 1")
205
+ cursor.bind_param(1, 1)
206
+ cursor.exec
207
+ cursor.fetch.should == ["X"]
208
+ cursor.bind_param(1, 0)
209
+ cursor.exec
210
+ cursor.fetch.should be_nil
211
+ cursor.close
212
+ end
213
+ end
214
+
215
+ describe "SQL with bind parameters when NLS_NUMERIC_CHARACTERS is set to ', '" do
216
+ before(:all) do
217
+ ENV['NLS_NUMERIC_CHARACTERS'] = ", "
218
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
219
+ @conn.exec "CREATE TABLE test_employees (age NUMBER(10,2))"
220
+ end
221
+
222
+ after(:all) do
223
+ ENV['NLS_NUMERIC_CHARACTERS'] = nil
224
+ @conn.exec "DROP TABLE test_employees" rescue nil
225
+ end
226
+
227
+ it "should execute prepared statement with decimal bind parameter " do
228
+ cursor = @conn.prepare("INSERT INTO test_employees VALUES(:1)")
229
+ cursor.bind_param(1, "1.5", :decimal)
230
+ cursor.exec
231
+ cursor.close
232
+ cursor = @conn.prepare("SELECT age FROM test_employees")
233
+ cursor.exec
234
+ cursor.fetch.should == [1.5]
235
+ cursor.close
236
+ end
237
+ end
238
+
239
+ describe "auto reconnection" do
240
+ before(:all) do
241
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
242
+ @conn = ActiveRecord::Base.connection.instance_variable_get("@connection")
243
+ @sys_conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(SYS_CONNECTION_PARAMS)
244
+ end
245
+
246
+ before(:each) do
247
+ ActiveRecord::Base.connection.reconnect! unless @conn.active?
248
+ end
249
+
250
+ def kill_current_session
251
+ audsid = @conn.select("SELECT userenv('sessionid') audsid FROM dual").first['audsid']
252
+ sid_serial = @sys_conn.select("SELECT s.sid||','||s.serial# sid_serial
253
+ FROM v$session s
254
+ WHERE audsid = '#{audsid}'").first['sid_serial']
255
+ @sys_conn.exec "ALTER SYSTEM KILL SESSION '#{sid_serial}' IMMEDIATE"
256
+ end
257
+
258
+ it "should reconnect and execute SQL statement if connection is lost and auto retry is enabled" do
259
+ # @conn.auto_retry = true
260
+ ActiveRecord::Base.connection.auto_retry = true
261
+ kill_current_session
262
+ @conn.exec("SELECT * FROM dual").should_not be_nil
263
+ end
264
+
265
+ it "should not reconnect and execute SQL statement if connection is lost and auto retry is disabled" do
266
+ # @conn.auto_retry = false
267
+ ActiveRecord::Base.connection.auto_retry = false
268
+ kill_current_session
269
+ lambda { @conn.exec("SELECT * FROM dual") }.should raise_error
270
+ end
271
+
272
+ it "should reconnect and execute SQL select if connection is lost and auto retry is enabled" do
273
+ # @conn.auto_retry = true
274
+ ActiveRecord::Base.connection.auto_retry = true
275
+ kill_current_session
276
+ @conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
277
+ end
278
+
279
+ it "should not reconnect and execute SQL select if connection is lost and auto retry is disabled" do
280
+ # @conn.auto_retry = false
281
+ ActiveRecord::Base.connection.auto_retry = false
282
+ kill_current_session
283
+ lambda { @conn.select("SELECT * FROM dual") }.should raise_error
284
+ end
285
+
286
+ end
287
+
288
+ describe "describe table" do
289
+ before(:all) do
290
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
291
+ @owner = CONNECTION_PARAMS[:username].upcase
292
+ end
293
+
294
+ it "should describe existing table" do
295
+ @conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
296
+ @conn.describe("test_employees").should == [@owner, "TEST_EMPLOYEES"]
297
+ @conn.exec "DROP TABLE test_employees" rescue nil
298
+ end
299
+
300
+ it "should not describe non-existing table" do
301
+ lambda { @conn.describe("test_xxx") }.should raise_error(ActiveRecord::ConnectionAdapters::OracleEnhancedConnectionException)
302
+ end
303
+
304
+ it "should describe table in other schema" do
305
+ @conn.describe("sys.dual").should == ["SYS", "DUAL"]
306
+ end
307
+
308
+ it "should describe existing view" do
309
+ @conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
310
+ @conn.exec "CREATE VIEW test_employees_v AS SELECT * FROM test_employees" rescue nil
311
+ @conn.describe("test_employees_v").should == [@owner, "TEST_EMPLOYEES_V"]
312
+ @conn.exec "DROP VIEW test_employees_v" rescue nil
313
+ @conn.exec "DROP TABLE test_employees" rescue nil
314
+ end
315
+
316
+ it "should describe view in other schema" do
317
+ @conn.describe("sys.v_$version").should == ["SYS", "V_$VERSION"]
318
+ end
319
+
320
+ it "should describe existing private synonym" do
321
+ @conn.exec "CREATE SYNONYM test_dual FOR sys.dual" rescue nil
322
+ @conn.describe("test_dual").should == ["SYS", "DUAL"]
323
+ @conn.exec "DROP SYNONYM test_dual" rescue nil
324
+ end
325
+
326
+ it "should describe existing public synonym" do
327
+ @conn.describe("all_tables").should == ["SYS", "ALL_TABLES"]
328
+ end
329
+
330
+ end
331
+
332
+ end
@@ -0,0 +1,427 @@
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
+ :wordlist => { :type => "BASIC_WORDLIST", :prefix_index => true },
410
+ :sync => 'ON COMMIT'
411
+ }
412
+ schema_define do
413
+ add_context_index :posts,
414
+ [:title, :body,
415
+ "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
416
+ ], options
417
+ end
418
+ 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"\], #{
419
+ options.inspect[1..-2].gsub(/[{}]/){|s| '\\'<<s }}$/
420
+ schema_define { remove_context_index :posts, :name => 'xxx_post_and_comments_i' }
421
+ end
422
+
423
+ end
424
+
425
+ end
426
+
427
+ end