bulk_data_methods 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. data/.gitignore +24 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +108 -0
  5. data/LICENSE +30 -0
  6. data/README +68 -0
  7. data/Rakefile +1 -0
  8. data/bulk_data_methods.gemspec +20 -0
  9. data/lib/bulk_data_methods/bulk_methods_mixin.rb +228 -0
  10. data/lib/bulk_data_methods/monkey_patch_postgres.rb +30 -0
  11. data/lib/bulk_data_methods/version.rb +3 -0
  12. data/lib/bulk_data_methods.rb +3 -0
  13. data/spec/bulk_data_methods/bulk_methods_mixin_spec.rb +436 -0
  14. data/spec/dummy/.rspec +1 -0
  15. data/spec/dummy/README +1 -0
  16. data/spec/dummy/Rakefile +7 -0
  17. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  18. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  19. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  20. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  21. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/spec/dummy/config/application.rb +65 -0
  23. data/spec/dummy/config/boot.rb +10 -0
  24. data/spec/dummy/config/database-sample.yml +32 -0
  25. data/spec/dummy/config/environment.rb +5 -0
  26. data/spec/dummy/config/environments/development.rb +37 -0
  27. data/spec/dummy/config/environments/production.rb +67 -0
  28. data/spec/dummy/config/environments/test.rb +37 -0
  29. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  30. data/spec/dummy/config/initializers/inflections.rb +15 -0
  31. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  32. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  33. data/spec/dummy/config/initializers/session_store.rb +8 -0
  34. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  35. data/spec/dummy/config/locales/en.yml +5 -0
  36. data/spec/dummy/config/routes.rb +58 -0
  37. data/spec/dummy/config.ru +4 -0
  38. data/spec/dummy/public/404.html +26 -0
  39. data/spec/dummy/public/422.html +26 -0
  40. data/spec/dummy/public/500.html +25 -0
  41. data/spec/dummy/public/favicon.ico +0 -0
  42. data/spec/dummy/script/rails +6 -0
  43. data/spec/dummy/spec/spec_helper.rb +38 -0
  44. data/spec/spec_helper.rb +33 -0
  45. data/spec/support/tables_spec_helper.rb +47 -0
  46. metadata +173 -0
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile ~/.gitignore_global
6
+
7
+ # Ignore bundler config
8
+ /.bundle
9
+
10
+ # Ignore the default SQLite database.
11
+ /db/*.sqlite3
12
+
13
+ # Ignore all logfiles and tempfiles.
14
+ /log/*.log
15
+ /tmp
16
+ .idea/*
17
+ database.yml
18
+ /log/*.pid
19
+ spec/dummy/db/*.sqlite3
20
+ spec/dummy/log/*.log
21
+ spec/dummy/tmp/
22
+ spec/dummy/.sass-cache
23
+ spec/dummy/config/database.yml
24
+ spec/dummy/db/schema.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bulk_data_methods.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,108 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bulk_data_methods (1.0.0)
5
+ pg
6
+ rails (>= 3.0.0)
7
+ rspec-rails
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionmailer (3.2.8)
13
+ actionpack (= 3.2.8)
14
+ mail (~> 2.4.4)
15
+ actionpack (3.2.8)
16
+ activemodel (= 3.2.8)
17
+ activesupport (= 3.2.8)
18
+ builder (~> 3.0.0)
19
+ erubis (~> 2.7.0)
20
+ journey (~> 1.0.4)
21
+ rack (~> 1.4.0)
22
+ rack-cache (~> 1.2)
23
+ rack-test (~> 0.6.1)
24
+ sprockets (~> 2.1.3)
25
+ activemodel (3.2.8)
26
+ activesupport (= 3.2.8)
27
+ builder (~> 3.0.0)
28
+ activerecord (3.2.8)
29
+ activemodel (= 3.2.8)
30
+ activesupport (= 3.2.8)
31
+ arel (~> 3.0.2)
32
+ tzinfo (~> 0.3.29)
33
+ activeresource (3.2.8)
34
+ activemodel (= 3.2.8)
35
+ activesupport (= 3.2.8)
36
+ activesupport (3.2.8)
37
+ i18n (~> 0.6)
38
+ multi_json (~> 1.0)
39
+ arel (3.0.2)
40
+ builder (3.0.3)
41
+ diff-lcs (1.1.3)
42
+ erubis (2.7.0)
43
+ hike (1.2.1)
44
+ i18n (0.6.1)
45
+ journey (1.0.4)
46
+ json (1.7.5)
47
+ mail (2.4.4)
48
+ i18n (>= 0.4.0)
49
+ mime-types (~> 1.16)
50
+ treetop (~> 1.4.8)
51
+ mime-types (1.19)
52
+ multi_json (1.3.6)
53
+ pg (0.14.1)
54
+ polyglot (0.3.3)
55
+ rack (1.4.1)
56
+ rack-cache (1.2)
57
+ rack (>= 0.4)
58
+ rack-ssl (1.3.2)
59
+ rack
60
+ rack-test (0.6.1)
61
+ rack (>= 1.0)
62
+ rails (3.2.8)
63
+ actionmailer (= 3.2.8)
64
+ actionpack (= 3.2.8)
65
+ activerecord (= 3.2.8)
66
+ activeresource (= 3.2.8)
67
+ activesupport (= 3.2.8)
68
+ bundler (~> 1.0)
69
+ railties (= 3.2.8)
70
+ railties (3.2.8)
71
+ actionpack (= 3.2.8)
72
+ activesupport (= 3.2.8)
73
+ rack-ssl (~> 1.3.2)
74
+ rake (>= 0.8.7)
75
+ rdoc (~> 3.4)
76
+ thor (>= 0.14.6, < 2.0)
77
+ rake (0.9.2.2)
78
+ rdoc (3.12)
79
+ json (~> 1.4)
80
+ rspec (2.11.0)
81
+ rspec-core (~> 2.11.0)
82
+ rspec-expectations (~> 2.11.0)
83
+ rspec-mocks (~> 2.11.0)
84
+ rspec-core (2.11.1)
85
+ rspec-expectations (2.11.3)
86
+ diff-lcs (~> 1.1.3)
87
+ rspec-mocks (2.11.3)
88
+ rspec-rails (2.11.0)
89
+ actionpack (>= 3.0)
90
+ activesupport (>= 3.0)
91
+ railties (>= 3.0)
92
+ rspec (~> 2.11.0)
93
+ sprockets (2.1.3)
94
+ hike (~> 1.2)
95
+ rack (~> 1.0)
96
+ tilt (~> 1.1, != 1.3.0)
97
+ thor (0.16.0)
98
+ tilt (1.3.3)
99
+ treetop (1.4.10)
100
+ polyglot
101
+ polyglot (>= 0.3.1)
102
+ tzinfo (0.3.33)
103
+
104
+ PLATFORMS
105
+ ruby
106
+
107
+ DEPENDENCIES
108
+ bulk_data_methods!
data/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ Copyright (c) 2010-2012, Fiksu, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ o Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ o Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the
14
+ distribution.
15
+
16
+ o Fiksu, Inc. nor the names of its contributors may be used to
17
+ endorse or promote products derived from this software without
18
+ specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README ADDED
@@ -0,0 +1,68 @@
1
+ bulk_data_methods
2
+ ==================
3
+ MixIn used to extend ActiveRecord::Base classes implementing bulk insert and update operations
4
+ through {#create_many} and {#update_many}.
5
+
6
+ Examples
7
+ ========
8
+
9
+ class Company < ActiveRecord::Base
10
+ extend BulkMethodsMixin
11
+ end
12
+ __________________________
13
+ BULK creation of many rows:
14
+
15
+ example no options used
16
+ rows = [
17
+ { :name => 'Keith', :salary => 1000 },
18
+ { :name => 'Alex', :salary => 2000 }
19
+ ]
20
+ Employee.create_many(rows)
21
+
22
+ example with :returning option to returns key value
23
+ rows = [
24
+ { :name => 'Keith', :salary => 1000 },
25
+ { :name => 'Alex', :salary => 2000 }
26
+ ]
27
+ options = { :returning => [:id] }
28
+ Employee.create_many(rows, options)
29
+
30
+ example with :slice_size option (will generate two insert queries)
31
+ rows = [
32
+ { :name => 'Keith', :salary => 1000 },
33
+ { :name => 'Alex', :salary => 2000 },
34
+ { :name => 'Mark', :salary => 3000 }
35
+ ]
36
+ options = { :slice_size => 2 }
37
+ Employee.create_many(rows, options)
38
+ _________________________
39
+ BULK updates of many rows:
40
+
41
+ example using "set_array" to add the value of "salary" to the specific employee's salary the default where clause matches IDs so, it works here.
42
+ rows = [
43
+ { :id => 1, :salary => 1000 },
44
+ { :id => 10, :salary => 2000 },
45
+ { :id => 23, :salary => 2500 }
46
+ ]
47
+ options = { :set_array => '"salary = datatable.salary"' }
48
+ Employee.update_many(rows, options)
49
+
50
+ example using where clause to match salary.
51
+ rows = [
52
+ { :id => 1, :salary => 1000, :company_id => 10 },
53
+ { :id => 10, :salary => 2000, :company_id => 12 },
54
+ { :id => 23, :salary => 2500, :company_id => 5 }
55
+ ]
56
+ options = {
57
+ :set_array => '"company_id = datatable.company_id"',
58
+ :where => '"#{table_name}.salary = datatable.salary"'
59
+ }
60
+ Employee.update_many(rows, options)
61
+
62
+ example setting where clause to the KEY of the hash passed in and the set_array is generated from the VALUES
63
+ rows = {
64
+ { :id => 1 } => { :salary => 100000, :company_id => 10 },
65
+ { :id => 10 } => { :salary => 110000, :company_id => 12 },
66
+ { :id => 23 } => { :salary => 90000, :company_id => 5 }
67
+ }
68
+ Employee.update_many(rows)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
2
+ require 'bulk_data_methods/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "bulk_data_methods"
6
+ s.version = BulkDataMethods::VERSION
7
+ s.license = 'New BSD License'
8
+ s.date = '2012-09-21'
9
+ s.summary = 'MixIn used to extend ActiveRecord::Base classes implementing bulk insert and update operations through {#create_many} and {#update_many}.'
10
+ s.description = 'MixIn used to extend ActiveRecord::Base classes implementing bulk insert and update operations through {#create_many} and {#update_many}.'
11
+ s.authors = ["Keith Gabryelski"]
12
+ s.email = 'keith@fiksu.com'
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.require_path = 'lib'
16
+ s.homepage = 'http://github.com/fiksu/bulk_data_methods'
17
+ s.add_dependency "pg"
18
+ s.add_dependency "rails", '>= 3.0.0'
19
+ s.add_dependency 'rspec-rails'
20
+ end
@@ -0,0 +1,228 @@
1
+ # MixIn used to extend ActiveRecord::Base classes implementing bulk insert and update operations
2
+ # through {#create_many} and {#update_many}.
3
+ # @example to use:
4
+ # class Company < ActiveRecord::Base
5
+ # extend BulkMethodsMixin
6
+ # end
7
+ #
8
+ module BulkMethodsMixin
9
+ # exception thrown when row data structures are inconsistent between rows in single call to {#create_many} or {#update_many}
10
+ class BulkUploadDataInconsistent < StandardError
11
+ def initialize(model, table_name, expected_columns, found_columns, while_doing)
12
+ super("#{model.name}: for table: #{table_name}; #{expected_columns} != #{found_columns}; #{while_doing}")
13
+ end
14
+ end
15
+
16
+ # BULK creation of many rows
17
+ #
18
+ # @example no options used
19
+ # rows = [
20
+ # { :name => 'Keith', :salary => 1000 },
21
+ # { :name => 'Alex', :salary => 2000 }
22
+ # ]
23
+ # Employee.create_many(rows)
24
+ #
25
+ # @example with :returning option to returns key value
26
+ # rows = [
27
+ # { :name => 'Keith', :salary => 1000 },
28
+ # { :name => 'Alex', :salary => 2000 }
29
+ # ]
30
+ # options = { :returning => [:id] }
31
+ # Employee.create_many(rows, options)
32
+ # [#<Employee id: 1>, #<Employee id: 2>]
33
+ #
34
+ # @example with :slice_size option (will generate two insert queries)
35
+ # rows = [
36
+ # { :name => 'Keith', :salary => 1000 },
37
+ # { :name => 'Alex', :salary => 2000 },
38
+ # { :name => 'Mark', :salary => 3000 }
39
+ # ]
40
+ # options = { :slice_size => 2 }
41
+ # Employee.create_many(rows, options)
42
+ #
43
+ # @param [Array<Hash>] rows ([]) data to be inserted into database
44
+ # @param [Hash] options ({}) options for bulk inserts
45
+ # @option options [Integer] :slice_size (1000) how many records will be created in a single SQL query
46
+ # @option options [Boolean] :check_consitency (true) ensure some modicum of sanity on the incoming dataset, specifically: does each row define the same set of key/value pairs
47
+ # @option options [Array or String] :returning (nil) list of fields to return.
48
+ # @return [Array<Hash>] rows returned from DB as option[:returning] requests
49
+ # @raise [BulkUploadDataInconsistent] raised when key/value pairs between rows are inconsistent (check disabled with option :check_consistency)
50
+ def create_many(rows, options = {})
51
+ return [] if rows.blank?
52
+ options[:slice_size] = 1000 unless options.has_key?(:slice_size)
53
+ options[:check_consistency] = true unless options.has_key?(:check_consistency)
54
+ returning_clause = ""
55
+ if options[:returning]
56
+ if options[:returning].is_a? Array
57
+ returning_list = options[:returning].join(',')
58
+ else
59
+ returning_list = options[:returning]
60
+ end
61
+ returning_clause = " returning #{returning_list}"
62
+ end
63
+ returning = []
64
+
65
+ created_at_value = Time.zone.now
66
+
67
+ num_sequences_needed = rows.reject{|r| r[:id].present?}.length
68
+ if num_sequences_needed > 0
69
+ row_ids = connection.next_sequence_values(sequence_name, num_sequences_needed)
70
+ else
71
+ row_ids = []
72
+ end
73
+ rows.each do |row|
74
+ # set the primary key if it needs to be set
75
+ row[:id] ||= row_ids.shift
76
+ end.each do |row|
77
+ # set :created_at if need be
78
+ row[:created_at] ||= created_at_value
79
+ end.group_by do |row|
80
+ respond_to?(:partition_table_name) ? partition_table_name(*partition_key_values(row)) : table_name
81
+ end.each do |table_name, rows_for_table|
82
+ column_names = rows_for_table[0].keys.sort{|a,b| a.to_s <=> b.to_s}
83
+ sql_insert_string = "insert into #{table_name} (#{column_names.join(',')}) values "
84
+ rows_for_table.map do |row|
85
+ if options[:check_consistency]
86
+ row_column_names = row.keys.sort{|a,b| a.to_s <=> b.to_s}
87
+ if column_names != row_column_names
88
+ raise BulkUploadDataInconsistent.new(self, table_name, column_names, row_column_names, "while attempting to build insert statement")
89
+ end
90
+ end
91
+ column_values = column_names.map do |column_name|
92
+ quote_value(row[column_name], columns_hash[column_name.to_s])
93
+ end.join(',')
94
+ "(#{column_values})"
95
+ end.each_slice(options[:slice_size]) do |insert_slice|
96
+ returning += find_by_sql(sql_insert_string + insert_slice.join(',') + returning_clause)
97
+ end
98
+ end
99
+ return returning
100
+ end
101
+
102
+ #
103
+ # BULK updates of many rows
104
+ #
105
+ # @return [Array<Hash>] rows returned from DB as option[:returning] requests
106
+ # @raise [BulkUploadDataInconsistent] raised when key/value pairs between rows are inconsistent (check disabled with option :check_consistency)
107
+ # @param [Hash] options ({}) options for bulk inserts
108
+ # @option options [Integer] :slice_size (1000) how many records will be created in a single SQL query
109
+ # @option options [Boolean] :check_consitency (true) ensure some modicum of sanity on the incoming dataset, specifically: does each row define the same set of key/value pairs
110
+ # @option options [Array] :returning (nil) list of fields to return.
111
+ # @option options [String] :returning (nil) single field to return.
112
+ #
113
+ # @overload update_many(rows = [], options = {})
114
+ # @param [Array<Hash>] rows ([]) data to be updated
115
+ # @option options [String] :set_array (built from first row passed in) the set clause
116
+ # @option options [String] :where ('"#{table_name}.id = datatable.id"') the where clause
117
+ #
118
+ # @overload update_many(rows = {}, options = {})
119
+ # @param [Hash<Hash, Hash>] rows ({}) data to be updated
120
+ # @option options [String] :set_array (built from the values in the first key/value pair of `rows`) the set clause
121
+ # @option options [String] :where (built from the keys in the first key/value pair of `rows`) the where clause
122
+ #
123
+ # @example using "set_array" to add the value of "salary" to the specific employee's salary the default where clause matches IDs so, it works here.
124
+ # rows = [
125
+ # { :id => 1, :salary => 1000 },
126
+ # { :id => 10, :salary => 2000 },
127
+ # { :id => 23, :salary => 2500 }
128
+ # ]
129
+ # options = { :set_array => '"salary = datatable.salary"' }
130
+ # Employee.update_many(rows, options)
131
+ #
132
+ # @example using where clause to match salary.
133
+ # rows = [
134
+ # { :id => 1, :salary => 1000, :company_id => 10 },
135
+ # { :id => 10, :salary => 2000, :company_id => 12 },
136
+ # { :id => 23, :salary => 2500, :company_id => 5 }
137
+ # ]
138
+ # options = {
139
+ # :set_array => '"company_id = datatable.company_id"',
140
+ # :where => '"#{table_name}.salary = datatable.salary"'
141
+ # }
142
+ # Employee.update_many(rows, options)
143
+ #
144
+ # @example setting where clause to the KEY of the hash passed in and the set_array is generated from the VALUES
145
+ # rows = {
146
+ # { :id => 1 } => { :salary => 100000, :company_id => 10 },
147
+ # { :id => 10 } => { :salary => 110000, :company_id => 12 },
148
+ # { :id => 23 } => { :salary => 90000, :company_id => 5 }
149
+ # }
150
+ # Employee.update_many(rows)
151
+ #
152
+ # @note Remember that you should probably set updated_at using "updated = datatable.updated_at"
153
+ # or "updated_at = now()" in the set_array if you want to follow
154
+ # the standard active record model for time columns (and you have an updated_at column)
155
+ def update_many(rows, options = {})
156
+ return [] if rows.blank?
157
+ if rows.is_a?(Hash)
158
+ options[:where] = '"' + rows.keys[0].keys.map{|key| '#{table_name}.' + "#{key} = datatable.#{key}"}.join(' and ') + '"'
159
+ options[:set_array] = '"' + rows.values[0].keys.map{|key| "#{key} = datatable.#{key}"}.join(',') + '"' unless options[:set_array]
160
+ r = []
161
+ rows.each do |key,value|
162
+ r << key.merge(value)
163
+ end
164
+ rows = r
165
+ end
166
+ unless options[:set_array]
167
+ column_names = rows[0].keys
168
+ columns_to_remove = [:id]
169
+ columns_to_remove += [partition_keys].map{|k| k.to_sym} if respond_to?(:partition_keys)
170
+ options[:set_array] = '"' + (column_names - columns_to_remove.flatten).map{|cn| "#{cn} = datatable.#{cn}"}.join(',') + '"'
171
+ end
172
+ options[:slice_size] = 1000 unless options[:slice_size]
173
+ options[:check_consistency] = true unless options.has_key?(:check_consistency)
174
+ returning_clause = ""
175
+ if options[:returning]
176
+ if options[:returning].is_a?(Array)
177
+ returning_list = options[:returning].map{|r| '#{table_name}.' + r.to_s}.join(',')
178
+ else
179
+ returning_list = options[:returning]
180
+ end
181
+ returning_clause = "\" returning #{returning_list}\""
182
+ end
183
+ options[:where] = '"#{table_name}.id = datatable.id"' unless options[:where]
184
+
185
+ returning = []
186
+
187
+ rows.group_by do |row|
188
+ respond_to?(:partition_table_name) ? partition_table_name(*partition_key_values(row)) : table_name
189
+ end.each do |table_name, rows_for_table|
190
+ column_names = rows_for_table[0].keys.sort{|a,b| a.to_s <=> b.to_s}
191
+ rows_for_table.each_slice(options[:slice_size]) do |update_slice|
192
+ datatable_rows = []
193
+ update_slice.each_with_index do |row,i|
194
+ if options[:check_consistency]
195
+ row_column_names = row.keys.sort{|a,b| a.to_s <=> b.to_s}
196
+ if column_names != row_column_names
197
+ raise BulkUploadDataInconsistent.new(self, table_name, column_names, row_column_names, "while attempting to build update statement")
198
+ end
199
+ end
200
+ datatable_rows << row.map do |column_name,column_value|
201
+ column_name = column_name.to_s
202
+ columns_hash_value = columns_hash[column_name]
203
+ if i == 0
204
+ "#{quote_value(column_value, columns_hash_value)}::#{columns_hash_value.sql_type} as #{column_name}"
205
+ else
206
+ quote_value(column_value, columns_hash_value)
207
+ end
208
+ end.join(',')
209
+ end
210
+ datatable = datatable_rows.join(' union select ')
211
+
212
+ sql_update_string = <<-SQL
213
+ update #{table_name} set
214
+ #{eval(options[:set_array])}
215
+ from
216
+ (select
217
+ #{datatable}
218
+ ) as datatable
219
+ where
220
+ #{eval(options[:where])}
221
+ #{eval(returning_clause)}
222
+ SQL
223
+ returning += find_by_sql(sql_update_string)
224
+ end
225
+ end
226
+ return returning
227
+ end
228
+ end
@@ -0,0 +1,30 @@
1
+ require 'active_record'
2
+ require 'active_record/base'
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+ require 'active_record/connection_adapters/postgresql_adapter'
5
+
6
+ module ActiveRecord::ConnectionAdapters
7
+ class PostgreSQLAdapter < AbstractAdapter
8
+ #
9
+ # Get the next value in a sequence. Used on INSERT operation for
10
+ # partitioning like by_id because the ID is required before the insert
11
+ # so that the specific child table is known ahead of time.
12
+ #
13
+ # @param [String] sequence_name the name of the sequence to fetch the next value from
14
+ # @return [Integer] the value from the sequence
15
+ def next_sequence_value(sequence_name)
16
+ return execute("select nextval('#{sequence_name}')").field_values("nextval").first.to_i
17
+ end
18
+
19
+ #
20
+ # Get the some next values in a sequence.
21
+ #
22
+ # @param [String] sequence_name the name of the sequence to fetch the next values from
23
+ # @param [Integer] batch_size count of values.
24
+ # @return [Array<Integer>] an array of values from the sequence
25
+ def next_sequence_values(sequence_name, batch_size)
26
+ result = execute("select nextval('#{sequence_name}') from generate_series(1, #{batch_size})")
27
+ return result.field_values("nextval").map(&:to_i)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module BulkDataMethods
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,3 @@
1
+ require "bulk_data_methods/version"
2
+ require "bulk_data_methods/bulk_methods_mixin"
3
+ require "bulk_data_methods/monkey_patch_postgres"