activerecord-oracle_enhanced-adapter 8.1.0-java

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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1971 -0
  3. data/License.txt +20 -0
  4. data/README.md +947 -0
  5. data/VERSION +1 -0
  6. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +359 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +325 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +629 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +57 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +465 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +739 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +394 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +886 -0
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
  30. data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
  31. data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
  32. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  34. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  35. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  36. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  37. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  38. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  39. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  40. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  41. data/lib/arel/visitors/oracle.rb +216 -0
  42. data/lib/arel/visitors/oracle12.rb +121 -0
  43. data/lib/arel/visitors/oracle_common.rb +51 -0
  44. data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
  45. data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +40 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/composite_spec.rb +84 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +589 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced/dbconsole_spec.rb +63 -0
  51. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
  52. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
  53. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
  54. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  55. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1318 -0
  56. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +485 -0
  57. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
  58. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
  59. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  60. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
  61. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
  62. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  63. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  64. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
  65. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  66. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +101 -0
  67. data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
  68. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  69. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  70. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
  71. data/spec/active_record/oracle_enhanced/type/text_spec.rb +295 -0
  72. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +107 -0
  73. data/spec/spec_config.yaml.template +11 -0
  74. data/spec/spec_helper.rb +225 -0
  75. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  76. data/spec/support/alter_system_user_password.sql +2 -0
  77. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  78. metadata +181 -0
@@ -0,0 +1,431 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter context index" do
4
+ include SchemaSpecHelper
5
+ include LoggerSpecHelper
6
+ include SchemaDumpingHelper
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 null: true
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 null: true
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!
82
+ end
83
+
84
+ before(:each) do
85
+ @post = nil
86
+ end
87
+
88
+ after(:each) do
89
+ @post.destroy if @post
90
+ end
91
+
92
+ it "should create single VARCHAR2 column index" do
93
+ @conn.add_context_index :posts, :title
94
+ @title_words.each do |word|
95
+ expect(Post.contains(:title, word).to_a).to eq([@post2, @post1])
96
+ end
97
+ @conn.remove_context_index :posts, :title
98
+ end
99
+
100
+ it "should create single CLOB column index" do
101
+ @conn.add_context_index :posts, :body
102
+ @body_words.each do |word|
103
+ expect(Post.contains(:body, word).to_a).to eq([@post2, @post1])
104
+ end
105
+ @conn.remove_context_index :posts, :body
106
+ end
107
+
108
+ it "should not include text index secondary tables in user tables list" do
109
+ @conn.add_context_index :posts, :title
110
+ expect(@conn.tables.any? { |t| t =~ /^dr\$/i }).to be_falsey
111
+ @conn.remove_context_index :posts, :title
112
+ end
113
+
114
+ it "should create multiple column index" do
115
+ @conn.add_context_index :posts, [:title, :body]
116
+ (@title_words + @body_words).each do |word|
117
+ expect(Post.contains(:title, word).to_a).to eq([@post2, @post1])
118
+ end
119
+ @conn.remove_context_index :posts, [:title, :body]
120
+ end
121
+
122
+ it "should index records with null values" do
123
+ @conn.add_context_index :posts, [:title, :body]
124
+ expect(Post.contains(:title, "withnull").to_a).to eq([@post_with_null_body, @post_with_null_title])
125
+ @conn.remove_context_index :posts, [:title, :body]
126
+ end
127
+
128
+ it "should create multiple column index with specified main index column" do
129
+ @conn.add_context_index :posts, [:title, :body],
130
+ index_column: :all_text, sync: "ON COMMIT"
131
+ @post = Post.create(title: "abc", body: "def")
132
+ expect(Post.contains(:all_text, "abc").to_a).to eq([@post])
133
+ expect(Post.contains(:all_text, "def").to_a).to eq([@post])
134
+ @post.update!(title: "ghi")
135
+ # index will not be updated as all_text column is not changed
136
+ expect(Post.contains(:all_text, "ghi").to_a).to be_empty
137
+ @post.update!(all_text: "1")
138
+ # index will be updated when all_text column is changed
139
+ expect(Post.contains(:all_text, "ghi").to_a).to eq([@post])
140
+ @conn.remove_context_index :posts, index_column: :all_text
141
+ end
142
+
143
+ it "should create multiple column index with trigger updated main index column" do
144
+ @conn.add_context_index :posts, [:title, :body],
145
+ index_column: :all_text, index_column_trigger_on: [:created_at, :updated_at],
146
+ sync: "ON COMMIT"
147
+ @post = Post.create(title: "abc", body: "def")
148
+ expect(Post.contains(:all_text, "abc").to_a).to eq([@post])
149
+ expect(Post.contains(:all_text, "def").to_a).to eq([@post])
150
+ @post.update!(title: "ghi")
151
+ # index should be updated as created_at column is changed
152
+ expect(Post.contains(:all_text, "ghi").to_a).to eq([@post])
153
+ @conn.remove_context_index :posts, index_column: :all_text
154
+ end
155
+
156
+ it "should use base letter conversion with BASIC_LEXER" do
157
+ @post = Post.create!(title: "āčē", body: "dummy")
158
+ @conn.add_context_index :posts, :title,
159
+ lexer: { type: "BASIC_LEXER", base_letter_type: "GENERIC", base_letter: true }
160
+ expect(Post.contains(:title, "āčē").to_a).to eq([@post])
161
+ expect(Post.contains(:title, "ace").to_a).to eq([@post])
162
+ expect(Post.contains(:title, "ACE").to_a).to eq([@post])
163
+ @conn.remove_context_index :posts, :title
164
+ end
165
+
166
+ it "should create transactional index and sync index within transaction on inserts and updates" do
167
+ @conn.add_context_index :posts, :title, transactional: true
168
+ Post.transaction do
169
+ @post = Post.create(title: "abc")
170
+ expect(Post.contains(:title, "abc").to_a).to eq([@post])
171
+ @post.update!(title: "ghi")
172
+ expect(Post.contains(:title, "ghi").to_a).to eq([@post])
173
+ end
174
+ @conn.remove_context_index :posts, :title
175
+ end
176
+
177
+ it "should use index when contains has schema_name.table_name syntax" do
178
+ @conn.add_context_index :posts, :title
179
+ @title_words.each do |word|
180
+ expect(Post.contains("posts.title", word).to_a).to eq([@post2, @post1])
181
+ end
182
+ @conn.remove_context_index :posts, :title
183
+ end
184
+ end
185
+
186
+ describe "on multiple tables" do
187
+ before(:all) do
188
+ @conn = ActiveRecord::Base.connection
189
+ create_tables
190
+ class ::Post < ActiveRecord::Base
191
+ has_many :comments, dependent: :destroy
192
+ has_context_index
193
+ end
194
+ class ::Comment < ActiveRecord::Base
195
+ belongs_to :post, counter_cache: true
196
+ end
197
+ end
198
+
199
+ after(:all) do
200
+ drop_tables
201
+ Object.send(:remove_const, "Comment")
202
+ Object.send(:remove_const, "Post")
203
+ ActiveRecord::Base.clear_cache!
204
+ end
205
+
206
+ after(:each) do
207
+ @conn.remove_context_index :posts, name: "post_and_comments_index" rescue nil
208
+ @conn.remove_context_index :posts, index_column: :all_text rescue nil
209
+ Post.destroy_all
210
+ end
211
+
212
+ it "should create multiple table index with specified main index column" do
213
+ @conn.add_context_index :posts,
214
+ [:title, :body,
215
+ # specify aliases always with AS keyword
216
+ "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
217
+ ],
218
+ name: "post_and_comments_index",
219
+ index_column: :all_text, index_column_trigger_on: [:updated_at, :comments_count],
220
+ sync: "ON COMMIT"
221
+ @post = Post.create!(title: "aaa", body: "bbb")
222
+ @post.comments.create!(author: "ccc", body: "ddd")
223
+ @post.comments.create!(author: "eee", body: "fff")
224
+ ["aaa", "bbb", "ccc", "ddd", "eee", "fff"].each do |word|
225
+ expect(Post.contains(:all_text, word).to_a).to eq([@post])
226
+ end
227
+ end
228
+
229
+ it "should create multiple table index with specified main index column (when subquery has newlines)" do
230
+ @conn.add_context_index :posts,
231
+ [:title, :body,
232
+ # specify aliases always with AS keyword
233
+ %{ SELECT
234
+ comments.author AS comment_author,
235
+ comments.body AS comment_body
236
+ FROM comments
237
+ WHERE comments.post_id = :id }
238
+ ],
239
+ name: "post_and_comments_index",
240
+ index_column: :all_text, index_column_trigger_on: [:updated_at, :comments_count],
241
+ sync: "ON COMMIT"
242
+ @post = Post.create!(title: "aaa", body: "bbb")
243
+ @post.comments.create!(author: "ccc", body: "ddd")
244
+ @post.comments.create!(author: "eee", body: "fff")
245
+ ["aaa", "bbb", "ccc", "ddd", "eee", "fff"].each do |word|
246
+ expect(Post.contains(:all_text, word).to_a).to eq([@post])
247
+ end
248
+ end
249
+
250
+ it "should find by search term within specified field" do
251
+ @post = Post.create!(title: "aaa", body: "bbb")
252
+ @post.comments.create!(author: "ccc", body: "ddd")
253
+ @conn.add_context_index :posts,
254
+ [:title, :body,
255
+ # specify aliases always with AS keyword
256
+ "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
257
+ ],
258
+ index_column: :all_text
259
+ expect(Post.contains(:all_text, "aaa within title").to_a).to eq([@post])
260
+ expect(Post.contains(:all_text, "aaa within body").to_a).to be_empty
261
+ expect(Post.contains(:all_text, "bbb within body").to_a).to eq([@post])
262
+ expect(Post.contains(:all_text, "bbb within title").to_a).to be_empty
263
+ expect(Post.contains(:all_text, "ccc within comment_author").to_a).to eq([@post])
264
+ expect(Post.contains(:all_text, "ccc within comment_body").to_a).to be_empty
265
+ expect(Post.contains(:all_text, "ddd within comment_body").to_a).to eq([@post])
266
+ expect(Post.contains(:all_text, "ddd within comment_author").to_a).to be_empty
267
+ end
268
+ end
269
+
270
+ describe "with specified tablespace" do
271
+ before(:all) do
272
+ @conn = ActiveRecord::Base.connection
273
+ create_table_posts
274
+ class ::Post < ActiveRecord::Base
275
+ has_context_index
276
+ end
277
+ @post = Post.create(title: "aaa", body: "bbb")
278
+ @tablespace = @conn.default_tablespace
279
+ set_logger
280
+ @conn = ActiveRecord::Base.connection
281
+ end
282
+
283
+ after(:all) do
284
+ drop_table_posts
285
+ Object.send(:remove_const, "Post")
286
+ ActiveRecord::Base.clear_cache!
287
+ end
288
+
289
+ after(:each) do
290
+ clear_logger
291
+ end
292
+
293
+ def verify_logged_statements
294
+ ["K_TABLE_CLAUSE", "R_TABLE_CLAUSE", "N_TABLE_CLAUSE", "I_INDEX_CLAUSE", "P_TABLE_CLAUSE"].each do |clause|
295
+ expect(@logger.output(:debug)).to match(/CTX_DDL\.SET_ATTRIBUTE\('index_posts_on_title_sto', '#{clause}', '.*TABLESPACE #{@tablespace}'\)/)
296
+ end
297
+ expect(@logger.output(:debug)).to match(/CREATE INDEX .* PARAMETERS \('STORAGE index_posts_on_title_sto'\)/)
298
+ end
299
+
300
+ it "should create index on single column" do
301
+ @conn.add_context_index :posts, :title, tablespace: @tablespace
302
+ verify_logged_statements
303
+ expect(Post.contains(:title, "aaa").to_a).to eq([@post])
304
+ @conn.remove_context_index :posts, :title
305
+ end
306
+
307
+ it "should create index on multiple columns" do
308
+ @conn.add_context_index :posts, [:title, :body], name: "index_posts_text", tablespace: @conn.default_tablespace
309
+ verify_logged_statements
310
+ expect(Post.contains(:title, "aaa AND bbb").to_a).to eq([@post])
311
+ @conn.remove_context_index :posts, name: "index_posts_text"
312
+ end
313
+ end
314
+
315
+ describe "schema dump" do
316
+ describe "without table prefixe and suffix" do
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
+ output = dump_table_schema "posts"
329
+ expect(output).to match(/add_context_index "posts", \["title"\], name: "index_posts_on_title"$/)
330
+ @conn.remove_context_index :posts, :title
331
+ end
332
+
333
+ it "should dump definition of multiple column index" do
334
+ @conn.add_context_index :posts, [:title, :body]
335
+ output = dump_table_schema "posts"
336
+ expect(output).to match(/add_context_index "posts", \[:title, :body\]$/)
337
+ @conn.remove_context_index :posts, [:title, :body]
338
+ end
339
+
340
+ it "should dump definition of multiple table index with options" do
341
+ options = {
342
+ name: "post_and_comments_index",
343
+ index_column: :all_text, index_column_trigger_on: :updated_at,
344
+ transactional: true,
345
+ sync: "ON COMMIT"
346
+ }
347
+ sub_query = "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
348
+ @conn.add_context_index :posts, [:title, :body, sub_query], options
349
+ output = dump_table_schema "posts"
350
+ expect(output).to match(/add_context_index "posts", \[:title, :body, "#{sub_query}"\], #{options.inspect[1..-2]}$/)
351
+ @conn.remove_context_index :posts, name: "post_and_comments_index"
352
+ end
353
+
354
+ it "should dump definition of multiple table index with options (when definition is larger than 4000 bytes)" do
355
+ options = {
356
+ name: "post_and_comments_index",
357
+ index_column: :all_text, index_column_trigger_on: :updated_at,
358
+ transactional: true,
359
+ sync: "ON COMMIT"
360
+ }
361
+ sub_query = "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id#{' AND 1=1' * 500}"
362
+ @conn.add_context_index :posts, [:title, :body, sub_query], options
363
+ output = dump_table_schema "posts"
364
+ expect(output).to match(/add_context_index "posts", \[:title, :body, "#{sub_query}"\], #{options.inspect[1..-2]}$/)
365
+ @conn.remove_context_index :posts, name: "post_and_comments_index"
366
+ end
367
+
368
+ it "should dump definition of multiple table index with options (when subquery has newlines)" do
369
+ options = {
370
+ name: "post_and_comments_index",
371
+ index_column: :all_text, index_column_trigger_on: :updated_at,
372
+ transactional: true,
373
+ sync: "ON COMMIT"
374
+ }
375
+ sub_query = "SELECT comments.author AS comment_author, comments.body AS comment_body\nFROM comments\nWHERE comments.post_id = :id"
376
+ @conn.add_context_index :posts, [:title, :body, sub_query], options
377
+ output = dump_table_schema "posts"
378
+ expect(output).to match(/add_context_index "posts", \[:title, :body, "#{sub_query.tr("\n", ' ')}"\], #{options.inspect[1..-2]}$/)
379
+ @conn.remove_context_index :posts, name: "post_and_comments_index"
380
+ end
381
+ end
382
+
383
+ describe "with table prefix and suffix" do
384
+ before(:all) do
385
+ ActiveRecord::Base.table_name_prefix = "xxx_"
386
+ ActiveRecord::Base.table_name_suffix = "_xxx"
387
+ create_tables
388
+ end
389
+
390
+ after(:all) do
391
+ drop_tables
392
+ ActiveRecord::Base.table_name_prefix = ""
393
+ ActiveRecord::Base.table_name_suffix = ""
394
+ end
395
+
396
+ it "should dump definition of single column index" do
397
+ schema_define { add_context_index :posts, :title }
398
+ output = dump_table_schema "posts"
399
+ expect(output).to match(/add_context_index "posts", \["title"\], name: "i_xxx_posts_xxx_title"$/)
400
+ schema_define { remove_context_index :posts, :title }
401
+ end
402
+
403
+ it "should dump definition of multiple column index" do
404
+ schema_define { add_context_index :posts, [:title, :body] }
405
+ output = dump_table_schema "posts"
406
+ expect(output).to match(/add_context_index "posts", \[:title, :body\]$/)
407
+ schema_define { remove_context_index :posts, [:title, :body] }
408
+ end
409
+
410
+ it "should dump definition of multiple table index with options" do
411
+ options = {
412
+ name: "xxx_post_and_comments_i",
413
+ index_column: :all_text, index_column_trigger_on: :updated_at,
414
+ lexer: { type: "BASIC_LEXER", base_letter_type: "GENERIC", base_letter: true },
415
+ wordlist: { type: "BASIC_WORDLIST", prefix_index: true },
416
+ sync: "ON COMMIT"
417
+ }
418
+ schema_define do
419
+ add_context_index :posts,
420
+ [:title, :body,
421
+ "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
422
+ ], options
423
+ end
424
+ output = dump_table_schema "posts"
425
+ expect(output).to match(/add_context_index "posts", \[:title, :body, "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"\], #{
426
+ options.inspect[1..-2].gsub(/[{}]/) { |s| +'\\' << s }}$/)
427
+ schema_define { remove_context_index :posts, name: "xxx_post_and_comments_i" }
428
+ end
429
+ end
430
+ end
431
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/oracle_enhanced/database_tasks"
4
+ require "stringio"
5
+ require "tempfile"
6
+
7
+ describe "Oracle Enhanced adapter database tasks" do
8
+ include SchemaSpecHelper
9
+
10
+ let(:config) { CONNECTION_PARAMS.with_indifferent_access }
11
+
12
+ describe "create" do
13
+ let(:new_user_config) { config.merge(username: "oracle_enhanced_test_user") }
14
+ before do
15
+ fake_terminal(SYSTEM_CONNECTION_PARAMS[:password]) do
16
+ ActiveRecord::Tasks::DatabaseTasks.create(new_user_config)
17
+ end
18
+ end
19
+
20
+ it "creates user" do
21
+ query = "SELECT COUNT(*) FROM dba_users WHERE UPPER(username) = '#{new_user_config[:username].upcase}'"
22
+ expect(ActiveRecord::Base.connection.select_value(query)).to eq(1)
23
+ end
24
+ it "grants permissions defined by OracleEnhancedAdapter.persmissions" do
25
+ query = "SELECT COUNT(*) FROM DBA_SYS_PRIVS WHERE GRANTEE = '#{new_user_config[:username].upcase}'"
26
+ permissions_count = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.permissions.size
27
+ expect(ActiveRecord::Base.connection.select_value(query)).to eq(permissions_count)
28
+ end
29
+ after do
30
+ ActiveRecord::Base.connection.execute("DROP USER #{new_user_config[:username]}")
31
+ end
32
+
33
+ def fake_terminal(input)
34
+ $stdin = StringIO.new
35
+ $stdout = StringIO.new
36
+ $stdin.puts(input)
37
+ $stdin.rewind
38
+ yield
39
+ ensure
40
+ $stdin = STDIN
41
+ $stdout = STDOUT
42
+ end
43
+ end
44
+
45
+ context "with test table" do
46
+ before(:all) do
47
+ $stdout, @original_stdout = StringIO.new, $stdout
48
+ $stderr, @original_stderr = StringIO.new, $stderr
49
+ end
50
+
51
+ after(:all) do
52
+ $stdout, $stderr = @original_stdout, @original_stderr
53
+ end
54
+
55
+ before do
56
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
57
+ schema_define do
58
+ create_table :test_posts, force: true do |t|
59
+ t.string :name, limit: 20
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "drop" do
65
+ before { ActiveRecord::Tasks::DatabaseTasks.drop(config) }
66
+
67
+ it "drops all tables" do
68
+ expect(ActiveRecord::Base.connection.table_exists?(:test_posts)).to be_falsey
69
+ end
70
+ end
71
+
72
+ describe "purge" do
73
+ before { ActiveRecord::Tasks::DatabaseTasks.purge(config) }
74
+
75
+ it "drops all tables" do
76
+ expect(ActiveRecord::Base.connection.table_exists?(:test_posts)).to be_falsey
77
+ expect(ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM RECYCLEBIN")).to eq(0)
78
+ end
79
+ end
80
+
81
+ describe "structure" do
82
+ let(:temp_file) { Tempfile.create(["oracle_enhanced", ".sql"]).path }
83
+ before do
84
+ ActiveRecord::Base.connection_pool.schema_migration.create_table
85
+ ActiveRecord::Base.connection.execute "INSERT INTO schema_migrations (version) VALUES ('20150101010000')"
86
+ end
87
+
88
+ describe "structure_dump" do
89
+ before { ActiveRecord::Tasks::DatabaseTasks.structure_dump(config, temp_file) }
90
+
91
+ it "dumps the database structure to a file without the schema information" do
92
+ contents = File.read(temp_file)
93
+ expect(contents).to include('CREATE TABLE "TEST_POSTS"')
94
+ expect(contents).not_to include("INSERT INTO schema_migrations")
95
+ end
96
+ end
97
+
98
+ describe "structure_load" do
99
+ before do
100
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(config, temp_file)
101
+ ActiveRecord::Tasks::DatabaseTasks.drop(config)
102
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(config, temp_file)
103
+ end
104
+
105
+ it "loads the database structure from a file" do
106
+ expect(ActiveRecord::Base.connection.table_exists?(:test_posts)).to be_truthy
107
+ end
108
+ end
109
+
110
+ after do
111
+ File.unlink(temp_file)
112
+ ActiveRecord::Base.connection_pool.schema_migration.drop_table
113
+ end
114
+ end
115
+
116
+ after do
117
+ schema_define do
118
+ drop_table :test_posts, if_exists: true
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "Oracle Enhanced adapter dbconsole" do
4
+ subject { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter }
5
+
6
+ it "uses sqlplus with user@db when password is not requested" do
7
+ expect(subject).to receive(:find_cmd_and_exec).with("sqlplus", "user@db")
8
+
9
+ config = make_db_config(adapter: "oracle_enhanced", database: "db", username: "user", password: "secret")
10
+
11
+ subject.dbconsole(config)
12
+ end
13
+
14
+ it "uses sqlplus with user/password@db when include_password is true" do
15
+ expect(subject).to receive(:find_cmd_and_exec).with("sqlplus", "user/secret@db")
16
+
17
+ config = make_db_config(adapter: "oracle_enhanced", database: "db", username: "user", password: "secret")
18
+
19
+ subject.dbconsole(config, include_password: true)
20
+ end
21
+
22
+ it "omits @database when no database is configured" do
23
+ expect(subject).to receive(:find_cmd_and_exec).with("sqlplus", "user")
24
+
25
+ config = make_db_config(adapter: "oracle_enhanced", username: "user")
26
+
27
+ subject.dbconsole(config)
28
+ end
29
+
30
+ it "passes an empty logon string when no username is configured" do
31
+ expect(subject).to receive(:find_cmd_and_exec).with("sqlplus", "")
32
+
33
+ config = make_db_config(adapter: "oracle_enhanced", database: "db")
34
+
35
+ subject.dbconsole(config)
36
+ end
37
+
38
+ it "is inherited by the emulated OracleAdapter" do
39
+ require "active_record/connection_adapters/emulation/oracle_adapter"
40
+ expect(ActiveRecord::ConnectionAdapters::OracleAdapter).to receive(:find_cmd_and_exec).with("sqlplus", "user@db")
41
+
42
+ config = make_db_config(adapter: "oracle", database: "db", username: "user", password: "secret")
43
+
44
+ ActiveRecord::ConnectionAdapters::OracleAdapter.dbconsole(config)
45
+ end
46
+
47
+ it "respects ActiveRecord.database_cli[:oracle] when set" do
48
+ original = ActiveRecord.database_cli[:oracle]
49
+ ActiveRecord.database_cli[:oracle] = "sqlcl"
50
+ expect(subject).to receive(:find_cmd_and_exec).with("sqlcl", "user@db")
51
+
52
+ config = make_db_config(adapter: "oracle_enhanced", database: "db", username: "user")
53
+
54
+ subject.dbconsole(config)
55
+ ensure
56
+ ActiveRecord.database_cli[:oracle] = original
57
+ end
58
+
59
+ private
60
+ def make_db_config(config)
61
+ ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config)
62
+ end
63
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter logging dbms_output from plsql" do
4
+ include LoggerSpecHelper
5
+
6
+ before(:all) do
7
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
8
+ ActiveRecord::Base.connection.execute <<~SQL
9
+ CREATE or REPLACE
10
+ FUNCTION MORE_THAN_FIVE_CHARACTERS_LONG (some_text VARCHAR2) RETURN INTEGER
11
+ AS
12
+ longer_than_five INTEGER;
13
+ BEGIN
14
+ dbms_output.put_line('before the if -' || some_text || '-');
15
+ IF length(some_text) > 5 THEN
16
+ dbms_output.put_line('it is longer than 5');
17
+ longer_than_five := 1;
18
+ ELSE
19
+ dbms_output.put_line('it is 5 or shorter');
20
+ longer_than_five := 0;
21
+ END IF;
22
+ dbms_output.put_line('about to return: ' || longer_than_five);
23
+ RETURN longer_than_five;
24
+ END;
25
+ SQL
26
+ end
27
+
28
+ after(:all) do
29
+ ActiveRecord::Base.connection.execute "DROP FUNCTION MORE_THAN_FIVE_CHARACTERS_LONG"
30
+ end
31
+
32
+ before(:each) do
33
+ set_logger
34
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
35
+ @conn = ActiveRecord::Base.connection
36
+ end
37
+
38
+ after(:each) do
39
+ clear_logger
40
+ end
41
+
42
+ it "should NOT log dbms output when dbms output is disabled" do
43
+ @conn.disable_dbms_output
44
+
45
+ expect(@conn.select_all("select more_than_five_characters_long('hi there') is_it_long from dual").to_a).to eq([{ "is_it_long" => 1 }])
46
+
47
+ expect(@logger.output(:debug)).not_to match(/^DBMS_OUTPUT/)
48
+ end
49
+
50
+ it "should log dbms output longer lines to the rails log" do
51
+ @conn.enable_dbms_output
52
+
53
+ expect(@conn.select_all("select more_than_five_characters_long('hi there') is_it_long from dual").to_a).to eq([{ "is_it_long" => 1 }])
54
+
55
+ expect(@logger.output(:debug)).to match(/^DBMS_OUTPUT: before the if -hi there-$/)
56
+ expect(@logger.output(:debug)).to match(/^DBMS_OUTPUT: it is longer than 5$/)
57
+ expect(@logger.output(:debug)).to match(/^DBMS_OUTPUT: about to return: 1$/)
58
+ end
59
+
60
+ it "should log dbms output shorter lines to the rails log" do
61
+ @conn.enable_dbms_output
62
+
63
+ expect(@conn.select_all("select more_than_five_characters_long('short') is_it_long from dual").to_a).to eq([{ "is_it_long" => 0 }])
64
+
65
+ expect(@logger.output(:debug)).to match(/^DBMS_OUTPUT: before the if -short-$/)
66
+ expect(@logger.output(:debug)).to match(/^DBMS_OUTPUT: it is 5 or shorter$/)
67
+ expect(@logger.output(:debug)).to match(/^DBMS_OUTPUT: about to return: 0$/)
68
+ end
69
+ end