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.
- checksums.yaml +7 -0
- data/History.md +1971 -0
- data/License.txt +20 -0
- data/README.md +947 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +359 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +325 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +629 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +57 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +465 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +739 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +394 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +886 -0
- data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
- data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
- data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
- data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
- data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
- data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
- data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
- data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
- data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
- data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
- data/lib/arel/visitors/oracle.rb +216 -0
- data/lib/arel/visitors/oracle12.rb +121 -0
- data/lib/arel/visitors/oracle_common.rb +51 -0
- data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +40 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/composite_spec.rb +84 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +589 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/dbconsole_spec.rb +63 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1318 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +485 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
- data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
- data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
- data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
- data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
- data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
- data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
- data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
- data/spec/active_record/oracle_enhanced/type/integer_spec.rb +101 -0
- data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
- data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
- data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
- data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
- data/spec/active_record/oracle_enhanced/type/text_spec.rb +295 -0
- data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +107 -0
- data/spec/spec_config.yaml.template +11 -0
- data/spec/spec_helper.rb +225 -0
- data/spec/support/alter_system_set_open_cursors.sql +1 -0
- data/spec/support/alter_system_user_password.sql +2 -0
- data/spec/support/create_oracle_enhanced_users.sql +31 -0
- metadata +181 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe "OracleEnhancedAdapter" do
|
|
4
|
+
include LoggerSpecHelper
|
|
5
|
+
include SchemaSpecHelper
|
|
6
|
+
|
|
7
|
+
before(:all) do
|
|
8
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "cache table columns" do
|
|
12
|
+
before(:all) do
|
|
13
|
+
@conn = ActiveRecord::Base.connection
|
|
14
|
+
schema_define do
|
|
15
|
+
create_table :test_employees, force: true do |t|
|
|
16
|
+
t.string :first_name, limit: 20
|
|
17
|
+
t.string :last_name, limit: 25
|
|
18
|
+
if ActiveRecord::Base.connection.supports_virtual_columns?
|
|
19
|
+
t.virtual :full_name, as: "(first_name || ' ' || last_name)"
|
|
20
|
+
else
|
|
21
|
+
t.string :full_name, limit: 46
|
|
22
|
+
end
|
|
23
|
+
t.date :hire_date
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
schema_define do
|
|
27
|
+
create_table :test_employees_without_pk, id: false, force: true do |t|
|
|
28
|
+
t.string :first_name, limit: 20
|
|
29
|
+
t.string :last_name, limit: 25
|
|
30
|
+
t.date :hire_date
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
@column_names = ["id", "first_name", "last_name", "full_name", "hire_date"]
|
|
34
|
+
@column_sql_types = ["NUMBER(38)", "VARCHAR2(20)", "VARCHAR2(25)", "VARCHAR2(46)", "DATE"]
|
|
35
|
+
class ::TestEmployee < ActiveRecord::Base
|
|
36
|
+
end
|
|
37
|
+
# Another class using the same table
|
|
38
|
+
class ::TestEmployee2 < ActiveRecord::Base
|
|
39
|
+
self.table_name = "test_employees"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
after(:all) do
|
|
44
|
+
@conn = ActiveRecord::Base.connection
|
|
45
|
+
Object.send(:remove_const, "TestEmployee")
|
|
46
|
+
Object.send(:remove_const, "TestEmployee2")
|
|
47
|
+
@conn.drop_table :test_employees, if_exists: true
|
|
48
|
+
@conn.drop_table :test_employees_without_pk, if_exists: true
|
|
49
|
+
ActiveRecord::Base.clear_cache!
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
before(:each) do
|
|
53
|
+
set_logger
|
|
54
|
+
@conn = ActiveRecord::Base.connection
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
after(:each) do
|
|
58
|
+
clear_logger
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "without column caching" do
|
|
62
|
+
it "should identify virtual columns as such" do
|
|
63
|
+
skip "Not supported in this database version" unless @conn.supports_virtual_columns?
|
|
64
|
+
te = TestEmployee.connection.columns("test_employees").detect(&:virtual?)
|
|
65
|
+
expect(te.name).to eq("full_name")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should get columns from database at first time" do
|
|
69
|
+
@conn.clear_table_columns_cache(:test_employees)
|
|
70
|
+
expect(TestEmployee.connection.columns("test_employees").map(&:name)).to eq(@column_names)
|
|
71
|
+
expect(@logger.logged(:debug).last).to match(/select .* from all_tab_cols/im)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "should not get columns from database at second time" do
|
|
75
|
+
TestEmployee.connection.columns("test_employees")
|
|
76
|
+
@logger.clear(:debug)
|
|
77
|
+
expect(TestEmployee.connection.columns("test_employees").map(&:name)).to eq(@column_names)
|
|
78
|
+
expect(@logger.logged(:debug).last).not_to match(/select .* from all_tab_cols/im)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should get primary key from database at first time" do
|
|
82
|
+
expect(TestEmployee.connection.pk_and_sequence_for("test_employees")).to eq(["id", "test_employees_seq"])
|
|
83
|
+
expect(@logger.logged(:debug).last).to match(/select .* from all_constraints/im)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "should get primary key from database at second time without query" do
|
|
87
|
+
expect(TestEmployee.connection.pk_and_sequence_for("test_employees")).to eq(["id", "test_employees_seq"])
|
|
88
|
+
@logger.clear(:debug)
|
|
89
|
+
expect(TestEmployee.connection.pk_and_sequence_for("test_employees")).to eq(["id", "test_employees_seq"])
|
|
90
|
+
expect(@logger.logged(:debug).last).to match(/select .* from all_constraints/im)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should have correct sql types when 2 models are using the same table and AR query cache is enabled" do
|
|
94
|
+
@conn.cache do
|
|
95
|
+
expect(TestEmployee.columns.map(&:sql_type)).to eq(@column_sql_types)
|
|
96
|
+
expect(TestEmployee2.columns.map(&:sql_type)).to eq(@column_sql_types)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "should get sequence value at next time" do
|
|
101
|
+
TestEmployee.create!
|
|
102
|
+
expect(@logger.logged(:debug).first).not_to match(/SELECT "TEST_EMPLOYEES_SEQ".NEXTVAL FROM dual/im)
|
|
103
|
+
@logger.clear(:debug)
|
|
104
|
+
TestEmployee.create!
|
|
105
|
+
expect(@logger.logged(:debug).first).to match(/SELECT "TEST_EMPLOYEES_SEQ".NEXTVAL FROM dual/im)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "session information" do
|
|
111
|
+
before(:all) do
|
|
112
|
+
@conn = ActiveRecord::Base.connection
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "should get current database name" do
|
|
116
|
+
# get database name if using //host:port/database connection string
|
|
117
|
+
database_name = CONNECTION_PARAMS[:database].split("/").last
|
|
118
|
+
expect(@conn.current_database.upcase).to eq(database_name.upcase)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "should get current database session user" do
|
|
122
|
+
expect(@conn.current_user.upcase).to eq(CONNECTION_PARAMS[:username].upcase)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "temporary tables" do
|
|
127
|
+
before(:all) do
|
|
128
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:table] = "UNUSED"
|
|
129
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:clob] = "UNUSED"
|
|
130
|
+
@conn = ActiveRecord::Base.connection
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
after(:all) do
|
|
134
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces = {}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
after(:each) do
|
|
138
|
+
@conn.drop_table :foos, if_exists: true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "should create ok" do
|
|
142
|
+
@conn.create_table :foos, temporary: true, id: false do |t|
|
|
143
|
+
t.integer :id
|
|
144
|
+
t.text :bar
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
it "should show up as temporary" do
|
|
148
|
+
@conn.create_table :foos, temporary: true, id: false do |t|
|
|
149
|
+
t.integer :id
|
|
150
|
+
end
|
|
151
|
+
expect(@conn.temporary_table?("foos")).to be_truthy
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe "`has_many` assoc has `dependent: :delete_all` with `order`" do
|
|
156
|
+
before(:all) do
|
|
157
|
+
schema_define do
|
|
158
|
+
create_table :test_posts do |t|
|
|
159
|
+
t.string :title
|
|
160
|
+
end
|
|
161
|
+
create_table :test_comments do |t|
|
|
162
|
+
t.integer :test_post_id
|
|
163
|
+
t.string :description
|
|
164
|
+
end
|
|
165
|
+
add_index :test_comments, :test_post_id
|
|
166
|
+
end
|
|
167
|
+
class ::TestPost < ActiveRecord::Base
|
|
168
|
+
has_many :test_comments, -> { order(:id) }, dependent: :delete_all
|
|
169
|
+
end
|
|
170
|
+
class ::TestComment < ActiveRecord::Base
|
|
171
|
+
belongs_to :test_post
|
|
172
|
+
end
|
|
173
|
+
TestPost.transaction do
|
|
174
|
+
post = TestPost.create!(title: "Title")
|
|
175
|
+
TestComment.create!(test_post_id: post.id, description: "Description")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
after(:all) do
|
|
180
|
+
schema_define do
|
|
181
|
+
drop_table :test_comments
|
|
182
|
+
drop_table :test_posts
|
|
183
|
+
end
|
|
184
|
+
Object.send(:remove_const, "TestPost")
|
|
185
|
+
Object.send(:remove_const, "TestComment")
|
|
186
|
+
ActiveRecord::Base.clear_cache!
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "should not occur `ActiveRecord::StatementInvalid: OCIError: ORA-00907: missing right parenthesis`" do
|
|
190
|
+
expect { TestPost.first.destroy }.not_to raise_error
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe "eager loading" do
|
|
195
|
+
before(:all) do
|
|
196
|
+
schema_define do
|
|
197
|
+
create_table :test_posts do |t|
|
|
198
|
+
t.string :title
|
|
199
|
+
end
|
|
200
|
+
create_table :test_comments do |t|
|
|
201
|
+
t.integer :test_post_id
|
|
202
|
+
t.string :description
|
|
203
|
+
end
|
|
204
|
+
add_index :test_comments, :test_post_id
|
|
205
|
+
end
|
|
206
|
+
class ::TestPost < ActiveRecord::Base
|
|
207
|
+
has_many :test_comments
|
|
208
|
+
end
|
|
209
|
+
class ::TestComment < ActiveRecord::Base
|
|
210
|
+
belongs_to :test_post
|
|
211
|
+
end
|
|
212
|
+
@ids = (1..1010).to_a
|
|
213
|
+
TestPost.transaction do
|
|
214
|
+
@ids.each do |id|
|
|
215
|
+
TestPost.create!(id: id, title: "Title #{id}")
|
|
216
|
+
TestComment.create!(test_post_id: id, description: "Description #{id}")
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
after(:all) do
|
|
222
|
+
schema_define do
|
|
223
|
+
drop_table :test_comments
|
|
224
|
+
drop_table :test_posts
|
|
225
|
+
end
|
|
226
|
+
Object.send(:remove_const, "TestPost")
|
|
227
|
+
Object.send(:remove_const, "TestComment")
|
|
228
|
+
ActiveRecord::Base.clear_cache!
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it "should load included association with more than 1000 records" do
|
|
232
|
+
posts = TestPost.includes(:test_comments).to_a
|
|
233
|
+
expect(posts.size).to eq(@ids.size)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
describe "lists" do
|
|
238
|
+
before(:all) do
|
|
239
|
+
schema_define do
|
|
240
|
+
create_table :test_posts do |t|
|
|
241
|
+
t.string :title
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
class ::TestPost < ActiveRecord::Base
|
|
245
|
+
has_many :test_comments
|
|
246
|
+
end
|
|
247
|
+
@ids = (1..1010).to_a
|
|
248
|
+
TestPost.transaction do
|
|
249
|
+
@ids.each do |id|
|
|
250
|
+
TestPost.create!(id: id, title: "Title #{id}")
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
after(:all) do
|
|
256
|
+
schema_define do
|
|
257
|
+
drop_table :test_posts
|
|
258
|
+
end
|
|
259
|
+
Object.send(:remove_const, "TestPost")
|
|
260
|
+
ActiveRecord::Base.clear_cache!
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
##
|
|
264
|
+
# See this GitHub issue for an explanation of homogenous lists.
|
|
265
|
+
# https://github.com/rails/rails/commit/72fd0bae5948c1169411941aeea6fef4c58f34a9
|
|
266
|
+
it "should allow more than 1000 items in a list where the list is homogenous" do
|
|
267
|
+
posts = TestPost.where(id: @ids).to_a
|
|
268
|
+
expect(posts.size).to eq(@ids.size)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it "should allow more than 1000 items in a list where the list is non-homogenous" do
|
|
272
|
+
posts = TestPost.where(id: [*@ids, nil]).to_a
|
|
273
|
+
expect(posts.size).to eq(@ids.size)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
describe "with statement pool" do
|
|
278
|
+
before(:all) do
|
|
279
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS.merge(statement_limit: 3))
|
|
280
|
+
@conn = ActiveRecord::Base.connection
|
|
281
|
+
schema_define do
|
|
282
|
+
drop_table :test_posts, if_exists: true
|
|
283
|
+
create_table :test_posts
|
|
284
|
+
end
|
|
285
|
+
class ::TestPost < ActiveRecord::Base
|
|
286
|
+
end
|
|
287
|
+
@statements = @conn.instance_variable_get(:@statements)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
before(:each) do
|
|
291
|
+
@conn.clear_cache!
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
after(:all) do
|
|
295
|
+
schema_define do
|
|
296
|
+
drop_table :test_posts
|
|
297
|
+
end
|
|
298
|
+
Object.send(:remove_const, "TestPost")
|
|
299
|
+
ActiveRecord::Base.clear_cache!
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it "should clear older cursors when statement limit is reached" do
|
|
303
|
+
binds = [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::OracleEnhanced::Integer.new)]
|
|
304
|
+
# free statement pool from dictionary selections to ensure next selects will increase statement pool
|
|
305
|
+
@statements.clear
|
|
306
|
+
expect {
|
|
307
|
+
4.times do |i|
|
|
308
|
+
@conn.exec_query("SELECT * FROM test_posts WHERE #{i}=#{i} AND id = :id", "SQL", binds)
|
|
309
|
+
end
|
|
310
|
+
}.to change(@statements, :length).by(+3)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "should cache UPDATE statements with bind variables" do
|
|
314
|
+
expect {
|
|
315
|
+
binds = [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::OracleEnhanced::Integer.new)]
|
|
316
|
+
@conn.exec_update("UPDATE test_posts SET id = :id", "SQL", binds)
|
|
317
|
+
}.to change(@statements, :length).by(+1)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it "should not cache UPDATE statements without bind variables" do
|
|
321
|
+
expect {
|
|
322
|
+
binds = []
|
|
323
|
+
@conn.exec_update("UPDATE test_posts SET id = 1", "SQL", binds)
|
|
324
|
+
}.not_to change(@statements, :length)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
describe "database_exists?" do
|
|
329
|
+
it "should raise `NotImplementedError`" do
|
|
330
|
+
expect {
|
|
331
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.database_exists?(CONNECTION_PARAMS)
|
|
332
|
+
}.to raise_error(NotImplementedError)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
describe "explain" do
|
|
337
|
+
before(:all) do
|
|
338
|
+
@conn = ActiveRecord::Base.connection
|
|
339
|
+
schema_define do
|
|
340
|
+
drop_table :test_posts, if_exists: true
|
|
341
|
+
create_table :test_posts
|
|
342
|
+
end
|
|
343
|
+
class ::TestPost < ActiveRecord::Base
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
after(:all) do
|
|
348
|
+
schema_define do
|
|
349
|
+
drop_table :test_posts
|
|
350
|
+
end
|
|
351
|
+
Object.send(:remove_const, "TestPost")
|
|
352
|
+
ActiveRecord::Base.clear_cache!
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it "should explain query" do
|
|
356
|
+
explain = TestPost.where(id: 1).explain
|
|
357
|
+
expect(explain.inspect).to include("Cost")
|
|
358
|
+
expect(explain.inspect).to include("INDEX UNIQUE SCAN")
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it "should explain query with binds" do
|
|
362
|
+
binds = [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::OracleEnhanced::Integer.new)]
|
|
363
|
+
explain = TestPost.where(id: binds).explain
|
|
364
|
+
expect(explain.inspect).to include("Cost")
|
|
365
|
+
expect(explain.inspect).to include("INDEX UNIQUE SCAN").or include("TABLE ACCESS FULL")
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
describe "using offset and limit" do
|
|
370
|
+
before(:all) do
|
|
371
|
+
@conn = ActiveRecord::Base.connection
|
|
372
|
+
schema_define do
|
|
373
|
+
create_table :test_employees, force: true do |t|
|
|
374
|
+
t.integer :sort_order
|
|
375
|
+
t.string :first_name, limit: 20
|
|
376
|
+
t.string :last_name, limit: 20
|
|
377
|
+
t.timestamps
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
@employee = Class.new(ActiveRecord::Base) do
|
|
381
|
+
self.table_name = :test_employees
|
|
382
|
+
end
|
|
383
|
+
@employee.create!(sort_order: 1, first_name: "Peter", last_name: "Parker")
|
|
384
|
+
@employee.create!(sort_order: 2, first_name: "Tony", last_name: "Stark")
|
|
385
|
+
@employee.create!(sort_order: 3, first_name: "Steven", last_name: "Rogers")
|
|
386
|
+
@employee.create!(sort_order: 4, first_name: "Bruce", last_name: "Banner")
|
|
387
|
+
@employee.create!(sort_order: 5, first_name: "Natasha", last_name: "Romanova")
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
after(:all) do
|
|
391
|
+
@conn.drop_table :test_employees, if_exists: true
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
after(:each) do
|
|
395
|
+
ActiveRecord::Base.clear_cache!
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
it "should return n records with limit(n)" do
|
|
399
|
+
expect(@employee.limit(3).to_a.size).to be(3)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it "should return less than n records with limit(n) if there exist less than n records" do
|
|
403
|
+
expect(@employee.limit(10).to_a.size).to be(5)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
it "should return the records starting from offset n with offset(n)" do
|
|
407
|
+
expect(@employee.order(:sort_order).first.first_name).to eq("Peter")
|
|
408
|
+
expect(@employee.order(:sort_order).offset(0).first.first_name).to eq("Peter")
|
|
409
|
+
expect(@employee.order(:sort_order).offset(1).first.first_name).to eq("Tony")
|
|
410
|
+
expect(@employee.order(:sort_order).offset(4).first.first_name).to eq("Natasha")
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
describe "valid_type?" do
|
|
415
|
+
before(:all) do
|
|
416
|
+
@conn = ActiveRecord::Base.connection
|
|
417
|
+
schema_define do
|
|
418
|
+
create_table :test_employees, force: true do |t|
|
|
419
|
+
t.string :first_name, limit: 20
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
after(:all) do
|
|
425
|
+
@conn.drop_table :test_employees, if_exists: true
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
it "returns true when passed a valid type" do
|
|
429
|
+
column = @conn.columns("test_employees").find { |col| col.name == "first_name" }
|
|
430
|
+
expect(@conn.valid_type?(column.type)).to be true
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it "returns false when passed an invalid type" do
|
|
434
|
+
expect(@conn.valid_type?(:foobar)).to be false
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
describe "serialized column" do
|
|
439
|
+
before(:all) do
|
|
440
|
+
schema_define do
|
|
441
|
+
create_table :test_serialized_columns do |t|
|
|
442
|
+
t.text :serialized
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
class ::TestSerializedColumn < ActiveRecord::Base
|
|
446
|
+
serialize :serialized, type: Array
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
after(:all) do
|
|
451
|
+
schema_define do
|
|
452
|
+
drop_table :test_serialized_columns
|
|
453
|
+
end
|
|
454
|
+
Object.send(:remove_const, "TestSerializedColumn")
|
|
455
|
+
ActiveRecord::Base.table_name_prefix = nil
|
|
456
|
+
ActiveRecord::Base.clear_cache!
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
before(:each) do
|
|
460
|
+
set_logger
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
after(:each) do
|
|
464
|
+
clear_logger
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it "should serialize" do
|
|
468
|
+
new_value = "new_value"
|
|
469
|
+
serialized_column = TestSerializedColumn.new
|
|
470
|
+
|
|
471
|
+
expect(serialized_column.serialized).to eq([])
|
|
472
|
+
serialized_column.serialized << new_value
|
|
473
|
+
expect(serialized_column.serialized).to eq([new_value])
|
|
474
|
+
serialized_column.save
|
|
475
|
+
expect(serialized_column.save!).to be(true)
|
|
476
|
+
|
|
477
|
+
serialized_column.reload
|
|
478
|
+
expect(serialized_column.serialized).to eq([new_value])
|
|
479
|
+
serialized_column.serialized = []
|
|
480
|
+
expect(serialized_column.save!).to be(true)
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
describe "Binary lob column" do
|
|
485
|
+
before(:all) do
|
|
486
|
+
schema_define do
|
|
487
|
+
create_table :test_binary_columns do |t|
|
|
488
|
+
t.binary :attachment
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
class ::TestBinaryColumn < ActiveRecord::Base
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
after(:all) do
|
|
496
|
+
schema_define do
|
|
497
|
+
drop_table :test_binary_columns
|
|
498
|
+
end
|
|
499
|
+
Object.send(:remove_const, "TestBinaryColumn")
|
|
500
|
+
ActiveRecord::Base.table_name_prefix = nil
|
|
501
|
+
ActiveRecord::Base.clear_cache!
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
before(:each) do
|
|
505
|
+
set_logger
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
after(:each) do
|
|
509
|
+
clear_logger
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
it "should serialize with non UTF-8 data" do
|
|
513
|
+
binary_value = +"Hello \x93\xfa\x96\x7b"
|
|
514
|
+
binary_value.force_encoding "UTF-8"
|
|
515
|
+
|
|
516
|
+
binary_column_object = TestBinaryColumn.new
|
|
517
|
+
binary_column_object.attachment = binary_value
|
|
518
|
+
|
|
519
|
+
expect(binary_column_object.save!).to be(true)
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
describe "quoting" do
|
|
524
|
+
before(:all) do
|
|
525
|
+
schema_define do
|
|
526
|
+
create_table :test_logs, force: true do |t|
|
|
527
|
+
t.timestamp :send_time
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
class TestLog < ActiveRecord::Base
|
|
531
|
+
validates_uniqueness_of :send_time
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
after(:all) do
|
|
536
|
+
schema_define do
|
|
537
|
+
drop_table :test_logs
|
|
538
|
+
end
|
|
539
|
+
Object.send(:remove_const, "TestLog")
|
|
540
|
+
ActiveRecord::Base.clear_cache! if ActiveRecord::Base.respond_to?(:"clear_cache!")
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
it "should create records including Time" do
|
|
544
|
+
TestLog.create! send_time: Time.now + 1.seconds
|
|
545
|
+
TestLog.create! send_time: Time.now + 2.seconds
|
|
546
|
+
expect(TestLog.count).to eq 2
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
describe "synonym_names" do
|
|
551
|
+
before(:all) do
|
|
552
|
+
schema_define do
|
|
553
|
+
create_table :test_comments, force: true do |t|
|
|
554
|
+
t.string :comment
|
|
555
|
+
end
|
|
556
|
+
add_synonym :synonym_comments, :test_comments
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
after(:all) do
|
|
561
|
+
schema_define do
|
|
562
|
+
drop_table :test_comments
|
|
563
|
+
remove_synonym :synonym_comments
|
|
564
|
+
end
|
|
565
|
+
ActiveRecord::Base.clear_cache! if ActiveRecord::Base.respond_to?(:"clear_cache!")
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
it "includes synonyms in data_source" do
|
|
569
|
+
conn = ActiveRecord::Base.connection
|
|
570
|
+
expect(conn).to be_data_source_exist("synonym_comments")
|
|
571
|
+
expect(conn.data_sources).to include("synonym_comments")
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
describe "dictionary selects with bind variables" do
|
|
576
|
+
before(:all) do
|
|
577
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
|
578
|
+
@conn = ActiveRecord::Base.connection
|
|
579
|
+
schema_define do
|
|
580
|
+
drop_table :test_posts, if_exists: true
|
|
581
|
+
create_table :test_posts
|
|
582
|
+
|
|
583
|
+
drop_table :users, if_exists: true
|
|
584
|
+
create_table :users, force: true do |t|
|
|
585
|
+
t.string :name
|
|
586
|
+
t.integer :group_id
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
drop_table :groups, if_exists: true
|
|
590
|
+
create_table :groups, force: true do |t|
|
|
591
|
+
t.string :name
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
class ::TestPost < ActiveRecord::Base
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
class User < ActiveRecord::Base
|
|
599
|
+
belongs_to :group
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
class Group < ActiveRecord::Base
|
|
603
|
+
has_one :user
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
before(:each) do
|
|
608
|
+
@conn.clear_cache!
|
|
609
|
+
set_logger
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
after(:each) do
|
|
613
|
+
clear_logger
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
after(:all) do
|
|
617
|
+
schema_define do
|
|
618
|
+
drop_table :test_posts
|
|
619
|
+
drop_table :users
|
|
620
|
+
drop_table :groups
|
|
621
|
+
end
|
|
622
|
+
Object.send(:remove_const, "TestPost")
|
|
623
|
+
ActiveRecord::Base.clear_cache!
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
it "should test table existence" do
|
|
627
|
+
expect(@conn.table_exists?("TEST_POSTS")).to be true
|
|
628
|
+
expect(@conn.table_exists?("NOT_EXISTING")).to be false
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
it "should return array from indexes with bind usage" do
|
|
632
|
+
expect(@conn.indexes("TEST_POSTS").class).to eq Array
|
|
633
|
+
expect(@logger.logged(:debug).last).to match(/:table_name/)
|
|
634
|
+
expect(@logger.logged(:debug).last).to match(/\["table_name", "TEST_POSTS"\]/)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
it "should return content from columns witt bind usage" do
|
|
638
|
+
expect(@conn.columns("TEST_POSTS").length).to be > 0
|
|
639
|
+
expect(@logger.logged(:debug).last).to match(/:table_name/)
|
|
640
|
+
expect(@logger.logged(:debug).last).to match(/\["table_name", "TEST_POSTS"\]/)
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
it "should return pk and sequence from pk_and_sequence_for with bind usage" do
|
|
644
|
+
expect(@conn.pk_and_sequence_for("TEST_POSTS").length).to eq 2
|
|
645
|
+
expect(@logger.logged(:debug).last).to match(/\["table_name", "TEST_POSTS"\]/)
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
it "should return pk from primary_keys with bind usage" do
|
|
649
|
+
expect(@conn.primary_keys("TEST_POSTS")).to eq ["id"]
|
|
650
|
+
expect(@logger.logged(:debug).last).to match(/\["table_name", "TEST_POSTS"\]/)
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
it "should not raise missing IN/OUT parameter like issue 1678" do
|
|
654
|
+
# "to_sql" enforces unprepared_statement including dictionary access SQLs
|
|
655
|
+
expect { User.joins(:group).to_sql }.not_to raise_exception
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
it "should return false from temporary_table? with bind usage" do
|
|
659
|
+
expect(@conn.temporary_table?("TEST_POSTS")).to be false
|
|
660
|
+
expect(@logger.logged(:debug).last).to match(/:table_name/)
|
|
661
|
+
expect(@logger.logged(:debug).last).to match(/\["table_name", "TEST_POSTS"\]/)
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
describe "Transaction" do
|
|
666
|
+
before(:all) do
|
|
667
|
+
schema_define do
|
|
668
|
+
create_table :test_posts do |t|
|
|
669
|
+
t.string :title
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
class ::TestPost < ActiveRecord::Base
|
|
673
|
+
end
|
|
674
|
+
Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
it "Raises Deadlocked when a deadlock is encountered" do
|
|
678
|
+
skip "Skip temporary due to #1599" if ActiveRecord::Base.connection.supports_fetch_first_n_rows_and_offset?
|
|
679
|
+
expect {
|
|
680
|
+
barrier = Concurrent::CyclicBarrier.new(2)
|
|
681
|
+
|
|
682
|
+
t1 = TestPost.create(title: "one")
|
|
683
|
+
t2 = TestPost.create(title: "two")
|
|
684
|
+
|
|
685
|
+
thread = Thread.new do
|
|
686
|
+
TestPost.transaction do
|
|
687
|
+
t1.lock!
|
|
688
|
+
barrier.wait
|
|
689
|
+
t2.update(title: "one")
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
begin
|
|
694
|
+
TestPost.transaction do
|
|
695
|
+
t2.lock!
|
|
696
|
+
barrier.wait
|
|
697
|
+
t1.update(title: "two")
|
|
698
|
+
end
|
|
699
|
+
ensure
|
|
700
|
+
thread.join
|
|
701
|
+
end
|
|
702
|
+
}.to raise_error(ActiveRecord::Deadlocked)
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
after(:all) do
|
|
706
|
+
schema_define do
|
|
707
|
+
drop_table :test_posts
|
|
708
|
+
end
|
|
709
|
+
Object.send(:remove_const, "TestPost") rescue nil
|
|
710
|
+
ActiveRecord::Base.clear_cache!
|
|
711
|
+
Thread.report_on_exception = @original_report_on_exception
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
describe "Sequence" do
|
|
716
|
+
before(:all) do
|
|
717
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
|
718
|
+
@conn = ActiveRecord::Base.connection
|
|
719
|
+
schema_define do
|
|
720
|
+
create_table :table_with_name_thats_just_ok,
|
|
721
|
+
sequence_name: "suitably_short_seq", force: true do |t|
|
|
722
|
+
t.column :foo, :string, null: false
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
after(:all) do
|
|
728
|
+
schema_define do
|
|
729
|
+
drop_table :table_with_name_thats_just_ok,
|
|
730
|
+
sequence_name: "suitably_short_seq" rescue nil
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
it "should create table with custom sequence name" do
|
|
735
|
+
expect(@conn.select_value("select suitably_short_seq.nextval from dual")).to eq(1)
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
describe "Hints" do
|
|
740
|
+
before(:all) do
|
|
741
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
|
742
|
+
@conn = ActiveRecord::Base.connection
|
|
743
|
+
schema_define do
|
|
744
|
+
drop_table :test_posts, if_exists: true
|
|
745
|
+
create_table :test_posts
|
|
746
|
+
end
|
|
747
|
+
class ::TestPost < ActiveRecord::Base
|
|
748
|
+
end
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
before(:each) do
|
|
752
|
+
@conn.clear_cache!
|
|
753
|
+
set_logger
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
after(:each) do
|
|
757
|
+
clear_logger
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
after(:all) do
|
|
761
|
+
schema_define do
|
|
762
|
+
drop_table :test_posts
|
|
763
|
+
end
|
|
764
|
+
Object.send(:remove_const, "TestPost")
|
|
765
|
+
ActiveRecord::Base.clear_cache!
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
it "should explain considers hints" do
|
|
769
|
+
post = TestPost.optimizer_hints("FULL (\"TEST_POSTS\")")
|
|
770
|
+
post = post.where(id: 1)
|
|
771
|
+
expect(post.explain.inspect).to include("| TABLE ACCESS FULL| TEST_POSTS |")
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
it "should explain considers hints with /*+ */" do
|
|
775
|
+
post = TestPost.optimizer_hints("/*+ FULL (\"TEST_POSTS\") */")
|
|
776
|
+
post = post.where(id: 1)
|
|
777
|
+
expect(post.explain.inspect).to include("| TABLE ACCESS FULL| TEST_POSTS |")
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
describe "homogeneous in" do
|
|
782
|
+
before(:all) do
|
|
783
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
|
784
|
+
@conn = ActiveRecord::Base.connection
|
|
785
|
+
schema_define do
|
|
786
|
+
create_table :test_posts, force: true
|
|
787
|
+
create_table :test_comments, force: true do |t|
|
|
788
|
+
t.integer :test_post_id
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
class ::TestPost < ActiveRecord::Base
|
|
792
|
+
has_many :test_comments
|
|
793
|
+
end
|
|
794
|
+
class ::TestComment < ActiveRecord::Base
|
|
795
|
+
belongs_to :test_post
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
after(:all) do
|
|
800
|
+
schema_define do
|
|
801
|
+
drop_table :test_posts, if_exists: true
|
|
802
|
+
drop_table :test_comments, if_exists: true
|
|
803
|
+
end
|
|
804
|
+
Object.send(:remove_const, "TestPost")
|
|
805
|
+
Object.send(:remove_const, "TestComment")
|
|
806
|
+
ActiveRecord::Base.clear_cache!
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
it "should not raise undefined method length" do
|
|
810
|
+
post = TestPost.create!
|
|
811
|
+
post.test_comments << TestComment.create!
|
|
812
|
+
expect(TestComment.where(test_post_id: TestPost.select(:id)).size).to eq(1)
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
end
|