Class: ActiveRecord::Base

Inherits:
Object
  • Object
show all
Extended by:
ActiveRecord::Bulkoperation::BatchUpdate::InstanceMethods
Defined in:
lib/activerecord_bulkoperation.rb,
lib/activerecord_bulkoperation/bulkoperation.rb,
lib/activerecord_bulkoperation/group_operations.rb,
lib/activerecord_bulkoperation/group_operations_select.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ActiveRecord::Bulkoperation::BatchUpdate::InstanceMethods

enhanced_write_lobs, execute_batch_update

Instance Attribute Details

#orginal_selected_recordObject (readonly)

Returns the value of attribute orginal_selected_record.



5
6
7
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 5

def orginal_selected_record
  @orginal_selected_record
end

Class Method Details

.build_delete_by_primary_key_sqlObject



64
65
66
67
68
69
70
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 64

def build_delete_by_primary_key_sql
  keys = primary_key_columns

  index = 1
  "DELETE FROM #{table_name} " \
  'WHERE ' +         "#{keys.map { |c| string = "#{c.name} = :#{index}"; index += 1; string }.join(' AND ') } "
end

.build_optimistic_delete_sqlObject



72
73
74
75
76
77
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 72

def build_optimistic_delete_sql
  index = 1
  "DELETE FROM #{table_name} " \
  'WHERE ' +         "#{columns.map { |c| string = build_optimistic_where_element(index, c); index += 1; index += 1 if c.null; string }.join(' AND ') } " 
 
end

.build_optimistic_update_sqlObject



79
80
81
82
83
84
85
86
87
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 79

def build_optimistic_update_sql
  index = 1
  "UPDATE #{table_name} " \
  'SET ' +
  "#{columns.map { |c| string = c.name + " = :#{index}"; index += 1; string }.join(', ') } " +
  'WHERE ' +
  "#{columns.map { |c| string = build_optimistic_where_element(index, c); index += 1; index += 1 if c.null; string }.join(' AND ') } "# +
  #"AND ROWID = :#{index}"
end

.build_update_by_primary_key_sqlObject



89
90
91
92
93
94
95
96
97
98
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 89

def build_update_by_primary_key_sql
  keys = primary_key_columns

  index = 1
  "UPDATE #{table_name} " \
  'SET ' +
  "#{columns.map { |c| string = c.name + " = :#{index}"; index += 1; string }.join(', ') } " +
  'WHERE ' +
  "#{keys.map { |c| string = "#{c.name} = :#{index}"; index += 1; string }.join(' AND ') } "
end

.check_group(group) ⇒ Object



71
72
73
74
75
76
77
78
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 71

def check_group(group)
  unless group.is_a?(Array) || group.is_a?(Set) || group.is_a?(ActiveRecord::Relation)
    raise ArgumentError, "Array expected. Got #{group.class.name}."
  end
  unless group.reject { |i| i.is_a? self }.empty?
    raise ArgumentError, "only records of #{name} expected. Unexpected #{group.reject { |i| i.is_a? self }.map { |i| i.class.name }.uniq.join(',')} found."
  end
end

.check_sequenceObject



29
30
31
32
33
34
35
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 29

def check_sequence
  if sequence_exists?("#{table_name}_seq")
    "#{table_name}_seq"
  else
    sequence_name
  end
end

.delete_group(group, options = {}) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 232

def delete_group(group, options = {})
  check_group(group)

  optimistic = options[:optimistic]
  optimistic = true if optimistic.nil?

  to_delete = group.reject(&:new_record?)

  if optimistic && !to_delete.reject(&:orginal_selected_record).empty?
    raise NoOrginalRecordFound
  end

  sql = optimistic ? build_optimistic_delete_sql : build_delete_by_primary_key_sql

  types = []

  if optimistic

    columns.each do |c|
      type = to_type_symbol(c)
      types << type
      types << type if c.null
    end

  else

    keys = primary_key_columns

    keys.each do |c|
      type = to_type_symbol(c)
      types << type
    end

  end

  values = []

  if optimistic

    to_delete.each do |record|
      row = []
      orginal = record.orginal_selected_record
      columns.each do |c|
        v = orginal[c.name]
        row << v
        row << v if c.null
      end

      values << row
    end

  else

    keys = primary_key_columns

    to_delete.each do |record|
      row = keys.map { |c| record[c.name] }
      values << row
    end

  end

  count = execute_batch_update(sql, types, values, optimistic)

  count
rescue ExternalDataChange => e
  raise e
rescue Exception => e
  raise StatementError, "#{sql} #{e.message}"
end

.establish_connection_with_activerecord_bulkoperation(*args) ⇒ Object Also known as: establish_connection



10
11
12
13
# File 'lib/activerecord_bulkoperation.rb', line 10

def establish_connection_with_activerecord_bulkoperation(*args)
  establish_connection_without_activerecord_bulkoperation(*args)
  ActiveSupport.run_load_hooks(:active_record_connection_established, connection_pool)
end

.extract_options_from_args!(args) ⇒ Object

:nodoc:



60
61
62
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 60

def extract_options_from_args!(args) #:nodoc:
  args.last.is_a?(Hash) ? args.pop : {}
end

.find_detail_references(table_name) ⇒ Object



54
55
56
57
58
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 54

def self.find_detail_references(table_name)
  connection.find_detail_references_sql_array(table_name)
  sql = sanitize_sql([sql, table_name.upcase])
  find_by_sql(sql)
end

.find_foreign_detail_tables(table_name) ⇒ Object



48
49
50
51
52
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 48

def find_foreign_detail_tables(table_name)
  arr = connection.find_foreign_detail_tables_sql_array(table_name)
  sql = sanitize_sql(arr)
  Array(find_by_sql(sql)).map { |e|e.table_name }
end

.find_foreign_master_tables(table_name) ⇒ Object



42
43
44
45
46
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 42

def find_foreign_master_tables(table_name)
  arr = connection.find_foreign_master_tables_sql_array(table_name)
  sql = sanitize_sql(arr)
  Array(find_by_sql(sql)).map { |e|e.table_name }
end

.find_group_by(args) ⇒ Object

old style group find like PItem.find_group_by( :i_company => [45,45,45], :i_itemno => [1,2,3], :i_size => [0,0,0] ) leads to a sql statement in the form of ‘SELECT * FROM table WHERE ((i_company, i_itemno, i_size) IN ((45, 1, 0), (45, 2, 0), (45, 3, 0)))’



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/activerecord_bulkoperation/group_operations_select.rb', line 9

def find_group_by(args)
  fail 'no hash given, use Table.all() instead' if args.nil? || args.empty? || (not args.is_a?(Hash))
  fail 'args is not a hash of arrays' if args.select { |k,v| k.is_a?(Symbol) || v.is_a?(Array) }.size != args.size
  
  conditions = ( args.is_a?(Hash) && args[:conditions] ) || args  
  tuple_size = conditions.size
  array_size = 0
  conditions.each do |k,v|
    array_size = v.size unless array_size > 0
    fail 'not all arrays are of the same size' unless v.size == array_size
  end

  symbols = conditions.keys
  values = Array.new(array_size) { Array.new(tuple_size) }

  # to recurse is golden, to transpose ... divine!
  i = 0
  (array_size*tuple_size).times do
    values[i/tuple_size][i%tuple_size] = conditions.values[i%tuple_size][i/tuple_size]
    i += 1
  end

  return where_tuple(symbols, values)
end

.flush_scheduled_operations(args = {}) ⇒ Object



14
15
16
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 14

def flush_scheduled_operations(args = {})
  ActiveRecord::Bulkoperation::Util::FlushDirtyObjects.get.flush_record_class(self, args)
end

.foreign_detail_tablesObject



63
64
65
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 63

def foreign_detail_tables
  @foreign_detail_tables ||= find_foreign_detail_tables(table_name)
end

.foreign_master_tablesObject



67
68
69
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 67

def foreign_master_tables
  @foreign_master_tables ||= find_foreign_master_tables(table_name)
end

.has_id_column?Boolean

Returns:

  • (Boolean)


18
19
20
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 18

def has_id_column?
  @has_id_column ||= columns_hash.key?('id')
end

.insert_group(group, _options = {}) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 80

def insert_group(group, _options = {})
  group.reject(&:new_record?)

  sql = "INSERT INTO #{table_name} " \
        '( ' \
        "#{columns.map(&:name).join(', ')} " \
        ') VALUES ( ' \
        "#{(1..columns.count).map { |i| ":#{i}" }.join(', ')} " \
        ')'

  types = []

  columns.each do |c|
    type = to_type_symbol(c)
    types << type
  end

  values = []

  group.each do |record|
    row = []

    columns.each do |c|
      v = record.read_attribute(c.name)
      row << v
    end

    values << row
  end

  result = execute_batch_update(sql, types, values)

  group.each(&:unset_new_record)

  result
end

.insert_on_missing_group(keys, group, _options = {}) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 192

def insert_on_missing_group(keys, group, _options = {})
  # fail 'the give key array is empty' if keys.empty?

  keys = Array(primary_key) if keys.nil? || keys.empty?

  sql = "
  merge into #{table_name} target
  using ( select #{columns.map { |c| ":#{columns.index(c) + 1} #{c.name}" }.join(', ')} from dual ) source
  on ( #{keys.map { |c| "target.#{c} = source.#{c}" }.join(' and ')}  )
  when not matched then
  insert ( #{columns.map(&:name).join(', ')} )
  values( #{columns.map { |c| "source.#{c.name}" }.join(', ')} )
  "

  types = columns.map { |c| to_type_symbol(c) }

  values = []

  group.each do |record|
    row = []

    columns.each do |c|
      v = record.read_attribute(c.name)
      row << v
    end

    values << row
  end

  begin
    result = execute_batch_update(sql, types, values, false)
  rescue StandardError => e
    raise ActiveRecord::StatementInvalid, "#{e.message}\n#{sql}"
  end

  group.each(&:unset_new_record)

  result
end

.merge_group(group, options = {}) ⇒ Object

will insert or update the given array

group array of activerecord objects



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 29

def merge_group(group, options = {})
  check_group(group)

  to_insert = group.select(&:new_record?)

  to_update = group.reject(&:new_record?)

  affected_rows = 0

  unless to_insert.empty?
    affected_rows += insert_group(to_insert, options)
  end
  unless to_update.empty?
    affected_rows += update_group(to_update, options)
  end

  affected_rows
end

.next_sequence_valueObject



37
38
39
40
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 37

def next_sequence_value
  @sequence_cache ||= ActiveRecord::Bulkoperation::Util::SequenceCache.new(check_sequence)
  @sequence_cache.next_value
end

.sequence_exists?(name) ⇒ Boolean

Returns:

  • (Boolean)


22
23
24
25
26
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 22

def sequence_exists?(name)
  arr = connection.find_by_sequence_name_sql_array(name)
  sql = sanitize_sql(arr)
  find_by_sql(sql).count == 1
end

.to_type_symbol(column) ⇒ Object

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 48

def to_type_symbol(column)
  return :string if column.sql_type.index('CHAR')
  if column.sql_type.index('DATE') || column.sql_type.index('TIMESTAMP')
    return :date
  end
  if column.sql_type.index('NUMBER') && (column.sql_type.count(',') == 0)
    return :integer
  end
  if column.sql_type.index('NUMBER') && (column.sql_type.count(',') == 1)
    return :float
  end

  raise ArgumentError, "type #{column.sql_type} of #{column.name} is unsupported"
end

.update_group(group, options = {}) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 117

def update_group(group, options = {})
  check_group(group)

  optimistic = options[:optimistic]
  optimistic = true if optimistic.nil?

  if optimistic && !group.reject(&:orginal_selected_record).empty?
    raise NoOrginalRecordFound, "#{name} ( #{table_name} )"
  end

  sql = optimistic ? build_optimistic_update_sql : build_update_by_primary_key_sql

  types = []

  columns.each do |c|
    type = to_type_symbol(c)
    types << type
  end

  if optimistic

    columns.each do |c|
      type = to_type_symbol(c)
      types << type
      types << type if c.null
    end

  else

    keys = primary_key_columns

    keys.each do |c|
      type = to_type_symbol(c)
      types << type
    end

  end

  values = []

  keys = primary_key_columns

  group.each do |record|
    row = []

    columns.each do |c|
      v = record.read_attribute(c.name)
      row << v
    end

    if optimistic

      orginal = record.orginal_selected_record
      columns.each do |c|
        v = orginal[c.name]
        row << v
        row << v if c.null
      end

    else

      keys.each { |c| row << record[c.name] }

    end

    values << row
  end

  count = execute_batch_update(sql, types, values, optimistic)

  count
end

.where_tuple(symbols_tuple, values_tuples) ⇒ Object

not really compatible to the rest of ActiveRecord but it works provide parameters like this: symbols_tuple = [ :col1, :col2, :col3 ] and values_tuples = [ [1, 4, 7], [2, 5, 8], [3, 6, 9]]



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/activerecord_bulkoperation/group_operations_select.rb', line 37

def where_tuple(symbols_tuple, values_tuples)
  if symbols_tuple.nil? || symbols_tuple.size == 0 || (not symbols_tuple.is_a?(Array)) || (not symbols_tuple.select { |s| not s.is_a?(Symbol) }.empty?)
    fail 'no symbols given or not every entry is a symbol'
  end

  tuple_size = symbols_tuple.size
  #fail "don't use this method if you're not looking for tuples." if tuple_size < 2

  if values_tuples.nil? || values_tuples.size == 0 || (not values_tuples.is_a?(Array)) || (not values_tuples.select { |s| not s.is_a?(Array) }.empty?)
    fail 'no values given or not every value is an array'
  end

  tuple_part = "(#{(['?']*tuple_size).join(',')})"
  in_stmt = "(#{([tuple_part]*values_tuples.size).join(', ')})"
  stmt = "(#{symbols_tuple.map { |sym| sym.to_s }.join(', ')}) IN #{in_stmt}"

  res = where(stmt, *(values_tuples.flatten!))

  return res
end

Instance Method Details

#callbacks_closed_scheduled_operationObject



14
15
16
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 14

def callbacks_closed_scheduled_operation
  (@callbacks_closed_scheduled_operation ||= Set.new)
end

#insert_on_missing(*unique_columns) ⇒ Object



167
168
169
170
171
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 167

def insert_on_missing( *unique_columns )
  unique_columns = Array( self.class.primary_key ) if unique_columns.nil? || unique_columns.empty?
  set_id_from_sequence
  self.class.insert_on_missing_group( unique_columns, [self] ) > 0
end

#on_closed_scheduled_operationObject



18
19
20
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 18

def on_closed_scheduled_operation
  defined?(@callbacks_closed_scheduled_operation) && @callbacks_closed_scheduled_operation && @callbacks_closed_scheduled_operation.each(&:on_closed_scheduled_operation)
end

#optimistic_deleteObject



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 218

def optimistic_delete
  fail NoPersistentRecord.new if  @new_record
  fail NoOrginalRecordFound.new unless orginal_selected_record

  sql = self.class.build_optimistic_delete_sql

  binds = get_optimistic_where_binds

  types  = []

  for c in self.class.columns
    type = self.class.to_type_symbol(c)
    types << type
    types << type if c.null
  end

  self.class.execute_batch_update(sql, types, [binds])
end

#optimistic_updateObject



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 192

def optimistic_update
  fail NoPersistentRecord.new if  @new_record
  fail NoOrginalRecordFound.new unless orginal_selected_record

  sql = self.class.build_optimistic_update_sql

  types  = []

  for c in self.class.columns
    type = self.class.to_type_symbol(c)
    types << type
  end

  for c in self.class.columns
    type = self.class.to_type_symbol(c)
    types << type
    types << type if c.null
  end

  binds = self.class.columns.map { |column| read_attribute(column.name) }

  get_optimistic_where_binds.each { |v| binds << v }

  self.class.execute_batch_update(sql, types, [binds])
end

#save_originalObject



7
8
9
10
11
12
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 7

def save_original
  unless defined?(@orginal_selected_record) && @orginal_selected_record
    @orginal_selected_record = @attributes.to_hash.clone
  end
  self
end

#schedule_deleteObject



173
174
175
176
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 173

def schedule_delete
  ActiveRecord::Bulkoperation::Util::FlushDirtyObjects.get.add_delete(self)
  self
end

#schedule_insert_on_missing(*unique_columns) ⇒ Object



161
162
163
164
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 161

def schedule_insert_on_missing( *unique_columns )
  ActiveRecord::Bulkoperation::Util::FlushDirtyObjects.get.add_insert_on_missing( unique_columns, self )
  self
end

#schedule_mergeObject



155
156
157
158
159
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 155

def schedule_merge
  set_id_from_sequence
  ActiveRecord::Bulkoperation::Util::FlushDirtyObjects.get.add_merge(self)
  self
end

#schedule_merge_on_change(args = {}) ⇒ Object

Checks if a active record object was changed and if schedule if for DB merge

args - Hash

return - self



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 136

def schedule_merge_on_change( args={} )
  if orginal_selected_record.nil?
    schedule_merge
    return self
  end

  ignore_columns = args[:ignore_columns] || []

  attributes.each_pair do |key, value|
    next if ignore_columns.include?(key)
    if value != orginal_selected_record[key]
      schedule_merge
      return self
    end
  end

  self
end

#set_id_from_sequenceObject

if id column is nil a value will be fetched from the sequence and set to the id property



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/activerecord_bulkoperation/bulkoperation.rb', line 179

def set_id_from_sequence
  if self.class.has_id_column? && @attributes.fetch_value( 'id' ).nil?
    new_id = self.class.next_sequence_value
    if self.class.primary_key == 'id'
      self.id = new_id
    else
      id_will_change!                                 # <- neu
	  @attributes.write_from_user( 'id', new_id )
    end
  end
end

#unset_new_recordObject



22
23
24
# File 'lib/activerecord_bulkoperation/group_operations.rb', line 22

def unset_new_record
  @new_record = false
end