activerecord-oracle_enhanced-adapter 1.2.0 → 1.2.1

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