activerecord-oracle_enhanced-adapter 1.5.5 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,