bulk_data_methods 1.0.0

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