ctreatma-activerecord-oracle_enhanced-adapter 1.4.1.1

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.
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