postgresql_cursor 0.6.1 → 0.6.2
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.
- 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
|
[](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
|