activerecord-oracle_enhanced-adapter 1.2.0 → 1.2.1

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.
@@ -1,3 +1,14 @@
1
+ == 1.2.1 2009-06-07
2
+
3
+ * Enhancements
4
+ * caching of table indexes query which makes schema dump much faster
5
+ * Bug fixes:
6
+ * return Date (and not DateTime) values for :date column value before year 1970
7
+ * fixed after_create/update/destroy callbacks with plsql custom methods
8
+ * fixed creation of large integers in JRuby
9
+ * Made test tasks respect RAILS_ENV
10
+ * fixed support for composite primary keys for tables with LOBs
11
+
1
12
  == 1.2.0 2009-03-22
2
13
 
3
14
  * Enhancements
@@ -29,6 +29,17 @@ Bugs and enhancement requests can be reported at http://rsim.lighthouseapp.com/p
29
29
 
30
30
  * sudo gem install activerecord-oracle_enhanced-adapter
31
31
 
32
+ == CONTRIBUTORS:
33
+
34
+ * Raimonds Simanovskis
35
+ * Jorge Dias
36
+ * James Wylder
37
+ * Rob Christie
38
+ * Nate Wieger
39
+ * Edgars Beigarts
40
+ * Lachlan Laycock
41
+ * toddwf
42
+
32
43
  == LICENSE:
33
44
 
34
45
  (The MIT License)
@@ -137,9 +137,9 @@ module ActiveRecord
137
137
  end
138
138
  end
139
139
 
140
- # RSI: convert Time value to Date for :date columns
140
+ # RSI: convert Time or DateTime value to Date for :date columns
141
141
  def self.string_to_date(string)
142
- return string.to_date if string.is_a?(Time)
142
+ return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
143
143
  super
144
144
  end
145
145
 
@@ -346,8 +346,11 @@ module ActiveRecord
346
346
  # unescaped table name should start with letter and
347
347
  # contain letters, digits, _, $ or #
348
348
  # can be prefixed with schema name
349
+ # CamelCase table names should be quoted
349
350
  def self.valid_table_name?(name)
350
- name.to_s =~ /^([A-Z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*$/i ? true : false
351
+ name = name.to_s
352
+ name =~ /^([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*$/ ||
353
+ name =~ /^([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*$/ ? true : false
351
354
  end
352
355
 
353
356
  # abstract_adapter calls quote_column_name from quote_table_name, so prevent that
@@ -515,15 +518,26 @@ module ActiveRecord
515
518
 
516
519
  # Writes LOB values from attributes, as indicated by the LOB columns of klass.
517
520
  def write_lobs(table_name, klass, attributes)
518
- id = quote(attributes[klass.primary_key])
521
+ # is class with composite primary key>
522
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
523
+ if is_with_cpk
524
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
525
+ else
526
+ id = quote(attributes[klass.primary_key])
527
+ end
519
528
  klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
520
529
  value = attributes[col.name]
521
530
  # RSI: changed sequence of next two lines - should check if value is nil before converting to yaml
522
531
  next if value.nil? || (value == '')
523
532
  value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
524
533
  uncached do
525
- lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
534
+ if is_with_cpk
535
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE",
536
+ 'Writable Large Object')[col.name]
537
+ else
538
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
526
539
  'Writable Large Object')[col.name]
540
+ end
527
541
  @connection.write_lob(lob, value, col.type == :binary)
528
542
  end
529
543
  end
@@ -556,36 +570,45 @@ module ActiveRecord
556
570
 
557
571
  # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
558
572
  def tables(name = nil) #:nodoc:
559
- select_all("select lower(table_name) name from all_tables where owner = sys_context('userenv','session_user')").map {|t| t['name']}
573
+ select_all("select decode(table_name,upper(table_name),lower(table_name),table_name) name from all_tables where owner = sys_context('userenv','session_user')").map {|t| t['name']}
560
574
  end
561
575
 
562
- def indexes(table_name, name = nil) #:nodoc:
563
- (owner, table_name) = @connection.describe(table_name)
564
- result = select_all(<<-SQL, name)
565
- SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
566
- FROM all_indexes i, all_ind_columns c
567
- WHERE i.table_name = '#{table_name}'
568
- AND i.owner = '#{owner}'
569
- AND i.table_owner = '#{owner}'
570
- AND c.index_name = i.index_name
571
- AND c.index_owner = i.owner
572
- AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
573
- ORDER BY i.index_name, c.column_position
574
- SQL
575
-
576
- current_index = nil
577
- indexes = []
576
+ cattr_accessor :all_schema_indexes
578
577
 
579
- result.each do |row|
580
- if current_index != row['index_name']
581
- indexes << IndexDefinition.new(table_name.downcase, row['index_name'], row['uniqueness'] == "UNIQUE", [])
582
- current_index = row['index_name']
578
+ # This method selects all indexes at once, and caches them in a class variable.
579
+ # Subsequent index calls get them from the variable, without going to the DB.
580
+ def indexes(table_name, name = nil)
581
+ (owner, table_name) = @connection.describe(table_name)
582
+ unless all_schema_indexes
583
+ result = select_all(<<-SQL)
584
+ SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
585
+ FROM all_indexes i, all_ind_columns c
586
+ WHERE i.owner = '#{owner}'
587
+ AND i.table_owner = '#{owner}'
588
+ AND c.index_name = i.index_name
589
+ AND c.index_owner = i.owner
590
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
591
+ ORDER BY i.index_name, c.column_position
592
+ SQL
593
+
594
+ current_index = nil
595
+ self.all_schema_indexes = []
596
+
597
+ result.each do |row|
598
+ # have to keep track of indexes because above query returns dups
599
+ # there is probably a better query we could figure out
600
+ if current_index != row['index_name']
601
+ self.all_schema_indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE", [])
602
+ current_index = row['index_name']
603
+ end
604
+
605
+ self.all_schema_indexes.last.columns << row['column_name']
583
606
  end
584
-
585
- indexes.last.columns << row['column_name']
586
607
  end
587
-
588
- indexes
608
+
609
+ # Return the indexes just for the requested table, since AR is structured that way
610
+ table_name = table_name.downcase
611
+ all_schema_indexes.select{|i| i.table == table_name}
589
612
  end
590
613
 
591
614
  # RSI: set ignored columns for table
@@ -723,7 +746,15 @@ module ActiveRecord
723
746
  execute "DROP SEQUENCE #{seq_name}" rescue nil
724
747
  end
725
748
 
749
+ # clear cached indexes when adding new index
750
+ def add_index(table_name, column_name, options = {})
751
+ self.all_schema_indexes = nil
752
+ super
753
+ end
754
+
755
+ # clear cached indexes when removing index
726
756
  def remove_index(table_name, options = {}) #:nodoc:
757
+ self.all_schema_indexes = nil
727
758
  execute "DROP INDEX #{index_name(table_name, options)}"
728
759
  end
729
760
 
@@ -822,12 +853,12 @@ module ActiveRecord
822
853
  end
823
854
 
824
855
  def structure_dump #:nodoc:
825
- s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
856
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
826
857
  structure << "create sequence #{seq.to_a.first.last};\n\n"
827
858
  end
828
859
 
829
860
  # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
830
- select_all("select table_name from all_tables where owner = sys_context('userenv','session_user')").inject(s) do |structure, table|
861
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
831
862
  ddl = "create table #{table.to_a.first.last} (\n "
832
863
  cols = select_all(%Q{
833
864
  select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
@@ -855,12 +886,12 @@ module ActiveRecord
855
886
  end
856
887
 
857
888
  def structure_drop #:nodoc:
858
- s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
889
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
859
890
  drop << "drop sequence #{seq.to_a.first.last};\n\n"
860
891
  end
861
892
 
862
893
  # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
863
- select_all("select table_name from all_tables where owner = sys_context('userenv','session_user')").inject(s) do |drop, table|
894
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
864
895
  drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
865
896
  end
866
897
  end
@@ -72,6 +72,9 @@ module ActiveRecord
72
72
  # Set session time zone to current time zone
73
73
  @raw_connection.setSessionTimeZone(java.util.TimeZone.default.getID)
74
74
 
75
+ # Set default number of rows to prefetch
76
+ # @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
77
+
75
78
  # default schema owner
76
79
  @owner = username.upcase
77
80
 
@@ -156,7 +159,7 @@ module ActiveRecord
156
159
  end
157
160
 
158
161
  def exec_no_retry(sql)
159
- cs = prepare_call(sql)
162
+ cs = @raw_connection.prepareCall(sql)
160
163
  case sql
161
164
  when /\A\s*UPDATE/i, /\A\s*INSERT/i, /\A\s*DELETE/i
162
165
  cs.executeUpdate
@@ -175,59 +178,35 @@ module ActiveRecord
175
178
  end
176
179
 
177
180
  def select_no_retry(sql, name = nil, return_column_names = false)
178
- stmt = prepare_statement(sql)
181
+ stmt = @raw_connection.prepareStatement(sql)
179
182
  rset = stmt.executeQuery
183
+
184
+ # Reuse the same hash for all rows
185
+ column_hash = {}
186
+
180
187
  metadata = rset.getMetaData
181
188
  column_count = metadata.getColumnCount
182
- cols = (1..column_count).map do |i|
183
- oracle_downcase(metadata.getColumnName(i))
184
- end
185
- col_types = (1..column_count).map do |i|
186
- metadata.getColumnTypeName(i)
189
+
190
+ cols_types_index = (1..column_count).map do |i|
191
+ col_name = oracle_downcase(metadata.getColumnName(i))
192
+ next if col_name == 'raw_rnum_'
193
+ column_hash[col_name] = nil
194
+ [col_name, metadata.getColumnTypeName(i).to_sym, i]
187
195
  end
196
+ cols_types_index.delete(nil)
188
197
 
189
198
  rows = []
190
-
199
+ get_lob_value = !(name == 'Writable Large Object')
200
+
191
201
  while rset.next
192
- hash = Hash.new
193
-
194
- cols.each_with_index do |col, i0|
195
- i = i0 + 1
196
- hash[col] =
197
- case column_type = col_types[i0]
198
- when /CLOB/
199
- name == 'Writable Large Object' ? rset.getClob(i) : get_ruby_value_from_result_set(rset, i, column_type)
200
- when /BLOB/
201
- name == 'Writable Large Object' ? rset.getBlob(i) : get_ruby_value_from_result_set(rset, i, column_type)
202
- when 'DATE'
203
- t = get_ruby_value_from_result_set(rset, i, column_type)
204
- # RSI: added emulate_dates_by_column_name functionality
205
- # if emulate_dates_by_column_name && self.class.is_date_column?(col)
206
- # d.to_date
207
- # elsif
208
- if t && OracleEnhancedAdapter.emulate_dates && (t.hour == 0 && t.min == 0 && t.sec == 0)
209
- t.to_date
210
- else
211
- # JRuby Time supports time before year 1900 therefore now need to fall back to DateTime
212
- t
213
- end
214
- # RSI: added emulate_integers_by_column_name functionality
215
- when "NUMBER"
216
- n = get_ruby_value_from_result_set(rset, i, column_type)
217
- if n && n.is_a?(Float) && OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
218
- n.to_i
219
- else
220
- n
221
- end
222
- else
223
- get_ruby_value_from_result_set(rset, i, column_type)
224
- end unless col == 'raw_rnum_'
202
+ hash = column_hash.dup
203
+ cols_types_index.each do |col, column_type, i|
204
+ hash[col] = get_ruby_value_from_result_set(rset, i, column_type, get_lob_value)
225
205
  end
226
-
227
206
  rows << hash
228
207
  end
229
208
 
230
- return_column_names ? [rows, cols] : rows
209
+ return_column_names ? [rows, cols_types_index.map(&:first)] : rows
231
210
  ensure
232
211
  rset.close rescue nil
233
212
  stmt.close rescue nil
@@ -284,50 +263,62 @@ module ActiveRecord
284
263
 
285
264
  private
286
265
 
287
- def prepare_statement(sql)
288
- @raw_connection.prepareStatement(sql)
289
- end
266
+ # def prepare_statement(sql)
267
+ # @raw_connection.prepareStatement(sql)
268
+ # end
290
269
 
291
- def prepare_call(sql, *bindvars)
292
- @raw_connection.prepareCall(sql)
293
- end
270
+ # def prepare_call(sql, *bindvars)
271
+ # @raw_connection.prepareCall(sql)
272
+ # end
294
273
 
295
- def get_ruby_value_from_result_set(rset, i, type_name)
274
+ def get_ruby_value_from_result_set(rset, i, type_name, get_lob_value = true)
296
275
  case type_name
297
- when "CHAR", "VARCHAR2", "LONG"
298
- rset.getString(i)
299
- when "CLOB"
300
- ora_value_to_ruby_value(rset.getClob(i))
301
- when "BLOB"
302
- ora_value_to_ruby_value(rset.getBlob(i))
303
- when "NUMBER"
304
- d = rset.getBigDecimal(i)
276
+ when :NUMBER
277
+ # d = rset.getBigDecimal(i)
278
+ # if d.nil?
279
+ # nil
280
+ # elsif d.scale == 0
281
+ # d.toBigInteger+0
282
+ # else
283
+ # # Is there better way how to convert Java BigDecimal to Ruby BigDecimal?
284
+ # d.toString.to_d
285
+ # end
286
+ d = rset.getNUMBER(i)
305
287
  if d.nil?
306
288
  nil
307
- elsif d.scale == 0
308
- d.toBigInteger+0
289
+ elsif d.isInt
290
+ Integer(d.stringValue)
309
291
  else
310
- # Is there better way how to convert Java BigDecimal to Ruby BigDecimal?
311
- d.toString.to_d
292
+ BigDecimal.new(d.stringValue)
312
293
  end
313
- when "DATE"
294
+ when :VARCHAR2, :CHAR, :LONG
295
+ rset.getString(i)
296
+ when :DATE
314
297
  if dt = rset.getDATE(i)
315
298
  d = dt.dateValue
316
299
  t = dt.timeValue
317
- Time.send(Base.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
300
+ if OracleEnhancedAdapter.emulate_dates && t.hours == 0 && t.minutes == 0 && t.seconds == 0
301
+ Date.new(d.year + 1900, d.month + 1, d.date)
302
+ else
303
+ Time.send(Base.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
304
+ end
318
305
  else
319
306
  nil
320
307
  end
321
- when /^TIMESTAMP/
308
+ when :TIMESTAMP, :TIMESTAMPTZ, :TIMESTAMPLTZ
322
309
  ts = rset.getTimestamp(i)
323
310
  ts && Time.send(Base.default_timezone, ts.year + 1900, ts.month + 1, ts.date, ts.hours, ts.minutes, ts.seconds,
324
311
  ts.nanos / 1000)
312
+ when :CLOB
313
+ get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
314
+ when :BLOB
315
+ get_lob_value ? lob_to_ruby_value(rset.getBlob(i)) : rset.getBlob(i)
325
316
  else
326
317
  nil
327
318
  end
328
319
  end
329
320
 
330
- def ora_value_to_ruby_value(val)
321
+ def lob_to_ruby_value(val)
331
322
  case val
332
323
  when ::Java::OracleSql::CLOB
333
324
  if val.isEmptyLob
@@ -341,8 +332,6 @@ module ActiveRecord
341
332
  else
342
333
  String.from_java_bytes(val.getBytes(1, val.length))
343
334
  end
344
- else
345
- val
346
335
  end
347
336
  end
348
337
 
@@ -84,70 +84,75 @@ module ActiveRecord
84
84
 
85
85
  def select(sql, name = nil, return_column_names = false)
86
86
  cursor = @raw_connection.exec(sql)
87
- cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
87
+ cols = []
88
+ # Ignore raw_rnum_ which is used to simulate LIMIT and OFFSET
89
+ cursor.get_col_names.each do |col_name|
90
+ col_name = oracle_downcase(col_name)
91
+ cols << col_name unless col_name == 'raw_rnum_'
92
+ end
93
+ # Reuse the same hash for all rows
94
+ column_hash = {}
95
+ cols.each {|c| column_hash[c] = nil}
88
96
  rows = []
97
+ get_lob_value = !(name == 'Writable Large Object')
89
98
 
90
99
  while row = cursor.fetch
91
- hash = Hash.new
100
+ hash = column_hash.dup
92
101
 
93
102
  cols.each_with_index do |col, i|
94
103
  hash[col] =
95
- case row[i]
104
+ case v = row[i]
105
+ # RSI: added emulate_integers_by_column_name functionality
106
+ when Float
107
+ # if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
108
+ # v.to_i
109
+ # else
110
+ # v
111
+ # end
112
+ v == (v_to_i = v.to_i) ? v_to_i : v
113
+ # ruby-oci8 2.0 returns OraNumber - convert it to Integer or BigDecimal
114
+ when OraNumber
115
+ v == (v_to_i = v.to_i) ? v_to_i : BigDecimal.new(v.to_s)
116
+ when String
117
+ v
96
118
  when OCI8::LOB
97
- if name == 'Writable Large Object'
98
- row[i]
99
- else
100
- data = row[i].read
119
+ if get_lob_value
120
+ data = v.read
101
121
  # In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
102
- data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && row[i].is_a?(OCI8::BLOB)
122
+ data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && v.is_a?(OCI8::BLOB)
103
123
  data
124
+ else
125
+ v
104
126
  end
105
127
  # ruby-oci8 1.0 returns OraDate
106
128
  when OraDate
107
- d = row[i]
108
129
  # RSI: added emulate_dates_by_column_name functionality
109
- # if emulate_dates_by_column_name && self.class.is_date_column?(col)
110
- # d.to_date
111
- # elsif
112
- if OracleEnhancedAdapter.emulate_dates && (d.hour == 0 && d.minute == 0 && d.second == 0)
113
- d.to_date
130
+ if OracleEnhancedAdapter.emulate_dates && (v.hour == 0 && v.minute == 0 && v.second == 0)
131
+ v.to_date
114
132
  else
115
133
  # code from Time.time_with_datetime_fallback
116
134
  begin
117
- Time.send(Base.default_timezone, d.year, d.month, d.day, d.hour, d.minute, d.second)
135
+ Time.send(Base.default_timezone, v.year, v.month, v.day, v.hour, v.minute, v.second)
118
136
  rescue
119
137
  offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
120
- ::DateTime.civil(d.year, d.month, d.day, d.hour, d.minute, d.second, offset)
138
+ ::DateTime.civil(v.year, v.month, v.day, v.hour, v.minute, v.second, offset)
121
139
  end
122
140
  end
123
141
  # ruby-oci8 2.0 returns Time or DateTime
124
142
  when Time, DateTime
125
- d = row[i]
126
- if OracleEnhancedAdapter.emulate_dates && (d.hour == 0 && d.min == 0 && d.sec == 0)
127
- d.to_date
143
+ if OracleEnhancedAdapter.emulate_dates && (v.hour == 0 && v.min == 0 && v.sec == 0)
144
+ v.to_date
128
145
  else
129
146
  # recreate Time or DateTime using Base.default_timezone
130
147
  begin
131
- Time.send(Base.default_timezone, d.year, d.month, d.day, d.hour, d.min, d.sec)
148
+ Time.send(Base.default_timezone, v.year, v.month, v.day, v.hour, v.min, v.sec)
132
149
  rescue
133
150
  offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
134
- ::DateTime.civil(d.year, d.month, d.day, d.hour, d.min, d.sec, offset)
151
+ ::DateTime.civil(v.year, v.month, v.day, v.hour, v.min, v.sec, offset)
135
152
  end
136
153
  end
137
- # RSI: added emulate_integers_by_column_name functionality
138
- when Float
139
- n = row[i]
140
- if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
141
- n.to_i
142
- else
143
- n
144
- end
145
- # ruby-oci8 2.0 returns OraNumber - convert it to Integer or BigDecimal
146
- when OraNumber
147
- n = row[i]
148
- n == (n_to_i = n.to_i) ? n_to_i : BigDecimal.new(n.to_s)
149
- else row[i]
150
- end unless col == 'raw_rnum_'
154
+ else v
155
+ end
151
156
  end
152
157
 
153
158
  rows << hash
@@ -38,17 +38,30 @@ module ActiveRecord #:nodoc:
38
38
  module InstanceMethods
39
39
  def self.included(base)
40
40
  base.instance_eval do
41
- alias_method_chain :create, :custom_method
41
+ if private_instance_methods.include?('create_without_callbacks') || private_instance_methods.include?(:create_without_callbacks)
42
+ alias_method :create_without_custom_method, :create_without_callbacks
43
+ alias_method :create_without_callbacks, :create_with_custom_method
44
+ else
45
+ alias_method_chain :create, :custom_method
46
+ end
42
47
  # insert after dirty checking in Rails 2.1
43
48
  # in Ruby 1.9 methods names are returned as symbols
44
49
  if private_instance_methods.include?('update_without_dirty') || private_instance_methods.include?(:update_without_dirty)
45
50
  alias_method :update_without_custom_method, :update_without_dirty
46
51
  alias_method :update_without_dirty, :update_with_custom_method
52
+ elsif private_instance_methods.include?('update_without_callbacks') || private_instance_methods.include?(:update_without_callbacks)
53
+ alias_method :update_without_custom_method, :update_without_callbacks
54
+ alias_method :update_without_callbacks, :update_with_custom_method
47
55
  else
48
56
  alias_method_chain :update, :custom_method
49
57
  end
50
58
  private :create, :update
51
- alias_method_chain :destroy, :custom_method
59
+ if public_instance_methods.include?('destroy_without_callbacks') || public_instance_methods.include?(:destroy_without_callbacks)
60
+ alias_method :destroy_without_custom_method, :destroy_without_callbacks
61
+ alias_method :destroy_without_callbacks, :destroy_with_custom_method
62
+ else
63
+ alias_method_chain :destroy, :custom_method
64
+ end
52
65
  public :destroy
53
66
  end
54
67
  end
@@ -1,11 +1,7 @@
1
1
  module ActiveRecord #:nodoc:
2
2
  module ConnectionAdapters #:nodoc:
3
3
  module OracleEnhancedVersion #:nodoc:
4
- MAJOR = 1
5
- MINOR = 2
6
- TINY = 0
7
-
8
- STRING = [MAJOR, MINOR, TINY].join('.')
4
+ VERSION = '1.2.1'
9
5
  end
10
6
  end
11
7
  end
@@ -47,6 +47,17 @@ describe "OracleEnhancedAdapter schema dump" do
47
47
  @new_conn = ActiveRecord::Base.oracle_enhanced_connection(CONNECTION_PARAMS)
48
48
  @new_conn.class.should == ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
49
49
  end
50
+
51
+ after(:all) do
52
+ # Workaround for undefining callback that was defined by JDBC adapter
53
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
54
+ ActiveRecord::Base.class_eval do
55
+ def after_save_with_oracle_lob
56
+ nil
57
+ end
58
+ end
59
+ end
60
+ end
50
61
 
51
62
  unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION =~ /^1\.9/
52
63
  it "should return the same tables list as original oracle adapter" do
@@ -66,11 +77,11 @@ describe "OracleEnhancedAdapter schema dump" do
66
77
  end
67
78
 
68
79
  it "should return the same structure dump as original oracle adapter" do
69
- @new_conn.structure_dump.should == @old_conn.structure_dump
80
+ @new_conn.structure_dump.split(";\n\n").sort.should == @old_conn.structure_dump.split(";\n\n").sort
70
81
  end
71
82
 
72
83
  it "should return the same structure drop as original oracle adapter" do
73
- @new_conn.structure_drop.should == @old_conn.structure_drop
84
+ @new_conn.structure_drop.split(";\n\n").sort.should == @old_conn.structure_drop.split(";\n\n").sort
74
85
  end
75
86
  end
76
87
 
@@ -530,14 +541,26 @@ describe "OracleEnhancedAdapter table quoting" do
530
541
  end
531
542
  end
532
543
 
544
+ def create_camel_case_table
545
+ ActiveRecord::Schema.define do
546
+ suppress_messages do
547
+ create_table "CamelCase" do |t|
548
+ t.string :name
549
+ t.integer :foo
550
+ end
551
+ end
552
+ end
553
+ end
554
+
533
555
  after(:each) do
534
556
  ActiveRecord::Schema.define do
535
557
  suppress_messages do
536
- drop_table "warehouse-things"
558
+ drop_table "warehouse-things" rescue nil
559
+ drop_table "CamelCase" rescue nil
537
560
  end
538
561
  end
539
- Object.send(:remove_const, "WarehouseThing")
540
- ActiveRecord::Base.table_name_prefix = nil
562
+ Object.send(:remove_const, "WarehouseThing") rescue nil
563
+ Object.send(:remove_const, "CamelCase") rescue nil
541
564
  end
542
565
 
543
566
  it "should allow creation of a table with non alphanumeric characters" do
@@ -548,6 +571,20 @@ describe "OracleEnhancedAdapter table quoting" do
548
571
 
549
572
  wh = WarehouseThing.create!(:name => "Foo", :foo => 2)
550
573
  wh.id.should_not be_nil
574
+
575
+ @conn.tables.should include("warehouse-things")
576
+ end
577
+
578
+ it "should allow creation of a table with CamelCase name" do
579
+ create_camel_case_table
580
+ class ::CamelCase < ActiveRecord::Base
581
+ set_table_name "CamelCase"
582
+ end
583
+
584
+ cc = CamelCase.create!(:name => "Foo", :foo => 2)
585
+ cc.id.should_not be_nil
586
+
587
+ @conn.tables.should include("CamelCase")
551
588
  end
552
589
 
553
590
  end
@@ -3,35 +3,101 @@ require File.dirname(__FILE__) + '/../../spec_helper.rb'
3
3
  describe "OracleEnhancedAdapter composite_primary_keys support" do
4
4
 
5
5
  before(:all) do
6
- if defined?(ActiveRecord::ConnectionAdapters::OracleAdapter)
7
- @old_oracle_adapter = ActiveRecord::ConnectionAdapters::OracleAdapter
8
- ActiveRecord::ConnectionAdapters.send(:remove_const, :OracleAdapter)
6
+ if defined?(::ActiveRecord::ConnectionAdapters::OracleAdapter)
7
+ @old_oracle_adapter = ::ActiveRecord::ConnectionAdapters::OracleAdapter
8
+ ::ActiveRecord::ConnectionAdapters.send(:remove_const, :OracleAdapter)
9
9
  end
10
10
  ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
11
- require 'composite_primary_keys'
12
- class ::JobHistory < ActiveRecord::Base
13
- set_table_name "job_history"
14
- set_primary_keys :employee_id, :start_date
11
+ if $cpk_oracle_adapter
12
+ ::ActiveRecord::ConnectionAdapters::OracleAdapter = $cpk_oracle_adapter
13
+ $cpk_oracle_adapter = nil
15
14
  end
15
+ require 'composite_primary_keys'
16
16
  end
17
17
 
18
18
  after(:all) do
19
- Object.send(:remove_const, 'CompositePrimaryKeys') if defined?(CompositePrimaryKeys)
20
- Object.send(:remove_const, 'JobHistory') if defined?(JobHistory)
19
+ # Object.send(:remove_const, 'CompositePrimaryKeys') if defined?(CompositePrimaryKeys)
20
+ if defined?(::ActiveRecord::ConnectionAdapters::OracleAdapter)
21
+ $cpk_oracle_adapter = ::ActiveRecord::ConnectionAdapters::OracleAdapter
22
+ ::ActiveRecord::ConnectionAdapters.send(:remove_const, :OracleAdapter)
23
+ end
21
24
  if @old_oracle_adapter
22
- ActiveRecord::ConnectionAdapters.send(:remove_const, :OracleAdapter)
23
- ActiveRecord::ConnectionAdapters::OracleAdapter = @old_oracle_adapter
25
+ ::ActiveRecord::ConnectionAdapters::OracleAdapter = @old_oracle_adapter
26
+ @old_oracle_adapter = nil
24
27
  end
25
28
  end
26
29
 
27
- it "should tell ActiveRecord that count distinct is not supported" do
28
- ActiveRecord::Base.connection.supports_count_distinct?.should be_false
29
- end
30
+ describe "do not use count distinct" do
31
+ before(:all) do
32
+ class ::JobHistory < ActiveRecord::Base
33
+ set_table_name "job_history"
34
+ set_primary_keys :employee_id, :start_date
35
+ end
36
+ end
37
+
38
+ after(:all) do
39
+ Object.send(:remove_const, 'JobHistory') if defined?(JobHistory)
40
+ end
41
+
42
+ it "should tell ActiveRecord that count distinct is not supported" do
43
+ ActiveRecord::Base.connection.supports_count_distinct?.should be_false
44
+ end
30
45
 
31
- it "should execute correct SQL COUNT DISTINCT statement on table with composite primary keys" do
32
- lambda { JobHistory.count(:distinct => true) }.should_not raise_error
46
+ it "should execute correct SQL COUNT DISTINCT statement on table with composite primary keys" do
47
+ lambda { JobHistory.count(:distinct => true) }.should_not raise_error
48
+ end
33
49
  end
34
50
 
51
+ describe "table with LOB" do
52
+ before(:all) do
53
+ ActiveRecord::Schema.define do
54
+ suppress_messages do
55
+ create_table :cpk_write_lobs_test, :primary_key => [:type_category, :date_value], :force => true do |t|
56
+ t.string :type_category, :limit => 15, :null => false
57
+ t.date :date_value, :null => false
58
+ t.text :results, :null => false
59
+ t.timestamps
60
+ end
61
+ create_table :non_cpk_write_lobs_test, :force => true do |t|
62
+ t.date :date_value, :null => false
63
+ t.text :results, :null => false
64
+ t.timestamps
65
+ end
66
+ end
67
+ end
68
+ class ::CpkWriteLobsTest < ActiveRecord::Base
69
+ set_table_name 'cpk_write_lobs_test'
70
+ set_primary_keys :type_category, :date_value
71
+ end
72
+ class ::NonCpkWriteLobsTest < ActiveRecord::Base
73
+ set_table_name 'non_cpk_write_lobs_test'
74
+ end
75
+ end
76
+
77
+ after(:all) do
78
+ ActiveRecord::Schema.define do
79
+ suppress_messages do
80
+ drop_table :cpk_write_lobs_test
81
+ drop_table :non_cpk_write_lobs_test
82
+ end
83
+ end
84
+ Object.send(:remove_const, "CpkWriteLobsTest")
85
+ Object.send(:remove_const, "NonCpkWriteLobsTest")
86
+ end
87
+
88
+ it "should create new record in table with CPK and LOB" do
89
+ lambda {
90
+ CpkWriteLobsTest.create(:type_category => 'AAA', :date_value => Date.today, :results => 'DATA '*10)
91
+ }.should_not raise_error
92
+ end
93
+
94
+ it "should create new record in table without CPK and with LOB" do
95
+ lambda {
96
+ NonCpkWriteLobsTest.create(:date_value => Date.today, :results => 'DATA '*10)
97
+ }.should_not raise_error
98
+ end
99
+ end
100
+
35
101
  # Other testing was done based on composite_primary_keys tests
36
102
 
37
103
  end
@@ -74,6 +74,13 @@ describe "OracleEnhancedAdapter date type detection based on column names" do
74
74
  column.type_cast(Time.now).class.should == Date
75
75
  end
76
76
 
77
+ it "should typecast DateTime value to Date value from DATE column if column name contains 'date' and emulate_dates_by_column_name is true" do
78
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
79
+ columns = @conn.columns('test_employees')
80
+ column = columns.detect{|c| c.name == "hire_date"}
81
+ column.type_cast(DateTime.new(1900,1,1)).class.should == Date
82
+ end
83
+
77
84
  describe "/ DATE values from ActiveRecord model" do
78
85
  before(:each) do
79
86
  ActiveRecord::Base.connection.clear_types_for_columns
@@ -85,9 +92,9 @@ describe "OracleEnhancedAdapter date type detection based on column names" do
85
92
  end
86
93
  end
87
94
 
88
- def create_test_employee
89
- @today = Date.new(2008,8,19)
90
- @now = Time.local(2008,8,19,17,03,59)
95
+ def create_test_employee(params={})
96
+ @today = params[:today] || Date.new(2008,8,19)
97
+ @now = params[:now] || Time.local(2008,8,19,17,03,59)
91
98
  @employee = TestEmployee.create(
92
99
  :first_name => "First",
93
100
  :last_name => "Last",
@@ -114,6 +121,12 @@ describe "OracleEnhancedAdapter date type detection based on column names" do
114
121
  @employee.hire_date.class.should == Date
115
122
  end
116
123
 
124
+ it "should return Date value from DATE column with old date value if column name contains 'date' and emulate_dates_by_column_name is true" do
125
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
126
+ create_test_employee(:today => Date.new(1900,1,1))
127
+ @employee.hire_date.class.should == Date
128
+ end
129
+
117
130
  it "should return Time value from DATE column if column name does not contain 'date' and emulate_dates_by_column_name is true" do
118
131
  ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
119
132
  create_test_employee
@@ -129,6 +142,15 @@ describe "OracleEnhancedAdapter date type detection based on column names" do
129
142
  @employee.hire_date.class.should == Date
130
143
  end
131
144
 
145
+ it "should return Date value from DATE column with old date value if emulate_dates_by_column_name is false but column is defined as date" do
146
+ class ::TestEmployee < ActiveRecord::Base
147
+ set_date_columns :hire_date
148
+ end
149
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = false
150
+ create_test_employee(:today => Date.new(1900,1,1))
151
+ @employee.hire_date.class.should == Date
152
+ end
153
+
132
154
  it "should return Time value from DATE column if emulate_dates_by_column_name is true but column is defined as datetime" do
133
155
  class ::TestEmployee < ActiveRecord::Base
134
156
  set_datetime_columns :hire_date
@@ -159,6 +159,26 @@ describe "OracleEnhancedAdapter custom methods for create, update and destroy" d
159
159
  @employee.update_time.should_not be_nil
160
160
  end
161
161
 
162
+ it "should rollback record when exception is raised in after_create callback" do
163
+ @employee = TestEmployee.new(
164
+ :first_name => "First",
165
+ :last_name => "Last",
166
+ :hire_date => @today
167
+ )
168
+ TestEmployee.class_eval { def after_create() raise "Make the transaction rollback" end }
169
+ begin
170
+ employees_count = TestEmployee.count
171
+ @employee.save
172
+ fail "Did not raise exception"
173
+ rescue => e
174
+ e.message.should == "Make the transaction rollback"
175
+ @employee.id.should == nil
176
+ TestEmployee.count.should == employees_count
177
+ ensure
178
+ TestEmployee.class_eval { remove_method :after_create }
179
+ end
180
+ end
181
+
162
182
  it "should update record" do
163
183
  @employee = TestEmployee.create(
164
184
  :first_name => "First",
@@ -173,6 +193,29 @@ describe "OracleEnhancedAdapter custom methods for create, update and destroy" d
173
193
  @employee.description.should == "Second Last"
174
194
  end
175
195
 
196
+ it "should rollback record when exception is raised in after_update callback" do
197
+ TestEmployee.class_eval { def after_update() raise "Make the transaction rollback" end }
198
+ begin
199
+ @employee = TestEmployee.create(
200
+ :first_name => "First",
201
+ :last_name => "Last",
202
+ :hire_date => @today,
203
+ :description => "description"
204
+ )
205
+ empl_id = @employee.id
206
+ @employee.reload
207
+ @employee.first_name = "Second"
208
+ @employee.save!
209
+ fail "Did not raise exception"
210
+ rescue => e
211
+ e.message.should == "Make the transaction rollback"
212
+ @employee.reload
213
+ @employee.first_name.should == "First"
214
+ ensure
215
+ TestEmployee.class_eval { remove_method :after_update }
216
+ end
217
+ end
218
+
176
219
  it "should not update record if nothing is changed and partial updates are enabled" do
177
220
  return pending("Not in this ActiveRecord version") unless TestEmployee.respond_to?(:partial_updates=)
178
221
  TestEmployee.partial_updates = true
@@ -214,6 +257,27 @@ describe "OracleEnhancedAdapter custom methods for create, update and destroy" d
214
257
  TestEmployee.find_by_employee_id(empl_id).should be_nil
215
258
  end
216
259
 
260
+ it "should rollback record when exception is raised in after_desotry callback" do
261
+ TestEmployee.class_eval { def after_destroy() raise "Make the transaction rollback" end }
262
+ @employee = TestEmployee.create(
263
+ :first_name => "First",
264
+ :last_name => "Last",
265
+ :hire_date => @today
266
+ )
267
+ @employee.reload
268
+ empl_id = @employee.id
269
+ begin
270
+ @employee.destroy
271
+ fail "Did not raise exception"
272
+ rescue => e
273
+ e.message.should == "Make the transaction rollback"
274
+ @employee.id.should == empl_id
275
+ TestEmployee.find_by_employee_id(empl_id).should_not be_nil
276
+ ensure
277
+ TestEmployee.class_eval { remove_method :after_destroy }
278
+ end
279
+ end
280
+
217
281
  it "should log create record" do
218
282
  log_to @buffer
219
283
  # reestablish plsql.connection as log_to might reset existing connection
@@ -58,28 +58,35 @@ module LoggerSpecHelper
58
58
  end
59
59
  end
60
60
 
61
+ DATABASE_NAME = ENV['DATABASE_NAME'] || 'orcl'
62
+ DATABASE_HOST = ENV['DATABASE_HOST'] || 'localhost'
63
+ DATABASE_PORT = ENV['DATABASE_PORT'] || 1521
64
+ DATABASE_USER = ENV['DATABASE_USER'] || 'hr'
65
+ DATABASE_PASSWORD = ENV['DATABASE_PASSWORD'] || 'hr'
66
+ DATABASE_SYS_PASSWORD = ENV['DATABASE_SYS_PASSWORD'] || 'admin'
67
+
61
68
  CONNECTION_PARAMS = {
62
69
  :adapter => "oracle_enhanced",
63
- :database => "xe",
64
- :host => "ubuntu810",
65
- :username => "hr",
66
- :password => "hr"
70
+ :database => DATABASE_NAME,
71
+ :host => DATABASE_HOST,
72
+ :username => DATABASE_USER,
73
+ :password => DATABASE_PASSWORD
67
74
  }
68
75
 
69
76
  JDBC_CONNECTION_PARAMS = {
70
77
  :adapter => "jdbc",
71
78
  :driver => "oracle.jdbc.driver.OracleDriver",
72
- :url => "jdbc:oracle:thin:@ubuntu810:1521:XE",
73
- :username => "hr",
74
- :password => "hr"
79
+ :url => "jdbc:oracle:thin:@#{DATABASE_HOST}:#{DATABASE_PORT}:#{DATABASE_NAME}",
80
+ :username => DATABASE_USER,
81
+ :password => DATABASE_PASSWORD
75
82
  }
76
83
 
77
84
  SYS_CONNECTION_PARAMS = {
78
85
  :adapter => "oracle_enhanced",
79
- :database => "xe",
80
- :host => "ubuntu810",
86
+ :database => DATABASE_NAME,
87
+ :host => DATABASE_HOST,
81
88
  :username => "sys",
82
- :password => "manager",
89
+ :password => DATABASE_SYS_PASSWORD,
83
90
  :privilege => "SYSDBA"
84
91
  }
85
92
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-oracle_enhanced-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raimonds Simanovskis
@@ -9,9 +9,29 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-22 00:00:00 +02:00
12
+ date: 2009-06-08 00:00:00 +03:00
13
13
  default_executable:
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: newgem
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.0
34
+ version:
15
35
  - !ruby/object:Gem::Dependency
16
36
  name: hoe
17
37
  type: :development
@@ -20,11 +40,21 @@ dependencies:
20
40
  requirements:
21
41
  - - ">="
22
42
  - !ruby/object:Gem::Version
23
- version: 1.8.2
43
+ version: 1.8.0
24
44
  version:
25
- description: Oracle enhaced adapter for Active Record
45
+ description: |-
46
+ Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases
47
+ from Rails which are extracted from current real projects' monkey patches of original Oracle adapter.
48
+
49
+ See http://github.com/rsim/oracle-enhanced/wikis for more information.
50
+
51
+ For questions and feature discussion please use http://groups.google.com/group/oracle-enhanced
52
+
53
+ Blog posts about oracle-enahnced can be found at http://blog.rayapps.com/category/oracle-enhanced
54
+
55
+ Bugs and enhancement requests can be reported at http://rsim.lighthouseapp.com/projects/11468-oracle-enhanced
26
56
  email:
27
- - raymonds72@gmail.com
57
+ - raimonds.simanovskis@gmail.com
28
58
  executables: []
29
59
 
30
60
  extensions: []
@@ -32,11 +62,11 @@ extensions: []
32
62
  extra_rdoc_files:
33
63
  - History.txt
34
64
  - License.txt
35
- - README.txt
65
+ - README.rdoc
36
66
  files:
37
67
  - History.txt
38
68
  - License.txt
39
- - README.txt
69
+ - README.rdoc
40
70
  - lib/active_record/connection_adapters/emulation/oracle_adapter.rb
41
71
  - lib/active_record/connection_adapters/oracle_enhanced.rake
42
72
  - lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
@@ -61,11 +91,13 @@ files:
61
91
  - spec/spec.opts
62
92
  - spec/spec_helper.rb
63
93
  has_rdoc: true
64
- homepage: http://oracle-enhanced.rubyforge.org
65
- post_install_message: ""
94
+ homepage: http://rubyforge.org/projects/oracle-enhanced/
95
+ licenses: []
96
+
97
+ post_install_message:
66
98
  rdoc_options:
67
99
  - --main
68
- - README.txt
100
+ - README.rdoc
69
101
  require_paths:
70
102
  - lib
71
103
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -83,9 +115,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
115
  requirements: []
84
116
 
85
117
  rubyforge_project: oracle-enhanced
86
- rubygems_version: 1.3.1
118
+ rubygems_version: 1.3.3
87
119
  signing_key:
88
- specification_version: 2
120
+ specification_version: 3
89
121
  summary: Oracle enhaced adapter for Active Record
90
122
  test_files: []
91
123