postgresql_cursor 0.6.3 → 0.6.5
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 +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +27 -0
- data/Appraisals +12 -0
- data/Rakefile +2 -1
- data/gemfiles/activerecord_4.gemfile +8 -0
- data/gemfiles/activerecord_5.gemfile +7 -0
- data/gemfiles/activerecord_6.gemfile +7 -0
- data/lib/postgresql_cursor/cursor.rb +74 -75
- data/lib/postgresql_cursor/version.rb +3 -1
- data/lib/postgresql_cursor.rb +1 -0
- data/postgresql_cursor.gemspec +34 -22
- data/test/helper.rb +6 -0
- data/test/test_postgresql_cursor.rb +72 -38
- metadata +49 -17
- data/Gemfile.lock +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79f076e2bf123498e0df89e2933531efae406a754cf14d049be33f973fc791c0
|
4
|
+
data.tar.gz: 260e4db366a8ccacb370d04c894b643c8363ed666e46fae77c81ec2b79d09b1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18720db87b3218629b6658a7fec653f56775eed3cea0af4cae6d0422a93b7f2dfa338c47752f5be84f46a1b72dbee4c0ec677ee7e6057894ad85c31937fef02b
|
7
|
+
data.tar.gz: 48aff84018a7924fff8c1eefbef1240b5adec9f43c12d29fd744b8af80ef283edae66249f5546d7c81741fed93d70838ac494031518a2814c4f44e43f5ff3944
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.6.5
|
4
|
+
- 2.7.1
|
5
|
+
before_install:
|
6
|
+
- sudo apt-get update
|
7
|
+
- sudo apt-get --yes remove postgresql\*
|
8
|
+
- sudo apt-get install -y postgresql-12 postgresql-client-12
|
9
|
+
- sudo cp /etc/postgresql/{9.6,12}/main/pg_hba.conf
|
10
|
+
- sudo service postgresql restart 12
|
11
|
+
gemfile:
|
12
|
+
- gemfiles/activerecord_4.gemfile
|
13
|
+
- gemfiles/activerecord_5.gemfile
|
14
|
+
- gemfiles/activerecord_6.gemfile
|
15
|
+
matrix:
|
16
|
+
exclude:
|
17
|
+
- rvm: 2.7.1
|
18
|
+
gemfile: gemfiles/activerecord_4.gemfile
|
19
|
+
services:
|
20
|
+
- postgresql
|
21
|
+
before_script:
|
22
|
+
- psql -c 'create database postgresql_cursor_test;' -U postgres
|
23
|
+
- psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres
|
24
|
+
- psql -c 'create table products ( id serial primary key, data varchar);' -U postgres -d postgresql_cursor_test
|
25
|
+
- psql -c 'create table prices ( id serial primary key, data varchar, product_id integer);' -U postgres -d postgresql_cursor_test
|
26
|
+
addons:
|
27
|
+
postgresql: '12.3'
|
data/Appraisals
ADDED
data/Rakefile
CHANGED
@@ -11,7 +11,7 @@ end
|
|
11
11
|
|
12
12
|
desc "Open and IRB Console with the gem and test-app loaded"
|
13
13
|
task :console do
|
14
|
-
sh "bundle exec irb -Ilib -I . -r postgresql_cursor -r test-app/app"
|
14
|
+
sh "bundle exec irb -Ilib -I . -r pg -r postgresql_cursor -r test-app/app"
|
15
15
|
#require 'irb'
|
16
16
|
#ARGV.clear
|
17
17
|
#IRB.start
|
@@ -21,4 +21,5 @@ desc "Setup testing database and table"
|
|
21
21
|
task :setup do
|
22
22
|
sh %q(createdb postgresql_cursor_test)
|
23
23
|
sh %Q<echo "create table products ( id serial primary key, data varchar);" | psql postgresql_cursor_test>
|
24
|
+
sh %Q<echo "create table prices ( id serial primary key, data varchar, product_id integer);" | psql postgresql_cursor_test>
|
24
25
|
end
|
@@ -41,29 +41,32 @@ module PostgreSQLCursor
|
|
41
41
|
# PostgreSQLCursor::Cursor.new("select ....")
|
42
42
|
#
|
43
43
|
# Returns the cursor object when called with new.
|
44
|
-
def initialize(sql, options={})
|
45
|
-
@sql
|
46
|
-
@options
|
44
|
+
def initialize(sql, options = {})
|
45
|
+
@sql = sql
|
46
|
+
@options = options
|
47
47
|
@connection = @options.fetch(:connection) { ::ActiveRecord::Base.connection }
|
48
|
-
@count
|
49
|
-
@iterate
|
50
|
-
@batched
|
48
|
+
@count = 0
|
49
|
+
@iterate = options[:instances] ? :each_instance : :each_row
|
50
|
+
@batched = false
|
51
51
|
end
|
52
52
|
|
53
|
-
# Specify the type to instantiate, or reset to return a Hash
|
54
|
-
|
55
|
-
|
53
|
+
# Specify the type to instantiate, or reset to return a Hash.
|
54
|
+
#
|
55
|
+
# Explicitly check for type class to prevent calling equality
|
56
|
+
# operator on active record relation, which will load it.
|
57
|
+
def iterate_type(type = nil)
|
58
|
+
if type.nil? || (type.instance_of?(Class) && type == Hash)
|
56
59
|
@iterate = :each_row
|
57
|
-
elsif type == Array
|
60
|
+
elsif type.instance_of?(Class) && type == Array
|
58
61
|
@iterate = :each_array
|
59
62
|
else
|
60
63
|
@iterate = :each_instance
|
61
|
-
@type
|
64
|
+
@type = type
|
62
65
|
end
|
63
66
|
self
|
64
67
|
end
|
65
68
|
|
66
|
-
def iterate_batched(batched=true)
|
69
|
+
def iterate_batched(batched = true)
|
67
70
|
@batched = batched
|
68
71
|
self
|
69
72
|
end
|
@@ -76,16 +79,16 @@ module PostgreSQLCursor
|
|
76
79
|
# Returns the count of rows processed
|
77
80
|
def each(&block)
|
78
81
|
if @iterate == :each_row
|
79
|
-
@batched ?
|
82
|
+
@batched ? each_row_batch(&block) : each_row(&block)
|
80
83
|
elsif @iterate == :each_array
|
81
|
-
@batched ?
|
84
|
+
@batched ? each_array_batch(&block) : each_array(&block)
|
82
85
|
else
|
83
|
-
@batched ?
|
86
|
+
@batched ? each_instance_batch(@type, &block) : each_instance(@type, &block)
|
84
87
|
end
|
85
88
|
end
|
86
89
|
|
87
90
|
def each_row(&block)
|
88
|
-
|
91
|
+
each_tuple do |row|
|
89
92
|
row = row.symbolize_keys if @options[:symbolize_keys]
|
90
93
|
block.call(row)
|
91
94
|
end
|
@@ -95,7 +98,7 @@ module PostgreSQLCursor
|
|
95
98
|
old_iterate = @iterate
|
96
99
|
@iterate = :each_array
|
97
100
|
begin
|
98
|
-
rv =
|
101
|
+
rv = each_tuple do |row|
|
99
102
|
block.call(row)
|
100
103
|
end
|
101
104
|
ensure
|
@@ -104,11 +107,11 @@ module PostgreSQLCursor
|
|
104
107
|
rv
|
105
108
|
end
|
106
109
|
|
107
|
-
def each_instance(klass=nil, &block)
|
110
|
+
def each_instance(klass = nil, &block)
|
108
111
|
klass ||= @type
|
109
|
-
|
112
|
+
each_tuple do |row|
|
110
113
|
if ::ActiveRecord::VERSION::MAJOR < 4
|
111
|
-
model = klass.send(:instantiate,row)
|
114
|
+
model = klass.send(:instantiate, row)
|
112
115
|
else
|
113
116
|
@column_types ||= column_types
|
114
117
|
model = klass.send(:instantiate, row, @column_types)
|
@@ -118,7 +121,7 @@ module PostgreSQLCursor
|
|
118
121
|
end
|
119
122
|
|
120
123
|
def each_row_batch(&block)
|
121
|
-
|
124
|
+
each_batch do |batch|
|
122
125
|
batch.map!(&:symbolize_keys) if @options[:symbolize_keys]
|
123
126
|
block.call(batch)
|
124
127
|
end
|
@@ -128,7 +131,7 @@ module PostgreSQLCursor
|
|
128
131
|
old_iterate = @iterate
|
129
132
|
@iterate = :each_array
|
130
133
|
begin
|
131
|
-
rv =
|
134
|
+
rv = each_batch do |batch|
|
132
135
|
block.call(batch)
|
133
136
|
end
|
134
137
|
ensure
|
@@ -137,15 +140,15 @@ module PostgreSQLCursor
|
|
137
140
|
rv
|
138
141
|
end
|
139
142
|
|
140
|
-
def each_instance_batch(klass=nil, &block)
|
143
|
+
def each_instance_batch(klass = nil, &block)
|
141
144
|
klass ||= @type
|
142
|
-
|
145
|
+
each_batch do |batch|
|
143
146
|
models = batch.map do |row|
|
144
147
|
if ::ActiveRecord::VERSION::MAJOR < 4
|
145
|
-
|
148
|
+
klass.send(:instantiate, row)
|
146
149
|
else
|
147
150
|
@column_types ||= column_types
|
148
|
-
|
151
|
+
klass.send(:instantiate, row, @column_types)
|
149
152
|
end
|
150
153
|
end
|
151
154
|
block.call(models)
|
@@ -160,11 +163,11 @@ module PostgreSQLCursor
|
|
160
163
|
options = cols.last.is_a?(Hash) ? cols.pop : {}
|
161
164
|
@options.merge!(options)
|
162
165
|
@options[:symbolize_keys] = true
|
163
|
-
|
164
|
-
cols
|
165
|
-
result
|
166
|
+
iterate_type(options[:class]) if options[:class]
|
167
|
+
cols = cols.map { |c| c.to_sym }
|
168
|
+
result = []
|
166
169
|
|
167
|
-
|
170
|
+
each do |row|
|
168
171
|
row = row.symbolize_keys if row.is_a?(Hash)
|
169
172
|
result << cols.map { |c| row[c] }
|
170
173
|
end
|
@@ -173,48 +176,44 @@ module PostgreSQLCursor
|
|
173
176
|
result
|
174
177
|
end
|
175
178
|
|
176
|
-
def each_tuple(&block)
|
177
|
-
has_do_until
|
178
|
-
has_do_while
|
179
|
-
@count
|
179
|
+
def each_tuple(&block) # :nodoc:
|
180
|
+
has_do_until = @options.has_key?(:until)
|
181
|
+
has_do_while = @options.has_key?(:while)
|
182
|
+
@count = 0
|
180
183
|
@column_types = nil
|
181
184
|
with_optional_transaction do
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
break if has_do_while && rc != @options[:while]
|
190
|
-
end
|
191
|
-
rescue Exception => e
|
192
|
-
raise e
|
193
|
-
ensure
|
194
|
-
close if @block
|
185
|
+
open
|
186
|
+
while (row = fetch)
|
187
|
+
break if row.size == 0
|
188
|
+
@count += 1
|
189
|
+
rc = block.call(row)
|
190
|
+
break if has_do_until && rc == @options[:until]
|
191
|
+
break if has_do_while && rc != @options[:while]
|
195
192
|
end
|
193
|
+
rescue => e
|
194
|
+
raise e
|
195
|
+
ensure
|
196
|
+
close if @block && connection.active?
|
196
197
|
end
|
197
198
|
@count
|
198
199
|
end
|
199
200
|
|
200
|
-
def each_batch(&block)
|
201
|
+
def each_batch(&block) # :nodoc:
|
201
202
|
has_do_until = @options.key?(:until)
|
202
203
|
has_do_while = @options.key?(:while)
|
203
204
|
@count = 0
|
204
205
|
@column_types = nil
|
205
206
|
with_optional_transaction do
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
break if has_do_while && rc != @options[:while]
|
214
|
-
end
|
215
|
-
ensure
|
216
|
-
close if @block
|
207
|
+
open
|
208
|
+
while (batch = fetch_block)
|
209
|
+
break if batch.empty?
|
210
|
+
@count += 1
|
211
|
+
rc = block.call(batch)
|
212
|
+
break if has_do_until && rc == @options[:until]
|
213
|
+
break if has_do_while && rc != @options[:while]
|
217
214
|
end
|
215
|
+
ensure
|
216
|
+
close if @block && connection.active?
|
218
217
|
end
|
219
218
|
@count
|
220
219
|
end
|
@@ -231,15 +230,15 @@ module PostgreSQLCursor
|
|
231
230
|
fields = @result.fields
|
232
231
|
fields.each_with_index do |fname, i|
|
233
232
|
ftype = @result.ftype i
|
234
|
-
fmod
|
235
|
-
types[fname] = @connection.get_type_map.fetch(ftype, fmod)
|
233
|
+
fmod = @result.fmod i
|
234
|
+
types[fname] = @connection.get_type_map.fetch(ftype, fmod) do |oid, mod|
|
236
235
|
warn "unknown OID: #{fname}(#{oid}) (#{sql})"
|
237
236
|
if ::ActiveRecord::VERSION::MAJOR <= 4
|
238
|
-
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Identity.new
|
237
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Identity.new
|
239
238
|
else
|
240
|
-
ActiveRecord::
|
239
|
+
::ActiveRecord::Type::Value.new
|
241
240
|
end
|
242
|
-
|
241
|
+
end
|
243
242
|
end
|
244
243
|
|
245
244
|
@column_types = types
|
@@ -248,8 +247,8 @@ module PostgreSQLCursor
|
|
248
247
|
# Public: Opens (actually, "declares") the cursor. Call this before fetching
|
249
248
|
def open
|
250
249
|
set_cursor_tuple_fraction
|
251
|
-
@cursor = @options[:cursor_name] || ("cursor_" + SecureRandom.uuid.
|
252
|
-
hold = @options[:with_hold] ?
|
250
|
+
@cursor = @options[:cursor_name] || ("cursor_" + SecureRandom.uuid.delete("-"))
|
251
|
+
hold = @options[:with_hold] ? "with hold " : ""
|
253
252
|
@result = @connection.execute("declare #{@cursor} no scroll cursor #{hold}for #{@sql}")
|
254
253
|
@block = []
|
255
254
|
end
|
@@ -257,23 +256,23 @@ module PostgreSQLCursor
|
|
257
256
|
# Public: Returns the next row from the cursor, or empty hash if end of results
|
258
257
|
#
|
259
258
|
# Returns a row as a hash of {'colname'=>value,...}
|
260
|
-
def fetch(options={})
|
259
|
+
def fetch(options = {})
|
261
260
|
open unless @block
|
262
|
-
fetch_block if @block.size==0
|
261
|
+
fetch_block if @block.size == 0
|
263
262
|
row = @block.shift
|
264
263
|
row = row.symbolize_keys if row && options[:symbolize_keys]
|
265
264
|
row
|
266
265
|
end
|
267
266
|
|
268
267
|
# Private: Fetches the next block of rows into @block
|
269
|
-
def fetch_block(block_size=nil)
|
270
|
-
block_size ||= @block_size ||= @options.fetch(:block_size
|
268
|
+
def fetch_block(block_size = nil)
|
269
|
+
block_size ||= @block_size ||= @options.fetch(:block_size, 1000)
|
271
270
|
@result = @connection.execute("fetch #{block_size} from #{@cursor}")
|
272
271
|
|
273
|
-
if @iterate == :each_array
|
274
|
-
@
|
272
|
+
@block = if @iterate == :each_array
|
273
|
+
@result.each_row.collect { |row| row }
|
275
274
|
else
|
276
|
-
@
|
275
|
+
@result.collect { |row| row }
|
277
276
|
end
|
278
277
|
end
|
279
278
|
|
@@ -295,8 +294,8 @@ module PostgreSQLCursor
|
|
295
294
|
# This is a value between 0.1 and 1.0 (PostgreSQL defaults to 0.1, this library defaults to 1.0)
|
296
295
|
# used to determine the expected fraction (percent) of result rows returned the the caller.
|
297
296
|
# This value determines the access path by the query planner.
|
298
|
-
def set_cursor_tuple_fraction(frac=1.0)
|
299
|
-
@cursor_tuple_fraction ||= @options.fetch(:fraction
|
297
|
+
def set_cursor_tuple_fraction(frac = 1.0)
|
298
|
+
@cursor_tuple_fraction ||= @options.fetch(:fraction, 1.0)
|
300
299
|
return @cursor_tuple_fraction if frac == @cursor_tuple_fraction
|
301
300
|
@cursor_tuple_fraction = frac
|
302
301
|
@result = @connection.execute("set cursor_tuple_fraction to #{frac}")
|
data/lib/postgresql_cursor.rb
CHANGED
data/postgresql_cursor.gemspec
CHANGED
@@ -1,32 +1,44 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
5
|
+
require "postgresql_cursor/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
11
|
-
spec.summary
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
spec.name = "postgresql_cursor"
|
9
|
+
spec.version = PostgresqlCursor::VERSION
|
10
|
+
spec.authors = ["Allen Fair"]
|
11
|
+
spec.email = ["allen.fair@gmail.com"]
|
12
|
+
spec.summary = <<-SUMMARY
|
13
|
+
ActiveRecord PostgreSQL Adapter extension for using a cursor to return a
|
14
|
+
large result set
|
15
|
+
SUMMARY
|
16
|
+
spec.description = <<-DESCRIPTION
|
17
|
+
PostgreSQL Cursor is an extension to the ActiveRecord PostgreSQLAdapter for
|
18
|
+
very large result sets. It provides a cursor open/fetch/close interface to
|
19
|
+
access data without loading all rows into memory, and instead loads the result
|
20
|
+
rows in 'chunks' (default of 1_000 rows), buffers them, and returns the rows
|
21
|
+
one at a time.
|
22
|
+
DESCRIPTION
|
23
|
+
spec.homepage = "http://github.com/afair/postgresql_cursor"
|
24
|
+
spec.license = "MIT"
|
15
25
|
|
16
|
-
spec.files
|
17
|
-
spec.executables
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.files = `git ls-files -z`.split("\x0")
|
27
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
28
|
spec.require_paths = ["lib"]
|
20
29
|
|
21
|
-
#
|
22
|
-
spec.add_dependency
|
23
|
-
|
24
|
-
|
25
|
-
#spec.add_dependency
|
26
|
-
#spec.add_dependency
|
27
|
-
#spec.add_dependency
|
30
|
+
# Remove this for jruby which should use 'activerecord-jdbcpostgresql-adapter'
|
31
|
+
# spec.add_dependency 'pg'
|
32
|
+
|
33
|
+
spec.add_dependency "activerecord", ">= 7.0.0"
|
34
|
+
# spec.add_dependency 'activerecord', '~> 3.1.0'
|
35
|
+
# spec.add_dependency 'activerecord', '~> 4.1.0'
|
36
|
+
# spec.add_dependency 'activerecord', '~> 5.0.0'
|
37
|
+
# spec.add_dependency 'activerecord', '~> 6.0.0'
|
28
38
|
|
39
|
+
spec.add_development_dependency "irb"
|
40
|
+
spec.add_development_dependency "minitest"
|
29
41
|
spec.add_development_dependency "pg"
|
30
42
|
spec.add_development_dependency "rake"
|
31
|
-
spec.add_development_dependency "
|
43
|
+
spec.add_development_dependency "appraisal"
|
32
44
|
end
|
data/test/helper.rb
CHANGED
@@ -10,6 +10,8 @@ ActiveRecord::Base.establish_connection(adapter: 'postgresql',
|
|
10
10
|
username: ENV['TEST_USER'] || ENV['USER'] || 'postgresql_cursor')
|
11
11
|
|
12
12
|
class Product < ActiveRecord::Base
|
13
|
+
has_many :prices
|
14
|
+
|
13
15
|
# create table records (id serial primary key);
|
14
16
|
def self.generate(max=1_000)
|
15
17
|
max.times do |i|
|
@@ -18,5 +20,9 @@ class Product < ActiveRecord::Base
|
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
23
|
+
class Price < ActiveRecord::Base
|
24
|
+
belongs_to :product
|
25
|
+
end
|
26
|
+
|
21
27
|
Product.destroy_all
|
22
28
|
Product.generate(1000)
|
@@ -3,16 +3,15 @@
|
|
3
3
|
# rake setup
|
4
4
|
# or create the database manually if your environment doesn't permit
|
5
5
|
################################################################################
|
6
|
-
require_relative
|
7
|
-
require
|
8
|
-
require
|
6
|
+
require_relative "helper"
|
7
|
+
require "minitest/autorun"
|
8
|
+
require "minitest/pride"
|
9
9
|
|
10
10
|
class TestPostgresqlCursor < Minitest::Test
|
11
|
-
|
12
11
|
def test_each
|
13
12
|
c = PostgreSQLCursor::Cursor.new("select * from products order by 1")
|
14
13
|
nn = 0
|
15
|
-
n = c.each { nn += 1}
|
14
|
+
n = c.each { nn += 1 }
|
16
15
|
assert_equal nn, n
|
17
16
|
end
|
18
17
|
|
@@ -29,22 +28,22 @@ class TestPostgresqlCursor < Minitest::Test
|
|
29
28
|
end
|
30
29
|
|
31
30
|
def test_each_while_until
|
32
|
-
c = PostgreSQLCursor::Cursor.new("select * from products order by 1", until:true)
|
33
|
-
n = c.each { |r| r[
|
31
|
+
c = PostgreSQLCursor::Cursor.new("select * from products order by 1", until: true)
|
32
|
+
n = c.each { |r| r["id"].to_i > 100 }
|
34
33
|
assert_equal 101, n
|
35
34
|
|
36
|
-
c = PostgreSQLCursor::Cursor.new("select * from products order by 1", while:true)
|
37
|
-
n = c.each { |r| r[
|
35
|
+
c = PostgreSQLCursor::Cursor.new("select * from products order by 1", while: true)
|
36
|
+
n = c.each { |r| r["id"].to_i < 100 }
|
38
37
|
assert_equal 100, n
|
39
38
|
end
|
40
39
|
|
41
40
|
def test_each_batch_while_until
|
42
41
|
c = PostgreSQLCursor::Cursor.new("select * from products order by id asc", until: true, block_size: 50)
|
43
|
-
n = c.each_batch { |b| b.last[
|
42
|
+
n = c.each_batch { |b| b.last["id"].to_i > 100 }
|
44
43
|
assert_equal 3, n
|
45
44
|
|
46
45
|
c = PostgreSQLCursor::Cursor.new("select * from products order by id asc", while: true, block_size: 50)
|
47
|
-
n = c.each_batch { |b| b.last[
|
46
|
+
n = c.each_batch { |b| b.last["id"].to_i < 100 }
|
48
47
|
assert_equal 2, n
|
49
48
|
end
|
50
49
|
|
@@ -68,20 +67,26 @@ class TestPostgresqlCursor < Minitest::Test
|
|
68
67
|
|
69
68
|
def test_relation
|
70
69
|
nn = 0
|
71
|
-
Product.where("id>0").each_row {|r| nn += 1 }
|
70
|
+
Product.where("id>0").each_row { |r| nn += 1 }
|
72
71
|
assert_equal 1000, nn
|
73
72
|
end
|
74
73
|
|
75
74
|
def test_relation_batch
|
76
75
|
nn = 0
|
77
76
|
row = nil
|
78
|
-
Product.where("id>0").each_row_batch(block_size: 100) { |b|
|
77
|
+
Product.where("id>0").each_row_batch(block_size: 100) { |b|
|
78
|
+
row = b.last
|
79
|
+
nn += 1
|
80
|
+
}
|
79
81
|
assert_equal 10, nn
|
80
82
|
assert_equal Hash, row.class
|
81
83
|
|
82
84
|
nn = 0
|
83
85
|
row = nil
|
84
|
-
Product.where("id>0").each_instance_batch(block_size: 100) { |b|
|
86
|
+
Product.where("id>0").each_instance_batch(block_size: 100) { |b|
|
87
|
+
row = b.last
|
88
|
+
nn += 1
|
89
|
+
}
|
85
90
|
assert_equal 10, nn
|
86
91
|
assert_equal Product, row.class
|
87
92
|
end
|
@@ -89,12 +94,18 @@ class TestPostgresqlCursor < Minitest::Test
|
|
89
94
|
def test_activerecord
|
90
95
|
nn = 0
|
91
96
|
row = nil
|
92
|
-
Product.each_row_by_sql("select * from products") {|r|
|
97
|
+
Product.each_row_by_sql("select * from products") { |r|
|
98
|
+
row = r
|
99
|
+
nn += 1
|
100
|
+
}
|
93
101
|
assert_equal 1000, nn
|
94
102
|
assert_equal Hash, row.class
|
95
103
|
|
96
104
|
nn = 0
|
97
|
-
Product.each_instance_by_sql("select * from products") {|r|
|
105
|
+
Product.each_instance_by_sql("select * from products") { |r|
|
106
|
+
row = r
|
107
|
+
nn += 1
|
108
|
+
}
|
98
109
|
assert_equal 1000, nn
|
99
110
|
assert_equal Product, row.class
|
100
111
|
end
|
@@ -102,32 +113,52 @@ class TestPostgresqlCursor < Minitest::Test
|
|
102
113
|
def test_activerecord_batch
|
103
114
|
nn = 0
|
104
115
|
row = nil
|
105
|
-
Product.each_row_batch_by_sql("select * from products", block_size: 100) { |b|
|
116
|
+
Product.each_row_batch_by_sql("select * from products", block_size: 100) { |b|
|
117
|
+
row = b.last
|
118
|
+
nn += 1
|
119
|
+
}
|
106
120
|
assert_equal 10, nn
|
107
121
|
assert_equal Hash, row.class
|
108
122
|
|
109
123
|
nn = 0
|
110
|
-
Product.each_instance_batch_by_sql("select * from products", block_size: 100) { |b|
|
124
|
+
Product.each_instance_batch_by_sql("select * from products", block_size: 100) { |b|
|
125
|
+
row = b.last
|
126
|
+
nn += 1
|
127
|
+
}
|
111
128
|
assert_equal 10, nn
|
112
129
|
assert_equal Product, row.class
|
113
130
|
end
|
114
131
|
|
115
132
|
def test_exception
|
116
|
-
|
117
|
-
|
118
|
-
raise "Oops"
|
119
|
-
end
|
120
|
-
rescue Exception => e
|
121
|
-
assert_equal e.message, 'Oops'
|
133
|
+
Product.each_row_by_sql("select * from products") do |r|
|
134
|
+
raise "Oops"
|
122
135
|
end
|
136
|
+
rescue => e
|
137
|
+
assert_equal e.message, "Oops"
|
123
138
|
end
|
124
139
|
|
125
140
|
def test_batch_exception
|
126
141
|
Product.each_row_batch_by_sql("select * from products") do |r|
|
127
|
-
raise
|
142
|
+
raise "Oops"
|
143
|
+
end
|
144
|
+
rescue => e
|
145
|
+
assert_equal e.message, "Oops"
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_exception_in_failed_transaction
|
149
|
+
Product.each_row_by_sql("select * from products") do |r|
|
150
|
+
Product.connection.execute("select kaboom")
|
151
|
+
end
|
152
|
+
rescue => e
|
153
|
+
assert_match(/PG::InFailedSqlTransaction/, e.message)
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_batch_exception_in_failed_transaction
|
157
|
+
Product.each_row_batch_by_sql("select * from products") do |r|
|
158
|
+
Product.connection.execute("select kaboom")
|
128
159
|
end
|
129
160
|
rescue => e
|
130
|
-
|
161
|
+
assert_match(/PG::InFailedSqlTransaction/, e.message)
|
131
162
|
end
|
132
163
|
|
133
164
|
def test_cursor
|
@@ -144,11 +175,11 @@ class TestPostgresqlCursor < Minitest::Test
|
|
144
175
|
def test_batched_cursor
|
145
176
|
cursor = Product.all.each_row_batch(block_size: 100)
|
146
177
|
assert cursor.respond_to?(:each)
|
147
|
-
b = cursor.map { |batch| batch.map { |r| r[
|
178
|
+
b = cursor.map { |batch| batch.map { |r| r["id"] } }
|
148
179
|
assert_equal 10, b.size
|
149
180
|
cursor = Product.each_row_batch_by_sql("select * from products", block_size: 100)
|
150
181
|
assert cursor.respond_to?(:each)
|
151
|
-
b = cursor.map { |batch| batch.map { |r| r[
|
182
|
+
b = cursor.map { |batch| batch.map { |r| r["id"] } }
|
152
183
|
assert_equal 10, b.size
|
153
184
|
end
|
154
185
|
|
@@ -162,9 +193,9 @@ class TestPostgresqlCursor < Minitest::Test
|
|
162
193
|
|
163
194
|
def test_with_hold
|
164
195
|
items = 0
|
165
|
-
Product.where("id < 4")
|
196
|
+
Product.where("id < 4").each_instance(with_hold: true, block_size: 1) do |row|
|
166
197
|
Product.transaction do
|
167
|
-
row.update(data:Time.now.to_f.to_s)
|
198
|
+
row.update(data: Time.now.to_f.to_s)
|
168
199
|
items += 1
|
169
200
|
end
|
170
201
|
end
|
@@ -173,22 +204,25 @@ class TestPostgresqlCursor < Minitest::Test
|
|
173
204
|
|
174
205
|
def test_fetch_symbolize_keys
|
175
206
|
Product.transaction do
|
176
|
-
#cursor = PostgreSQLCursor::Cursor.new("select * from products order by 1")
|
207
|
+
# cursor = PostgreSQLCursor::Cursor.new("select * from products order by 1")
|
177
208
|
cursor = Product.all.each_row
|
178
209
|
r = cursor.fetch
|
179
210
|
assert r.has_key?("id")
|
180
|
-
r = cursor.fetch(symbolize_keys:true)
|
211
|
+
r = cursor.fetch(symbolize_keys: true)
|
181
212
|
assert r.has_key?(:id)
|
182
213
|
cursor.close
|
183
214
|
end
|
184
215
|
end
|
185
216
|
|
186
217
|
def test_bad_sql
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
218
|
+
ActiveRecord::Base.each_row_by_sql("select * from bad_table") {}
|
219
|
+
raise "Did Not Raise Expected Exception"
|
220
|
+
rescue => e
|
221
|
+
assert_match(/bad_table/, e.message)
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_relation_association_is_not_loaded
|
225
|
+
cursor = Product.first.prices.each_instance
|
226
|
+
refute cursor.instance_variable_get(:@type).loaded?
|
193
227
|
end
|
194
228
|
end
|
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.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Allen Fair
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 7.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 7.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: irb
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: pg
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +81,7 @@ dependencies:
|
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
84
|
+
name: appraisal
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
58
86
|
requirements:
|
59
87
|
- - ">="
|
@@ -66,10 +94,12 @@ dependencies:
|
|
66
94
|
- - ">="
|
67
95
|
- !ruby/object:Gem::Version
|
68
96
|
version: '0'
|
69
|
-
description:
|
70
|
-
|
71
|
-
|
72
|
-
|
97
|
+
description: |2
|
98
|
+
PostgreSQL Cursor is an extension to the ActiveRecord PostgreSQLAdapter for
|
99
|
+
very large result sets. It provides a cursor open/fetch/close interface to
|
100
|
+
access data without loading all rows into memory, and instead loads the result
|
101
|
+
rows in 'chunks' (default of 1_000 rows), buffers them, and returns the rows
|
102
|
+
one at a time.
|
73
103
|
email:
|
74
104
|
- allen.fair@gmail.com
|
75
105
|
executables: []
|
@@ -78,11 +108,15 @@ extra_rdoc_files: []
|
|
78
108
|
files:
|
79
109
|
- ".document"
|
80
110
|
- ".gitignore"
|
111
|
+
- ".travis.yml"
|
112
|
+
- Appraisals
|
81
113
|
- Gemfile
|
82
|
-
- Gemfile.lock
|
83
114
|
- LICENSE
|
84
115
|
- README.md
|
85
116
|
- Rakefile
|
117
|
+
- gemfiles/activerecord_4.gemfile
|
118
|
+
- gemfiles/activerecord_5.gemfile
|
119
|
+
- gemfiles/activerecord_6.gemfile
|
86
120
|
- lib/postgresql_cursor.rb
|
87
121
|
- lib/postgresql_cursor/active_record/connection_adapters/postgresql_type_map.rb
|
88
122
|
- lib/postgresql_cursor/active_record/relation/cursor_iterators.rb
|
@@ -100,7 +134,7 @@ homepage: http://github.com/afair/postgresql_cursor
|
|
100
134
|
licenses:
|
101
135
|
- MIT
|
102
136
|
metadata: {}
|
103
|
-
post_install_message:
|
137
|
+
post_install_message:
|
104
138
|
rdoc_options: []
|
105
139
|
require_paths:
|
106
140
|
- lib
|
@@ -115,11 +149,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
149
|
- !ruby/object:Gem::Version
|
116
150
|
version: '0'
|
117
151
|
requirements: []
|
118
|
-
rubygems_version: 3.
|
119
|
-
signing_key:
|
152
|
+
rubygems_version: 3.3.7
|
153
|
+
signing_key:
|
120
154
|
specification_version: 4
|
121
155
|
summary: ActiveRecord PostgreSQL Adapter extension for using a cursor to return a
|
122
156
|
large result set
|
123
|
-
test_files:
|
124
|
-
- test/helper.rb
|
125
|
-
- test/test_postgresql_cursor.rb
|
157
|
+
test_files: []
|
data/Gemfile.lock
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
postgresql_cursor (0.6.3)
|
5
|
-
activerecord (>= 3.1.0)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
activemodel (6.0.0)
|
11
|
-
activesupport (= 6.0.0)
|
12
|
-
activerecord (6.0.0)
|
13
|
-
activemodel (= 6.0.0)
|
14
|
-
activesupport (= 6.0.0)
|
15
|
-
activesupport (6.0.0)
|
16
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
-
i18n (>= 0.7, < 2)
|
18
|
-
minitest (~> 5.1)
|
19
|
-
tzinfo (~> 1.1)
|
20
|
-
zeitwerk (~> 2.1, >= 2.1.8)
|
21
|
-
concurrent-ruby (1.1.5)
|
22
|
-
i18n (1.6.0)
|
23
|
-
concurrent-ruby (~> 1.0)
|
24
|
-
minitest (5.12.0)
|
25
|
-
pg (1.1.4)
|
26
|
-
rake (13.0.0)
|
27
|
-
thread_safe (0.3.6)
|
28
|
-
tzinfo (1.2.5)
|
29
|
-
thread_safe (~> 0.1)
|
30
|
-
zeitwerk (2.1.10)
|
31
|
-
|
32
|
-
PLATFORMS
|
33
|
-
ruby
|
34
|
-
|
35
|
-
DEPENDENCIES
|
36
|
-
minitest
|
37
|
-
pg
|
38
|
-
postgresql_cursor!
|
39
|
-
rake
|
40
|
-
|
41
|
-
BUNDLED WITH
|
42
|
-
1.17.3
|