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.
- data/.rspec +2 -0
- data/Gemfile +52 -0
- data/History.md +301 -0
- data/License.txt +20 -0
- data/README.md +123 -0
- data/RUNNING_TESTS.md +45 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1399 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +46 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
- data/lib/activerecord-oracle_enhanced-adapter-with-schema.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1388 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +141 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +378 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +440 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
- data/spec/spec_helper.rb +189 -0
- 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
|