activerecord-oracle_enhanced-adapter 1.6.9 → 1.7.0.beta1

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.
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