activerecord-postgresql-cursors 0.0.1
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/MIT-LICENSE +23 -0
- data/README.rdoc +52 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/activerecord-postgresql-cursors.gemspec +45 -0
- data/init.rb +2 -0
- data/lib/activerecord-postgresql-cursors.rb +109 -0
- data/lib/postgresql_cursors_2.rb +75 -0
- data/lib/postgresql_cursors_3.rb +75 -0
- data/test/cursor_test.rb +97 -0
- data/test/test_helper.rb +68 -0
- metadata +75 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2011 2167961 Ontario Inc., Zoocasa <code@zoocasa.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
== ActiveRecord PostgreSQL Cursors
|
2
|
+
|
3
|
+
This extension allows you to loop through record sets using cursors in an
|
4
|
+
Enumerable fashion. This allows you to cut down memory usage by only pulling
|
5
|
+
in individual records on each loop rather than pulling everything into
|
6
|
+
memory all at once.
|
7
|
+
|
8
|
+
To use a cursor, just change the first parameter to an
|
9
|
+
ActiveRecord::Base.find to :cursor instead of :first or :all or
|
10
|
+
whatever or use the ActiveRecord::Base.cursor method directly.
|
11
|
+
|
12
|
+
MyModel.find(:cursor, :conditions => 'some_column = true').each do |r|
|
13
|
+
puts r.inspect
|
14
|
+
end
|
15
|
+
|
16
|
+
MyModel.find(:cursor).collect { |r| r.foo / PI }.avg
|
17
|
+
|
18
|
+
MyModel.cursor.each do |r|
|
19
|
+
puts r.inspect
|
20
|
+
end
|
21
|
+
|
22
|
+
All ActiveRecord::Base.find options are available and should work as-is.
|
23
|
+
As a bonus, the PostgreSQLCursor object returned includes Enumerable,
|
24
|
+
so you can iterate to your heart's content.
|
25
|
+
|
26
|
+
This extension should work in both Rails 2.3 as well as Rails 3.
|
27
|
+
|
28
|
+
At the moment, this is a non-scrollable cursor -- it will only fetch
|
29
|
+
forward. Also note that these cursors are non-updateable/insensitive to
|
30
|
+
updates to the underlying data. You can write to the records themselves,
|
31
|
+
but changes outside of the cursor's transaction will not affect the
|
32
|
+
data being retrieved from the point of the cursor's creation, or rather
|
33
|
+
more specifically from the time you begin iterating.
|
34
|
+
|
35
|
+
The cursor itself is wrapped in a transaction as is required by PostgreSQL
|
36
|
+
and the cursor name is automatically generated using random numbers or
|
37
|
+
a name supplied during cursor creation. On raised SQL exceptions, the
|
38
|
+
transaction is ABORTed and the cursor CLOSEd.
|
39
|
+
|
40
|
+
Associations are handled, so you can use :include in your find options. Of
|
41
|
+
course, this requires some nonsense when moving the cursor around, but it
|
42
|
+
works all the same. In some cases, pre-loading and eager loading of
|
43
|
+
associations and whatnot creates an initial query that will grab the initial
|
44
|
+
IDs of the model being fetched and then create the actual cursor query out
|
45
|
+
of those large joins that ActiveRecord sometimes generates. This larger
|
46
|
+
query will be the query that's wrapped in the transaction and in a cursor,
|
47
|
+
while the first query is just used to build the larger joined query. This
|
48
|
+
allows for a brief window between the point that the cursor query is
|
49
|
+
created and the time it is executed. In these cases, it may be wise to wrap
|
50
|
+
your use or cursors in your own transaction to ensure that changes made to
|
51
|
+
the underlying data don't interfere with your cursor's visbility.
|
52
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rubygems/package_task'
|
6
|
+
require 'rake/testtask'
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
$:.push 'lib'
|
10
|
+
|
11
|
+
version = File.read('VERSION') rescue ''
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "activerecord-postgresql-cursors"
|
17
|
+
gem.summary = "Provides some support for PostgreSQL cursors in ActiveRecord."
|
18
|
+
gem.description = gem.summary
|
19
|
+
gem.email = "code@zoocasa.com"
|
20
|
+
gem.homepage = "http://github.com/zoocasa/activerecord-postgresql-cursors"
|
21
|
+
gem.authors = [ "J Smith" ]
|
22
|
+
end
|
23
|
+
Jeweler::GemcutterTasks.new
|
24
|
+
rescue LoadError
|
25
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Test PostgreSQL extensions'
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.pattern = 'test/**/*_test.rb'
|
31
|
+
t.verbose = false
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Build docs'
|
35
|
+
Rake::RDocTask.new do |t|
|
36
|
+
require 'rdoc'
|
37
|
+
t.title = "ActiveRecord PostgreSQL Cursors #{version}"
|
38
|
+
t.main = 'README.rdoc'
|
39
|
+
t.rdoc_dir = 'doc'
|
40
|
+
t.rdoc_files.include('README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb')
|
41
|
+
end
|
42
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{activerecord-postgresql-cursors}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = [%q{J Smith}]
|
12
|
+
s.date = %q{2011-07-11}
|
13
|
+
s.description = %q{Provides some support for PostgreSQL cursors in ActiveRecord.}
|
14
|
+
s.email = %q{code@zoocasa.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"MIT-LICENSE",
|
20
|
+
"README.rdoc",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"activerecord-postgresql-cursors.gemspec",
|
24
|
+
"init.rb",
|
25
|
+
"lib/activerecord-postgresql-cursors.rb",
|
26
|
+
"lib/postgresql_cursors_2.rb",
|
27
|
+
"lib/postgresql_cursors_3.rb",
|
28
|
+
"test/cursor_test.rb",
|
29
|
+
"test/test_helper.rb"
|
30
|
+
]
|
31
|
+
s.homepage = %q{http://github.com/zoocasa/activerecord-postgresql-cursors}
|
32
|
+
s.require_paths = [%q{lib}]
|
33
|
+
s.rubygems_version = %q{1.8.5}
|
34
|
+
s.summary = %q{Provides some support for PostgreSQL cursors in ActiveRecord.}
|
35
|
+
|
36
|
+
if s.respond_to? :specification_version then
|
37
|
+
s.specification_version = 3
|
38
|
+
|
39
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
40
|
+
else
|
41
|
+
end
|
42
|
+
else
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
data/init.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
# Exception raised when database cursors aren't supported, which they
|
4
|
+
# absolutely should be in our app.
|
5
|
+
class CursorsNotSupported < ActiveRecordError; end
|
6
|
+
|
7
|
+
module Associations
|
8
|
+
module ClassMethods
|
9
|
+
class JoinDependency
|
10
|
+
# Extra method we can use to clear out a couple of things in
|
11
|
+
# JoinDependency so we can use some of the methods for our
|
12
|
+
# cursors code.
|
13
|
+
def clear_with_cursor
|
14
|
+
@reflections = []
|
15
|
+
@base_records_hash = {}
|
16
|
+
@base_records_in_order = []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# PostgreSQLCursor is an Enumerable class so you can use each, map,
|
23
|
+
# any? and all of those nice Enumerable methods.
|
24
|
+
#
|
25
|
+
# At the moment, cursors aren't scrollable and are fetch forward-only
|
26
|
+
# and read-only.
|
27
|
+
#
|
28
|
+
# This class isn't really meant to be used outside of the
|
29
|
+
# ActiveRecord::Base#find method.
|
30
|
+
class PostgreSQLCursor
|
31
|
+
include Enumerable
|
32
|
+
|
33
|
+
attr_accessor :cursor_name
|
34
|
+
|
35
|
+
def initialize(model, cursor_name, query, join_dependency = nil)
|
36
|
+
@model = model
|
37
|
+
@cursor_name = if cursor_name
|
38
|
+
@model.connection.quote_table_name(cursor_name.gsub(/"/, '\"'))
|
39
|
+
end
|
40
|
+
@query = query
|
41
|
+
@join_dependency = join_dependency
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
%{#<ActiveRecord::PostgreSQLCursor cursor_name: "#{cursor_name}", query: "#{@query}">}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Calls block once for each record in the cursor, passing that
|
49
|
+
# record as a parameter.
|
50
|
+
def each
|
51
|
+
@model.transaction do
|
52
|
+
begin
|
53
|
+
declare_cursor
|
54
|
+
if @join_dependency
|
55
|
+
rows = Array.new
|
56
|
+
last_id = nil
|
57
|
+
while row = fetch_forward
|
58
|
+
current_id = row[@join_dependency.join_base.aliased_primary_key]
|
59
|
+
last_id ||= current_id
|
60
|
+
if last_id == current_id
|
61
|
+
rows << row
|
62
|
+
last_id = current_id
|
63
|
+
else
|
64
|
+
yield @join_dependency.instantiate(rows).first
|
65
|
+
@join_dependency.clear_with_cursor
|
66
|
+
rows = [ row ]
|
67
|
+
end
|
68
|
+
last_id = current_id
|
69
|
+
end
|
70
|
+
|
71
|
+
if !rows.empty?
|
72
|
+
yield @join_dependency.instantiate(rows).first
|
73
|
+
end
|
74
|
+
else
|
75
|
+
while row = fetch_forward
|
76
|
+
yield row
|
77
|
+
end
|
78
|
+
end
|
79
|
+
ensure
|
80
|
+
close_cursor
|
81
|
+
end
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def cursor_name
|
88
|
+
@cursor_name ||= "cursor_#{(rand * 1000000).ceil}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def fetch_forward #:nodoc:
|
92
|
+
@model.find_by_sql(%{FETCH FORWARD FROM #{cursor_name}}).first
|
93
|
+
end
|
94
|
+
|
95
|
+
def declare_cursor #:nodoc:
|
96
|
+
@model.connection.execute(%{DECLARE #{cursor_name} CURSOR FOR #{@query}})
|
97
|
+
end
|
98
|
+
|
99
|
+
def close_cursor #:nodoc:
|
100
|
+
@model.connection.execute(%{CLOSE #{cursor_name}})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
106
|
+
require File.join(File.dirname(__FILE__), 'postgresql_cursors_3')
|
107
|
+
else
|
108
|
+
require File.join(File.dirname(__FILE__), 'postgresql_cursors_2')
|
109
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
# Override ActiveRecord::Base#find to allow for cursors in
|
6
|
+
# PostgreSQL. To use a cursor, set the first argument of
|
7
|
+
# find to :cursor. A PostgreSQLCursor object will be returned,
|
8
|
+
# which can then be used as an Enumerable to loop through the
|
9
|
+
# results.
|
10
|
+
#
|
11
|
+
# By default, cursor names are generated automatically using
|
12
|
+
# "cursor_#{rand}", where rand is a big ol' random number that
|
13
|
+
# is pretty unlikely to clash if you're using nested cursors.
|
14
|
+
# Alternatively, you can supply a specific cursor name by
|
15
|
+
# supplying a :cursor_name option.
|
16
|
+
def find_with_cursors *args
|
17
|
+
if args.first.to_s == 'cursor'
|
18
|
+
options = args.extract_options!
|
19
|
+
cursor_name = options.delete(:cursor_name)
|
20
|
+
validate_find_options(options)
|
21
|
+
set_readonly_option!(options)
|
22
|
+
find_cursor(cursor_name, options)
|
23
|
+
else
|
24
|
+
find_without_cursors(*args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
alias_method_chain :find, :cursors
|
28
|
+
|
29
|
+
def cursor(*args)
|
30
|
+
find(:cursor, *args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
# Find method for using cursors. This works just like the regular
|
36
|
+
# ActiveRecord::Base#find_every method, except it returns a
|
37
|
+
# PostgreSQLCursor object that can be used to loop through records.
|
38
|
+
def self.find_cursor(cursor_name, options)
|
39
|
+
unless connection.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
40
|
+
raise CursorsNotSupported, "#{connection.class} doesn't support cursors"
|
41
|
+
end
|
42
|
+
|
43
|
+
catch :invalid_query do
|
44
|
+
if options[:include]
|
45
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
46
|
+
return ActiveRecord::PostgreSQLCursor.new(
|
47
|
+
self,
|
48
|
+
cursor_name,
|
49
|
+
construct_finder_sql_with_included_associations(
|
50
|
+
options,
|
51
|
+
join_dependency
|
52
|
+
),
|
53
|
+
join_dependency
|
54
|
+
)
|
55
|
+
else
|
56
|
+
return ActiveRecord::PostgreSQLCursor.new(
|
57
|
+
self,
|
58
|
+
cursor_name,
|
59
|
+
construct_finder_sql(
|
60
|
+
options
|
61
|
+
)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class PostgreSQLCursor
|
70
|
+
def initialize_with_rails_2(model, cursor_name, query, join_dependency = nil)
|
71
|
+
initialize_without_rails_2(model, cursor_name, query, join_dependency)
|
72
|
+
end
|
73
|
+
alias_method_chain :initialize, :rails_2
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
module CursorExtensions
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
alias_method_chain :find, :cursors
|
8
|
+
end
|
9
|
+
|
10
|
+
# Override ActiveRecord::Base#find to allow for cursors in
|
11
|
+
# PostgreSQL. To use a cursor, set the first argument of
|
12
|
+
# find to :cursor. A PostgreSQLCursor object will be returned,
|
13
|
+
# which can then be used as an Enumerable to loop through the
|
14
|
+
# results.
|
15
|
+
#
|
16
|
+
# By default, cursor names are generated automatically using
|
17
|
+
# "cursor_#{rand}", where rand is a big ol' random number that
|
18
|
+
# is pretty unlikely to clash if you're using nested cursors.
|
19
|
+
# Alternatively, you can supply a specific cursor name by
|
20
|
+
# supplying a :cursor_name option.
|
21
|
+
def find_with_cursors(*args)
|
22
|
+
if args.first.to_s == 'cursor'
|
23
|
+
options = args.extract_options!
|
24
|
+
cursor_name = options.delete(:cursor_name)
|
25
|
+
find_cursor(cursor_name, options)
|
26
|
+
else
|
27
|
+
find_without_cursors(*args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def cursor(*args)
|
32
|
+
find_with_cursors('cursor', *args)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
# Find method for using cursors. This works just like the regular
|
37
|
+
# ActiveRecord::Base#find_every method, except it returns a
|
38
|
+
# PostgreSQLCursor object that can be used to loop through records.
|
39
|
+
def find_cursor(cursor_name, options)
|
40
|
+
unless connection.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
41
|
+
raise CursorsNotSupported, "#{connection.class} doesn't support cursors"
|
42
|
+
end
|
43
|
+
|
44
|
+
relation = apply_finder_options(options)
|
45
|
+
including = (relation.eager_load_values + relation.includes_values).uniq
|
46
|
+
|
47
|
+
if including.present?
|
48
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
|
49
|
+
join_relation = relation.construct_relation_for_association_find(join_dependency)
|
50
|
+
|
51
|
+
ActiveRecord::PostgreSQLCursor.new(self, cursor_name, join_relation, join_dependency)
|
52
|
+
else
|
53
|
+
ActiveRecord::PostgreSQLCursor.new(self, cursor_name, relation)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class PostgreSQLCursor
|
59
|
+
def initialize_with_rails_3(model, cursor_name, relation, join_dependency = nil)
|
60
|
+
@relation = relation
|
61
|
+
initialize_without_rails_3(model, cursor_name, relation.to_sql, join_dependency)
|
62
|
+
end
|
63
|
+
alias_method_chain :initialize, :rails_3
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class ActiveRecord::Relation
|
68
|
+
include ActiveRecord::CursorExtensions
|
69
|
+
end
|
70
|
+
|
71
|
+
class ActiveRecord::Base
|
72
|
+
class << self
|
73
|
+
delegate :cursor, :to => :scoped
|
74
|
+
end
|
75
|
+
end
|
data/test/cursor_test.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
$: << File.dirname(__FILE__)
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class PostgreSQLCursorTests < Test::Unit::TestCase
|
6
|
+
include PostgreSQLCursorTestHelper
|
7
|
+
|
8
|
+
def test_find_cursor
|
9
|
+
cursor = Foo.find(:cursor, :order => 'id')
|
10
|
+
|
11
|
+
assert(cursor.is_a?(ActiveRecord::PostgreSQLCursor))
|
12
|
+
|
13
|
+
assert_equal(%w{ one two three four five }, cursor.collect(&:name))
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_cursor_scoped
|
17
|
+
cursor = Foo.cursor(:order => 'id')
|
18
|
+
|
19
|
+
assert(cursor.is_a?(ActiveRecord::PostgreSQLCursor))
|
20
|
+
|
21
|
+
assert_equal(%w{ one two three four five }, cursor.collect(&:name))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_cursor_while_updating
|
25
|
+
cursor = Foo.cursor(:order => 'id')
|
26
|
+
|
27
|
+
cursor.each do |row|
|
28
|
+
row.name = "#{row.name}_updated"
|
29
|
+
assert(row.save)
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_equal(%w{ one_updated two_updated three_updated four_updated five_updated }, cursor.collect(&:name))
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_with_associations
|
36
|
+
cursor = Foo.cursor(:order => 'id')
|
37
|
+
|
38
|
+
cursor.each do |row|
|
39
|
+
assert(row.is_a?(Foo))
|
40
|
+
row.bars.each do |bar|
|
41
|
+
assert(bar.is_a?(Bar))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_with_associations_eager_loading
|
47
|
+
cursor = Foo.cursor(:order => 'foos.id', :include => :bars)
|
48
|
+
|
49
|
+
cursor.each do |row|
|
50
|
+
assert(row.is_a?(Foo))
|
51
|
+
row.bars.each do |bar|
|
52
|
+
assert(bar.is_a?(Bar))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_nested_cursors
|
58
|
+
cursor = Foo.cursor(:order => 'foos.id')
|
59
|
+
|
60
|
+
cursor.each do |row|
|
61
|
+
bars_cursor = row.bars.cursor
|
62
|
+
assert(bars_cursor.is_a?(ActiveRecord::PostgreSQLCursor))
|
63
|
+
|
64
|
+
bars_cursor.each do |bar|
|
65
|
+
assert(bar.is_a?(Bar))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
71
|
+
def test_as_relation
|
72
|
+
cursor = Foo.order('foos.id').where('foos.id >= 3').cursor
|
73
|
+
assert_equal(3, cursor.to_a.length)
|
74
|
+
|
75
|
+
cursor.each do |row|
|
76
|
+
assert(row.is_a?(Foo))
|
77
|
+
assert_equal(2, row.bars.length)
|
78
|
+
row.bars.each do |bar|
|
79
|
+
assert(bar.is_a?(Bar))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_as_relation_with_associations
|
85
|
+
cursor = Foo.includes(:bars).order('foos.id').where('foos.id >= 3').cursor
|
86
|
+
assert_equal(3, cursor.to_a.length)
|
87
|
+
|
88
|
+
cursor.each do |row|
|
89
|
+
assert(row.is_a?(Foo))
|
90
|
+
assert_equal(2, row.bars.length)
|
91
|
+
row.bars.each do |bar|
|
92
|
+
assert(bar.is_a?(Bar))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
ACTIVERECORD_GEM_VERSION = ENV['ACTIVERECORD_GEM_VERSION'] || '~> 3.0.3'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'activerecord', ACTIVERECORD_GEM_VERSION
|
6
|
+
|
7
|
+
require 'active_support'
|
8
|
+
require 'active_support/core_ext/module/aliasing'
|
9
|
+
require 'active_record'
|
10
|
+
require 'test/unit'
|
11
|
+
require 'logger'
|
12
|
+
require File.join(File.dirname(__FILE__), *%w{ .. lib activerecord-postgresql-cursors })
|
13
|
+
|
14
|
+
puts "Testing against ActiveRecord #{Gem.loaded_specs['activerecord'].version.to_s}"
|
15
|
+
|
16
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
17
|
+
ActiveRecord::Base.configurations = {
|
18
|
+
'arunit' => {
|
19
|
+
:adapter => 'postgresql',
|
20
|
+
:database => 'postgresql_cursors_unit_tests',
|
21
|
+
:min_messages => 'warning',
|
22
|
+
:schema_search_path => 'public'
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
ActiveRecord::Base.establish_connection 'arunit'
|
27
|
+
ARBC = ActiveRecord::Base.connection
|
28
|
+
|
29
|
+
if !ARBC.table_exists?('foos')
|
30
|
+
ActiveRecord::Migration.create_table(:foos) do |t|
|
31
|
+
t.text :name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if !ARBC.table_exists?('bars')
|
36
|
+
ActiveRecord::Migration.create_table(:bars) do |t|
|
37
|
+
t.text :name
|
38
|
+
t.integer :foo_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
class Bar < ActiveRecord::Base
|
44
|
+
belongs_to :foo
|
45
|
+
end
|
46
|
+
|
47
|
+
class Foo < ActiveRecord::Base
|
48
|
+
has_many :bars
|
49
|
+
end
|
50
|
+
|
51
|
+
module PostgreSQLCursorTestHelper
|
52
|
+
def setup
|
53
|
+
Foo.delete_all
|
54
|
+
Bar.delete_all
|
55
|
+
ARBC.execute(%{select setval('foos_id_seq', 1, false)})
|
56
|
+
ARBC.execute(%{select setval('bars_id_seq', 1, false)})
|
57
|
+
|
58
|
+
%w{ six seven eight nine ten eleven twelve thirteen fourteen fifteen }.each do |name|
|
59
|
+
Bar.create(:name => name)
|
60
|
+
end
|
61
|
+
|
62
|
+
%w{ one two three four five }.each_with_index do |name, i|
|
63
|
+
foo = Foo.new(:name => name)
|
64
|
+
foo.bar_ids = [ i + 1, i + 6 ]
|
65
|
+
foo.save
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-postgresql-cursors
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- J Smith
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-11 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Provides some support for PostgreSQL cursors in ActiveRecord.
|
22
|
+
email: code@zoocasa.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- MIT-LICENSE
|
31
|
+
- README.rdoc
|
32
|
+
- Rakefile
|
33
|
+
- VERSION
|
34
|
+
- activerecord-postgresql-cursors.gemspec
|
35
|
+
- init.rb
|
36
|
+
- lib/activerecord-postgresql-cursors.rb
|
37
|
+
- lib/postgresql_cursors_2.rb
|
38
|
+
- lib/postgresql_cursors_3.rb
|
39
|
+
- test/cursor_test.rb
|
40
|
+
- test/test_helper.rb
|
41
|
+
homepage: http://github.com/zoocasa/activerecord-postgresql-cursors
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.5
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Provides some support for PostgreSQL cursors in ActiveRecord.
|
74
|
+
test_files: []
|
75
|
+
|