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,1318 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter schema definition" do
4
+ include SchemaSpecHelper
5
+ include LoggerSpecHelper
6
+
7
+ before(:all) do
8
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
9
+ @oracle11g_or_higher = !! !! ActiveRecord::Base.connection.select_value(
10
+ "select * from product_component_version where product like 'Oracle%' and to_number(substr(version,1,2)) >= 11")
11
+ @oracle12cr2_or_higher = !! !! ActiveRecord::Base.connection.select_value(
12
+ "select * from product_component_version where product like 'Oracle%' and to_number(substr(version,1,4)) >= 12.2")
13
+ end
14
+
15
+ describe "option to create sequence when adding a column" do
16
+ before do
17
+ @conn = ActiveRecord::Base.connection
18
+ schema_define do
19
+ create_table :keyboards, force: true, id: false do |t|
20
+ t.string :name
21
+ end
22
+ add_column :keyboards, :id, :primary_key
23
+ end
24
+ class ::Keyboard < ActiveRecord::Base; end
25
+ end
26
+
27
+ it "creates a sequence when adding a column with create_sequence = true" do
28
+ _, sequence_name = ActiveRecord::Base.connection.pk_and_sequence_for(:keyboards)
29
+
30
+ expect(sequence_name).to eq(Keyboard.sequence_name)
31
+ end
32
+ end
33
+
34
+ describe "table and sequence creation with non-default primary key" do
35
+ before(:all) do
36
+ @conn = ActiveRecord::Base.connection
37
+ schema_define do
38
+ create_table :keyboards, force: true, id: false do |t|
39
+ t.primary_key :key_number
40
+ t.string :name
41
+ end
42
+ create_table :id_keyboards, force: true do |t|
43
+ t.string :name
44
+ end
45
+ end
46
+ class ::Keyboard < ActiveRecord::Base
47
+ self.primary_key = :key_number
48
+ end
49
+ class ::IdKeyboard < ActiveRecord::Base
50
+ end
51
+ end
52
+
53
+ after(:all) do
54
+ schema_define do
55
+ drop_table :keyboards
56
+ drop_table :id_keyboards
57
+ end
58
+ Object.send(:remove_const, "Keyboard")
59
+ Object.send(:remove_const, "IdKeyboard")
60
+ ActiveRecord::Base.clear_cache!
61
+ end
62
+
63
+ it "should create sequence for non-default primary key" do
64
+ expect(ActiveRecord::Base.connection.next_sequence_value(Keyboard.sequence_name)).not_to be_nil
65
+ end
66
+
67
+ it "should create sequence for default primary key" do
68
+ expect(ActiveRecord::Base.connection.next_sequence_value(IdKeyboard.sequence_name)).not_to be_nil
69
+ end
70
+ end
71
+
72
+ describe "primary_key inside create_table block with type and keyword options" do
73
+ before(:all) do
74
+ @conn = ActiveRecord::Base.connection
75
+ end
76
+
77
+ after(:each) do
78
+ schema_define do
79
+ drop_table :test_lookups, if_exists: true
80
+ end
81
+ end
82
+
83
+ it "accepts a type argument and keyword options without raising ArgumentError" do
84
+ expect {
85
+ schema_define do
86
+ create_table :test_lookups, force: true, id: false do |t|
87
+ t.primary_key :zlookupid, :string, limit: 1, null: false
88
+ t.string :name
89
+ end
90
+ end
91
+ }.not_to raise_error
92
+
93
+ columns = @conn.columns(:test_lookups)
94
+ pk = columns.find { |c| c.name == "zlookupid" }
95
+ expect(pk).not_to be_nil
96
+ expect(pk.sql_type).to match(/VARCHAR2\(1\)/i)
97
+ expect(pk.null).to be(false)
98
+ end
99
+ end
100
+
101
+ describe "default sequence name" do
102
+ it "should return sequence name without truncating too much" do
103
+ seq_name_length = ActiveRecord::Base.connection.sequence_name_length
104
+ tname = "#{DATABASE_USER}" + "." + "a" * (seq_name_length - DATABASE_USER.length) + "z" * (DATABASE_USER).length
105
+ expect(ActiveRecord::Base.connection.default_sequence_name(tname)).to match (/z_seq$/)
106
+ end
107
+ end
108
+
109
+ describe "sequence creation parameters" do
110
+ def create_test_employees_table(sequence_start_value = nil)
111
+ schema_define do
112
+ options = sequence_start_value ? { sequence_start_value: sequence_start_value } : {}
113
+ create_table :test_employees, **options do |t|
114
+ t.string :first_name
115
+ t.string :last_name
116
+ end
117
+ end
118
+ end
119
+
120
+ def save_default_sequence_start_value
121
+ @saved_sequence_start_value = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value
122
+ end
123
+
124
+ def restore_default_sequence_start_value
125
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = @saved_sequence_start_value
126
+ end
127
+
128
+ before(:all) do
129
+ @conn = ActiveRecord::Base.connection
130
+ end
131
+
132
+ before(:each) do
133
+ save_default_sequence_start_value
134
+ end
135
+
136
+ after(:each) do
137
+ restore_default_sequence_start_value
138
+ schema_define do
139
+ drop_table :test_employees
140
+ end
141
+ Object.send(:remove_const, "TestEmployee")
142
+ ActiveRecord::Base.clear_cache!
143
+ end
144
+
145
+ it "should use default sequence start value 1" do
146
+ expect(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value).to eq(1)
147
+
148
+ create_test_employees_table
149
+ class ::TestEmployee < ActiveRecord::Base; end
150
+
151
+ employee = TestEmployee.create!
152
+ expect(employee.id).to eq(1)
153
+ end
154
+
155
+ it "should use specified default sequence start value" do
156
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 10000
157
+
158
+ create_test_employees_table
159
+ class ::TestEmployee < ActiveRecord::Base; end
160
+
161
+ employee = TestEmployee.create!
162
+ expect(employee.id).to eq(10000)
163
+ end
164
+
165
+ it "should use sequence start value from table definition" do
166
+ create_test_employees_table(10)
167
+ class ::TestEmployee < ActiveRecord::Base; end
168
+
169
+ employee = TestEmployee.create!
170
+ expect(employee.id).to eq(10)
171
+ end
172
+
173
+ it "should use sequence start value and other options from table definition" do
174
+ create_test_employees_table("100 NOCACHE INCREMENT BY 10")
175
+ class ::TestEmployee < ActiveRecord::Base; end
176
+
177
+ employee = TestEmployee.create!
178
+ expect(employee.id).to eq(100)
179
+ employee = TestEmployee.create!
180
+ expect(employee.id).to eq(110)
181
+ end
182
+ end
183
+
184
+ describe "table and column comments" do
185
+ def create_test_employees_table(table_comment = nil, column_comments = {})
186
+ schema_define do
187
+ create_table :test_employees, comment: table_comment do |t|
188
+ t.string :first_name, comment: column_comments[:first_name]
189
+ t.string :last_name, comment: column_comments[:last_name]
190
+ end
191
+ end
192
+ end
193
+
194
+ before(:all) do
195
+ @conn = ActiveRecord::Base.connection
196
+ end
197
+
198
+ before(:each) do
199
+ @conn.clear_cache!
200
+ set_logger
201
+ end
202
+
203
+ after(:each) do
204
+ clear_logger
205
+ schema_define do
206
+ drop_table :test_employees
207
+ end
208
+ Object.send(:remove_const, "TestEmployee")
209
+ ActiveRecord::Base.table_name_prefix = ""
210
+ ActiveRecord::Base.clear_cache!
211
+ end
212
+
213
+ it "should create table with table comment" do
214
+ table_comment = "Test Employees"
215
+ create_test_employees_table(table_comment)
216
+ class ::TestEmployee < ActiveRecord::Base; end
217
+ expect(@conn.table_comment("test_employees")).to eq(table_comment)
218
+ end
219
+
220
+ it "should create table with columns comment" do
221
+ column_comments = { first_name: "Given Name", last_name: "Surname" }
222
+ create_test_employees_table(nil, column_comments)
223
+ class ::TestEmployee < ActiveRecord::Base; end
224
+
225
+ [:first_name, :last_name].each do |attr|
226
+ expect(@conn.column_comment("test_employees", attr.to_s)).to eq(column_comments[attr])
227
+ end
228
+ [:first_name, :last_name].each do |attr|
229
+ expect(TestEmployee.columns_hash[attr.to_s].comment).to eq(column_comments[attr])
230
+ end
231
+ end
232
+
233
+ it "should create table with table and columns comment and custom table name prefix" do
234
+ ActiveRecord::Base.table_name_prefix = "xxx_"
235
+ table_comment = "Test Employees"
236
+ column_comments = { first_name: "Given Name", last_name: "Surname" }
237
+ create_test_employees_table(table_comment, column_comments)
238
+ class ::TestEmployee < ActiveRecord::Base; end
239
+
240
+ expect(@conn.table_comment(TestEmployee.table_name)).to eq(table_comment)
241
+ [:first_name, :last_name].each do |attr|
242
+ expect(@conn.column_comment(TestEmployee.table_name, attr.to_s)).to eq(column_comments[attr])
243
+ end
244
+ [:first_name, :last_name].each do |attr|
245
+ expect(TestEmployee.columns_hash[attr.to_s].comment).to eq(column_comments[attr])
246
+ end
247
+ end
248
+
249
+ it "should query table_comment using bind variables" do
250
+ table_comment = "Test Employees"
251
+ create_test_employees_table(table_comment)
252
+ class ::TestEmployee < ActiveRecord::Base; end
253
+ expect(@conn.table_comment(TestEmployee.table_name)).to eq(table_comment)
254
+ expect(@logger.logged(:debug).last).to match(/:table_name/)
255
+ expect(@logger.logged(:debug).last).to match(/\["table_name", "TEST_EMPLOYEES"\]\]/)
256
+ end
257
+
258
+ it "should query column_comment using bind variables" do
259
+ table_comment = "Test Employees"
260
+ column_comment = { first_name: "Given Name" }
261
+ create_test_employees_table(table_comment, column_comment)
262
+ class ::TestEmployee < ActiveRecord::Base; end
263
+ expect(@conn.column_comment(TestEmployee.table_name, :first_name)).to eq(column_comment[:first_name])
264
+ expect(@logger.logged(:debug).last).to match(/:table_name/)
265
+ expect(@logger.logged(:debug).last).to match(/:column_name/)
266
+ expect(@logger.logged(:debug).last).to match(/\["table_name", "TEST_EMPLOYEES"\], \["column_name", "FIRST_NAME"\]\]/)
267
+ end
268
+ end
269
+
270
+ describe "drop tables" do
271
+ before(:each) do
272
+ @conn = ActiveRecord::Base.connection
273
+ end
274
+
275
+ it "should drop table with :if_exists option no raise error" do
276
+ expect do
277
+ @conn.drop_table("nonexistent_table", if_exists: true)
278
+ end.not_to raise_error
279
+ end
280
+ end
281
+
282
+ describe "rename tables and sequences" do
283
+ before(:each) do
284
+ @conn = ActiveRecord::Base.connection
285
+ schema_define do
286
+ create_table :test_employees, force: true do |t|
287
+ t.string :first_name
288
+ t.string :last_name
289
+ end
290
+
291
+ create_table :test_employees_no_pkey, force: true, id: false do |t|
292
+ t.string :first_name
293
+ t.string :last_name
294
+ end
295
+ end
296
+ end
297
+
298
+ after(:each) do
299
+ schema_define do
300
+ drop_table :test_employees_no_primary_key, if_exists: true
301
+ drop_table :test_employees, if_exists: true
302
+ drop_table :new_test_employees, if_exists: true
303
+ drop_table :test_employees_no_pkey, if_exists: true
304
+ drop_table :new_test_employees_no_pkey, if_exists: true
305
+ drop_table :aaaaaaaaaaaaaaaaaaaaaaaaaaa, if_exists: true
306
+ end
307
+ end
308
+
309
+ it "should rename table name with new one" do
310
+ expect do
311
+ @conn.rename_table("test_employees", "new_test_employees")
312
+ end.not_to raise_error
313
+ end
314
+
315
+ it "should raise error when new table name length is too long" do
316
+ expect do
317
+ @conn.rename_table("test_employees", "a" * 31)
318
+ end.to raise_error(ArgumentError)
319
+ end
320
+
321
+ it "should not raise error when new sequence name length is too long" do
322
+ expect do
323
+ @conn.rename_table("test_employees", "a" * 27)
324
+ end.not_to raise_error
325
+ end
326
+
327
+ it "should rename table when table has no primary key and sequence" do
328
+ expect do
329
+ @conn.rename_table("test_employees_no_pkey", "new_test_employees_no_pkey")
330
+ end.not_to raise_error
331
+ end
332
+ end
333
+
334
+ describe "add index" do
335
+ before(:all) do
336
+ @conn = ActiveRecord::Base.connection
337
+ end
338
+
339
+ it "should return default index name if it is not larger than 30 characters" do
340
+ expect(@conn.index_name("employees", column: "first_name")).to eq("index_employees_on_first_name")
341
+ end
342
+
343
+ it "should return shortened index name by removing 'index', 'on' and 'and' keywords" do
344
+ if @oracle12cr2_or_higher
345
+ expect(@conn.index_name("employees", column: ["first_name", "email"])).to eq("index_employees_on_first_name_and_email")
346
+ else
347
+ expect(@conn.index_name("employees", column: ["first_name", "email"])).to eq("i_employees_first_name_email")
348
+ end
349
+ end
350
+
351
+ it "should return shortened index name by shortening table and column names" do
352
+ if @oracle12cr2_or_higher
353
+ expect(@conn.index_name("employees", column: ["first_name", "last_name"])).to eq("index_employees_on_first_name_and_last_name")
354
+ else
355
+ expect(@conn.index_name("employees", column: ["first_name", "last_name"])).to eq("i_emp_fir_nam_las_nam")
356
+ end
357
+ end
358
+
359
+ it "should raise error if too large index name cannot be shortened" do
360
+ if @oracle12cr2_or_higher
361
+ expect(@conn.index_name("test_employees", column: ["first_name", "middle_name", "last_name"])).to eq(
362
+ ("index_test_employees_on_first_name_and_middle_name_and_last_name"))
363
+ else
364
+ expect(@conn.index_name("test_employees", column: ["first_name", "middle_name", "last_name"])).to eq(
365
+ "i" + OpenSSL::Digest::SHA1.hexdigest("index_test_employees_on_first_name_and_middle_name_and_last_name")[0, 29]
366
+ )
367
+ end
368
+ end
369
+ end
370
+
371
+ describe "rename index" do
372
+ before(:each) do
373
+ @conn = ActiveRecord::Base.connection
374
+ schema_define do
375
+ create_table :test_employees do |t|
376
+ t.string :first_name
377
+ t.string :last_name
378
+ end
379
+ add_index :test_employees, :first_name
380
+ end
381
+ class ::TestEmployee < ActiveRecord::Base; end
382
+ end
383
+
384
+ after(:each) do
385
+ schema_define do
386
+ drop_table :test_employees
387
+ end
388
+ Object.send(:remove_const, "TestEmployee")
389
+ ActiveRecord::Base.clear_cache!
390
+ end
391
+
392
+ it "should raise error when current index name and new index name are identical" do
393
+ expect do
394
+ @conn.rename_index("test_employees", "i_test_employees_first_name", "i_test_employees_first_name")
395
+ end.to raise_error(ActiveRecord::StatementInvalid)
396
+ end
397
+
398
+ it "should raise error when new index name length is too long" do
399
+ skip if @oracle12cr2_or_higher
400
+ expect do
401
+ @conn.rename_index("test_employees", "i_test_employees_first_name", "a" * 31)
402
+ end.to raise_error(ArgumentError)
403
+ end
404
+
405
+ it "should raise error when current index name does not exist" do
406
+ expect do
407
+ @conn.rename_index("test_employees", "nonexist_index_name", "new_index_name")
408
+ end.to raise_error(ActiveRecord::StatementInvalid)
409
+ end
410
+
411
+ it "should rename index name with new one" do
412
+ skip if @oracle12cr2_or_higher
413
+ expect do
414
+ @conn.rename_index("test_employees", "i_test_employees_first_name", "new_index_name")
415
+ end.not_to raise_error
416
+ end
417
+ end
418
+
419
+ describe "add timestamps" do
420
+ before(:each) do
421
+ @conn = ActiveRecord::Base.connection
422
+ schema_define do
423
+ create_table :test_employees, force: true
424
+ end
425
+ class ::TestEmployee < ActiveRecord::Base; end
426
+ end
427
+
428
+ after(:each) do
429
+ schema_define do
430
+ drop_table :test_employees, if_exists: true
431
+ end
432
+ Object.send(:remove_const, "TestEmployee")
433
+ ActiveRecord::Base.clear_cache!
434
+ end
435
+
436
+ it "should add created_at and updated_at" do
437
+ expect do
438
+ @conn.add_timestamps("test_employees")
439
+ end.not_to raise_error
440
+
441
+ TestEmployee.reset_column_information
442
+ expect(TestEmployee.columns_hash["created_at"]).not_to be_nil
443
+ expect(TestEmployee.columns_hash["updated_at"]).not_to be_nil
444
+ end
445
+ end
446
+
447
+ describe "ignore options for LOB columns" do
448
+ after(:each) do
449
+ schema_define do
450
+ drop_table :test_posts
451
+ end
452
+ end
453
+
454
+ it "should ignore :limit option for :text column" do
455
+ expect do
456
+ schema_define do
457
+ create_table :test_posts, force: true do |t|
458
+ t.text :body, limit: 10000
459
+ end
460
+ end
461
+ end.not_to raise_error
462
+ end
463
+
464
+ it "should ignore :limit option for :binary column" do
465
+ expect do
466
+ schema_define do
467
+ create_table :test_posts, force: true do |t|
468
+ t.binary :picture, limit: 10000
469
+ end
470
+ end
471
+ end.not_to raise_error
472
+ end
473
+ end
474
+
475
+ describe "foreign key constraints" do
476
+ let(:table_name_prefix) { "" }
477
+ let(:table_name_suffix) { "" }
478
+
479
+ before(:each) do
480
+ ActiveRecord::Base.table_name_prefix = table_name_prefix
481
+ ActiveRecord::Base.table_name_suffix = table_name_suffix
482
+ schema_define do
483
+ create_table :test_posts, force: true do |t|
484
+ t.string :title
485
+ end
486
+ create_table :test_comments, force: true do |t|
487
+ t.string :body, limit: 4000
488
+ t.references :test_post
489
+ t.integer :post_id
490
+ end
491
+ end
492
+ class ::TestPost < ActiveRecord::Base
493
+ has_many :test_comments
494
+ end
495
+ class ::TestComment < ActiveRecord::Base
496
+ belongs_to :test_post
497
+ end
498
+ set_logger
499
+ end
500
+
501
+ after(:each) do
502
+ Object.send(:remove_const, "TestPost")
503
+ Object.send(:remove_const, "TestComment")
504
+ schema_define do
505
+ drop_table :test_comments, if_exists: true
506
+ drop_table :test_posts, if_exists: true
507
+ end
508
+ ActiveRecord::Base.table_name_prefix = ""
509
+ ActiveRecord::Base.table_name_suffix = ""
510
+ ActiveRecord::Base.clear_cache!
511
+ clear_logger
512
+ end
513
+
514
+ it "should add foreign key" do
515
+ fk_name = "fk_rails_#{OpenSSL::Digest::SHA256.hexdigest("test_comments_test_post_id_fk").first(10)}"
516
+
517
+ schema_define do
518
+ add_foreign_key :test_comments, :test_posts
519
+ end
520
+ expect do
521
+ TestComment.create(body: "test", test_post_id: 1)
522
+ end.to raise_error() { |e| expect(e.message).to match(/ORA-02291.*\.#{fk_name}/i) }
523
+ end
524
+
525
+ it "should add foreign key with name" do
526
+ schema_define do
527
+ add_foreign_key :test_comments, :test_posts, name: "comments_posts_fk"
528
+ end
529
+ expect do
530
+ TestComment.create(body: "test", test_post_id: 1)
531
+ end.to raise_error() { |e| expect(e.message).to match(/ORA-02291.*\.COMMENTS_POSTS_FK/) }
532
+ end
533
+
534
+ it "should add foreign key with column" do
535
+ fk_name = "fk_rails_#{OpenSSL::Digest::SHA256.hexdigest("test_comments_post_id_fk").first(10)}"
536
+
537
+ schema_define do
538
+ add_foreign_key :test_comments, :test_posts, column: "post_id"
539
+ end
540
+ expect do
541
+ TestComment.create(body: "test", post_id: 1)
542
+ end.to raise_error() { |e| expect(e.message).to match(/ORA-02291.*\.#{fk_name}/i) }
543
+ end
544
+
545
+ it "should add foreign key with delete dependency" do
546
+ schema_define do
547
+ add_foreign_key :test_comments, :test_posts, on_delete: :cascade
548
+ end
549
+ p = TestPost.create(title: "test")
550
+ c = TestComment.create(body: "test", test_post: p)
551
+ TestPost.delete(p.id)
552
+ expect(TestComment.find_by_id(c.id)).to be_nil
553
+ end
554
+
555
+ it "should add foreign key with nullify dependency" do
556
+ schema_define do
557
+ add_foreign_key :test_comments, :test_posts, on_delete: :nullify
558
+ end
559
+ p = TestPost.create(title: "test")
560
+ c = TestComment.create(body: "test", test_post: p)
561
+ TestPost.delete(p.id)
562
+ expect(TestComment.find_by_id(c.id).test_post_id).to be_nil
563
+ end
564
+
565
+ it "should remove foreign key by table name" do
566
+ schema_define do
567
+ add_foreign_key :test_comments, :test_posts
568
+ remove_foreign_key :test_comments, :test_posts
569
+ end
570
+ expect do
571
+ TestComment.create(body: "test", test_post_id: 1)
572
+ end.not_to raise_error
573
+ end
574
+
575
+ it "should remove foreign key by constraint name" do
576
+ schema_define do
577
+ add_foreign_key :test_comments, :test_posts, name: "comments_posts_fk"
578
+ remove_foreign_key :test_comments, name: "comments_posts_fk"
579
+ end
580
+ expect do
581
+ TestComment.create(body: "test", test_post_id: 1)
582
+ end.not_to raise_error
583
+ end
584
+
585
+ it "should remove foreign key by column name" do
586
+ schema_define do
587
+ add_foreign_key :test_comments, :test_posts
588
+ remove_foreign_key :test_comments, column: "test_post_id"
589
+ end
590
+ expect do
591
+ TestComment.create(body: "test", test_post_id: 1)
592
+ end.not_to raise_error
593
+ end
594
+
595
+ it "should query foreign_keys using bind variables" do
596
+ schema_define do
597
+ add_foreign_key :test_comments, :test_posts
598
+ end
599
+ ActiveRecord::Base.connection.foreign_keys(:test_comments)
600
+ expect(@logger.logged(:debug).last).to match(/:desc_table_name/)
601
+ expect(@logger.logged(:debug).last).to match(/\["desc_table_name", "TEST_COMMENTS"\]\]/)
602
+ end
603
+ end
604
+
605
+ describe "lob in table definition" do
606
+ before do
607
+ class ::TestPost < ActiveRecord::Base
608
+ end
609
+ end
610
+
611
+ it "should use default tablespace for clobs" do
612
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:clob] = DATABASE_NON_DEFAULT_TABLESPACE
613
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:nclob] = nil
614
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:blob] = nil
615
+ schema_define do
616
+ create_table :test_posts, force: true do |t|
617
+ t.text :test_clob
618
+ t.ntext :test_nclob
619
+ t.binary :test_blob
620
+ end
621
+ end
622
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_CLOB'")).to eq(DATABASE_NON_DEFAULT_TABLESPACE)
623
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_NCLOB'")).not_to eq(DATABASE_NON_DEFAULT_TABLESPACE)
624
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_BLOB'")).not_to eq(DATABASE_NON_DEFAULT_TABLESPACE)
625
+ end
626
+
627
+ it "should use default tablespace for nclobs" do
628
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:nclob] = DATABASE_NON_DEFAULT_TABLESPACE
629
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:clob] = nil
630
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:blob] = nil
631
+ schema_define do
632
+ create_table :test_posts, force: true do |t|
633
+ t.text :test_clob
634
+ t.ntext :test_nclob
635
+ t.binary :test_blob
636
+ end
637
+ end
638
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_NCLOB'")).to eq(DATABASE_NON_DEFAULT_TABLESPACE)
639
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_CLOB'")).not_to eq(DATABASE_NON_DEFAULT_TABLESPACE)
640
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_BLOB'")).not_to eq(DATABASE_NON_DEFAULT_TABLESPACE)
641
+ end
642
+
643
+ it "should use default tablespace for blobs" do
644
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:blob] = DATABASE_NON_DEFAULT_TABLESPACE
645
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:clob] = nil
646
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:nclob] = nil
647
+ schema_define do
648
+ create_table :test_posts, force: true do |t|
649
+ t.text :test_clob
650
+ t.ntext :test_nclob
651
+ t.binary :test_blob
652
+ end
653
+ end
654
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_BLOB'")).to eq(DATABASE_NON_DEFAULT_TABLESPACE)
655
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_CLOB'")).not_to eq(DATABASE_NON_DEFAULT_TABLESPACE)
656
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_NCLOB'")).not_to eq(DATABASE_NON_DEFAULT_TABLESPACE)
657
+ end
658
+
659
+ after do
660
+ Object.send(:remove_const, "TestPost")
661
+ schema_define do
662
+ drop_table :test_posts, if_exists: true
663
+ end
664
+ end
665
+ end
666
+
667
+ describe "primary key in table definition" do
668
+ before do
669
+ @conn = ActiveRecord::Base.connection
670
+
671
+ class ::TestPost < ActiveRecord::Base
672
+ end
673
+ end
674
+
675
+ it "should use default tablespace for primary key" do
676
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:index] = nil
677
+ schema_define do
678
+ create_table :test_posts, force: true
679
+ end
680
+
681
+ index_name = @conn.select_value(
682
+ "SELECT index_name FROM all_constraints
683
+ WHERE table_name = 'TEST_POSTS'
684
+ AND constraint_type = 'P'
685
+ AND owner = SYS_CONTEXT('userenv', 'current_schema')")
686
+
687
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_indexes WHERE index_name = '#{index_name}'")).to eq("USERS")
688
+ end
689
+
690
+ it "should use non default tablespace for primary key" do
691
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:index] = DATABASE_NON_DEFAULT_TABLESPACE
692
+ schema_define do
693
+ create_table :test_posts, force: true
694
+ end
695
+
696
+ index_name = @conn.select_value(
697
+ "SELECT index_name FROM all_constraints
698
+ WHERE table_name = 'TEST_POSTS'
699
+ AND constraint_type = 'P'
700
+ AND owner = SYS_CONTEXT('userenv', 'current_schema')")
701
+
702
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_indexes WHERE index_name = '#{index_name}'")).to eq(DATABASE_NON_DEFAULT_TABLESPACE)
703
+ end
704
+
705
+ after do
706
+ Object.send(:remove_const, "TestPost")
707
+ schema_define do
708
+ drop_table :test_posts, if_exists: true
709
+ end
710
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:index] = nil
711
+ end
712
+ end
713
+
714
+ describe "foreign key in table definition" do
715
+ before(:each) do
716
+ schema_define do
717
+ create_table :test_posts, force: true do |t|
718
+ t.string :title
719
+ end
720
+ end
721
+ class ::TestPost < ActiveRecord::Base
722
+ has_many :test_comments
723
+ end
724
+ class ::TestComment < ActiveRecord::Base
725
+ belongs_to :test_post
726
+ end
727
+ end
728
+
729
+ after(:each) do
730
+ Object.send(:remove_const, "TestPost")
731
+ Object.send(:remove_const, "TestComment")
732
+ schema_define do
733
+ drop_table :test_comments, if_exists: true
734
+ drop_table :test_posts, if_exists: true
735
+ end
736
+ ActiveRecord::Base.clear_cache!
737
+ end
738
+
739
+ it "should add foreign key in create_table" do
740
+ schema_define do
741
+ create_table :test_comments, force: true do |t|
742
+ t.string :body, limit: 4000
743
+ t.references :test_post
744
+ t.foreign_key :test_posts
745
+ end
746
+ end
747
+ expect do
748
+ TestComment.create(body: "test", test_post_id: 1)
749
+ end.to raise_error() { |e| expect(e.message).to match(/ORA-02291/) }
750
+ end
751
+
752
+ it "should add foreign key in create_table references" do
753
+ schema_define do
754
+ create_table :test_comments, force: true do |t|
755
+ t.string :body, limit: 4000
756
+ t.references :test_post, foreign_key: true
757
+ end
758
+ end
759
+ expect do
760
+ TestComment.create(body: "test", test_post_id: 1)
761
+ end.to raise_error() { |e| expect(e.message).to match(/ORA-02291/) }
762
+ end
763
+
764
+ it "should add foreign key in change_table" do
765
+ schema_define do
766
+ create_table :test_comments, force: true do |t|
767
+ t.string :body, limit: 4000
768
+ t.references :test_post
769
+ end
770
+ change_table :test_comments do |t|
771
+ t.foreign_key :test_posts
772
+ end
773
+ end
774
+ expect do
775
+ TestComment.create(body: "test", test_post_id: 1)
776
+ end.to raise_error() { |e| expect(e.message).to match(/ORA-02291/) }
777
+ end
778
+
779
+ it "should add foreign key in change_table references" do
780
+ schema_define do
781
+ create_table :test_comments, force: true do |t|
782
+ t.string :body, limit: 4000
783
+ end
784
+ change_table :test_comments do |t|
785
+ t.references :test_post, foreign_key: true
786
+ end
787
+ end
788
+ expect do
789
+ TestComment.create(body: "test", test_post_id: 1)
790
+ end.to raise_error() { |e| expect(e.message).to match(/ORA-02291/) }
791
+ end
792
+ end
793
+
794
+ describe "disable referential integrity" do
795
+ before(:all) do
796
+ @conn = ActiveRecord::Base.connection
797
+ end
798
+
799
+ before(:each) do
800
+ schema_define do
801
+ create_table :test_posts, force: true do |t|
802
+ t.string :title
803
+ end
804
+ create_table :test_comments, force: true do |t|
805
+ t.string :body, limit: 4000
806
+ t.references :test_post, foreign_key: true
807
+ end
808
+ create_table "test_Mixed_Comments", force: true do |t|
809
+ t.string :body, limit: 4000
810
+ t.references :test_post, foreign_key: true
811
+ end
812
+ end
813
+ end
814
+
815
+ after(:each) do
816
+ schema_define do
817
+ drop_table "test_Mixed_Comments", if_exists: true
818
+ drop_table :test_comments, if_exists: true
819
+ drop_table :test_posts, if_exists: true
820
+ end
821
+ end
822
+
823
+ it "should disable all foreign keys" do
824
+ expect do
825
+ @conn.execute "INSERT INTO test_comments (id, body, test_post_id) VALUES (1, 'test', 1)"
826
+ end.to raise_error(ActiveRecord::InvalidForeignKey)
827
+ @conn.disable_referential_integrity do
828
+ expect do
829
+ @conn.execute "INSERT INTO \"test_Mixed_Comments\" (id, body, test_post_id) VALUES (2, 'test', 2)"
830
+ @conn.execute "INSERT INTO test_comments (id, body, test_post_id) VALUES (2, 'test', 2)"
831
+ @conn.execute "INSERT INTO test_posts (id, title) VALUES (2, 'test')"
832
+ end.not_to raise_error
833
+ end
834
+ expect do
835
+ @conn.execute "INSERT INTO test_comments (id, body, test_post_id) VALUES (3, 'test', 3)"
836
+ end.to raise_error(ActiveRecord::InvalidForeignKey)
837
+ end
838
+ end
839
+
840
+ describe "synonyms" do
841
+ before(:all) do
842
+ @conn = ActiveRecord::Base.connection
843
+ @username = CONNECTION_PARAMS[:username]
844
+ schema_define do
845
+ create_table :test_posts, force: true do |t|
846
+ t.string :title
847
+ end
848
+ end
849
+ end
850
+
851
+ after(:all) do
852
+ schema_define do
853
+ drop_table :test_posts
854
+ end
855
+ end
856
+
857
+ before(:each) do
858
+ class ::TestPost < ActiveRecord::Base
859
+ self.table_name = "synonym_to_posts"
860
+ end
861
+ end
862
+
863
+ after(:each) do
864
+ Object.send(:remove_const, "TestPost")
865
+ schema_define do
866
+ remove_synonym :synonym_to_posts
867
+ remove_synonym :synonym_to_posts_seq
868
+ end
869
+ ActiveRecord::Base.clear_cache!
870
+ end
871
+
872
+ it "should create synonym to table and sequence" do
873
+ schema_name = @username
874
+ schema_define do
875
+ add_synonym :synonym_to_posts, "#{schema_name}.test_posts", force: true
876
+ add_synonym :synonym_to_posts_seq, "#{schema_name}.test_posts_seq", force: true
877
+ end
878
+ expect do
879
+ TestPost.create(title: "test")
880
+ end.not_to raise_error
881
+ end
882
+ end
883
+
884
+ describe "alter columns with column cache" do
885
+ include LoggerSpecHelper
886
+
887
+ before(:all) do
888
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:clob)
889
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:nclob)
890
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:blob)
891
+ end
892
+
893
+ after(:all) do
894
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:clob)
895
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:nclob)
896
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:blob)
897
+ end
898
+
899
+ before(:each) do
900
+ schema_define do
901
+ create_table :test_posts, force: true do |t|
902
+ t.string :title, null: false
903
+ t.string :content
904
+ end
905
+ end
906
+ class ::TestPost < ActiveRecord::Base; end
907
+ expect(TestPost.columns_hash["title"].null).to be_falsey
908
+ end
909
+
910
+ after(:each) do
911
+ Object.send(:remove_const, "TestPost")
912
+ schema_define { drop_table :test_posts }
913
+ ActiveRecord::Base.clear_cache!
914
+ end
915
+
916
+ it "should change column to nullable" do
917
+ schema_define do
918
+ change_column :test_posts, :title, :string, null: true
919
+ end
920
+ TestPost.reset_column_information
921
+ expect(TestPost.columns_hash["title"].null).to be_truthy
922
+ end
923
+
924
+ it "should add column" do
925
+ schema_define do
926
+ add_column :test_posts, :body, :string
927
+ end
928
+ TestPost.reset_column_information
929
+ expect(TestPost.columns_hash["body"]).not_to be_nil
930
+ end
931
+
932
+ it "should add longer column" do
933
+ skip unless @oracle12cr2_or_higher
934
+ schema_define do
935
+ add_column :test_posts, "a" * 128, :string
936
+ end
937
+ TestPost.reset_column_information
938
+ expect(TestPost.columns_hash["a" * 128]).not_to be_nil
939
+ end
940
+
941
+ it "should add text type lob column with non_default tablespace" do
942
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:clob] = DATABASE_NON_DEFAULT_TABLESPACE
943
+ schema_define do
944
+ add_column :test_posts, :body, :text
945
+ end
946
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'BODY'")).to eq(DATABASE_NON_DEFAULT_TABLESPACE)
947
+ end
948
+
949
+ it "should add ntext type lob column with non_default tablespace" do
950
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:nclob] = DATABASE_NON_DEFAULT_TABLESPACE
951
+ schema_define do
952
+ add_column :test_posts, :body, :ntext
953
+ end
954
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'BODY'")).to eq(DATABASE_NON_DEFAULT_TABLESPACE)
955
+ end
956
+
957
+ it "should add blob column with non_default tablespace" do
958
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:blob] = DATABASE_NON_DEFAULT_TABLESPACE
959
+ schema_define do
960
+ add_column :test_posts, :attachment, :binary
961
+ end
962
+ expect(TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'ATTACHMENT'")).to eq(DATABASE_NON_DEFAULT_TABLESPACE)
963
+ end
964
+
965
+ it "should rename column" do
966
+ schema_define do
967
+ rename_column :test_posts, :title, :subject
968
+ end
969
+ TestPost.reset_column_information
970
+ expect(TestPost.columns_hash["subject"]).not_to be_nil
971
+ expect(TestPost.columns_hash["title"]).to be_nil
972
+ end
973
+
974
+ it "should remove column" do
975
+ schema_define do
976
+ remove_column :test_posts, :title
977
+ end
978
+ TestPost.reset_column_information
979
+ expect(TestPost.columns_hash["title"]).to be_nil
980
+ end
981
+
982
+ it "should remove column when using change_table" do
983
+ schema_define do
984
+ change_table :test_posts do |t|
985
+ t.remove :title
986
+ end
987
+ end
988
+ TestPost.reset_column_information
989
+ expect(TestPost.columns_hash["title"]).to be_nil
990
+ end
991
+
992
+ it "should remove multiple columns when using change_table" do
993
+ schema_define do
994
+ change_table :test_posts do |t|
995
+ t.remove :title, :content
996
+ end
997
+ end
998
+ TestPost.reset_column_information
999
+ expect(TestPost.columns_hash["title"]).to be_nil
1000
+ expect(TestPost.columns_hash["content"]).to be_nil
1001
+ end
1002
+
1003
+ it "should ignore type and options parameter and remove column" do
1004
+ schema_define do
1005
+ remove_column :test_posts, :title, :string, {}
1006
+ end
1007
+ TestPost.reset_column_information
1008
+ expect(TestPost.columns_hash["title"]).to be_nil
1009
+ end
1010
+ end
1011
+
1012
+ describe "virtual columns in create_table" do
1013
+ before(:each) do
1014
+ skip "Not supported in this database version" unless @oracle11g_or_higher
1015
+ end
1016
+
1017
+ it "should raise error if column expression is not provided" do
1018
+ expect {
1019
+ schema_define do
1020
+ create_table :test_fractions do |t|
1021
+ t.integer :field1
1022
+ t.virtual :field2
1023
+ end
1024
+ end
1025
+ }.to raise_error(RuntimeError, "No virtual column definition found.")
1026
+ end
1027
+ end
1028
+
1029
+ describe "virtual columns" do
1030
+ before(:each) do
1031
+ skip "Not supported in this database version" unless @oracle11g_or_higher
1032
+ expr = "( numerator/NULLIF(denominator,0) )*100"
1033
+ schema_define do
1034
+ create_table :test_fractions, force: true do |t|
1035
+ t.integer :numerator, default: 0
1036
+ t.integer :denominator, default: 0
1037
+ t.virtual :percent, as: expr
1038
+ end
1039
+ end
1040
+ class ::TestFraction < ActiveRecord::Base
1041
+ self.table_name = "test_fractions"
1042
+ end
1043
+ TestFraction.reset_column_information
1044
+ end
1045
+
1046
+ after(:each) do
1047
+ if @oracle11g_or_higher
1048
+ schema_define do
1049
+ drop_table :test_fractions
1050
+ end
1051
+ end
1052
+ end
1053
+
1054
+ it "should include virtual columns and not try to update them" do
1055
+ tf = TestFraction.columns.detect { |c| c.virtual? }
1056
+ expect(tf).not_to be_nil
1057
+ expect(tf.name).to eq("percent")
1058
+ expect(tf.virtual?).to be true
1059
+ expect do
1060
+ tf = TestFraction.new(numerator: 20, denominator: 100)
1061
+ expect(tf.percent).to be_nil # not whatever is in DATA_DEFAULT column
1062
+ tf.save!
1063
+ tf.reload
1064
+ end.not_to raise_error
1065
+ expect(tf.percent.to_i).to eq(20)
1066
+ end
1067
+
1068
+ it "should add virtual column" do
1069
+ schema_define do
1070
+ add_column :test_fractions, :rem, :virtual, as: "remainder(numerator, NULLIF(denominator,0))"
1071
+ end
1072
+ TestFraction.reset_column_information
1073
+ tf = TestFraction.columns.detect { |c| c.name == "rem" }
1074
+ expect(tf).not_to be_nil
1075
+ expect(tf.virtual?).to be true
1076
+ expect do
1077
+ tf = TestFraction.new(numerator: 7, denominator: 5)
1078
+ expect(tf.rem).to be_nil
1079
+ tf.save!
1080
+ tf.reload
1081
+ end.not_to raise_error
1082
+ expect(tf.rem.to_i).to eq(2)
1083
+ end
1084
+
1085
+ it "should add virtual column with explicit type" do
1086
+ schema_define do
1087
+ add_column :test_fractions, :expression, :virtual, as: "TO_CHAR(numerator) || '/' || TO_CHAR(denominator)", type: :string, limit: 100
1088
+ end
1089
+ TestFraction.reset_column_information
1090
+ tf = TestFraction.columns.detect { |c| c.name == "expression" }
1091
+ expect(tf).not_to be_nil
1092
+ expect(tf.virtual?).to be true
1093
+ expect(tf.type).to be :string
1094
+ expect(tf.limit).to be 100
1095
+ expect do
1096
+ tf = TestFraction.new(numerator: 7, denominator: 5)
1097
+ expect(tf.expression).to be_nil
1098
+ tf.save!
1099
+ tf.reload
1100
+ end.not_to raise_error
1101
+ expect(tf.expression).to eq("7/5")
1102
+ end
1103
+
1104
+ it "should change virtual column definition" do
1105
+ schema_define do
1106
+ change_column :test_fractions, :percent, :virtual,
1107
+ as: "ROUND((numerator/NULLIF(denominator,0))*100, 2)", type: :decimal, precision: 15, scale: 2
1108
+ end
1109
+ TestFraction.reset_column_information
1110
+ tf = TestFraction.columns.detect { |c| c.name == "percent" }
1111
+ expect(tf).not_to be_nil
1112
+ expect(tf.virtual?).to be true
1113
+ expect(tf.type).to be :decimal
1114
+ expect(tf.precision).to be 15
1115
+ expect(tf.scale).to be 2
1116
+ expect do
1117
+ tf = TestFraction.new(numerator: 11, denominator: 17)
1118
+ expect(tf.percent).to be_nil
1119
+ tf.save!
1120
+ tf.reload
1121
+ end.not_to raise_error
1122
+ expect(tf.percent).to eq("64.71".to_d)
1123
+ end
1124
+
1125
+ it "should change virtual column type" do
1126
+ schema_define do
1127
+ change_column :test_fractions, :percent, :virtual, type: :decimal, precision: 12, scale: 5
1128
+ end
1129
+ TestFraction.reset_column_information
1130
+ tf = TestFraction.columns.detect { |c| c.name == "percent" }
1131
+ expect(tf).not_to be_nil
1132
+ expect(tf.virtual?).to be true
1133
+ expect(tf.type).to be :decimal
1134
+ expect(tf.precision).to be 12
1135
+ expect(tf.scale).to be 5
1136
+ expect do
1137
+ tf = TestFraction.new(numerator: 11, denominator: 17)
1138
+ expect(tf.percent).to be_nil
1139
+ tf.save!
1140
+ tf.reload
1141
+ end.not_to raise_error
1142
+ expect(tf.percent).to eq("64.70588".to_d)
1143
+ end
1144
+ end
1145
+
1146
+ describe "materialized views" do
1147
+ before(:all) do
1148
+ @conn = ActiveRecord::Base.connection
1149
+ schema_define do
1150
+ create_table :test_employees, force: true do |t|
1151
+ t.string :first_name
1152
+ t.string :last_name
1153
+ end
1154
+ end
1155
+ @conn.execute("create materialized view sum_test_employees as select first_name, count(*) from test_employees group by first_name")
1156
+ class ::TestEmployee < ActiveRecord::Base; end
1157
+ end
1158
+
1159
+ after(:all) do
1160
+ @conn.execute("drop materialized view sum_test_employees") rescue nil
1161
+ schema_define do
1162
+ drop_table :sum_test_employees, if_exists: true
1163
+ drop_table :test_employees, if_exists: true
1164
+ end
1165
+ end
1166
+
1167
+ it "tables should not return materialized views" do
1168
+ expect(@conn.tables).not_to include("sum_test_employees")
1169
+ end
1170
+
1171
+ it "materialized_views should return materialized views" do
1172
+ expect(@conn.materialized_views).to include("sum_test_employees")
1173
+ end
1174
+ end
1175
+
1176
+ describe "miscellaneous options" do
1177
+ before(:all) do
1178
+ @conn = ActiveRecord::Base.connection
1179
+ end
1180
+
1181
+ before(:each) do
1182
+ @conn.instance_variable_set :@would_execute_sql, @would_execute_sql = +""
1183
+ class << @conn
1184
+ def execute(sql, name = nil); @would_execute_sql << sql << ";\n"; end
1185
+ end
1186
+ end
1187
+
1188
+ after(:each) do
1189
+ class << @conn
1190
+ remove_method :execute
1191
+ end
1192
+ @conn.instance_eval { remove_instance_variable :@would_execute_sql }
1193
+ end
1194
+
1195
+ it "should support the :options option to create_table" do
1196
+ schema_define do
1197
+ create_table :test_posts, options: "NOLOGGING", force: true do |t|
1198
+ t.string :title, null: false
1199
+ end
1200
+ end
1201
+ expect(@would_execute_sql).to match(/CREATE +TABLE .* \(.*\) NOLOGGING/)
1202
+ end
1203
+
1204
+ it "should support the :tablespace option to create_table" do
1205
+ schema_define do
1206
+ create_table :test_posts, tablespace: "bogus", force: true do |t|
1207
+ t.string :title, null: false
1208
+ end
1209
+ end
1210
+ expect(@would_execute_sql).to match(/CREATE +TABLE .* \(.*\) TABLESPACE bogus/)
1211
+ end
1212
+
1213
+ describe "creating a table with a tablespace defaults set" do
1214
+ after(:each) do
1215
+ @conn.drop_table :tablespace_tests, if_exists: true
1216
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:table)
1217
+ end
1218
+
1219
+ it "should use correct tablespace" do
1220
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:table] = DATABASE_NON_DEFAULT_TABLESPACE
1221
+ @conn.create_table :tablespace_tests do |t|
1222
+ t.string :foo
1223
+ end
1224
+ expect(@would_execute_sql).to match(/CREATE +TABLE .* \(.*\) TABLESPACE #{DATABASE_NON_DEFAULT_TABLESPACE}/)
1225
+ end
1226
+ end
1227
+
1228
+ describe "creating an index-organized table" do
1229
+ after(:each) do
1230
+ @conn.drop_table :tablespace_tests, if_exists: true
1231
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:table)
1232
+ end
1233
+
1234
+ it "should use correct tablespace" do
1235
+ @conn.create_table :tablespace_tests, id: false, organization: "INDEX INITRANS 4 COMPRESS 1", tablespace: "bogus" do |t|
1236
+ t.integer :id
1237
+ end
1238
+ expect(@would_execute_sql).to match(/CREATE +TABLE .*\(.*\)\s+ORGANIZATION INDEX INITRANS 4 COMPRESS 1 TABLESPACE bogus/)
1239
+ end
1240
+ end
1241
+
1242
+ it "should support the :options option to add_index" do
1243
+ schema_define do
1244
+ add_index :keyboards, :name, options: "NOLOGGING"
1245
+ end
1246
+ expect(@would_execute_sql).to match(/CREATE +INDEX .* ON .* \(.*\) NOLOGGING/)
1247
+ end
1248
+
1249
+ it "should support the :tablespace option to add_index" do
1250
+ schema_define do
1251
+ add_index :keyboards, :name, tablespace: "bogus"
1252
+ end
1253
+ expect(@would_execute_sql).to match(/CREATE +INDEX .* ON .* \(.*\) TABLESPACE bogus/)
1254
+ end
1255
+
1256
+ it "should use default_tablespaces in add_index" do
1257
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:index] = DATABASE_NON_DEFAULT_TABLESPACE
1258
+ schema_define do
1259
+ add_index :keyboards, :name
1260
+ end
1261
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces.delete(:index)
1262
+ expect(@would_execute_sql).to match(/CREATE +INDEX .* ON .* \(.*\) TABLESPACE #{DATABASE_NON_DEFAULT_TABLESPACE}/)
1263
+ end
1264
+
1265
+ it "should create unique function index but not create unique constraints" do
1266
+ schema_define do
1267
+ add_index :keyboards, "lower(name)", unique: true, name: :index_keyboards_on_lower_name
1268
+ end
1269
+ expect(@would_execute_sql).not_to include("ADD CONSTRAINT")
1270
+ end
1271
+
1272
+ it "should add unique constraint only to the index where it was defined" do
1273
+ schema_define do
1274
+ add_index :keyboards, ["name"], unique: true, name: :this_index
1275
+ end
1276
+ expect(@would_execute_sql.lines.last).to match(/ALTER +TABLE .* ADD CONSTRAINT .* UNIQUE \(.*\) USING INDEX "THIS_INDEX";/)
1277
+ end
1278
+ end
1279
+
1280
+ describe "load schema" do
1281
+ let(:versions) {
1282
+ %w(20160101000000 20160102000000 20160103000000)
1283
+ }
1284
+
1285
+ before do
1286
+ @conn = ActiveRecord::Base.connection
1287
+ ActiveRecord::Base.connection_pool.schema_migration.create_table
1288
+ end
1289
+
1290
+ context "multi insert is supported" do
1291
+ it "should loads the migration schema table from insert versions sql" do
1292
+ skip "Not supported in this database version" unless ActiveRecord::Base.connection.supports_multi_insert?
1293
+
1294
+ expect {
1295
+ @conn.execute @conn.insert_versions_sql(versions)
1296
+ }.not_to raise_error
1297
+
1298
+ expect(@conn.select_value("SELECT COUNT(version) FROM schema_migrations")).to eq versions.count
1299
+ end
1300
+ end
1301
+
1302
+ context "multi insert is NOT supported" do
1303
+ it "should loads the migration schema table from insert versions sql" do
1304
+ skip "Not supported in this database version" if ActiveRecord::Base.connection.supports_multi_insert?
1305
+
1306
+ expect {
1307
+ versions.each { |version| @conn.execute @conn.insert_versions_sql(version) }
1308
+ }.not_to raise_error
1309
+
1310
+ expect(@conn.select_value("SELECT COUNT(version) FROM schema_migrations")).to eq versions.count
1311
+ end
1312
+ end
1313
+
1314
+ after do
1315
+ ActiveRecord::Base.connection_pool.schema_migration.drop_table
1316
+ end
1317
+ end
1318
+ end