postgresql_cursor 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +42 -26
- data/lib/postgresql_cursor/active_record/relation/cursor_iterators.rb +41 -0
- data/lib/postgresql_cursor/active_record/sql_cursor.rb +76 -2
- data/lib/postgresql_cursor/cursor.rb +77 -5
- data/lib/postgresql_cursor/version.rb +1 -1
- data/test-app/Gemfile +2 -2
- data/test/test_postgresql_cursor.rb +78 -5
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8f60d787898c546a7c79c8ff6ca3aee03a54793e9a4e3c0798fc39ad6d6ae7a0
|
4
|
+
data.tar.gz: c175f5708bd1f0b1ba06c005254e7fb513d5ea72f9a7b373f16a42d969cb45eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 547822d06073ffc68611179e93332d4840f250fe130d4448a0563986e6e41f9e30f26e3894ba326c4981ad51a44bff77bb326f4e4700f3267f80e6fc4e0008d5
|
7
|
+
data.tar.gz: 2479ab683e1a598ad18ef5a287106dda78113ab6e745c9ff3bd4eed982c5af9f0a2ad7d1b933b73328229bf39208aa0d25ed513607b06b417742091e7a4c7df6
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#PostgreSQLCursor for handling large Result Sets
|
1
|
+
# PostgreSQLCursor for handling large Result Sets
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/postgresql_cursor.svg)](http://badge.fury.io/rb/postgresql_cursor)
|
4
4
|
|
@@ -13,16 +13,12 @@ set is exhausted. By fetching a smaller chunk of data, this reduces the
|
|
13
13
|
amount of memory your application uses and prevents the potential crash
|
14
14
|
of running out of memory.
|
15
15
|
|
16
|
-
This extension is not intended to support the "FOR UPDATE / WHERE
|
17
|
-
CURRENT OF" syntax to process and update each row in place. The primary
|
18
|
-
goal is to read a large number of rows using buffering.
|
19
|
-
|
20
16
|
Supports Rails/ActiveRecord v3.1 (v3.2 recommended) higher (including
|
21
17
|
v5.0) and Ruby 1.9 and higher. Not all features work in ActiveRecord v3.1.
|
22
18
|
Support for this gem will only be for officially supported versions of
|
23
19
|
ActiveRecord and Ruby; others can try older versions of the gem.
|
24
20
|
|
25
|
-
##Using Cursors
|
21
|
+
## Using Cursors
|
26
22
|
|
27
23
|
PostgreSQLCursor was developed to take advantage of PostgreSQL's cursors. Cursors allow the program
|
28
24
|
to declare a cursor to run a given query returning "chunks" of rows to the application program while
|
@@ -79,7 +75,7 @@ Notes:
|
|
79
75
|
* Aliases each_hash and each_hash_by_sql are provided for each_row and each_row_by_sql
|
80
76
|
if you prefer to express what types are being returned.
|
81
77
|
|
82
|
-
###PostgreSQLCursor is an Enumerable
|
78
|
+
### PostgreSQLCursor is an Enumerable
|
83
79
|
|
84
80
|
If you do not pass in a block, the cursor is returned, which mixes in the Enumerable
|
85
81
|
libary. With that, you can pass it around, or chain in the awesome enumerable things
|
@@ -91,7 +87,7 @@ Product.each_row.map {|r| r["id"].to_i } #=> [1, 2, 3, ...]
|
|
91
87
|
Product.each_instance.map {|r| r.id }.each {|id| p id } #=> [1, 2, 3, ...]
|
92
88
|
Product.each_instance.lazy.inject(0) {|sum,r| sum + r.quantity } #=> 499500
|
93
89
|
```
|
94
|
-
###Hashes vs. Instances
|
90
|
+
### Hashes vs. Instances
|
95
91
|
|
96
92
|
The each_row method returns the Hash of strings for speed (as this allows you to process a lot of rows).
|
97
93
|
Hashes are returned with String values, and you must take care of any type conversion.
|
@@ -103,7 +99,7 @@ If you find you need the types cast for your attributes, consider using each_ins
|
|
103
99
|
insead. ActiveRecord's read casting algorithm will only cast the values you need and
|
104
100
|
has become more efficient over time.
|
105
101
|
|
106
|
-
###Select and Pluck
|
102
|
+
### Select and Pluck
|
107
103
|
|
108
104
|
To limit the columns returned to just those you need, use `.select(:id, :name)`
|
109
105
|
query method.
|
@@ -128,7 +124,7 @@ Product.pluck_rows(:id) #=> ["1", "2", ...]
|
|
128
124
|
Product.pluck_instances(:id, :quantity) #=> [[1, 503], [2, 932], ...]
|
129
125
|
```
|
130
126
|
|
131
|
-
###Associations and Eager Loading
|
127
|
+
### Associations and Eager Loading
|
132
128
|
|
133
129
|
ActiveRecord performs some magic when eager-loading associated row. It
|
134
130
|
will usually not join the tables, and prefers to load the data in
|
@@ -138,7 +134,33 @@ This library hooks onto the `to_sql` feature of the query builder. As a
|
|
138
134
|
result, it can't do the join if ActiveRecord decided not to join, nor
|
139
135
|
can it construct the association objects eagerly.
|
140
136
|
|
141
|
-
##
|
137
|
+
## Locking and Updating Each Row (FOR UPDATE Queries)
|
138
|
+
|
139
|
+
When you use the AREL `lock` method, a "FOR UPDATE" clause is added to
|
140
|
+
the query. This causes the block of rows returned from each FETCH
|
141
|
+
operation (see the `block_size` option) to be locked for you to update.
|
142
|
+
The lock is released on those rows once the block is exhausted and the
|
143
|
+
next FETCH or CLOSE statement is executed.
|
144
|
+
|
145
|
+
This example will run through a large table and potentially update each
|
146
|
+
row, locking only a set of rows at a time to allow concurrent use.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
Product.lock.each_instance(block_size:100) do |p|
|
150
|
+
p.update(price: p.price * 1.05)
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
Also, pay attention to the `block_size` you request. Locking large
|
155
|
+
blocks of rows for an extended time can cause deadlocks or other
|
156
|
+
performance issues in your application. On a busy table, or if the
|
157
|
+
processing of each row consumes a lot of time or resources, try a
|
158
|
+
`block_size` <= 10.
|
159
|
+
|
160
|
+
See the [PostgreSQL Select Documentation](https://www.postgresql.org/docs/current/static/sql-select.html)
|
161
|
+
for more information and limitations when using "FOR UPDATE" locking.
|
162
|
+
|
163
|
+
## Background: Why PostgreSQL Cursors?
|
142
164
|
|
143
165
|
ActiveRecord is designed and optimized for web performance. In a web transaction, only a "page" of
|
144
166
|
around 20 rows is returned to the user. When you do this
|
@@ -155,7 +177,7 @@ When there is a very large number of rows, this requires a lot more memory to ho
|
|
155
177
|
does not return that memory after processing the array, and the causes your process to "bloat". If you
|
156
178
|
don't have enough memory, it will cause an exception.
|
157
179
|
|
158
|
-
###ActiveRecord.find_each and find_in_batches
|
180
|
+
### ActiveRecord.find_each and find_in_batches
|
159
181
|
|
160
182
|
To solve this problem, ActiveRecord gives us two alternative methods that work in "chunks" of your data:
|
161
183
|
|
@@ -179,7 +201,7 @@ There are drawbacks with these methods:
|
|
179
201
|
### How it works
|
180
202
|
|
181
203
|
Under the covers, the library calls the PostgreSQL cursor operations
|
182
|
-
with the
|
204
|
+
with the pseudo-code:
|
183
205
|
|
184
206
|
SET cursor_tuple_fraction TO 1.0;
|
185
207
|
DECLARE cursor_1 CURSOR WITH HOLD FOR select * from widgets;
|
@@ -189,17 +211,11 @@ with the psuedo-code:
|
|
189
211
|
until rows.size < 100;
|
190
212
|
CLOSE cursor_1;
|
191
213
|
|
192
|
-
##Meta
|
193
|
-
###Author
|
194
|
-
Allen Fair, [@allenfair](https://twitter.com/allenfair),
|
195
|
-
|
196
|
-
Thanks to:
|
197
|
-
|
198
|
-
* Iulian Dogariu, http://github.com/iulianu (Fixes)
|
199
|
-
* Julian Mehnle, julian@mehnle.net (Suggestions)
|
200
|
-
* ...And all the other contributers!
|
214
|
+
## Meta
|
215
|
+
### Author
|
216
|
+
Allen Fair, [@allenfair](https://twitter.com/allenfair), [github://afair](https://github.com/afair)
|
201
217
|
|
202
|
-
###Note on Patches/Pull Requests
|
218
|
+
### Note on Patches/Pull Requests
|
203
219
|
|
204
220
|
* Fork the project.
|
205
221
|
* Make your feature addition or bug fix.
|
@@ -209,11 +225,11 @@ Thanks to:
|
|
209
225
|
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
210
226
|
* Send me a pull request. Bonus points for topic branches.
|
211
227
|
|
212
|
-
###Code of Conduct
|
228
|
+
### Code of Conduct
|
213
229
|
|
214
230
|
This project adheres to the [Open Code of Conduct](http://todogroup.org/opencodeofconduct/#postgresql_cursor/2016@allenfair.com).
|
215
231
|
By participating, you are expected to honor this code.
|
216
232
|
|
217
|
-
###Copyright
|
233
|
+
### Copyright
|
218
234
|
|
219
|
-
Copyright (c) 2010-
|
235
|
+
Copyright (c) 2010-2017 Allen Fair. See (MIT) LICENSE for details.
|
@@ -42,6 +42,47 @@ module PostgreSQLCursor
|
|
42
42
|
cursor.iterate_type(self)
|
43
43
|
end
|
44
44
|
|
45
|
+
# Public: Executes the query, yielding each batch of up to block_size
|
46
|
+
# rows where each row is a hash to the given block.
|
47
|
+
#
|
48
|
+
# Parameters: same as each_row
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
# Post.where(user_id:123).each_row_batch do |batch|
|
52
|
+
# Post.process_batch(batch)
|
53
|
+
# end
|
54
|
+
# Post.each_row_batch.map { |batch| Post.transform_batch(batch) }
|
55
|
+
#
|
56
|
+
# Returns the number of rows yielded to the block
|
57
|
+
def each_row_batch(options={}, &block)
|
58
|
+
options = {:connection => self.connection}.merge(options)
|
59
|
+
cursor = PostgreSQLCursor::Cursor.new(to_unprepared_sql, options)
|
60
|
+
return cursor.each_row_batch(&block) if block_given?
|
61
|
+
cursor.iterate_batched
|
62
|
+
end
|
63
|
+
alias :each_hash_batch :each_row_batch
|
64
|
+
|
65
|
+
# Public: Like each_row, but yields an array of instantiated model
|
66
|
+
# objects to the block
|
67
|
+
#
|
68
|
+
# Parameters: same as each_row
|
69
|
+
#
|
70
|
+
# Example:
|
71
|
+
# Post.where(user_id:123).each_instance_batch do |batch|
|
72
|
+
# Post.process_batch(batch)
|
73
|
+
# end
|
74
|
+
# Post.where(user_id:123).each_instance_batch.map do |batch|
|
75
|
+
# Post.transform_batch(batch)
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# Returns the number of rows yielded to the block
|
79
|
+
def each_instance_batch(options={}, &block)
|
80
|
+
options = {:connection => self.connection}.merge(options)
|
81
|
+
cursor = PostgreSQLCursor::Cursor.new(to_unprepared_sql, options)
|
82
|
+
return cursor.each_instance_batch(self, &block) if block_given?
|
83
|
+
cursor.iterate_type(self).iterate_batched
|
84
|
+
end
|
85
|
+
|
45
86
|
# Plucks the column names from the rows, and return them in an array
|
46
87
|
def pluck_rows(*cols)
|
47
88
|
options = cols.last.is_a?(Hash) ? cols.pop : {}
|
@@ -74,7 +74,81 @@ module PostgreSQLCursor
|
|
74
74
|
cursor.iterate_type(self)
|
75
75
|
end
|
76
76
|
|
77
|
-
#
|
77
|
+
# Public: Executes the query, yielding an array of up to block_size rows
|
78
|
+
# where each row is a hash to the given block.
|
79
|
+
#
|
80
|
+
# Parameters: same as each_row
|
81
|
+
#
|
82
|
+
# Example:
|
83
|
+
# Post.each_row_batch { |batch| Post.process_batch(batch) }
|
84
|
+
#
|
85
|
+
# Returns the number of rows yielded to the block
|
86
|
+
def each_row_batch(options={}, &block)
|
87
|
+
options = {:connection => self.connection}.merge(options)
|
88
|
+
all.each_row_batch(options, &block)
|
89
|
+
end
|
90
|
+
alias :each_hash_batch :each_row_batch
|
91
|
+
|
92
|
+
# Public: Like each_row_batch, but yields an array of instantiated model
|
93
|
+
# objects to the block
|
94
|
+
#
|
95
|
+
# Parameters: same as each_row
|
96
|
+
#
|
97
|
+
# Example:
|
98
|
+
# Post.each_instance_batch { |batch| Post.process_batch(batch) }
|
99
|
+
#
|
100
|
+
# Returns the number of rows yielded to the block
|
101
|
+
def each_instance_batch(options={}, &block)
|
102
|
+
options = {:connection => self.connection}.merge(options)
|
103
|
+
all.each_instance_batch(options, &block)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Public: Yields each batch of up to block_size rows as an array of rows
|
107
|
+
# where each row as a hash to the given block
|
108
|
+
#
|
109
|
+
# Parameters: see each_row_by_sql
|
110
|
+
#
|
111
|
+
# Example:
|
112
|
+
# Post.each_row_batch_by_sql("select * from posts") do |batch|
|
113
|
+
# Post.process_batch(batch)
|
114
|
+
# end
|
115
|
+
# Post.each_row_batch_by_sql("select * from posts").map do |batch|
|
116
|
+
# Post.transform_batch(batch)
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# Returns the number of rows yielded to the block
|
120
|
+
def each_row_batch_by_sql(sql, options={}, &block)
|
121
|
+
options = {:connection => self.connection}.merge(options)
|
122
|
+
cursor = PostgreSQLCursor::Cursor.new(sql, options)
|
123
|
+
return cursor.each_row_batch(&block) if block_given?
|
124
|
+
cursor.iterate_batched
|
125
|
+
end
|
126
|
+
alias :each_hash_batch_by_sql :each_row_batch_by_sql
|
127
|
+
|
128
|
+
# Public: Yields each batch up to block_size of rows as model instances
|
129
|
+
# to the given block
|
130
|
+
#
|
131
|
+
# As this instantiates a model object, it is slower than each_row_batch_by_sql
|
132
|
+
#
|
133
|
+
# Paramaters: see each_row_by_sql
|
134
|
+
#
|
135
|
+
# Example:
|
136
|
+
# Post.each_instance_batch_by_sql("select * from posts") do |batch|
|
137
|
+
# Post.process_batch(batch)
|
138
|
+
# end
|
139
|
+
# Post.each_instance_batch_by_sql("select * from posts").map do |batch|
|
140
|
+
# Post.transform_batch(batch)
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# Returns the number of rows yielded to the block
|
144
|
+
def each_instance_batch_by_sql(sql, options={}, &block)
|
145
|
+
options = {:connection => self.connection}.merge(options)
|
146
|
+
cursor = PostgreSQLCursor::Cursor.new(sql, options)
|
147
|
+
return cursor.each_instance_batch(self, &block) if block_given?
|
148
|
+
cursor.iterate_type(self).iterate_batched
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns an array of the given column names. Use if you need cursors and don't expect
|
78
152
|
# this to comsume too much memory. Values are strings. Like ActiveRecord's pluck.
|
79
153
|
def pluck_rows(*cols)
|
80
154
|
options = cols.last.is_a?(Hash) ? cols.pop : {}
|
@@ -82,7 +156,7 @@ module PostgreSQLCursor
|
|
82
156
|
end
|
83
157
|
alias :pluck_row :pluck_rows
|
84
158
|
|
85
|
-
# Returns
|
159
|
+
# Returns an array of the given column names. Use if you need cursors and don't expect
|
86
160
|
# this to comsume too much memory. Values are instance types. Like ActiveRecord's pluck.
|
87
161
|
def pluck_instances(*cols)
|
88
162
|
options = cols.last.is_a?(Hash) ? cols.pop : {}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_record/connection_adapters/postgresql/oid'
|
2
|
+
|
1
3
|
################################################################################
|
2
4
|
# PostgreSQLCursor: library class provides postgresql cursor for large result
|
3
5
|
# set processing. Requires ActiveRecord, but can be adapted to other DBI/ORM libraries.
|
@@ -17,6 +19,14 @@
|
|
17
19
|
# ActiveRecordModel.each_row_by_sql("select ...") { |hash| ... }
|
18
20
|
# ActiveRecordModel.each_instance_by_sql("select ...") { |model| ... }
|
19
21
|
#
|
22
|
+
|
23
|
+
|
24
|
+
if ::ActiveRecord::VERSION::MAJOR <= 4
|
25
|
+
OID = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID
|
26
|
+
else
|
27
|
+
OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
|
28
|
+
end
|
29
|
+
|
20
30
|
module PostgreSQLCursor
|
21
31
|
class Cursor
|
22
32
|
include Enumerable
|
@@ -43,6 +53,7 @@ module PostgreSQLCursor
|
|
43
53
|
@connection = @options.fetch(:connection) { ::ActiveRecord::Base.connection }
|
44
54
|
@count = 0
|
45
55
|
@iterate = options[:instances] ? :each_instance : :each_row
|
56
|
+
@batched = false
|
46
57
|
end
|
47
58
|
|
48
59
|
# Specify the type to instantiate, or reset to return a Hash
|
@@ -58,6 +69,11 @@ module PostgreSQLCursor
|
|
58
69
|
self
|
59
70
|
end
|
60
71
|
|
72
|
+
def iterate_batched(batched=true)
|
73
|
+
@batched = batched
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
61
77
|
# Public: Yields each row of the result set to the passed block
|
62
78
|
#
|
63
79
|
# Yields the row to the block. The row is a hash with symbolized keys.
|
@@ -66,11 +82,11 @@ module PostgreSQLCursor
|
|
66
82
|
# Returns the count of rows processed
|
67
83
|
def each(&block)
|
68
84
|
if @iterate == :each_row
|
69
|
-
self.each_row(&block)
|
85
|
+
@batched ? self.each_row_batch(&block) : self.each_row(&block)
|
70
86
|
elsif @iterate == :each_array
|
71
|
-
self.each_array(&block)
|
87
|
+
@batched ? self.each_array_batch(&block) : self.each_array(&block)
|
72
88
|
else
|
73
|
-
self.each_instance(@type, &block)
|
89
|
+
@batched ? self.each_instance_batch(@type, &block) : self.each_instance(@type, &block)
|
74
90
|
end
|
75
91
|
end
|
76
92
|
|
@@ -107,6 +123,41 @@ module PostgreSQLCursor
|
|
107
123
|
end
|
108
124
|
end
|
109
125
|
|
126
|
+
def each_row_batch(&block)
|
127
|
+
self.each_batch do |batch|
|
128
|
+
batch.map!(&:symbolize_keys) if @options[:symbolize_keys]
|
129
|
+
block.call(batch)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def each_array_batch(&block)
|
134
|
+
old_iterate = @iterate
|
135
|
+
@iterate = :each_array
|
136
|
+
begin
|
137
|
+
rv = self.each_batch do |batch|
|
138
|
+
block.call(batch)
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
@iterate = old_iterate
|
142
|
+
end
|
143
|
+
rv
|
144
|
+
end
|
145
|
+
|
146
|
+
def each_instance_batch(klass=nil, &block)
|
147
|
+
klass ||= @type
|
148
|
+
self.each_batch do |batch|
|
149
|
+
models = batch.map do |row|
|
150
|
+
if ::ActiveRecord::VERSION::MAJOR < 4
|
151
|
+
model = klass.send(:instantiate, row)
|
152
|
+
else
|
153
|
+
@column_types ||= column_types
|
154
|
+
model = klass.send(:instantiate, row, @column_types)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
block.call(models)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
110
161
|
# Returns an array of columns plucked from the result rows.
|
111
162
|
# Experimental function, as this could still use too much memory
|
112
163
|
# and negate the purpose of this libarary.
|
@@ -152,6 +203,28 @@ module PostgreSQLCursor
|
|
152
203
|
@count
|
153
204
|
end
|
154
205
|
|
206
|
+
def each_batch(&block) #:nodoc:
|
207
|
+
has_do_until = @options.key?(:until)
|
208
|
+
has_do_while = @options.key?(:while)
|
209
|
+
@count = 0
|
210
|
+
@column_types = nil
|
211
|
+
with_optional_transaction do
|
212
|
+
begin
|
213
|
+
open
|
214
|
+
while (batch = fetch_block)
|
215
|
+
break if batch.empty?
|
216
|
+
@count += 1
|
217
|
+
rc = block.call(batch)
|
218
|
+
break if has_do_until && rc == @options[:until]
|
219
|
+
break if has_do_while && rc != @options[:while]
|
220
|
+
end
|
221
|
+
ensure
|
222
|
+
close if @block
|
223
|
+
end
|
224
|
+
end
|
225
|
+
@count
|
226
|
+
end
|
227
|
+
|
155
228
|
def cast_types(row)
|
156
229
|
row
|
157
230
|
end
|
@@ -177,10 +250,9 @@ module PostgreSQLCursor
|
|
177
250
|
# Public: Opens (actually, "declares") the cursor. Call this before fetching
|
178
251
|
def open
|
179
252
|
set_cursor_tuple_fraction
|
180
|
-
#@cursor = Digest::MD5.hexdigest(@sql)
|
181
253
|
@cursor = SecureRandom.uuid.gsub("-","")
|
182
254
|
hold = @options[:with_hold] ? 'with hold ' : ''
|
183
|
-
@result = @connection.execute("declare cursor_#{@cursor} cursor #{hold}for #{@sql}")
|
255
|
+
@result = @connection.execute("declare cursor_#{@cursor} no scroll cursor #{hold}for #{@sql}")
|
184
256
|
@block = []
|
185
257
|
end
|
186
258
|
|
data/test-app/Gemfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# A sample Gemfile
|
2
2
|
source "https://rubygems.org"
|
3
3
|
|
4
|
-
gem 'activerecord', '~>
|
4
|
+
gem 'activerecord', '~> 5.2.0'
|
5
5
|
#gem 'activerecord', '~> 3.2.0'
|
6
6
|
#gem 'activerecord', '~> 4.0.0'
|
7
7
|
#gem 'activerecord', '~> 4.1.0'
|
@@ -12,4 +12,4 @@ gem 'activerecord', '~> 3.1.0'
|
|
12
12
|
#gem 'arel', github: 'rails/arel', branch: 'master'
|
13
13
|
|
14
14
|
gem 'pg'
|
15
|
-
gem 'postgresql_cursor', path:"
|
15
|
+
gem 'postgresql_cursor', path:"../"
|
@@ -16,6 +16,13 @@ class TestPostgresqlCursor < Minitest::Test
|
|
16
16
|
assert_equal nn, n
|
17
17
|
end
|
18
18
|
|
19
|
+
def test_each_batch
|
20
|
+
c = PostgreSQLCursor::Cursor.new("select * from products order by 1")
|
21
|
+
nn = 0
|
22
|
+
n = c.each_batch { |b| nn += 1 }
|
23
|
+
assert_equal nn, n
|
24
|
+
end
|
25
|
+
|
19
26
|
def test_enumerables
|
20
27
|
assert_equal true, PostgreSQLCursor::Cursor.new("select * from products order by 1").any?
|
21
28
|
assert_equal false, PostgreSQLCursor::Cursor.new("select * from products where id<0").any?
|
@@ -23,12 +30,22 @@ class TestPostgresqlCursor < Minitest::Test
|
|
23
30
|
|
24
31
|
def test_each_while_until
|
25
32
|
c = PostgreSQLCursor::Cursor.new("select * from products order by 1", until:true)
|
26
|
-
n = c.each { |r| r[
|
27
|
-
assert_equal
|
33
|
+
n = c.each { |r| r['id'].to_i > 100 }
|
34
|
+
assert_equal 101, n
|
28
35
|
|
29
36
|
c = PostgreSQLCursor::Cursor.new("select * from products order by 1", while:true)
|
30
|
-
n = c.each { |r| r[
|
31
|
-
assert_equal
|
37
|
+
n = c.each { |r| r['id'].to_i < 100 }
|
38
|
+
assert_equal 100, n
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_each_batch_while_until
|
42
|
+
c = PostgreSQLCursor::Cursor.new("select * from products order by id asc", until: true, block_size: 50)
|
43
|
+
n = c.each_batch { |b| b.last['id'].to_i > 100 }
|
44
|
+
assert_equal 3, n
|
45
|
+
|
46
|
+
c = PostgreSQLCursor::Cursor.new("select * from products order by id asc", while: true, block_size: 50)
|
47
|
+
n = c.each_batch { |b| b.last['id'].to_i < 100 }
|
48
|
+
assert_equal 2, n
|
32
49
|
end
|
33
50
|
|
34
51
|
def test_each_array
|
@@ -39,12 +56,36 @@ class TestPostgresqlCursor < Minitest::Test
|
|
39
56
|
end
|
40
57
|
end
|
41
58
|
|
59
|
+
def test_each_array_batch
|
60
|
+
c = PostgreSQLCursor::Cursor.new("select * from products where id = 1")
|
61
|
+
c.each_array_batch do |b|
|
62
|
+
assert_equal 1, b.size
|
63
|
+
ary = b.first
|
64
|
+
assert_equal Array, ary.class
|
65
|
+
assert_equal 1, ary[0].to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
42
69
|
def test_relation
|
43
70
|
nn = 0
|
44
71
|
Product.where("id>0").each_row {|r| nn += 1 }
|
45
72
|
assert_equal 1000, nn
|
46
73
|
end
|
47
74
|
|
75
|
+
def test_relation_batch
|
76
|
+
nn = 0
|
77
|
+
row = nil
|
78
|
+
Product.where("id>0").each_row_batch(block_size: 100) { |b| row = b.last; nn += 1 }
|
79
|
+
assert_equal 10, nn
|
80
|
+
assert_equal Hash, row.class
|
81
|
+
|
82
|
+
nn = 0
|
83
|
+
row = nil
|
84
|
+
Product.where("id>0").each_instance_batch(block_size: 100) { |b| row = b.last; nn += 1 }
|
85
|
+
assert_equal 10, nn
|
86
|
+
assert_equal Product, row.class
|
87
|
+
end
|
88
|
+
|
48
89
|
def test_activerecord
|
49
90
|
nn = 0
|
50
91
|
row = nil
|
@@ -58,6 +99,19 @@ class TestPostgresqlCursor < Minitest::Test
|
|
58
99
|
assert_equal Product, row.class
|
59
100
|
end
|
60
101
|
|
102
|
+
def test_activerecord_batch
|
103
|
+
nn = 0
|
104
|
+
row = nil
|
105
|
+
Product.each_row_batch_by_sql("select * from products", block_size: 100) { |b| row = b.last; nn += 1 }
|
106
|
+
assert_equal 10, nn
|
107
|
+
assert_equal Hash, row.class
|
108
|
+
|
109
|
+
nn = 0
|
110
|
+
Product.each_instance_batch_by_sql("select * from products", block_size: 100) { |b| row = b.last; nn += 1 }
|
111
|
+
assert_equal 10, nn
|
112
|
+
assert_equal Product, row.class
|
113
|
+
end
|
114
|
+
|
61
115
|
def test_exception
|
62
116
|
begin
|
63
117
|
Product.each_row_by_sql("select * from products") do |r|
|
@@ -68,6 +122,14 @@ class TestPostgresqlCursor < Minitest::Test
|
|
68
122
|
end
|
69
123
|
end
|
70
124
|
|
125
|
+
def test_batch_exception
|
126
|
+
Product.each_row_batch_by_sql("select * from products") do |r|
|
127
|
+
raise 'Oops'
|
128
|
+
end
|
129
|
+
rescue => e
|
130
|
+
assert_equal e.message, 'Oops'
|
131
|
+
end
|
132
|
+
|
71
133
|
def test_cursor
|
72
134
|
cursor = Product.all.each_row
|
73
135
|
assert cursor.respond_to?(:each)
|
@@ -79,12 +141,23 @@ class TestPostgresqlCursor < Minitest::Test
|
|
79
141
|
assert_equal 1000, r.size
|
80
142
|
end
|
81
143
|
|
144
|
+
def test_batched_cursor
|
145
|
+
cursor = Product.all.each_row_batch(block_size: 100)
|
146
|
+
assert cursor.respond_to?(:each)
|
147
|
+
b = cursor.map { |batch| batch.map { |r| r['id'] } }
|
148
|
+
assert_equal 10, b.size
|
149
|
+
cursor = Product.each_row_batch_by_sql("select * from products", block_size: 100)
|
150
|
+
assert cursor.respond_to?(:each)
|
151
|
+
b = cursor.map { |batch| batch.map { |r| r['id'] } }
|
152
|
+
assert_equal 10, b.size
|
153
|
+
end
|
154
|
+
|
82
155
|
def test_pluck
|
83
156
|
r = Product.pluck_rows(:id)
|
84
157
|
assert_equal 1000, r.size
|
85
158
|
r = Product.all.pluck_instances(:id)
|
86
159
|
assert_equal 1000, r.size
|
87
|
-
assert_equal
|
160
|
+
assert_equal Integer, r.first.class
|
88
161
|
end
|
89
162
|
|
90
163
|
def test_with_hold
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: postgresql_cursor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Allen Fair
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
116
|
version: '0'
|
117
117
|
requirements: []
|
118
118
|
rubyforge_project:
|
119
|
-
rubygems_version: 2.
|
119
|
+
rubygems_version: 2.7.7
|
120
120
|
signing_key:
|
121
121
|
specification_version: 4
|
122
122
|
summary: ActiveRecord PostgreSQL Adapter extension for using a cursor to return a
|