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