activerecord-oracle_enhanced-adapter-with-schema 0.0.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 (46) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +52 -0
  3. data/History.md +301 -0
  4. data/License.txt +20 -0
  5. data/README.md +123 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +59 -0
  8. data/VERSION +1 -0
  9. data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -0
  10. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1399 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +46 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  30. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  31. data/lib/activerecord-oracle_enhanced-adapter-with-schema.rb +25 -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 +1388 -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 +440 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
  45. data/spec/spec_helper.rb +189 -0
  46. metadata +260 -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