activerecord-oracle_enhanced-adapter 1.6.9 → 1.7.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -11
  3. data/History.md +126 -14
  4. data/README.md +9 -6
  5. data/RUNNING_TESTS.md +1 -1
  6. data/Rakefile +1 -16
  7. data/VERSION +1 -1
  8. data/activerecord-oracle_enhanced-adapter.gemspec +15 -52
  9. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +8 -22
  10. data/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb +53 -45
  11. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +6 -1
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +23 -62
  13. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +46 -56
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +35 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +34 -21
  16. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +36 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +1 -1
  18. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +174 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +17 -8
  20. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +17 -11
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +160 -178
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +42 -94
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +50 -54
  24. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +15 -11
  25. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +197 -301
  26. data/lib/active_record/oracle_enhanced/type/integer.rb +3 -2
  27. data/lib/active_record/oracle_enhanced/type/national_character_string.rb +25 -0
  28. data/lib/active_record/oracle_enhanced/type/raw.rb +14 -2
  29. data/lib/active_record/oracle_enhanced/type/string.rb +28 -0
  30. data/lib/active_record/oracle_enhanced/type/text.rb +32 -0
  31. data/lib/activerecord-oracle_enhanced-adapter.rb +12 -17
  32. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +113 -135
  33. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +51 -59
  34. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +40 -41
  35. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +6 -6
  36. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +281 -233
  37. data/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb +7 -7
  38. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +10 -10
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +22 -22
  40. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +2 -2
  41. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +36 -37
  42. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +86 -46
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +194 -294
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +53 -39
  45. data/spec/spec_helper.rb +0 -6
  46. metadata +42 -143
  47. data/.travis.yml +0 -39
  48. data/.travis/oracle/download.sh +0 -14
  49. data/.travis/oracle/install.sh +0 -31
  50. data/.travis/setup_accounts.sh +0 -9
  51. data/lib/active_record/connection_adapters/oracle_enhanced/dirty.rb +0 -40
  52. data/lib/active_record/oracle_enhanced/type/timestamp.rb +0 -11
  53. data/spec/spec_config.yaml.template +0 -11
  54. data/spec/support/alter_system_user_password.sql +0 -2
  55. data/spec/support/create_oracle_enhanced_users.sql +0 -31
@@ -38,7 +38,7 @@ module ActiveRecord
38
38
  db_link = nil
39
39
  default_owner = @owner
40
40
  end
41
- real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.upcase : name
41
+ real_name = ActiveRecord::ConnectionAdapters::OracleEnhanced::Quoting.valid_table_name?(name) ? name.upcase : name
42
42
  if real_name.include?('.')
43
43
  table_owner, table_name = real_name.split('.')
44
44
  else
@@ -99,6 +99,11 @@ module ActiveRecord
99
99
  end
100
100
 
101
101
  end
102
+
103
+ # Returns array with major and minor version of database (e.g. [12, 1])
104
+ def database_version
105
+ raise NoMethodError, "Not implemented for this raw driver"
106
+ end
102
107
 
103
108
  class OracleEnhancedConnectionException < StandardError #:nodoc:
104
109
  end
@@ -16,11 +16,10 @@ module ActiveRecord
16
16
  reload_type_map
17
17
  end
18
18
 
19
- def exec_query(sql, name = 'SQL', binds = [])
20
- type_casted_binds = binds.map { |col, val|
21
- [col, type_cast(val, col)]
22
- }
23
- log(sql, name, type_casted_binds) do
19
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
20
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
21
+
22
+ log(sql, name, binds) do
24
23
  cursor = nil
25
24
  cached = false
26
25
  if without_prepared_statement?(binds)
@@ -32,10 +31,7 @@ module ActiveRecord
32
31
 
33
32
  cursor = @statements[sql]
34
33
 
35
- type_casted_binds.each_with_index do |bind, i|
36
- col, val = bind
37
- cursor.bind_param(i + 1, val, col)
38
- end
34
+ cursor.bind_params(type_casted_binds)
39
35
 
40
36
  cached = true
41
37
  end
@@ -86,39 +82,21 @@ module ActiveRecord
86
82
  exec_query(sql, name, binds).rows
87
83
  end
88
84
 
89
- # Executes an INSERT statement and returns the new record's ID
90
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
91
- # if primary key value is already prefetched from sequence
92
- # or if there is no primary key
93
- if id_value || pk.nil?
94
- execute(sql, name)
95
- return id_value
96
- end
97
-
98
- sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk))
99
- log(sql, name) do
100
- @connection.exec_with_returning(sql_with_returning)
101
- end
102
- end
103
- protected :insert_sql
104
-
105
85
  # New method in ActiveRecord 3.1
106
86
  # Will add RETURNING clause in case of trigger generated primary keys
107
87
  def sql_for_insert(sql, pk, id_value, sequence_name, binds)
108
- unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
88
+ unless id_value || pk == false || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
109
89
  sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
110
- returning_id_col = new_column("returning_id", nil, Type::Value.new, "number", true, "dual", true, true)
111
- (binds = binds.dup) << [returning_id_col, nil]
90
+ (binds = binds.dup) << ActiveRecord::Relation::QueryAttribute.new("returning_id", nil, ActiveRecord::OracleEnhanced::Type::Integer.new)
112
91
  end
113
- [sql, binds]
92
+ super
114
93
  end
115
94
 
116
95
  # New method in ActiveRecord 3.1
117
96
  def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
118
- type_casted_binds = binds.map { |col, val|
119
- [col, type_cast(val, col)]
120
- }
121
- log(sql, name, type_casted_binds) do
97
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
98
+
99
+ log(sql, name, binds) do
122
100
  returning_id_col = returning_id_index = nil
123
101
  if without_prepared_statement?(binds)
124
102
  cursor = @connection.prepare(sql)
@@ -129,23 +107,15 @@ module ActiveRecord
129
107
 
130
108
  cursor = @statements[sql]
131
109
 
132
- type_casted_binds.each_with_index do |bind, i|
133
- col, val = bind
134
- if col.returning_id?
135
- returning_id_col = [col]
136
- returning_id_index = i + 1
137
- cursor.bind_returning_param(returning_id_index, Integer)
138
- else
139
- cursor.bind_param(i + 1, val, col)
140
- end
141
- end
110
+ cursor.bind_params(type_casted_binds)
142
111
  end
143
112
 
144
113
  cursor.exec_update
145
114
 
146
115
  rows = []
116
+ returning_id_index = 1 if sql =~ /:returning_id/
147
117
  if returning_id_index
148
- returning_id = cursor.get_returning_param(returning_id_index, Integer)
118
+ returning_id = cursor.get_returning_param(returning_id_index, Integer).to_i
149
119
  rows << [returning_id]
150
120
  end
151
121
  ActiveRecord::Result.new(returning_id_col || [], rows)
@@ -154,24 +124,21 @@ module ActiveRecord
154
124
 
155
125
  # New method in ActiveRecord 3.1
156
126
  def exec_update(sql, name, binds)
157
- type_casted_binds = binds.map { |col, val|
158
- [col, type_cast(val, col)]
159
- }
160
- log(sql, name, type_casted_binds) do
127
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
128
+
129
+ log(sql, name, binds) do
161
130
  cached = false
162
131
  if without_prepared_statement?(binds)
163
132
  cursor = @connection.prepare(sql)
164
133
  else
165
134
  cursor = if @statements.key?(sql)
166
- @statements[sql]
167
- else
168
- @statements[sql] = @connection.prepare(sql)
169
- end
135
+ @statements[sql]
136
+ else
137
+ @statements[sql] = @connection.prepare(sql)
138
+ end
139
+
140
+ cursor.bind_params(type_casted_binds)
170
141
 
171
- type_casted_binds.each_with_index do |bind, i|
172
- col, val = bind
173
- cursor.bind_param(i + 1, val, col)
174
- end
175
142
  cached = true
176
143
  end
177
144
 
@@ -248,12 +215,6 @@ module ActiveRecord
248
215
  end
249
216
  end
250
217
 
251
- private
252
-
253
- def select(sql, name = nil, binds = [])
254
- exec_query(sql, name, binds)
255
- end
256
-
257
218
  end
258
219
  end
259
220
  end
@@ -109,9 +109,8 @@ module ActiveRecord
109
109
  host, port = config[:host], config[:port]
110
110
  privilege = config[:privilege] && config[:privilege].to_s
111
111
 
112
- # connection using TNS alias, or connection-string from DATABASE_URL
113
- using_tns_alias = !host && !config[:url] && ENV['TNS_ADMIN']
114
- if database && (using_tns_alias || host == 'connection-string')
112
+ # connection using TNS alias
113
+ if database && !host && !config[:url] && ENV['TNS_ADMIN']
115
114
  url = "jdbc:oracle:thin:@#{database}"
116
115
  else
117
116
  unless database.match(/^(\:|\/)/)
@@ -320,39 +319,64 @@ module ActiveRecord
320
319
  Cursor.new(self, @raw_connection.prepareStatement(sql))
321
320
  end
322
321
 
322
+ def database_version
323
+ @database_version ||= (md = raw_connection.getMetaData) && [md.getDatabaseMajorVersion, md.getDatabaseMinorVersion]
324
+ end
325
+
323
326
  class Cursor
324
327
  def initialize(connection, raw_statement)
325
328
  @connection = connection
326
329
  @raw_statement = raw_statement
327
330
  end
328
331
 
332
+ def bind_params( *bind_vars )
333
+ index = 1
334
+ bind_vars.flatten.each do |var|
335
+ if Hash === var
336
+ var.each { |key, val| bind_param key, val }
337
+ else
338
+ bind_param index, var
339
+ index += 1
340
+ end
341
+ end
342
+ end
343
+
329
344
  def bind_param(position, value, column = nil)
330
- col_type = column && column.type
331
- java_value = ruby_to_java_value(value, col_type)
345
+ if column
346
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
347
+ *******************************************************
348
+ Passing a column to `bind_param` will be deprecated.
349
+ `type_casted_binds` should be already type casted
350
+ so that `bind_param` should not need to know column.
351
+ *******************************************************
352
+ MSG
353
+ end
354
+
332
355
  case value
333
356
  when Integer
334
- @raw_statement.setLong(position, java_value)
357
+ @raw_statement.setLong(position, value)
335
358
  when Float
336
- @raw_statement.setFloat(position, java_value)
359
+ @raw_statement.setFloat(position, value)
337
360
  when BigDecimal
338
- @raw_statement.setBigDecimal(position, java_value)
361
+ @raw_statement.setBigDecimal(position, value)
362
+ when Java::OracleSql::BLOB
363
+ @raw_statement.setBlob(position, value)
364
+ when ActiveRecord::OracleEnhanced::Type::Text::Data
365
+ @raw_statement.setClob(position, value)
366
+ when ActiveRecord::OracleEnhanced::Type::Raw
367
+ @raw_statement.setString(position, ActiveRecord::ConnectionAdapters::OracleEnhanced::Quoting.encode_raw(value))
339
368
  when String
340
- case col_type
341
- when :text
342
- @raw_statement.setClob(position, java_value)
343
- when :binary
344
- @raw_statement.setBlob(position, java_value)
345
- when :raw
346
- @raw_statement.setString(position, OracleEnhancedAdapter.encode_raw(java_value))
347
- when :decimal
348
- @raw_statement.setBigDecimal(position, java_value)
349
- else
350
- @raw_statement.setString(position, java_value)
351
- end
369
+ @raw_statement.setString(position, value)
370
+ when Java::OracleSql::DATE
371
+ @raw_statement.setDATE(position, value)
352
372
  when Date, DateTime
353
- @raw_statement.setDATE(position, java_value)
373
+ # TODO: Really needed or not
374
+ @raw_statement.setDATE(position, value)
375
+ when Java::JavaSql::Timestamp
376
+ @raw_statement.setTimestamp(position, value)
354
377
  when Time
355
- @raw_statement.setTimestamp(position, java_value)
378
+ # TODO: Really needed or not
379
+ @raw_statement.setTimestamp(position, value)
356
380
  when NilClass
357
381
  if column && column.object_type?
358
382
  @raw_statement.setNull(position, java.sql.Types::STRUCT, column.sql_type)
@@ -428,38 +452,6 @@ module ActiveRecord
428
452
  @raw_statement.close
429
453
  end
430
454
 
431
- private
432
-
433
- def ruby_to_java_value(value, col_type = nil)
434
- case value
435
- when Fixnum, Float
436
- value
437
- when String
438
- case col_type
439
- when :text
440
- clob = Java::OracleSql::CLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::CLOB::DURATION_SESSION)
441
- clob.setString(1, value)
442
- clob
443
- when :binary
444
- blob = Java::OracleSql::BLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION)
445
- blob.setBytes(1, value.to_java_bytes)
446
- blob
447
- when :decimal
448
- java.math.BigDecimal.new(value.to_s)
449
- else
450
- value
451
- end
452
- when BigDecimal
453
- java.math.BigDecimal.new(value.to_s)
454
- when Date, DateTime
455
- Java::oracle.sql.DATE.new(value.strftime("%Y-%m-%d %H:%M:%S"))
456
- when Time
457
- Java::java.sql.Timestamp.new(value.year-1900, value.month-1, value.day, value.hour, value.min, value.sec, value.usec * 1000)
458
- else
459
- value
460
- end
461
- end
462
-
463
455
  end
464
456
 
465
457
  def select(sql, name = nil, return_column_names = false)
@@ -532,8 +524,6 @@ module ActiveRecord
532
524
  else
533
525
  BigDecimal.new(d.stringValue)
534
526
  end
535
- when :BINARY_FLOAT
536
- rset.getFloat(i)
537
527
  when :VARCHAR2, :CHAR, :LONG, :NVARCHAR2, :NCHAR
538
528
  rset.getString(i)
539
529
  when :DATE
@@ -0,0 +1,35 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module OracleEnhanced
4
+ module JDBCQuoting
5
+ def _type_cast(value)
6
+ case value
7
+ when ActiveModel::Type::Binary::Data
8
+ blob = Java::OracleSql::BLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION)
9
+ blob.setBytes(1, value.to_s.to_java_bytes)
10
+ blob
11
+ when ActiveRecord::OracleEnhanced::Type::Text::Data
12
+ #TODO: may need CLOB specific handling
13
+ value.to_s
14
+ when Date, DateTime
15
+ Java::oracle.sql.DATE.new(value.strftime("%Y-%m-%d %H:%M:%S"))
16
+ when Time
17
+ Java::java.sql.Timestamp.new(value.year-1900, value.month-1, value.day, value.hour, value.min, value.sec, value.usec * 1000)
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ module ActiveRecord
28
+ module ConnectionAdapters
29
+ module OracleEnhanced
30
+ module Quoting
31
+ prepend JDBCQuoting
32
+ end
33
+ end
34
+ end
35
+ end
@@ -10,7 +10,7 @@ rescue LoadError => e
10
10
  end
11
11
 
12
12
  # check ruby-oci8 version
13
- required_oci8_version = [2, 0, 3]
13
+ required_oci8_version = [2, 2, 0]
14
14
  oci8_version_ints = OCI8::VERSION.scan(/\d+/).map{|s| s.to_i}
15
15
  if (oci8_version_ints <=> required_oci8_version) < 0
16
16
  raise LoadError, "ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version #{required_oci8_version.join('.')} or later."
@@ -117,26 +117,37 @@ module ActiveRecord
117
117
  @raw_cursor = raw_cursor
118
118
  end
119
119
 
120
- def bind_param(position, value, column = nil)
121
- if column && column.object_type?
122
- if @connection.raw_connection.respond_to? :get_tdo_by_typename
123
- @raw_cursor.bind_param(position, value, :named_type, column.sql_type)
120
+ def bind_params( *bind_vars )
121
+ index = 1
122
+ bind_vars.flatten.each do |var|
123
+ if Hash === var
124
+ var.each { |key, val| bind_param key, val }
124
125
  else
125
- raise "Use ruby-oci8 2.1.6 or later to bind Oracle objects."
126
+ bind_param index, var
127
+ index += 1
126
128
  end
129
+ end
130
+ end
131
+
132
+ def bind_param(position, value, column = nil)
133
+ if column
134
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
135
+ *******************************************************
136
+ Passing a column to `bind_param` will be deprecated.
137
+ `type_casted_binds` should be already type casted
138
+ so that `bind_param` should not need to know column.
139
+ *******************************************************
140
+ MSG
141
+ end
142
+
143
+ if column && column.object_type?
144
+ @raw_cursor.bind_param(position, value, :named_type, column.sql_type)
127
145
  elsif value.nil?
128
146
  @raw_cursor.bind_param(position, nil, String)
129
147
  else
130
148
  case col_type = column && column.type
131
- when :text, :binary
132
- # ruby-oci8 cannot create CLOB/BLOB from ''
133
- lob_value = value == '' ? ' ' : value
134
- bind_type = col_type == :text ? OCI8::CLOB : OCI8::BLOB
135
- ora_value = bind_type.new(@connection.raw_oci_connection, lob_value)
136
- ora_value.size = 0 if value == ''
137
- @raw_cursor.bind_param(position, ora_value)
138
149
  when :raw
139
- @raw_cursor.bind_param(position, OracleEnhancedAdapter.encode_raw(value))
150
+ @raw_cursor.bind_param(position, ActiveRecord::ConnectionAdapters::OracleEnhanced::Quoting.encode_raw(value))
140
151
  when :decimal
141
152
  @raw_cursor.bind_param(position, BigDecimal.new(value.to_s))
142
153
  else
@@ -216,7 +227,7 @@ module ActiveRecord
216
227
  def describe(name)
217
228
  # fall back to SELECT based describe if using database link
218
229
  return super if name.to_s.include?('@')
219
- quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
230
+ quoted_name = ActiveRecord::ConnectionAdapters::OracleEnhanced::Quoting.valid_table_name?(name) ? name : "\"#{name}\""
220
231
  @raw_connection.describe(quoted_name)
221
232
  rescue OCIException => e
222
233
  if e.code == 4043
@@ -239,12 +250,12 @@ module ActiveRecord
239
250
 
240
251
  def typecast_result_value(value, get_lob_value)
241
252
  case value
242
- when Integer
253
+ when Fixnum, Bignum
243
254
  value
244
255
  when String
245
256
  value
246
257
  when Float, BigDecimal
247
- # return Integer if value is integer (to avoid issues with _before_type_cast values for id attributes)
258
+ # return Fixnum or Bignum if value is integer (to avoid issues with _before_type_cast values for id attributes)
248
259
  value == (v_to_i = value.to_i) ? v_to_i : value
249
260
  when OraNumber
250
261
  # change OraNumber value (returned in early versions of ruby-oci8 2.0.x) to BigDecimal
@@ -269,6 +280,10 @@ module ActiveRecord
269
280
  end
270
281
  end
271
282
 
283
+ def database_version
284
+ @database_version ||= (version = raw_connection.oracle_server_version) && [version.major, version.minor]
285
+ end
286
+
272
287
  private
273
288
 
274
289
  def date_without_time?(value)
@@ -317,11 +332,8 @@ module ActiveRecord
317
332
  # get session time_zone from configuration or from TZ environment variable
318
333
  time_zone = config[:time_zone] || ENV['TZ']
319
334
 
320
- # using a connection string via DATABASE_URL
321
- connection_string = if host == 'connection-string'
322
- database
323
335
  # connection using host, port and database name
324
- elsif host || port
336
+ connection_string = if host || port
325
337
  host ||= 'localhost'
326
338
  host = "[#{host}]" if host =~ /^[^\[].*:/ # IPv6
327
339
  port ||= 1521
@@ -332,6 +344,7 @@ module ActiveRecord
332
344
  else
333
345
  database
334
346
  end
347
+
335
348
  conn = OCI8.new username, password, connection_string, privilege
336
349
  conn.autocommit = true
337
350
  conn.non_blocking = true if async