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,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter handling of DECIMAL columns" do
4
+ include SchemaSpecHelper
5
+
6
+ before(:all) do
7
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
8
+ @conn = ActiveRecord::Base.connection
9
+ schema_define do
10
+ create_table :test2_employees, force: true do |t|
11
+ t.string :first_name, limit: 20
12
+ t.string :last_name, limit: 25
13
+ t.string :email, limit: 25
14
+ t.string :phone_number, limit: 25
15
+ t.date :hire_date
16
+ t.integer :job_id
17
+ t.integer :salary
18
+ t.decimal :commission_pct, scale: 2, precision: 2
19
+ t.decimal :hourly_rate
20
+ t.integer :manager_id, limit: 6
21
+ t.integer :is_manager, limit: 1
22
+ t.decimal :department_id, scale: 0, precision: 4
23
+ t.timestamps
24
+ end
25
+ end
26
+ class ::Test2Employee < ActiveRecord::Base
27
+ end
28
+ end
29
+
30
+ after(:all) do
31
+ Object.send(:remove_const, "Test2Employee")
32
+ @conn.drop_table :test2_employees, if_exists: true
33
+ end
34
+
35
+ it "should set DECIMAL column type as decimal" do
36
+ columns = @conn.columns("test2_employees")
37
+ column = columns.detect { |c| c.name == "hourly_rate" }
38
+ expect(column.type).to eq(:decimal)
39
+ end
40
+
41
+ it "should DECIMAL column type returns an exact value" do
42
+ employee = Test2Employee.create(hourly_rate: 4.40125)
43
+
44
+ employee.reload
45
+
46
+ expect(employee.hourly_rate).to eq(4.40125)
47
+ end
48
+
49
+ it "should DECIMAL column type rounds if scale is specified and value exceeds scale" do
50
+ employee = Test2Employee.create(commission_pct: 0.1575)
51
+
52
+ employee.reload
53
+
54
+ expect(employee.commission_pct).to eq(0.16)
55
+ end
56
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter dirty object tracking" do
4
+ include SchemaSpecHelper
5
+
6
+ before(:all) do
7
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
8
+ schema_define do
9
+ create_table :test_employees, force: true do |t|
10
+ t.string :first_name, limit: 20
11
+ t.string :last_name, limit: 25
12
+ t.integer :job_id, limit: 6, null: true
13
+ t.decimal :salary, precision: 8, scale: 2
14
+ t.text :comments
15
+ t.date :hire_date
16
+ end
17
+ end
18
+
19
+ class TestEmployee < ActiveRecord::Base
20
+ end
21
+ end
22
+
23
+ after(:all) do
24
+ schema_define do
25
+ drop_table :test_employees
26
+ end
27
+ Object.send(:remove_const, "TestEmployee")
28
+ ActiveRecord::Base.clear_cache!
29
+ end
30
+
31
+ it "should not mark empty string (stored as NULL) as changed when reassigning it" do
32
+ @employee = TestEmployee.create!(first_name: "")
33
+ @employee.first_name = ""
34
+ expect(@employee).not_to be_changed
35
+ @employee.reload
36
+ @employee.first_name = ""
37
+ expect(@employee).not_to be_changed
38
+ end
39
+
40
+ it "should not mark empty integer (stored as NULL) as changed when reassigning it" do
41
+ @employee = TestEmployee.create!(job_id: "")
42
+ @employee.job_id = ""
43
+ expect(@employee).not_to be_changed
44
+ @employee.reload
45
+ @employee.job_id = ""
46
+ expect(@employee).not_to be_changed
47
+ end
48
+
49
+ it "should not mark empty decimal (stored as NULL) as changed when reassigning it" do
50
+ @employee = TestEmployee.create!(salary: "")
51
+ @employee.salary = ""
52
+ expect(@employee).not_to be_changed
53
+ @employee.reload
54
+ @employee.salary = ""
55
+ expect(@employee).not_to be_changed
56
+ end
57
+
58
+ it "should not mark empty text (stored as NULL) as changed when reassigning it" do
59
+ @employee = TestEmployee.create!(comments: nil)
60
+ @employee.comments = nil
61
+ expect(@employee).not_to be_changed
62
+ @employee.reload
63
+ @employee.comments = nil
64
+ expect(@employee).not_to be_changed
65
+ end
66
+
67
+ it "should not mark empty text (stored as empty_clob()) as changed when reassigning it" do
68
+ @employee = TestEmployee.create!(comments: "")
69
+ @employee.comments = ""
70
+ expect(@employee).not_to be_changed
71
+ @employee.reload
72
+ @employee.comments = ""
73
+ expect(@employee).not_to be_changed
74
+ end
75
+
76
+ it "should mark empty text (stored as empty_clob()) as changed when assigning nil to it" do
77
+ @employee = TestEmployee.create!(comments: "")
78
+ @employee.comments = nil
79
+ expect(@employee).to be_changed
80
+ @employee.reload
81
+ @employee.comments = nil
82
+ expect(@employee).to be_changed
83
+ end
84
+
85
+ it "should mark empty text (stored as NULL) as changed when assigning '' to it" do
86
+ @employee = TestEmployee.create!(comments: nil)
87
+ @employee.comments = ""
88
+ expect(@employee).to be_changed
89
+ @employee.reload
90
+ @employee.comments = ""
91
+ expect(@employee).to be_changed
92
+ end
93
+
94
+ it "should not mark empty date (stored as NULL) as changed when reassigning it" do
95
+ @employee = TestEmployee.create!(hire_date: "")
96
+ @employee.hire_date = ""
97
+ expect(@employee).not_to be_changed
98
+ @employee.reload
99
+ @employee.hire_date = ""
100
+ expect(@employee).not_to be_changed
101
+ end
102
+
103
+ it "should not mark integer as changed when reassigning it" do
104
+ @employee = TestEmployee.new
105
+ @employee.job_id = 0
106
+ expect(@employee.save!).to be_truthy
107
+
108
+ expect(@employee).not_to be_changed
109
+
110
+ @employee.job_id = "0"
111
+ expect(@employee).not_to be_changed
112
+ end
113
+
114
+ it "should not update unchanged CLOBs" do
115
+ @conn = nil
116
+ @raw_connection = nil
117
+ @employee = TestEmployee.create!(
118
+ comments: "initial"
119
+ )
120
+ expect(@employee.save!).to be_truthy
121
+ @employee.reload
122
+ expect(@employee.comments).to eq("initial")
123
+
124
+ oci_conn = @conn.instance_variable_get("@raw_connection")
125
+ class << oci_conn
126
+ def write_lob(lob, value, is_binary = false); raise "don't do this'"; end
127
+ end
128
+ @employee.comments = +"initial"
129
+ expect(@employee.comments_changed?).to be false
130
+ expect { @employee.save! }.not_to raise_error
131
+ class << oci_conn
132
+ remove_method :write_lob
133
+ end
134
+ end
135
+
136
+ it "should be able to handle attributes which are not backed by a column" do
137
+ TestEmployee.create!(comments: "initial")
138
+ @employee = TestEmployee.select("#{TestEmployee.quoted_table_name}.*, 24 ranking").first
139
+ expect { @employee.ranking = 25 }.to_not raise_error
140
+ end
141
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter handling of BINARY_FLOAT columns" do
4
+ include SchemaSpecHelper
5
+
6
+ before(:all) do
7
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
8
+ @conn = ActiveRecord::Base.connection
9
+ schema_define do
10
+ create_table :test2_employees, force: true do |t|
11
+ t.string :first_name, limit: 20
12
+ t.string :last_name, limit: 25
13
+ t.string :email, limit: 25
14
+ t.string :phone_number, limit: 25
15
+ t.date :hire_date
16
+ t.integer :job_id
17
+ t.integer :salary
18
+ t.decimal :commission_pct, scale: 2, precision: 2
19
+ t.float :hourly_rate
20
+ t.integer :manager_id, limit: 6
21
+ t.integer :is_manager, limit: 1
22
+ t.decimal :department_id, scale: 0, precision: 4
23
+ t.timestamps
24
+ end
25
+ end
26
+ class ::Test2Employee < ActiveRecord::Base
27
+ end
28
+ end
29
+
30
+ after(:all) do
31
+ Object.send(:remove_const, "Test2Employee")
32
+ @conn.drop_table :test2_employees, if_exists: true
33
+ end
34
+
35
+ it "should set BINARY_FLOAT column type as float" do
36
+ columns = @conn.columns("test2_employees")
37
+ column = columns.detect { |c| c.name == "hourly_rate" }
38
+ expect(column.type).to eq(:float)
39
+ end
40
+
41
+ it "should BINARY_FLOAT column type returns an approximate value" do
42
+ employee = Test2Employee.create(hourly_rate: 4.4)
43
+
44
+ employee.reload
45
+
46
+ expect(employee.hourly_rate).to eq(4.400000095367432)
47
+ end
48
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter integer type detection based on attribute settings" do
4
+ before(:all) do
5
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
6
+ conn = ActiveRecord::Base.lease_connection
7
+ conn.execute "DROP TABLE test2_employees" rescue nil
8
+ conn.execute <<~SQL
9
+ CREATE TABLE test2_employees (
10
+ id NUMBER PRIMARY KEY,
11
+ first_name VARCHAR2(20),
12
+ last_name VARCHAR2(25),
13
+ email VARCHAR2(25),
14
+ phone_number VARCHAR2(20),
15
+ hire_date DATE,
16
+ job_id NUMBER,
17
+ salary NUMBER,
18
+ commission_pct NUMBER(2,2),
19
+ manager_id NUMBER(6),
20
+ is_manager NUMBER(1),
21
+ department_id NUMBER(4,0),
22
+ created_at DATE
23
+ )
24
+ SQL
25
+ conn.execute "DROP SEQUENCE test2_employees_seq" rescue nil
26
+ conn.execute <<~SQL
27
+ CREATE SEQUENCE test2_employees_seq MINVALUE 1
28
+ INCREMENT BY 1 START WITH 10040 CACHE 20 NOORDER NOCYCLE
29
+ SQL
30
+ end
31
+
32
+ after(:all) do
33
+ conn = ActiveRecord::Base.lease_connection
34
+ conn.execute "DROP TABLE test2_employees"
35
+ conn.execute "DROP SEQUENCE test2_employees_seq"
36
+ ActiveRecord::Base.release_connection
37
+ end
38
+
39
+ describe "/ NUMBER values from ActiveRecord model" do
40
+ before(:each) do
41
+ class ::Test2Employee < ActiveRecord::Base
42
+ end
43
+ end
44
+
45
+ after(:each) do
46
+ Object.send(:remove_const, "Test2Employee")
47
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = true
48
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.clear_type_map!
49
+ ActiveRecord::Base.clear_cache!
50
+ end
51
+
52
+ def create_employee2
53
+ @employee2 = Test2Employee.create(
54
+ first_name: "First",
55
+ last_name: "Last",
56
+ job_id: 1,
57
+ is_manager: 1,
58
+ salary: 1000
59
+ )
60
+ @employee2.reload
61
+ end
62
+
63
+ it "should return BigDecimal value from NUMBER column if by default" do
64
+ create_employee2
65
+ expect(@employee2.job_id.class).to eq(BigDecimal)
66
+ end
67
+
68
+ it "should return Integer value from NUMBER column if attribute is set to integer" do
69
+ class ::Test2Employee < ActiveRecord::Base
70
+ attribute :job_id, :integer
71
+ end
72
+ create_employee2
73
+ expect(@employee2.job_id).to be_a(Integer)
74
+ end
75
+
76
+ it "should return Integer value from NUMBER column with integer value using _before_type_cast method" do
77
+ create_employee2
78
+ expect(@employee2.job_id_before_type_cast).to be_a(Integer)
79
+ end
80
+
81
+ it "should return Boolean value from NUMBER(1) column if emulate booleans is used" do
82
+ create_employee2
83
+ expect(@employee2.is_manager.class).to eq(TrueClass)
84
+ end
85
+
86
+ it "should return Integer value from NUMBER(1) column if attribute is set to integer" do
87
+ class ::Test2Employee < ActiveRecord::Base
88
+ attribute :is_manager, :integer
89
+ end
90
+ create_employee2
91
+ expect(@employee2.is_manager).to be_a(Integer)
92
+ end
93
+
94
+ it "should return Integer value from NUMBER(1) column if emulate_booleans is set to false" do
95
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
96
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
97
+ create_employee2
98
+ expect(@employee2.is_manager).to be_a(Integer)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter attribute API support for JSON type" do
4
+ include SchemaSpecHelper
5
+
6
+ before(:all) do
7
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
8
+ @conn = ActiveRecord::Base.connection
9
+ @oracle12c_or_higher = !! @conn.select_value(
10
+ "select * from product_component_version where product like 'Oracle%' and to_number(substr(version,1,2)) >= 12")
11
+ skip "Not supported in this database version" unless @oracle12c_or_higher
12
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
13
+ schema_define do
14
+ create_table :test_posts, force: true do |t|
15
+ t.string :title
16
+ t.text :article
17
+ end
18
+ execute "alter table test_posts add constraint test_posts_title_is_json check (title is json)"
19
+ execute "alter table test_posts add constraint test_posts_article_is_json check (article is json)"
20
+ end
21
+
22
+ class ::TestPost < ActiveRecord::Base
23
+ attribute :title, :json
24
+ attribute :article, :json
25
+ end
26
+ end
27
+
28
+ after(:all) do
29
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
30
+ schema_define do
31
+ drop_table :test_posts, if_exists: true
32
+ end
33
+ end
34
+
35
+ before(:each) do
36
+ TestPost.delete_all
37
+ end
38
+
39
+ it "should support attribute api for JSON" do
40
+ post = TestPost.create!(title: { "publish" => true, "foo" => "bar" }, article: { "bar" => "baz" })
41
+ post.reload
42
+ expect(post.title).to eq ({ "publish" => true, "foo" => "bar" })
43
+ expect(post.article).to eq ({ "bar" => "baz" })
44
+ post.title = ({ "publish" => false, "foo" => "bar2" })
45
+ post.save
46
+ expect(post.reload.title).to eq ({ "publish" => false, "foo" => "bar2" })
47
+ end
48
+
49
+ it "should support IS JSON" do
50
+ TestPost.create!(title: { "publish" => true, "foo" => "bar" })
51
+ count_json = TestPost.where("title is json")
52
+ expect(count_json.size).to eq 1
53
+ count_non_json = TestPost.where("title is not json")
54
+ expect(count_non_json.size).to eq 0
55
+ end
56
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter quoting of NCHAR and NVARCHAR2 columns" do
4
+ before(:all) do
5
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
6
+ @conn = ActiveRecord::Base.connection
7
+ @conn.execute <<~SQL
8
+ CREATE TABLE test_items (
9
+ id NUMBER(6,0) PRIMARY KEY,
10
+ nchar_column NCHAR(20),
11
+ nvarchar2_column NVARCHAR2(20),
12
+ char_column CHAR(20),
13
+ varchar2_column VARCHAR2(20)
14
+ )
15
+ SQL
16
+ @conn.execute "CREATE SEQUENCE test_items_seq"
17
+ end
18
+
19
+ after(:all) do
20
+ @conn.execute "DROP TABLE test_items"
21
+ @conn.execute "DROP SEQUENCE test_items_seq"
22
+ end
23
+
24
+ before(:each) do
25
+ class ::TestItem < ActiveRecord::Base
26
+ end
27
+ end
28
+
29
+ after(:each) do
30
+ Object.send(:remove_const, "TestItem")
31
+ ActiveRecord::Base.clear_cache!
32
+ end
33
+
34
+ it "should quote with N prefix" do
35
+ columns = @conn.columns("test_items")
36
+ %w(nchar_column nvarchar2_column char_column varchar2_column).each do |col|
37
+ column = columns.detect { |c| c.name == col }
38
+ type = @conn.lookup_cast_type(column.sql_type)
39
+ value = type.serialize("abc")
40
+ expect(@conn.quote(value)).to eq(column.sql_type[0, 1] == "N" ? "N'abc'" : "'abc'")
41
+ nilvalue = type.serialize(nil)
42
+ expect(@conn.quote(nilvalue)).to eq("NULL")
43
+ end
44
+ end
45
+
46
+ it "should create record" do
47
+ nchar_data = "āčē"
48
+ item = TestItem.create(
49
+ nchar_column: nchar_data,
50
+ nvarchar2_column: nchar_data
51
+ ).reload
52
+ expect(item.nchar_column).to eq(nchar_data)
53
+ expect(item.nvarchar2_column).to eq(nchar_data)
54
+ end
55
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe "OracleEnhancedAdapter handling of NCLOB columns" do
4
+ include SchemaSpecHelper
5
+
6
+ before(:all) do
7
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
8
+ @conn = ActiveRecord::Base.connection
9
+ schema_define do
10
+ create_table :test_employees, force: true do |t|
11
+ t.string :first_name, limit: 20
12
+ t.string :last_name, limit: 25
13
+ t.ntext :comments
14
+ end
15
+ create_table :test2_employees, force: true do |t|
16
+ t.string :first_name, limit: 20
17
+ t.string :last_name, limit: 25
18
+ t.ntext :comments
19
+ end
20
+ create_table :test_serialize_employees, force: true do |t|
21
+ t.string :first_name, limit: 20
22
+ t.string :last_name, limit: 25
23
+ end
24
+ add_column :test_serialize_employees, :comments, :ntext
25
+ end
26
+
27
+ # Some random multibyte characters. They say Hello (Kon'nichiwa) World (Sekai) in Japanese.
28
+ @nclob_data = "こんにちは"
29
+ @nclob_data2 = "世界"
30
+
31
+ class ::TestEmployee < ActiveRecord::Base; end
32
+ class ::Test2Employee < ActiveRecord::Base
33
+ serialize :comments
34
+ end
35
+ class ::TestEmployeeReadOnlyNClob < ActiveRecord::Base
36
+ self.table_name = "test_employees"
37
+ attr_readonly :comments
38
+ end
39
+ class ::TestSerializeEmployee < ActiveRecord::Base
40
+ serialize :comments
41
+ attr_readonly :comments
42
+ end
43
+ end
44
+
45
+ after(:all) do
46
+ @conn.drop_table :test_employees, if_exists: true
47
+ @conn.drop_table :test2_employees, if_exists: true
48
+ @conn.drop_table :test_serialize_employees, if_exists: true
49
+ Object.send(:remove_const, "TestEmployee")
50
+ Object.send(:remove_const, "Test2Employee")
51
+ Object.send(:remove_const, "TestEmployeeReadOnlyNClob")
52
+ Object.send(:remove_const, "TestSerializeEmployee")
53
+ ActiveRecord::Base.clear_cache!
54
+ end
55
+
56
+ it "should create record without NCLOB data when attribute is serialized" do
57
+ @employee = Test2Employee.create!(
58
+ first_name: "First",
59
+ last_name: "Last"
60
+ )
61
+ expect(@employee).to be_valid
62
+ @employee.reload
63
+ expect(@employee.comments).to be_nil
64
+ end
65
+
66
+ it "should accept Symbol value for NCLOB column" do
67
+ @employee = TestEmployee.create!(
68
+ comments: :test_comment
69
+ )
70
+ expect(@employee).to be_valid
71
+ end
72
+
73
+ it "should respect attr_readonly setting for NCLOB column" do
74
+ @employee = TestEmployeeReadOnlyNClob.create!(
75
+ first_name: "First",
76
+ comments: @nclob_data
77
+ )
78
+ expect(@employee).to be_valid
79
+ @employee.reload
80
+ expect(@employee.comments).to eq(@nclob_data)
81
+ @employee.comments = @nclob_data2
82
+ expect(@employee.save).to be(true)
83
+ @employee.reload
84
+ expect(@employee.comments).to eq(@nclob_data)
85
+ end
86
+
87
+ it "should work for serialized readonly NCLOB columns", serialized: true do
88
+ @employee = TestSerializeEmployee.new(
89
+ first_name: "First",
90
+ comments: nil
91
+ )
92
+ expect(@employee.comments).to be_nil
93
+ expect(@employee.save).to be(true)
94
+ expect(@employee).to be_valid
95
+ @employee.reload
96
+ expect(@employee.comments).to be_nil
97
+ @employee.comments = {}
98
+ expect(@employee.save).to be(true)
99
+ @employee.reload
100
+ # should not set readonly
101
+ expect(@employee.comments).to be_nil
102
+ end
103
+
104
+ it "should create record with NCLOB data" do
105
+ @employee = TestEmployee.create!(
106
+ first_name: "First",
107
+ last_name: "Last",
108
+ comments: @nclob_data
109
+ )
110
+ @employee.reload
111
+ expect(@employee.comments).to eq(@nclob_data)
112
+ end
113
+
114
+ it "should update record with NCLOB data" do
115
+ @employee = TestEmployee.create!(
116
+ first_name: "First",
117
+ last_name: "Last"
118
+ )
119
+ @employee.reload
120
+ expect(@employee.comments).to be_nil
121
+ @employee.comments = @nclob_data
122
+ @employee.save!
123
+ @employee.reload
124
+ expect(@employee.comments).to eq(@nclob_data)
125
+ end
126
+
127
+ it "should update record with zero-length NCLOB data" do
128
+ @employee = TestEmployee.create!(
129
+ first_name: "First",
130
+ last_name: "Last"
131
+ )
132
+ @employee.reload
133
+ expect(@employee.comments).to be_nil
134
+ @employee.comments = ""
135
+ @employee.save!
136
+ @employee.reload
137
+ expect(@employee.comments).to eq("")
138
+ end
139
+
140
+ it "should update record that has existing NCLOB data with different NCLOB data" do
141
+ @employee = TestEmployee.create!(
142
+ first_name: "First",
143
+ last_name: "Last",
144
+ comments: @nclob_data
145
+ )
146
+ @employee.reload
147
+ @employee.comments = @nclob_data2
148
+ @employee.save!
149
+ @employee.reload
150
+ expect(@employee.comments).to eq(@nclob_data2)
151
+ end
152
+
153
+ it "should update record that has existing NCLOB data with nil" do
154
+ @employee = TestEmployee.create!(
155
+ first_name: "First",
156
+ last_name: "Last",
157
+ comments: @nclob_data
158
+ )
159
+ @employee.reload
160
+ @employee.comments = nil
161
+ @employee.save!
162
+ @employee.reload
163
+ expect(@employee.comments).to be_nil
164
+ end
165
+
166
+ it "should update record that has existing NCLOB data with zero-length NCLOB data" do
167
+ @employee = TestEmployee.create!(
168
+ first_name: "First",
169
+ last_name: "Last",
170
+ comments: @nclob_data
171
+ )
172
+ @employee.reload
173
+ @employee.comments = ""
174
+ @employee.save!
175
+ @employee.reload
176
+ expect(@employee.comments).to eq("")
177
+ end
178
+
179
+ it "should update record that has zero-length NCLOB data with non-empty NCLOB data" do
180
+ @employee = TestEmployee.create!(
181
+ first_name: "First",
182
+ last_name: "Last",
183
+ comments: ""
184
+ )
185
+ @employee.reload
186
+ expect(@employee.comments).to eq("")
187
+ @employee.comments = @nclob_data
188
+ @employee.save!
189
+ @employee.reload
190
+ expect(@employee.comments).to eq(@nclob_data)
191
+ end
192
+
193
+ it "should store serializable ruby data structures" do
194
+ ruby_data1 = { "arbitrary1" => ["ruby", :data, 123] }
195
+ ruby_data2 = { "arbitrary2" => ["ruby", :data, 123] }
196
+ @employee = Test2Employee.create!(
197
+ comments: ruby_data1
198
+ )
199
+ @employee.reload
200
+ expect(@employee.comments).to eq(ruby_data1)
201
+ @employee.comments = ruby_data2
202
+ @employee.save
203
+ @employee.reload
204
+ expect(@employee.comments).to eq(ruby_data2)
205
+ end
206
+
207
+ it "should keep unchanged serialized data when other columns changed" do
208
+ @employee = Test2Employee.create!(
209
+ first_name: "First",
210
+ last_name: "Last",
211
+ comments: @nclob_data
212
+ )
213
+ @employee.first_name = "Steve"
214
+ @employee.save
215
+ @employee.reload
216
+ expect(@employee.comments).to eq(@nclob_data)
217
+ end
218
+
219
+ it "should keep serialized data after save" do
220
+ @employee = Test2Employee.new
221
+ @employee.comments = { length: { is: 1 } }
222
+ @employee.save
223
+ @employee.reload
224
+ expect(@employee.comments).to eq(length: { is: 1 })
225
+ @employee.comments = { length: { is: 2 } }
226
+ @employee.save
227
+ @employee.reload
228
+ expect(@employee.comments).to eq(length: { is: 2 })
229
+ end
230
+ end