postgresql_cursor 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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