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