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.
@@ -1,76 +1,75 @@
1
- = PostgreSQLCursor
1
+ = PostgreSQLCursor for handling large Result Sets
2
2
 
3
- PostgreSQL Cursor is an extension to the ActiveRecord PostgreSQLAdapter for very large result sets.
4
- It provides a cursor open/fetch/close interface to access data without loading all rows into memory,
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
- For web pages, an application would not want to process a large amount of data, usually employing a
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
- Previous solutions employ pagination to fetch each block, then re-running the query for the next "page".
13
- This gem avoids re-executing the query by using PostgreSQL's cursor feature.
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
- The ActiveRecord methods for the cursor return instances of the model class by default, however you
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
- ActiveRecord v3 has initial support in this release. It is deprecating using the conditions in the find()
20
- method in favor of Arel scopes and usage. I added a method to Arel for iterate over the results in a buffered
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
- NOTE: This gem is intended to replace the 'postgresql-cursor' (with hyphen, not underscore) library.
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
- ==Installation
27
- [sudo] gem install postgresql_cursor
21
+ == Enter find_each
28
22
 
29
- This does not require Rails to work, just ActiveRecord < 3.0.0 and the 'pg' gem. Rails 3 support is forthcoming.
23
+ To solve this problem, ActiveRecord gives us two alternative methods that work in "chunks" of your data:
30
24
 
31
- You can then configure your Rails application by adding to the config/environment.rb file
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
- ==Usage
27
+ Model.where("id>0").find_in_batches do |batch|
28
+ batch.each { |model| model.process! }
29
+ end
38
30
 
39
- This library is intended to be used via ActiveRecord. It provides two methods on ActiveRecord::Base
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
- Calling each() the returned cursor will yield a record to the block for each row. It handles a transaction
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
- * *find_with_cursor*( <i>find options</i>, :cursor=>{<i>cursor options</i>}, &block) returns a cursor for the data matching the find options. It takes an optional block accepting a column=>value hash which returns the object desired via each or next, instead of an instance of the model.
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
- * *find_by_sql_with_cursor*(<i>select statement</i>, <i>cursor options</i>, &block) takes a custom SQL statement and returns each row as above.
40
+ == PostgreSQLCursor FTW!
49
41
 
50
- ==Examples
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
- # Active Record v3 with Arel scopes. This is the new, preferred method of use
53
- Account.active.each_row { |hash| puts hash.inspect }
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
- Account.find_with_cursor(:conditions=>["status = ?", 'good']).each do |row|
56
- puts row.to_json
57
- end
58
-
59
- Account.find_by_sql_with_cursor("select ...", :buffer_size=>1000).each do |row|
60
- row.process
61
- end
62
-
63
- Account.transaction do
64
- cursor = Account.find_with_cursor(...) { |record| record.symbolize_keys }
65
- while record = cursor.next do
66
- record.process # => {:column=>value, ...}
67
- cursor.close if cursor.count > 1000 # Halts loop after 1000 records
68
- end
69
- end
70
-
71
- Account.find_with_cursor(...) { |record| record.symbolize_keys }.each do |row|
72
- row.process
73
- end
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
@@ -43,7 +43,7 @@ task :test => :check_dependencies
43
43
 
44
44
  task :default => :test
45
45
 
46
- require 'rake/rdoctask'
46
+ require 'rdoc/task'
47
47
  Rake::RDocTask.new do |rdoc|
48
48
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
49
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
@@ -1,135 +1,176 @@
1
- require 'active_record'
2
-
3
- # Class to operate a PostgreSQL cursor to buffer a set of rows, and return single rows for processing.
4
- # Use this class when processing a very large number of records, which would otherwise all be instantiated
5
- # in memory by find(). This also adds helpers to ActiveRecord::Base for *find_with_cursor()* and
6
- # *find_by_sql_with_cursor()* to return instances of the cursor ready to fetch.
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
- # Use each() with a block to accept an instance of the Model (or whatever you define with a block on
9
- # initialize()). It will open, buffer, yield each row, then close the cursor.
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
- attr_reader :count, :buffer_reads
15
- @@counter=0
16
-
17
- # Define a new cursor, with a SQL statement, as a string with parameters already replaced, and options for
18
- # the cursor
19
- # * :buffer_size=>number of records to buffer, default 10000.
20
- # Pass a optional block which takes a Hash of "column"=>"value", and returns an object to be yielded for each row.
21
- def initialize(sql,*args, &block)
22
- @options = args.last.is_a?(Hash) ? args.pop : {}
23
- @@counter += 1
24
- @instantiator = block || lambda {|r| r }
25
- @sql = sql
26
- @name = "pgcursor_#{@@counter}"
27
- @connection = ActiveRecord::Base.connection
28
- @buffer_size = @options[:buffer_size] || 10_000
29
- @count = 0
30
- @state = :ready
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
- # Iterates through the rows, yields them to the block. It wraps the processing in a transaction
34
- # (required by PostgreSQL), opens the cursor, buffers the results, returns each row, and closes the cursor.
35
- def each
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
- @result = open
38
- while (row = fetch ) do
39
- yield row
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
- # Starts buffered result set processing for a given SQL statement. The DB
74
+
75
+ # Public: Opens (actually, "declares") the cursor. Call this before fetching
47
76
  def open
48
- raise "Open Cursor state not ready" unless @state == :ready
49
- @result = @connection.execute("declare #{@name} cursor for #{@sql}")
50
- @state = :empty
51
- @buffer_reads = 0
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 a string of the current status
56
- def status #:nodoc:
57
- "row=#{@count} buffer=#{@buffer.size} state=#{@state} buffer_size=#{@buffer_size} reads=#{@buffer_reads}"
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 memory
61
- def fetch_buffer #:nodoc:
62
- return unless @state == :empty
63
- @result = @connection.execute("fetch #{@buffer_size} from #{@name}")
64
- @buffer = @result.collect {|row| row }
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
- # Returns the next row from the cursor, or nil when end of data.
71
- # The row returned is a hash[:colname]
72
- def fetch
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
- alias_method :next, :fetch
83
-
84
- # Closes the cursor to clean up resources. Call this method during process of each() to
85
- # exit the loop
86
- def close
87
- pg_result = @connection.execute("close #{@name}")
88
- @state = :closed
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
- # Returns a PostgreSQLCursor instance to access the results, on which you are able to call
97
- # each (though the cursor is not Enumerable and no other methods are available).
98
- # No :all argument is needed, and other find() options can be specified.
99
- # Specify the :cursor=>{...} option to override options for the cursor such has :buffer_size=>n.
100
- # Pass an optional block that takes a Hash of the record and returns what you want to return.
101
- # For example, return the Hash back to process a Hash instead of a table instance for better speed.
102
- def find_with_cursor(*args, &block)
103
- find_options = args.last.is_a?(Hash) ? args.pop : {}
104
- options = find_options.delete(:cursor) || {}
105
- #validate_find_options(find_options)
106
- #set_readonly_option!(find_options)
107
- #sql = construct_finder_sql(find_options)
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
- # Returns a PostgreSQLCursor instance to access the results of the sql
116
- # Specify the :cursor=>{...} option to override options for the cursor such has :buffer_size=>n.
117
- # Pass an optional block that takes a Hash of the record and returns what you want to return.
118
- # For example, return the Hash back to process a Hash instead of a table instance for better speed.
119
- def find_by_sql_with_cursor(sql, options={})
120
- PostgreSQLCursor.new(sql, options) { |r| block_given? ? yield(r) : instantiate(r) }
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
- #Rails 3: add method to use PostgreSQL cursors
148
+ # Defines extension to ActiveRecord/AREL to use this library
127
149
  class ActiveRecord::Relation
128
- @@relation_each_row_seq = 0
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
- @@relation_each_row_seq += 1
132
- PostgreSQLCursor.new( to_sql, options).each { |r| block_given? ? yield(r) : instantiate(r) }
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
@@ -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 the gemspec command
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 = %q{postgresql_cursor}
8
- s.version = "0.3.1"
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 = %q{2010-08-06}
13
- s.description = %q{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 = %q{allen.fair@gmail.com}
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
- "README.rdoc"
17
+ "README.rdoc"
18
18
  ]
19
19
  s.files = [
20
20
  ".document",
21
- ".gitignore",
22
- "LICENSE",
23
- "README.rdoc",
24
- "Rakefile",
25
- "VERSION",
26
- "lib/postgresql_cursor.rb",
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
- "test/test_postgresql_cursor.rb"
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
@@ -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 test_empty_set
13
- c = Model.find_with_cursor(:conditions=>["id<?",0])
14
- count = c.each { |r| puts r.class }
15
- assert_equal count, 0
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 test_block
19
- Model.transaction do
20
- c = Model.find_with_cursor(:conditions=>["id<?",10]) { |r| r }
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 test_sql
27
- c = Model.find_by_sql_with_cursor("select * from #{Model.table_name}")
28
- mycount=0
29
- count = c.each { |r| mycount += 1 }
30
- assert_equal mycount, count
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 test_loop
34
- Model.transaction do
35
- cursor = Model.find_with_cursor() { |record| record.symbolize_keys }
36
- while record = cursor.next do
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
- hash: 17
5
- prerelease: false
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
- date: 2010-08-06 00:00:00 -04:00
19
- default_executable:
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
- prerelease: false
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
- hash: 3
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
- requirement: &id002 !ruby/object:Gem::Requirement
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
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
47
33
  type: :runtime
48
- version_requirements: *id002
49
- 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.
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
- - --charset=UTF-8
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
- hash: 3
84
- segments:
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
- hash: 3
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.3.7
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 large result set
103
- test_files:
104
- - test/helper.rb
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: []
data/.gitignore DELETED
@@ -1,21 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC