activerecord-oracle_enhanced-adapter 1.3.2 → 1.4.0

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 (32) hide show
  1. data/Gemfile +0 -2
  2. data/History.txt +19 -0
  3. data/README.md +378 -0
  4. data/RUNNING_TESTS.md +45 -0
  5. data/Rakefile +1 -1
  6. data/VERSION +1 -1
  7. data/activerecord-oracle_enhanced-adapter.gemspec +6 -9
  8. data/lib/active_record/connection_adapters/oracle_enhanced.rake +34 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +209 -57
  10. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +22 -1
  11. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +17 -3
  12. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +19 -3
  13. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +75 -17
  14. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +41 -2
  15. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +3 -3
  16. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +40 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +10 -3
  18. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +49 -10
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +1 -1
  20. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +54 -54
  21. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +33 -5
  22. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +66 -5
  23. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +162 -13
  24. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +1 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +43 -0
  27. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +150 -1
  28. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +5 -4
  29. data/spec/spec_helper.rb +3 -1
  30. metadata +38 -52
  31. data/README.rdoc +0 -89
  32. data/RUNNING_TESTS.rdoc +0 -28
@@ -61,7 +61,11 @@ module ActiveRecord
61
61
 
62
62
  # After setting large objects to empty, select the OCI8::LOB
63
63
  # and write back the data.
64
- after_save :enhanced_write_lobs
64
+ if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR >= 1
65
+ after_update :enhanced_write_lobs
66
+ else
67
+ after_save :enhanced_write_lobs
68
+ end
65
69
  def enhanced_write_lobs #:nodoc:
66
70
  if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
67
71
  !(self.class.custom_create_method || self.class.custom_update_method)
@@ -74,6 +78,23 @@ module ActiveRecord
74
78
  def self.table_comment
75
79
  connection.table_comment(self.table_name)
76
80
  end
81
+
82
+ if ActiveRecord::VERSION::MAJOR < 3
83
+ def attributes_with_quotes_with_virtual_columns(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
84
+ virtual_columns = self.class.columns.select(& :virtual?).map(&:name)
85
+ attributes_with_quotes_without_virtual_columns(include_primary_key, include_readonly_attributes, attribute_names - virtual_columns)
86
+ end
87
+
88
+ alias_method_chain :attributes_with_quotes, :virtual_columns
89
+ else
90
+ def arel_attributes_values_with_virtual_columns(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
91
+ virtual_columns = self.class.columns.select(& :virtual?).map(&:name)
92
+ arel_attributes_values_without_virtual_columns(include_primary_key, include_readonly_attributes, attribute_names - virtual_columns)
93
+ end
94
+
95
+ alias_method_chain :arel_attributes_values, :virtual_columns
96
+ end
97
+
77
98
  end
78
99
 
79
100
  end
@@ -2,22 +2,29 @@ module ActiveRecord
2
2
  module ConnectionAdapters #:nodoc:
3
3
  class OracleEnhancedColumn < Column
4
4
 
5
- attr_reader :table_name, :forced_column_type, :nchar #:nodoc:
5
+ attr_reader :table_name, :forced_column_type, :nchar, :virtual_column_data_default #:nodoc:
6
6
 
7
- def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil) #:nodoc:
7
+ def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false) #:nodoc:
8
8
  @table_name = table_name
9
9
  @forced_column_type = forced_column_type
10
+ @virtual = virtual
10
11
  super(name, default, sql_type, null)
12
+ @virtual_column_data_default = default.inspect if virtual
11
13
  # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)?
12
14
  # Define only when needed as adapter "quote" method will check at first if instance variable is defined.
13
15
  @nchar = true if @type == :string && sql_type[0,1] == 'N'
14
16
  end
15
17
 
16
18
  def type_cast(value) #:nodoc:
19
+ return OracleEnhancedColumn::string_to_raw(value) if type == :raw
17
20
  return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
18
21
  super
19
22
  end
20
23
 
24
+ def virtual?
25
+ @virtual
26
+ end
27
+
21
28
  # convert something to a boolean
22
29
  # added y as boolean value
23
30
  def self.value_to_boolean(value) #:nodoc:
@@ -42,6 +49,11 @@ module ActiveRecord
42
49
  super
43
50
  end
44
51
 
52
+ # convert RAW column values back to byte strings.
53
+ def self.string_to_raw(string) #:nodoc:
54
+ string
55
+ end
56
+
45
57
  # Get column comment from schema definition.
46
58
  # Will work only if using default ActiveRecord connection.
47
59
  def comment
@@ -63,6 +75,8 @@ module ActiveRecord
63
75
  else
64
76
  :decimal
65
77
  end
78
+ when /raw/i
79
+ :raw
66
80
  when /char/i
67
81
  if OracleEnhancedAdapter.emulate_booleans_from_strings &&
68
82
  OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name)
@@ -119,4 +133,4 @@ module ActiveRecord
119
133
 
120
134
  end
121
135
 
122
- end
136
+ end
@@ -13,6 +13,16 @@ module ActiveRecord
13
13
  # Use +contains+ ActiveRecord model instance method to add CONTAINS where condition
14
14
  # and order by score of matched results.
15
15
  #
16
+ # Options:
17
+ #
18
+ # * <tt>:name</tt>
19
+ # * <tt>:index_column</tt>
20
+ # * <tt>:index_column_trigger_on</tt>
21
+ # * <tt>:tablespace</tt>
22
+ # * <tt>:sync</tt> - 'MANUAL', 'EVERY "interval-string"' or 'ON COMMIT' (defaults to 'MANUAL').
23
+ # * <tt>:lexer</tt> - Lexer options (e.g. <tt>:type => 'BASIC_LEXER', :base_letter => true</tt>).
24
+ # * <tt>:transactional</tt> - When +true+, the CONTAINS operator will process inserted and updated rows.
25
+ #
16
26
  # ===== Examples
17
27
  #
18
28
  # ====== Creating single column index
@@ -54,6 +64,9 @@ module ActiveRecord
54
64
  # ====== Creating index using lexer
55
65
  # add_context_index :posts, :title, :lexer => { :type => 'BASIC_LEXER', :base_letter => true, ... }
56
66
  #
67
+ # ====== Creating transactional index (will reindex changed rows when querying)
68
+ # add_context_index :posts, :title, :transactional => true
69
+ #
57
70
  def add_context_index(table_name, column_name, options = {})
58
71
  self.all_schema_indexes = nil
59
72
  column_names = Array(column_name)
@@ -63,7 +76,7 @@ module ActiveRecord
63
76
 
64
77
  quoted_column_name = quote_column_name(options[:index_column] || column_names.first)
65
78
  if options[:index_column_trigger_on]
66
- raise ArgumentError, "Option :index_column should be specified together with :index_column_cource option" \
79
+ raise ArgumentError, "Option :index_column should be specified together with :index_column_trigger_on option" \
67
80
  unless options[:index_column]
68
81
  create_index_column_trigger(table_name, index_name, options[:index_column], options[:index_column_trigger_on])
69
82
  end
@@ -93,6 +106,9 @@ module ActiveRecord
93
106
  create_lexer_preference(lexer_name, lexer_type, lexer_options)
94
107
  parameters << "LEXER #{lexer_name}"
95
108
  end
109
+ if options[:transactional]
110
+ parameters << "TRANSACTIONAL"
111
+ end
96
112
  unless parameters.empty?
97
113
  sql << " PARAMETERS ('#{parameters.join(' ')}')"
98
114
  end
@@ -119,8 +135,8 @@ module ActiveRecord
119
135
 
120
136
  def create_datastore_procedure(table_name, procedure_name, column_names, options)
121
137
  quoted_table_name = quote_table_name(table_name)
122
- select_queries = column_names.select{|c| c.to_s =~ /^SELECT /i}
123
- column_names = column_names - select_queries
138
+ select_queries, column_names = column_names.partition { |c| c.to_s =~ /^\s*SELECT\s+/i }
139
+ select_queries = select_queries.map { |s| s.strip.gsub(/\s+/, ' ') }
124
140
  keys, selected_columns = parse_select_queries(select_queries)
125
141
  quoted_column_names = (column_names+keys).map{|col| quote_column_name(col)}
126
142
  execute compress_lines(<<-SQL)
@@ -2,19 +2,28 @@ begin
2
2
  require "java"
3
3
  require "jruby"
4
4
 
5
- # ojdbc14.jar file should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path
6
-
7
- ojdbc_jar = "ojdbc14.jar"
5
+ # ojdbc6.jar or ojdbc5.jar file should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path
6
+
7
+ java_version = java.lang.System.getProperty("java.version")
8
+ ojdbc_jar = if java_version =~ /^1.5/
9
+ "ojdbc5.jar"
10
+ elsif java_version >= '1.6'
11
+ "ojdbc6.jar"
12
+ else
13
+ nil
14
+ end
8
15
 
9
- unless ENV_JAVA['java.class.path'] =~ Regexp.new(ojdbc_jar)
16
+ unless ojdbc_jar.nil? || ENV_JAVA['java.class.path'] =~ Regexp.new(ojdbc_jar)
10
17
  # On Unix environment variable should be PATH, on Windows it is sometimes Path
11
- env_path = ENV["PATH"] || ENV["Path"] || ''
12
- if ojdbc_jar_path = env_path.split(/[:;]/).concat($LOAD_PATH).find{|d| File.exists?(File.join(d,ojdbc_jar))}
18
+ env_path = (ENV["PATH"] || ENV["Path"] || '').split(/[:;]/)
19
+ # Look for JDBC driver at first in lib subdirectory (application specific JDBC file version)
20
+ # then in Ruby load path and finally in environment PATH
21
+ if ojdbc_jar_path = ['./lib'].concat($LOAD_PATH).concat(env_path).find{|d| File.exists?(File.join(d,ojdbc_jar))}
13
22
  require File.join(ojdbc_jar_path,ojdbc_jar)
14
23
  end
15
24
  end
16
25
 
17
- java.sql.DriverManager.registerDriver Java::oracle.jdbc.driver.OracleDriver.new
26
+ java.sql.DriverManager.registerDriver Java::oracle.jdbc.OracleDriver.new
18
27
 
19
28
  # set tns_admin property from TNS_ADMIN environment variable
20
29
  if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
@@ -23,7 +32,7 @@ begin
23
32
 
24
33
  rescue LoadError, NameError
25
34
  # JDBC driver is unavailable.
26
- raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. Please install ojdbc14.jar library."
35
+ raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. Please install #{ojdbc_jar || "Oracle JDBC"} library."
27
36
  end
28
37
 
29
38
 
@@ -199,7 +208,7 @@ module ActiveRecord
199
208
  begin
200
209
  yield if block_given?
201
210
  rescue NativeException => e
202
- raise unless e.message =~ /^java\.sql\.SQL(Recoverable)?Exception: (Closed Connection|Io exception:|No more data to read from socket)/
211
+ raise unless e.message =~ /^java\.sql\.SQL(Recoverable)?Exception: (Closed Connection|Io exception:|No more data to read from socket|IO Error:)/
203
212
  @active = false
204
213
  raise unless should_retry
205
214
  should_retry = false
@@ -288,17 +297,26 @@ module ActiveRecord
288
297
  @raw_statement = raw_statement
289
298
  end
290
299
 
291
- def bind_param(position, value)
292
- java_value = ruby_to_java_value(value)
300
+ def bind_param(position, value, col_type = nil)
301
+ java_value = ruby_to_java_value(value, col_type)
293
302
  case value
294
303
  when Integer
295
- @raw_statement.setInt(position, java_value)
304
+ @raw_statement.setLong(position, java_value)
296
305
  when Float
297
306
  @raw_statement.setFloat(position, java_value)
298
307
  when BigDecimal
299
308
  @raw_statement.setBigDecimal(position, java_value)
300
309
  when String
301
- @raw_statement.setString(position, java_value)
310
+ case col_type
311
+ when :text
312
+ @raw_statement.setClob(position, java_value)
313
+ when :binary
314
+ @raw_statement.setBlob(position, java_value)
315
+ when :raw
316
+ @raw_statement.setString(position, OracleEnhancedAdapter.encode_raw(java_value))
317
+ else
318
+ @raw_statement.setString(position, java_value)
319
+ end
302
320
  when Date, DateTime
303
321
  @raw_statement.setDATE(position, java_value)
304
322
  when Time
@@ -313,12 +331,24 @@ module ActiveRecord
313
331
  end
314
332
  end
315
333
 
334
+ def bind_returning_param(position, bind_type)
335
+ @returning_positions ||= []
336
+ @returning_positions << position
337
+ if bind_type == Integer
338
+ @raw_statement.registerReturnParameter(position, java.sql.Types::BIGINT)
339
+ end
340
+ end
341
+
316
342
  def exec
317
343
  @raw_result_set = @raw_statement.executeQuery
318
344
  get_metadata
319
345
  true
320
346
  end
321
347
 
348
+ def exec_update
349
+ @raw_statement.executeUpdate
350
+ end
351
+
322
352
  def get_metadata
323
353
  metadata = @raw_result_set.getMetaData
324
354
  column_count = metadata.getColumnCount
@@ -345,16 +375,41 @@ module ActiveRecord
345
375
  end
346
376
  end
347
377
 
378
+ def get_returning_param(position, type)
379
+ rs_position = @returning_positions.index(position) + 1
380
+ rs = @raw_statement.getReturnResultSet
381
+ if rs.next
382
+ # Assuming that primary key will not be larger as long max value
383
+ returning_id = rs.getLong(rs_position)
384
+ rs.wasNull ? nil : returning_id
385
+ else
386
+ nil
387
+ end
388
+ end
389
+
348
390
  def close
349
391
  @raw_statement.close
350
392
  end
351
393
 
352
394
  private
353
395
 
354
- def ruby_to_java_value(value)
396
+ def ruby_to_java_value(value, col_type = nil)
355
397
  case value
356
- when Fixnum, String, Float
398
+ when Fixnum, Float
357
399
  value
400
+ when String
401
+ case col_type
402
+ when :text
403
+ clob = Java::OracleSql::CLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::CLOB::DURATION_SESSION)
404
+ clob.setString(1, value)
405
+ clob
406
+ when :binary
407
+ blob = Java::OracleSql::BLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION)
408
+ blob.setBytes(1, value.to_java_bytes)
409
+ blob
410
+ else
411
+ value
412
+ end
358
413
  when BigDecimal
359
414
  java.math.BigDecimal.new(value.to_s)
360
415
  when Date, DateTime
@@ -438,7 +493,7 @@ module ActiveRecord
438
493
  else
439
494
  BigDecimal.new(d.stringValue)
440
495
  end
441
- when :VARCHAR2, :CHAR, :LONG
496
+ when :VARCHAR2, :CHAR, :LONG, :NVARCHAR2, :NCHAR
442
497
  rset.getString(i)
443
498
  when :DATE
444
499
  if dt = rset.getDATE(i)
@@ -452,7 +507,7 @@ module ActiveRecord
452
507
  else
453
508
  nil
454
509
  end
455
- when :TIMESTAMP, :TIMESTAMPTZ, :TIMESTAMPLTZ
510
+ when :TIMESTAMP, :TIMESTAMPTZ, :TIMESTAMPLTZ, :"TIMESTAMP WITH TIME ZONE", :"TIMESTAMP WITH LOCAL TIME ZONE"
456
511
  ts = rset.getTimestamp(i)
457
512
  ts && Time.send(Base.default_timezone, ts.year + 1900, ts.month + 1, ts.date, ts.hours, ts.minutes, ts.seconds,
458
513
  ts.nanos / 1000)
@@ -460,6 +515,9 @@ module ActiveRecord
460
515
  get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
461
516
  when :BLOB
462
517
  get_lob_value ? lob_to_ruby_value(rset.getBlob(i)) : rset.getBlob(i)
518
+ when :RAW
519
+ raw_value = rset.getRAW(i)
520
+ raw_value && raw_value.getBytes.to_a.pack('C*')
463
521
  else
464
522
  nil
465
523
  end
@@ -26,6 +26,16 @@ module ActiveRecord
26
26
  @owner = config[:username].to_s.upcase
27
27
  end
28
28
 
29
+ def raw_oci_connection
30
+ if @raw_connection.is_a? OCI8
31
+ @raw_connection
32
+ # ActiveRecord Oracle enhanced adapter puts OCI8EnhancedAutoRecover wrapper around OCI8
33
+ # in this case we need to pass original OCI8 connection
34
+ else
35
+ @raw_connection.instance_variable_get(:@connection)
36
+ end
37
+ end
38
+
29
39
  def auto_retry
30
40
  @raw_connection.auto_retry if @raw_connection
31
41
  end
@@ -103,14 +113,38 @@ module ActiveRecord
103
113
  @raw_cursor = raw_cursor
104
114
  end
105
115
 
106
- def bind_param(position, value)
107
- @raw_cursor.bind_param(position, value)
116
+ def bind_param(position, value, col_type = nil)
117
+ if value.nil?
118
+ @raw_cursor.bind_param(position, nil, String)
119
+ else
120
+ case col_type
121
+ when :text, :binary
122
+ # ruby-oci8 cannot create CLOB/BLOB from ''
123
+ lob_value = value == '' ? ' ' : value
124
+ bind_type = col_type == :text ? OCI8::CLOB : OCI8::BLOB
125
+ ora_value = bind_type.new(@connection.raw_oci_connection, lob_value)
126
+ ora_value.size = 0 if value == ''
127
+ @raw_cursor.bind_param(position, ora_value)
128
+ when :raw
129
+ @raw_cursor.bind_param(position, OracleEnhancedAdapter.encode_raw(value))
130
+ else
131
+ @raw_cursor.bind_param(position, value)
132
+ end
133
+ end
134
+ end
135
+
136
+ def bind_returning_param(position, bind_type)
137
+ @raw_cursor.bind_param(position, nil, bind_type)
108
138
  end
109
139
 
110
140
  def exec
111
141
  @raw_cursor.exec
112
142
  end
113
143
 
144
+ def exec_update
145
+ @raw_cursor.exec
146
+ end
147
+
114
148
  def get_col_names
115
149
  @raw_cursor.get_col_names
116
150
  end
@@ -124,9 +158,14 @@ module ActiveRecord
124
158
  end
125
159
  end
126
160
 
161
+ def get_returning_param(position, type)
162
+ @raw_cursor[position]
163
+ end
164
+
127
165
  def close
128
166
  @raw_cursor.close
129
167
  end
168
+
130
169
  end
131
170
 
132
171
  def select(sql, name = nil, return_column_names = false)
@@ -1,10 +1,10 @@
1
1
  # define accessors before requiring ruby-plsql as these accessors are used in clob writing callback and should be
2
2
  # available also if ruby-plsql could not be loaded
3
3
  ActiveRecord::Base.class_eval do
4
- if respond_to? :class_inheritable_accessor
5
- class_inheritable_accessor :custom_create_method, :custom_update_method, :custom_delete_method
6
- elsif respond_to? :class_attribute
4
+ if respond_to? :class_attribute
7
5
  class_attribute :custom_create_method, :custom_update_method, :custom_delete_method
6
+ elsif respond_to? :class_inheritable_accessor
7
+ class_inheritable_accessor :custom_create_method, :custom_update_method, :custom_delete_method
8
8
  end
9
9
  end
10
10
 
@@ -10,11 +10,36 @@ module ActiveRecord
10
10
  :tablespace, :columns) #:nodoc:
11
11
  end
12
12
 
13
+ module OracleEnhancedColumnDefinition
14
+ def self.included(base) #:nodoc:
15
+ base.class_eval do
16
+ alias_method_chain :to_sql, :virtual_columns
17
+ alias to_s :to_sql
18
+ end
19
+ end
20
+
21
+ def to_sql_with_virtual_columns
22
+ if type==:virtual
23
+ "#{base.quote_column_name(name)} AS (#{default})"
24
+ else
25
+ to_sql_without_virtual_columns
26
+ end
27
+ end
28
+
29
+ def lob?
30
+ ['CLOB', 'BLOB'].include?(sql_type)
31
+ end
32
+ end
33
+
13
34
  module OracleEnhancedSchemaDefinitions #:nodoc:
14
35
  def self.included(base)
15
36
  base::TableDefinition.class_eval do
16
37
  include OracleEnhancedTableDefinition
17
38
  end
39
+
40
+ base::ColumnDefinition.class_eval do
41
+ include OracleEnhancedColumnDefinition
42
+ end
18
43
 
19
44
  # Available starting from ActiveRecord 2.1
20
45
  base::Table.class_eval do
@@ -31,10 +56,21 @@ module ActiveRecord
31
56
  alias to_s :to_sql
32
57
  end
33
58
 
59
+ def raw(name, options={})
60
+ column(name, :raw, options)
61
+ end
62
+
34
63
  def self.included(base) #:nodoc:
35
64
  base.class_eval do
36
65
  alias_method_chain :references, :foreign_keys
37
66
  alias_method_chain :to_sql, :foreign_keys
67
+
68
+ def virtual(* args)
69
+ options = args.extract_options!
70
+ column_names = args
71
+ column_names.each { |name| column(name, :virtual, options) }
72
+ end
73
+
38
74
  end
39
75
  end
40
76
 
@@ -88,6 +124,10 @@ module ActiveRecord
88
124
  sql << ', ' << (foreign_keys * ', ') unless foreign_keys.blank?
89
125
  sql
90
126
  end
127
+
128
+ def lob_columns
129
+ columns.select(&:lob?)
130
+ end
91
131
 
92
132
  private
93
133
  def foreign_keys