postgresql_cursor 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +55 -56
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/postgresql_cursor.rb +146 -105
- data/postgresql_cursor.gemspec +18 -25
- data/test/helper.rb +3 -1
- data/test/test_postgresql_cursor.rb +32 -31
- metadata +45 -69
- data/.gitignore +0 -21
data/README.rdoc
CHANGED
@@ -1,76 +1,75 @@
|
|
1
|
-
= PostgreSQLCursor
|
1
|
+
= PostgreSQLCursor for handling large Result Sets
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
and instead loads the result rows in "chunks" (default of 10_000 rows), buffers them, and returns the
|
6
|
-
rows one at a time.
|
3
|
+
PostgreSQLCursor extends ActiveRecord to allow for efficient processing of queries
|
4
|
+
returning a large number of rows.
|
7
5
|
|
8
|
-
|
9
|
-
Pagination scheme to present it to the users. Background processes sometimes need to generate a large
|
10
|
-
amount of data, and the ActiveRecord approach to load all data into memory is not the best fit here.
|
6
|
+
== Why use this?
|
11
7
|
|
12
|
-
|
13
|
-
|
8
|
+
ActiveRecord is designed and optimized for web performance. In a web transaction, only a "page" of
|
9
|
+
around 20 rows is returned to the user. When you do this
|
14
10
|
|
15
|
-
|
16
|
-
can pass in a block to override this strategy. For instance, returning Hashes instead of AR instances
|
17
|
-
is faster. Julian's benchmarks showed returning instances was a factor of 4 slower than returning a hash.
|
11
|
+
Model.find(:all, :conditions=>["id>0"]
|
18
12
|
|
19
|
-
|
20
|
-
|
21
|
-
way.
|
22
|
-
Model.scope_methods.each_row { |hash| puts hash.inspect }
|
13
|
+
The database returns all matching result set rows to ActiveRecord, which instantiates each row with
|
14
|
+
the data returned. This function returns an array of all these rows to the caller.
|
23
15
|
|
24
|
-
|
16
|
+
Asyncronous, Background, or Offline processing may require processing a large amount of data.
|
17
|
+
When there is a very large number of rows, this requires a lot more memory to hold the data. Ruby
|
18
|
+
does not return that memory after processing the array, and the causes your process to "bloat". If you
|
19
|
+
don't have enough memory, it will cause an exception.
|
25
20
|
|
26
|
-
==
|
27
|
-
[sudo] gem install postgresql_cursor
|
21
|
+
== Enter find_each
|
28
22
|
|
29
|
-
|
23
|
+
To solve this problem, ActiveRecord gives us two alternative methods that work in "chunks" of your data:
|
30
24
|
|
31
|
-
|
32
|
-
config.gem 'postgresql_cursor'
|
33
|
-
or require the gem on your non-rails application
|
34
|
-
require 'rubygems'
|
35
|
-
require 'postgresql_cursor'
|
25
|
+
Model.where("id>0").find_each { |model| model.process! }
|
36
26
|
|
37
|
-
|
27
|
+
Model.where("id>0").find_in_batches do |batch|
|
28
|
+
batch.each { |model| model.process! }
|
29
|
+
end
|
38
30
|
|
39
|
-
|
40
|
-
like the find() method, that will return cursor objects instead of an array of model instances.
|
31
|
+
Optionally, you can specify a :batch_size option as the size of the "chunk", and defaults to 1000.
|
41
32
|
|
42
|
-
|
43
|
-
block, open, buffering, and closing the cursor. In this way, it operates like an Array object. Note that
|
44
|
-
it does not implement Enumerable, so no other methods are available.
|
33
|
+
There are drawbacks with these methods:
|
45
34
|
|
46
|
-
*
|
35
|
+
* You cannot specify the order, it will be ordered by the primary key (usually id)
|
36
|
+
* The primary key must be numeric
|
37
|
+
* The query is rerun for each chunk (1000 rows), starting at the next id sequence.
|
38
|
+
* You cannot use overly complex queries as that will be rerun and incur more overhead.
|
47
39
|
|
48
|
-
|
40
|
+
== PostgreSQLCursor FTW!
|
49
41
|
|
50
|
-
|
42
|
+
PostgreSQLCursor was developed to take advantage of PostgreSQL's cursors. Cursors allow the program
|
43
|
+
to declare a cursor to run a given query returning "chunks" of rows to the application program while
|
44
|
+
retaining the position of the full result set in the database. This overcomes all the disadvantages
|
45
|
+
of using find_each and find_in_batches.
|
51
46
|
|
52
|
-
|
53
|
-
|
47
|
+
Also, with PostgreSQL, you have on option to have raw hashes of the row returned instead of the
|
48
|
+
instantiated models. An informal benchmark showed that returning instances is a factor of 4 times
|
49
|
+
slower than returning hashes. If you are can work with the data in this form, you will find better
|
50
|
+
performance.
|
54
51
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
52
|
+
With PostgreSQL, you can work with cursors as follows:
|
53
|
+
|
54
|
+
Model.where("id>0").each_row { |hash| Model.process(hash) }
|
55
|
+
|
56
|
+
Model.where("id>0").each_instance { |model| model.process! }
|
57
|
+
Model.where("id>0").each_instance(buffer_size:100000) { |model| model.process! }
|
58
|
+
|
59
|
+
Model.each_row_by_sql("select * from models") { |hash| Model.process(hash) }
|
60
|
+
|
61
|
+
Model.each_instance_by_sql("select * from models") { |model| model.process }
|
62
|
+
|
63
|
+
All these methods take an options hash to control things more:
|
64
|
+
|
65
|
+
buffer_size:n The number of rows to fetch from the database each time (default 1000)
|
66
|
+
while:value Continue looping as long as the block returns this value
|
67
|
+
until:value Continue looping until the block returns this value
|
68
|
+
connection:conn Use this connection instead of the current model connection
|
69
|
+
fraction:float A value to set for the cursor_tuple_fraction variable.
|
70
|
+
PostgreSQL uses 0.1 (optimize for 10% of result set)
|
71
|
+
This library uses 1.0 (Optimize for 100% of the result set)
|
72
|
+
Do not override this value unless you understand it.
|
74
73
|
|
75
74
|
==Authors
|
76
75
|
Allen Fair, allen.fair@gmail.com, http://github.com/afair
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/postgresql_cursor.rb
CHANGED
@@ -1,135 +1,176 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
1
|
+
# PostgreSQLCursor: library class provides postgresql cursor for large result
|
2
|
+
# set processing. Requires ActiveRecord, but can be adapted to other DBI/ORM libraries.
|
3
|
+
# If you don't use AR, this assumes #connection and #instantiate methods are available.
|
4
|
+
#
|
5
|
+
# options - Hash to control operation and loop breaks
|
6
|
+
# connection: instance - ActiveRecord connection to use
|
7
|
+
# fraction: 0.1..1.0 - The cursor_tuple_fraction (default 1.0)
|
8
|
+
# block_size: 1..n - The number of rows to fetch per db block fetch
|
9
|
+
# while: value - Exits loop when block does not return this value.
|
10
|
+
# until: value - Exits loop when block returns this value.
|
7
11
|
#
|
8
|
-
#
|
9
|
-
#
|
12
|
+
# Exmaples:
|
13
|
+
# PostgreSQLCursor.new("select ...").each { |hash| ... }
|
14
|
+
# ActiveRecordModel.where(...).each_row { |hash| ... }
|
15
|
+
# ActiveRecordModel.each_row_by_sql("select ...") { |hash| ... }
|
16
|
+
# ActiveRecordModel.each_instance_by_sql("select ...") { |model| ... }
|
10
17
|
#
|
11
|
-
# PostgreSQL requires that a cursor is executed within a transaction block, which you must provide unless
|
12
|
-
# you use each() to run through the result set.
|
13
18
|
class PostgreSQLCursor
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
19
|
+
include Enumerable
|
20
|
+
attr_reader :sql, :options, :connection, :count, :result
|
21
|
+
@@cursor_seq = 0
|
22
|
+
|
23
|
+
# Public: Start a new PostgreSQL cursor query
|
24
|
+
# sql - The SQL statement with interpolated values
|
25
|
+
# options - hash of processing controls
|
26
|
+
# while: value - Exits loop when block does not return this value.
|
27
|
+
# until: value - Exits loop when block returns this value.
|
28
|
+
# fraction: 0.1..1.0 - The cursor_tuple_fraction (default 1.0)
|
29
|
+
# block_size: 1..n - The number of rows to fetch per db block fetch
|
30
|
+
# Defaults to 1000
|
31
|
+
#
|
32
|
+
# Examples
|
33
|
+
#
|
34
|
+
# PostgreSQLCursor.new("select ....")
|
35
|
+
#
|
36
|
+
# Returns the cursor object when called with new.
|
37
|
+
def initialize(sql, options={})
|
38
|
+
@sql = sql
|
39
|
+
@options = options
|
40
|
+
@connection = @options.fetch(:connection) { ActiveRecord::Base.connection }
|
41
|
+
@count = 0
|
31
42
|
end
|
32
|
-
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
43
|
+
|
44
|
+
# Public: Yields each row of the result set to the passed block
|
45
|
+
#
|
46
|
+
#
|
47
|
+
# Yields the row to the block. The row is a hash with symbolized keys.
|
48
|
+
# {colname: value, ....}
|
49
|
+
#
|
50
|
+
# Returns the count of rows processed
|
51
|
+
def each(&block)
|
52
|
+
has_do_until = @options.has_key?(:until)
|
53
|
+
has_do_while = @options.has_key?(:while)
|
54
|
+
@count = 0
|
36
55
|
@connection.transaction do
|
37
|
-
|
38
|
-
|
39
|
-
|
56
|
+
begin
|
57
|
+
open
|
58
|
+
while (row = fetch) do
|
59
|
+
break if row.size==0
|
60
|
+
@count += 1
|
61
|
+
row = row.symbolize_keys
|
62
|
+
rc = yield row
|
63
|
+
# TODO: Handle exceptions raised within block
|
64
|
+
break if has_do_until && rc == @options[:until]
|
65
|
+
break if has_do_while && rc != @options[:while]
|
66
|
+
end
|
67
|
+
rescue e
|
68
|
+
close
|
69
|
+
raise e
|
40
70
|
end
|
41
|
-
close
|
42
|
-
@count
|
43
71
|
end
|
72
|
+
@count
|
44
73
|
end
|
45
|
-
|
46
|
-
#
|
74
|
+
|
75
|
+
# Public: Opens (actually, "declares") the cursor. Call this before fetching
|
47
76
|
def open
|
48
|
-
|
49
|
-
@
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@buffer = nil
|
77
|
+
set_cursor_tuple_fraction
|
78
|
+
@cursor = @@cursor_seq += 1
|
79
|
+
@result = @connection.execute("declare cursor_#{@cursor} cursor for #{@sql}")
|
80
|
+
@block = []
|
53
81
|
end
|
54
82
|
|
55
|
-
# Returns
|
56
|
-
|
57
|
-
|
83
|
+
# Public: Returns the next row from the cursor, or empty hash if end of results
|
84
|
+
#
|
85
|
+
# Returns a row as a hash of {'colname'=>value,...}
|
86
|
+
def fetch
|
87
|
+
fetch_block if @block.size==0
|
88
|
+
@block.shift
|
58
89
|
end
|
59
90
|
|
60
|
-
# Fetches the next block of rows into
|
61
|
-
def
|
62
|
-
|
63
|
-
@result = @connection.execute("fetch #{
|
64
|
-
@
|
65
|
-
@state = @buffer.size > 0 ? :buffered : :eof
|
66
|
-
@buffer_reads += 1
|
67
|
-
@buffer
|
91
|
+
# Private: Fetches the next block of rows into @block
|
92
|
+
def fetch_block(block_size=nil)
|
93
|
+
block_size ||= @block_size ||= @options.fetch(:block_size) { 1000 }
|
94
|
+
@result = @connection.execute("fetch #{block_size} from cursor_#{@cursor}")
|
95
|
+
@block = @result.collect {|row| row } # Make our own
|
68
96
|
end
|
69
97
|
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
open if @state == :ready
|
74
|
-
fetch_buffer if @state == :empty
|
75
|
-
return nil if @state == :eof || @state == :closed
|
76
|
-
@state = :empty if @buffer.size <= 1
|
77
|
-
@count+= 1
|
78
|
-
row = @buffer.shift
|
79
|
-
@instantiator.call(row)
|
98
|
+
# Public: Closes the cursor
|
99
|
+
def close
|
100
|
+
@connection.execute("close cursor_#{@cursor}")
|
80
101
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
#
|
85
|
-
#
|
86
|
-
def
|
87
|
-
|
88
|
-
@
|
102
|
+
|
103
|
+
# Private: Sets the PostgreSQL cursor_tuple_fraction value = 1.0 to assume all rows will be fetched
|
104
|
+
# This is a value between 0.1 and 1.0 (PostgreSQL defaults to 0.1, this library defaults to 1.0)
|
105
|
+
# used to determine the expected fraction (percent) of result rows returned the the caller.
|
106
|
+
# This value determines the access path by the query planner.
|
107
|
+
def set_cursor_tuple_fraction(frac=1.0)
|
108
|
+
@cursor_tuple_fraction ||= @options.fetch(:fraction) { 1.0 }
|
109
|
+
return @cursor_tuple_fraction if frac == @cursor_tuple_fraction
|
110
|
+
@cursor_tuple_fraction = frac
|
111
|
+
@result = @connection.execute("set cursor_tuple_fraction to #{frac}")
|
112
|
+
frac
|
89
113
|
end
|
90
|
-
|
114
|
+
|
91
115
|
end
|
92
116
|
|
117
|
+
# Defines extension to ActiveRecord to use this library
|
93
118
|
class ActiveRecord::Base
|
94
|
-
class <<self
|
95
119
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
sql = ActiveRecord::SpawnMethods.apply_finder_options(args.first).to_sql
|
110
|
-
puts sql
|
111
|
-
|
112
|
-
PostgreSQLCursor.new(sql, options) { |r| block_given? ? yield(r) : instantiate(r) }
|
113
|
-
end
|
120
|
+
# Public: Returns each row as a hash to the given block
|
121
|
+
#
|
122
|
+
# sql - Full SQL statement, variables interpolated
|
123
|
+
# options - Hash to control
|
124
|
+
# fraction: 0.1..1.0 - The cursor_tuple_fraction (default 1.0)
|
125
|
+
# block_size: 1..n - The number of rows to fetch per db block fetch
|
126
|
+
# while: value - Exits loop when block does not return this value.
|
127
|
+
# until: value - Exits loop when block returns this value.
|
128
|
+
#
|
129
|
+
# Returns the number of rows yielded to the block
|
130
|
+
def self.each_row_by_sql(sql, options={}, &block)
|
131
|
+
PostgreSQLCursor.new(sql, options).each(&block)
|
132
|
+
end
|
114
133
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
134
|
+
# Public: Returns each row as a model instance to the given block
|
135
|
+
# As this instantiates a model object, it is slower than each_row_by_sql
|
136
|
+
#
|
137
|
+
# Paramaters: see each_row_by_sql
|
138
|
+
#
|
139
|
+
# Returns the number of rows yielded to the block
|
140
|
+
def self.each_instance_by_sql(sql, options={}, &block)
|
141
|
+
PostgreSQLCursor.new(sql, options).each do |row|
|
142
|
+
model = instantiate(row)
|
143
|
+
yield model
|
121
144
|
end
|
122
|
-
|
123
145
|
end
|
124
146
|
end
|
125
147
|
|
126
|
-
#
|
148
|
+
# Defines extension to ActiveRecord/AREL to use this library
|
127
149
|
class ActiveRecord::Relation
|
128
|
-
|
129
|
-
|
150
|
+
|
151
|
+
# Public: Executes the query, returning each row as a hash
|
152
|
+
# to the given block.
|
153
|
+
#
|
154
|
+
# options - Hash to control
|
155
|
+
# fraction: 0.1..1.0 - The cursor_tuple_fraction (default 1.0)
|
156
|
+
# block_size: 1..n - The number of rows to fetch per db block fetch
|
157
|
+
# while: value - Exits loop when block does not return this value.
|
158
|
+
# until: value - Exits loop when block returns this value.
|
159
|
+
#
|
160
|
+
# Returns the number of rows yielded to the block
|
130
161
|
def each_row(options={}, &block)
|
131
|
-
|
132
|
-
|
133
|
-
end
|
162
|
+
PostgreSQLCursor.new(to_sql).each(&block)
|
163
|
+
end
|
134
164
|
|
165
|
+
# Public: Like each_row, but returns an instantiated model object to the block
|
166
|
+
#
|
167
|
+
# Paramaters: same as each_row
|
168
|
+
#
|
169
|
+
# Returns the number of rows yielded to the block
|
170
|
+
def each_instance(options={}, &block)
|
171
|
+
PostgreSQLCursor.new(to_sql, options).each do |row|
|
172
|
+
model = instantiate(row)
|
173
|
+
yield model
|
174
|
+
end
|
175
|
+
end
|
135
176
|
end
|
data/postgresql_cursor.gemspec
CHANGED
@@ -1,45 +1,38 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.
|
7
|
+
s.name = "postgresql_cursor"
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Allen Fair"]
|
12
|
-
s.date =
|
13
|
-
s.description =
|
14
|
-
s.email =
|
12
|
+
s.date = "2012-07-21"
|
13
|
+
s.description = "PostgreSQL Cursor is an extension to the ActiveRecord PostgreSQLAdapter for very large result sets. It provides a cursor open/fetch/close interface to access data without loading all rows into memory, and instead loads the result rows in \"chunks\" (default of 10_000 rows), buffers them, and returns the rows one at a time."
|
14
|
+
s.email = "allen.fair@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE",
|
17
|
-
|
17
|
+
"README.rdoc"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
"postgresql_cursor.gemspec",
|
28
|
-
"test/helper.rb",
|
29
|
-
"test/test_postgresql_cursor.rb"
|
30
|
-
]
|
31
|
-
s.homepage = %q{http://github.com/afair/postgresql_cursor}
|
32
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
33
|
-
s.require_paths = ["lib"]
|
34
|
-
s.rubygems_version = %q{1.3.7}
|
35
|
-
s.summary = %q{ActiveRecord PostgreSQL Adapter extension for using a cursor to return a large result set}
|
36
|
-
s.test_files = [
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"lib/postgresql_cursor.rb",
|
26
|
+
"postgresql_cursor.gemspec",
|
37
27
|
"test/helper.rb",
|
38
|
-
|
28
|
+
"test/test_postgresql_cursor.rb"
|
39
29
|
]
|
30
|
+
s.homepage = "http://github.com/afair/postgresql_cursor"
|
31
|
+
s.require_paths = ["lib"]
|
32
|
+
s.rubygems_version = "1.8.16"
|
33
|
+
s.summary = "ActiveRecord PostgreSQL Adapter extension for using a cursor to return a large result set"
|
40
34
|
|
41
35
|
if s.respond_to? :specification_version then
|
42
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
43
36
|
s.specification_version = 3
|
44
37
|
|
45
38
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
data/test/helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
|
+
require 'active_record'
|
3
4
|
|
4
5
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
6
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
@@ -7,7 +8,8 @@ require 'postgresql_cursor'
|
|
7
8
|
|
8
9
|
ActiveRecord::Base.establish_connection :database=>'allen_test', :adapter=>'postgresql', :username=>'allen'
|
9
10
|
class Model < ActiveRecord::Base
|
10
|
-
set_table_name "records"
|
11
|
+
#set_table_name "records"
|
12
|
+
self.table_name = "records"
|
11
13
|
|
12
14
|
# create table records (id serial primary key);
|
13
15
|
def self.generate(max=1_000_000)
|
@@ -1,44 +1,45 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestPostgresqlCursor < Test::Unit::TestCase
|
4
|
-
|
5
|
-
def test_cursor
|
6
|
-
c = Model.find_with_cursor(:conditions=>["id>?",0], :cursor=>{:buffer_size=>10})
|
7
|
-
mycount=0
|
8
|
-
count = c.each { |r| mycount += 1 }
|
9
|
-
assert_equal mycount, count
|
10
|
-
end
|
11
4
|
|
12
|
-
def
|
13
|
-
c =
|
14
|
-
|
15
|
-
|
5
|
+
def test_each
|
6
|
+
c = PostgreSQLCursor.new("select * from records order by 1")
|
7
|
+
nn = 0
|
8
|
+
n = c.each { nn += 1}
|
9
|
+
assert_equal nn, n
|
16
10
|
end
|
17
11
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
r = c.next
|
22
|
-
assert_equal r.class, Hash
|
23
|
-
end
|
12
|
+
def test_enumerables
|
13
|
+
assert_equal true, PostgreSQLCursor.new("select * from records order by 1").any?
|
14
|
+
assert_equal false, PostgreSQLCursor.new("select * from records where id<0").any?
|
24
15
|
end
|
25
16
|
|
26
|
-
def
|
27
|
-
c =
|
28
|
-
|
29
|
-
|
30
|
-
|
17
|
+
def test_each_while_until
|
18
|
+
c = PostgreSQLCursor.new("select * from records order by 1", until:true)
|
19
|
+
n = c.each { |r| r[:id].to_i > 100 }
|
20
|
+
assert_equal 101, n
|
21
|
+
|
22
|
+
c = PostgreSQLCursor.new("select * from records order by 1", while:true)
|
23
|
+
n = c.each { |r| r[:id].to_i < 100 }
|
24
|
+
assert_equal 100, n
|
31
25
|
end
|
32
26
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
assert record[:id].class, Fixnum
|
38
|
-
cursor.close if cursor.count >= 10
|
39
|
-
end
|
40
|
-
assert_equal cursor.count, 10
|
41
|
-
end
|
27
|
+
def test_relation
|
28
|
+
nn = 0
|
29
|
+
Model.where("id>0").each_row {|r| nn += 1 }
|
30
|
+
assert_equal 1000, nn
|
42
31
|
end
|
43
32
|
|
33
|
+
def test_activerecord
|
34
|
+
nn = 0
|
35
|
+
Model.each_row_by_sql("select * from records") {|r| nn += 1 }
|
36
|
+
assert_equal 1000, nn
|
37
|
+
|
38
|
+
nn = 0
|
39
|
+
row = nil
|
40
|
+
Model.each_instance_by_sql("select * from records") {|r| row = r; nn += 1 }
|
41
|
+
assert_equal 1000, nn
|
42
|
+
assert_equal Model, row.class
|
43
|
+
end
|
44
|
+
|
44
45
|
end
|
metadata
CHANGED
@@ -1,63 +1,50 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: postgresql_cursor
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 3
|
9
|
-
- 1
|
10
|
-
version: 0.3.1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Allen Fair
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-07-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: activerecord
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &2164034600 !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
version: "0"
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
33
22
|
type: :runtime
|
34
|
-
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: pg
|
37
23
|
prerelease: false
|
38
|
-
|
24
|
+
version_requirements: *2164034600
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: pg
|
27
|
+
requirement: &2164034040 !ruby/object:Gem::Requirement
|
39
28
|
none: false
|
40
|
-
requirements:
|
41
|
-
- -
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
segments:
|
45
|
-
- 0
|
46
|
-
version: "0"
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
47
33
|
type: :runtime
|
48
|
-
|
49
|
-
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2164034040
|
36
|
+
description: PostgreSQL Cursor is an extension to the ActiveRecord PostgreSQLAdapter
|
37
|
+
for very large result sets. It provides a cursor open/fetch/close interface to access
|
38
|
+
data without loading all rows into memory, and instead loads the result rows in
|
39
|
+
"chunks" (default of 10_000 rows), buffers them, and returns the rows one at a time.
|
50
40
|
email: allen.fair@gmail.com
|
51
41
|
executables: []
|
52
|
-
|
53
42
|
extensions: []
|
54
|
-
|
55
|
-
extra_rdoc_files:
|
43
|
+
extra_rdoc_files:
|
56
44
|
- LICENSE
|
57
45
|
- README.rdoc
|
58
|
-
files:
|
46
|
+
files:
|
59
47
|
- .document
|
60
|
-
- .gitignore
|
61
48
|
- LICENSE
|
62
49
|
- README.rdoc
|
63
50
|
- Rakefile
|
@@ -66,40 +53,29 @@ files:
|
|
66
53
|
- postgresql_cursor.gemspec
|
67
54
|
- test/helper.rb
|
68
55
|
- test/test_postgresql_cursor.rb
|
69
|
-
has_rdoc: true
|
70
56
|
homepage: http://github.com/afair/postgresql_cursor
|
71
57
|
licenses: []
|
72
|
-
|
73
58
|
post_install_message:
|
74
|
-
rdoc_options:
|
75
|
-
|
76
|
-
require_paths:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
77
61
|
- lib
|
78
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
63
|
none: false
|
80
|
-
requirements:
|
81
|
-
- -
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
|
84
|
-
|
85
|
-
- 0
|
86
|
-
version: "0"
|
87
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
69
|
none: false
|
89
|
-
requirements:
|
90
|
-
- -
|
91
|
-
- !ruby/object:Gem::Version
|
92
|
-
|
93
|
-
segments:
|
94
|
-
- 0
|
95
|
-
version: "0"
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
96
74
|
requirements: []
|
97
|
-
|
98
75
|
rubyforge_project:
|
99
|
-
rubygems_version: 1.
|
76
|
+
rubygems_version: 1.8.16
|
100
77
|
signing_key:
|
101
78
|
specification_version: 3
|
102
|
-
summary: ActiveRecord PostgreSQL Adapter extension for using a cursor to return a
|
103
|
-
|
104
|
-
|
105
|
-
- test/test_postgresql_cursor.rb
|
79
|
+
summary: ActiveRecord PostgreSQL Adapter extension for using a cursor to return a
|
80
|
+
large result set
|
81
|
+
test_files: []
|