activerecord-oracle_enhanced-adapter 1.5.5 → 1.5.6

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.
@@ -0,0 +1,262 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module OracleEnhancedDatabaseStatements
4
+ # DATABASE STATEMENTS ======================================
5
+ #
6
+ # see: abstract/database_statements.rb
7
+
8
+ # Executes a SQL statement
9
+ def execute(sql, name = nil)
10
+ log(sql, name) { @connection.exec(sql) }
11
+ end
12
+
13
+ def substitute_at(column, index)
14
+ Arel::Nodes::BindParam.new (":a#{index + 1}")
15
+ end
16
+
17
+ def clear_cache!
18
+ @statements.clear
19
+ end
20
+
21
+ def exec_query(sql, name = 'SQL', binds = [])
22
+ type_casted_binds = binds.map { |col, val|
23
+ [col, type_cast(val, col)]
24
+ }
25
+ log(sql, name, type_casted_binds) do
26
+ cursor = nil
27
+ cached = false
28
+ if without_prepared_statement?(binds)
29
+ cursor = @connection.prepare(sql)
30
+ else
31
+ unless @statements.key? sql
32
+ @statements[sql] = @connection.prepare(sql)
33
+ end
34
+
35
+ cursor = @statements[sql]
36
+
37
+ binds.each_with_index do |bind, i|
38
+ col, val = bind
39
+ cursor.bind_param(i + 1, type_cast(val, col), col)
40
+ end
41
+
42
+ cached = true
43
+ end
44
+
45
+ cursor.exec
46
+
47
+ if name == 'EXPLAIN' and sql =~ /^EXPLAIN/
48
+ res = true
49
+ else
50
+ columns = cursor.get_col_names.map do |col_name|
51
+ @connection.oracle_downcase(col_name)
52
+ end
53
+ rows = []
54
+ fetch_options = {:get_lob_value => (name != 'Writable Large Object')}
55
+ while row = cursor.fetch(fetch_options)
56
+ rows << row
57
+ end
58
+ res = ActiveRecord::Result.new(columns, rows)
59
+ end
60
+
61
+ cursor.close unless cached
62
+ res
63
+ end
64
+ end
65
+
66
+ def supports_statement_cache?
67
+ true
68
+ end
69
+
70
+ def supports_explain?
71
+ true
72
+ end
73
+
74
+ def explain(arel, binds = [])
75
+ sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}"
76
+ return if sql =~ /FROM all_/
77
+ if ORACLE_ENHANCED_CONNECTION == :jdbc
78
+ exec_query(sql, 'EXPLAIN', binds)
79
+ else
80
+ exec_query(sql, 'EXPLAIN')
81
+ end
82
+ select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
83
+ end
84
+
85
+ # Returns an array of arrays containing the field values.
86
+ # Order is the same as that returned by #columns.
87
+ def select_rows(sql, name = nil, binds = [])
88
+ exec_query(sql, name, binds).rows
89
+ end
90
+
91
+ # Executes an INSERT statement and returns the new record's ID
92
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
93
+ # if primary key value is already prefetched from sequence
94
+ # or if there is no primary key
95
+ if id_value || pk.nil?
96
+ execute(sql, name)
97
+ return id_value
98
+ end
99
+
100
+ sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk))
101
+ log(sql, name) do
102
+ @connection.exec_with_returning(sql_with_returning)
103
+ end
104
+ end
105
+ protected :insert_sql
106
+
107
+ # New method in ActiveRecord 3.1
108
+ # Will add RETURNING clause in case of trigger generated primary keys
109
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
110
+ unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
111
+ sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
112
+ returning_id_col = OracleEnhancedColumn.new("returning_id", nil, "number", true, "dual", :integer, true, true)
113
+ (binds = binds.dup) << [returning_id_col, nil]
114
+ end
115
+ [sql, binds]
116
+ end
117
+
118
+ # New method in ActiveRecord 3.1
119
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
120
+ type_casted_binds = binds.map { |col, val|
121
+ [col, type_cast(val, col)]
122
+ }
123
+ log(sql, name, type_casted_binds) do
124
+ returning_id_col = returning_id_index = nil
125
+ if without_prepared_statement?(binds)
126
+ cursor = @connection.prepare(sql)
127
+ else
128
+ unless @statements.key? (sql)
129
+ @statements[sql] = @connection.prepare(sql)
130
+ end
131
+
132
+ cursor = @statements[sql]
133
+
134
+ binds.each_with_index do |bind, i|
135
+ col, val = bind
136
+ if col.returning_id?
137
+ returning_id_col = [col]
138
+ returning_id_index = i + 1
139
+ cursor.bind_returning_param(returning_id_index, Integer)
140
+ else
141
+ cursor.bind_param(i + 1, type_cast(val, col), col)
142
+ end
143
+ end
144
+ end
145
+
146
+ cursor.exec_update
147
+
148
+ rows = []
149
+ if returning_id_index
150
+ returning_id = cursor.get_returning_param(returning_id_index, Integer)
151
+ rows << [returning_id]
152
+ end
153
+ ActiveRecord::Result.new(returning_id_col || [], rows)
154
+ end
155
+ end
156
+
157
+ # New method in ActiveRecord 3.1
158
+ def exec_update(sql, name, binds)
159
+ log(sql, name, binds) do
160
+ cached = false
161
+ if without_prepared_statement?(binds)
162
+ cursor = @connection.prepare(sql)
163
+ else
164
+ cursor = if @statements.key?(sql)
165
+ @statements[sql]
166
+ else
167
+ @statements[sql] = @connection.prepare(sql)
168
+ end
169
+
170
+ binds.each_with_index do |bind, i|
171
+ col, val = bind
172
+ cursor.bind_param(i + 1, type_cast(val, col), col)
173
+ end
174
+ cached = true
175
+ end
176
+
177
+ res = cursor.exec_update
178
+ cursor.close unless cached
179
+ res
180
+ end
181
+ end
182
+
183
+ alias :exec_delete :exec_update
184
+
185
+ def begin_db_transaction #:nodoc:
186
+ @connection.autocommit = false
187
+ end
188
+
189
+ def transaction_isolation_levels
190
+ # Oracle database supports `READ COMMITTED` and `SERIALIZABLE`
191
+ # No read uncommitted nor repeatable read supppoted
192
+ # http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10005.htm#SQLRF55422
193
+ {
194
+ read_committed: "READ COMMITTED",
195
+ serializable: "SERIALIZABLE"
196
+ }
197
+ end
198
+
199
+ def begin_isolated_db_transaction(isolation)
200
+ begin_db_transaction
201
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
202
+ end
203
+
204
+ def commit_db_transaction #:nodoc:
205
+ @connection.commit
206
+ ensure
207
+ @connection.autocommit = true
208
+ end
209
+
210
+ def rollback_db_transaction #:nodoc:
211
+ @connection.rollback
212
+ ensure
213
+ @connection.autocommit = true
214
+ end
215
+
216
+ def create_savepoint(name = current_savepoint_name) #:nodoc:
217
+ execute("SAVEPOINT #{current_savepoint_name}")
218
+ end
219
+
220
+ def rollback_to_savepoint(name = current_savepoint_name) #:nodoc:
221
+ execute("ROLLBACK TO #{current_savepoint_name}")
222
+ end
223
+
224
+ def release_savepoint(name = current_savepoint_name) #:nodoc:
225
+ # there is no RELEASE SAVEPOINT statement in Oracle
226
+ end
227
+
228
+ # Returns default sequence name for table.
229
+ # Will take all or first 26 characters of table name and append _seq suffix
230
+ def default_sequence_name(table_name, primary_key = nil)
231
+ table_name.to_s.gsub /(^|\.)([\w$-]{1,#{sequence_name_length-4}})([\w$-]*)$/, '\1\2_seq'
232
+ end
233
+
234
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
235
+ def insert_fixture(fixture, table_name) #:nodoc:
236
+ super
237
+
238
+ if ActiveRecord::Base.pluralize_table_names
239
+ klass = table_name.to_s.singularize.camelize
240
+ else
241
+ klass = table_name.to_s.camelize
242
+ end
243
+
244
+ klass = klass.constantize rescue nil
245
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
246
+ write_lobs(table_name, klass, fixture, klass.lob_columns)
247
+ end
248
+ end
249
+
250
+ private
251
+
252
+ def select(sql, name = nil, binds = [])
253
+ exec_query(sql, name, binds)
254
+ end
255
+
256
+ end
257
+ end
258
+ end
259
+
260
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
261
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedDatabaseStatements
262
+ end
@@ -1,3 +1,5 @@
1
+ require 'active_record/base'
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  class OracleEnhancedAdapter
@@ -9,8 +11,10 @@ module ActiveRecord
9
11
  end
10
12
 
11
13
  def create
12
- print "Please provide the SYSTEM password for your Oracle installation\n>"
13
- system_password = $stdin.gets.strip
14
+ system_password = ENV.fetch('ORACLE_SYSTEM_PASSWORD') {
15
+ print "Please provide the SYSTEM password for your Oracle installation (set ORACLE_SYSTEM_PASSWORD to avoid this prompt)\n>"
16
+ $stdin.gets.strip
17
+ }
14
18
  establish_connection(@config.merge('username' => 'SYSTEM', 'password' => system_password))
15
19
  begin
16
20
  connection.execute "CREATE USER #{@config['username']} IDENTIFIED BY #{@config['password']}"
@@ -24,6 +28,7 @@ module ActiveRecord
24
28
  connection.execute "GRANT unlimited tablespace TO #{@config['username']}"
25
29
  connection.execute "GRANT create session TO #{@config['username']}"
26
30
  connection.execute "GRANT create table TO #{@config['username']}"
31
+ connection.execute "GRANT create view TO #{@config['username']}"
27
32
  connection.execute "GRANT create sequence TO #{@config['username']}"
28
33
  end
29
34
 
@@ -40,9 +45,6 @@ module ActiveRecord
40
45
  def structure_dump(filename)
41
46
  establish_connection(@config)
42
47
  File.open(filename, 'w:utf-8') { |f| f << connection.structure_dump }
43
- if connection.supports_migrations?
44
- File.open(filename, 'a') { |f| f << connection.dump_schema_information }
45
- end
46
48
  if @config['structure_dump'] == 'db_stored_code'
47
49
  File.open(filename, 'a') { |f| f << connection.structure_dump_db_stored_code }
48
50
  end
@@ -58,4 +60,3 @@ module ActiveRecord
58
60
  end
59
61
 
60
62
  ActiveRecord::Tasks::DatabaseTasks.register_task(/(oci|oracle)/, ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::DatabaseTasks)
61
-
@@ -26,8 +26,6 @@ begin
26
26
  # check any compatible JDBC driver in the priority order
27
27
  ojdbc_jars.any? do |ojdbc_jar|
28
28
  if File.exists?(file_path = File.join(dir, ojdbc_jar))
29
- puts "WARNING: JDK #{java_version} is not officially supported by #{ojdbc_jar}" if java_version >= '1.8'
30
- puts "See http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-faq-090281.html#01_03 for supported versions"
31
29
  require file_path
32
30
  true
33
31
  end
@@ -2,9 +2,11 @@ require 'delegate'
2
2
 
3
3
  begin
4
4
  require "oci8"
5
- rescue LoadError
6
- # OCI8 driver is unavailable.
7
- raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. Please install ruby-oci8 gem."
5
+ rescue LoadError => e
6
+ # OCI8 driver is unavailable or failed to load a required library.
7
+ raise LoadError, "ERROR: '#{e.message}'. "\
8
+ "ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. "\
9
+ "You may need install ruby-oci8 gem."
8
10
  end
9
11
 
10
12
  # check ruby-oci8 version
@@ -275,7 +277,7 @@ module ActiveRecord
275
277
  value.hour == 0 && value.min == 0 && value.sec == 0
276
278
  end
277
279
  end
278
-
280
+
279
281
  def create_time_with_default_timezone(value)
280
282
  year, month, day, hour, min, sec, usec = case value
281
283
  when Time
@@ -295,7 +297,7 @@ module ActiveRecord
295
297
  end
296
298
 
297
299
  end
298
-
300
+
299
301
  # The OracleEnhancedOCIFactory factors out the code necessary to connect and
300
302
  # configure an Oracle/OCI connection.
301
303
  class OracleEnhancedOCIFactory #:nodoc:
@@ -342,8 +344,8 @@ module ActiveRecord
342
344
  conn
343
345
  end
344
346
  end
345
-
346
-
347
+
348
+
347
349
  end
348
350
  end
349
351
 
@@ -117,6 +117,8 @@ module ActiveRecord
117
117
  super(name)
118
118
  seq_name = options[:sequence_name] || default_sequence_name(name)
119
119
  execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
120
+ rescue ActiveRecord::StatementInvalid => e
121
+ raise e unless options[:if_exists]
120
122
  ensure
121
123
  clear_table_columns_cache(name)
122
124
  self.all_schema_indexes = nil
@@ -45,7 +45,7 @@ module ActiveRecord #:nodoc:
45
45
  col << "(#{column['data_precision'].to_i}"
46
46
  col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
47
47
  col << ')'
48
- elsif column['data_type'].include?('CHAR')
48
+ elsif column['data_type'].include?('CHAR') || column['data_type'] == 'RAW'
49
49
  length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
50
50
  col << "(#{length})"
51
51
  end
@@ -61,7 +61,7 @@ module ActiveRecord #:nodoc:
61
61
  col << "(#{column['data_precision'].to_i}"
62
62
  col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
63
63
  col << ')'
64
- elsif column['data_type'].include?('CHAR')
64
+ elsif column['data_type'].include?('CHAR') || column['data_type'] == 'RAW'
65
65
  length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
66
66
  col << "(#{length})"
67
67
  end
@@ -172,8 +172,8 @@ module ActiveRecord #:nodoc:
172
172
  select_all("SELECT owner, synonym_name, table_name, table_owner
173
173
  FROM all_synonyms
174
174
  WHERE owner = SYS_CONTEXT('userenv', 'session_user') ").each do |synonym|
175
- structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']}"
176
- structure << " FOR #{synonym['table_owner']}.#{synonym['table_name']}"
175
+ structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']}
176
+ FOR #{synonym['table_owner']}.#{synonym['table_name']}"
177
177
  end
178
178
 
179
179
  join_with_statement_token(structure)
@@ -240,7 +240,6 @@ module ActiveRecord #:nodoc:
240
240
 
241
241
  def execute_structure_dump(string)
242
242
  string.split(STATEMENT_TOKEN).each do |ddl|
243
- ddl.chop! if ddl[-1,1] == ';'
244
243
  execute(ddl) unless ddl.blank?
245
244
  end
246
245
  end
@@ -461,6 +461,9 @@ describe "OracleEnhancedAdapter" do
461
461
  @conn.tables.should include("CamelCase")
462
462
  end
463
463
 
464
+ it "properly quotes database links" do
465
+ @conn.quote_table_name('asdf@some.link').should eq('"ASDF"@"SOME.LINK"')
466
+ end
464
467
  end
465
468
 
466
469
  describe "access table over database link" do
@@ -174,8 +174,6 @@ describe "OracleEnhancedAdapter context index" do
174
174
  describe "on multiple tables" do
175
175
  before(:all) do
176
176
  @conn = ActiveRecord::Base.connection
177
- @oracle12c = !! @conn.select_value(
178
- "select * from product_component_version where product like 'Oracle%' and to_number(substr(version,1,2)) = 12")
179
177
  create_tables
180
178
  class ::Post < ActiveRecord::Base
181
179
  has_many :comments, dependent: :destroy
@@ -200,7 +198,6 @@ describe "OracleEnhancedAdapter context index" do
200
198
  end
201
199
 
202
200
  it "should create multiple table index with specified main index column" do
203
- pending "It always fails when Oracle 12c 12.1.0 used." if @oracle12c
204
201
  @conn.add_context_index :posts,
205
202
  [:title, :body,
206
203
  # specify aliases always with AS keyword
@@ -218,7 +215,6 @@ describe "OracleEnhancedAdapter context index" do
218
215
  end
219
216
 
220
217
  it "should create multiple table index with specified main index column (when subquery has newlines)" do
221
- pending "It always fails when Oracle 12c 12.1.0 used." if @oracle12c
222
218
  @conn.add_context_index :posts,
223
219
  [:title, :body,
224
220
  # specify aliases always with AS keyword
@@ -381,9 +377,6 @@ describe "OracleEnhancedAdapter context index" do
381
377
 
382
378
  describe "with table prefix and suffix" do
383
379
  before(:all) do
384
- @conn = ActiveRecord::Base.connection
385
- @oracle12c = !! @conn.select_value(
386
- "select * from product_component_version where product like 'Oracle%' and to_number(substr(version,1,2)) = 12")
387
380
  ActiveRecord::Base.table_name_prefix = 'xxx_'
388
381
  ActiveRecord::Base.table_name_suffix = '_xxx'
389
382
  create_tables
@@ -408,7 +401,6 @@ describe "OracleEnhancedAdapter context index" do
408
401
  end
409
402
 
410
403
  it "should dump definition of multiple table index with options" do
411
- pending "It always fails when Oracle 12c 12.1.0 used." if @oracle12c
412
404
  options = {
413
405
  name: 'xxx_post_and_comments_i',
414
406
  index_column: :all_text, index_column_trigger_on: :updated_at,